xref: /illumos-gate/usr/src/lib/libreparse/common/fs_reparse_lib.c (revision e4d060fb4c00d44cd578713eb9a921f594b733b8)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <strings.h>
31 #include <string.h>
32 #include <dirent.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/param.h>
36 #include <sys/errno.h>
37 #include <limits.h>
38 #include <libnvpair.h>
39 #include <dlfcn.h>
40 #include <libintl.h>
41 #include <sys/systeminfo.h>
42 #include <sys/fs_reparse.h>
43 #include "rp_plugin.h"
44 
45 #define	MAXISALEN	257	/* based on sysinfo(2) man page */
46 
47 static rp_proto_handle_t rp_proto_handle;
48 static rp_proto_plugin_t *rp_proto_list;
49 
50 int rp_plugin_init(void);
51 static void proto_plugin_fini(void);
52 static rp_plugin_ops_t *rp_find_protocol(const char *svctype);
53 
54 extern int errno;
55 static int rp_plugin_inited = 0;
56 
57 /*
58  * reparse_create()
59  *
60  * Create a symlink at the specified 'path' as a reparse point.
61  * This function will fail if path refers to an existing file system
62  * object or an object named string already exists at the given path.
63  *
64  * return 0 if ok else return error code.
65  */
66 int
67 reparse_create(const char *path, const char *string)
68 {
69 	int err;
70 	struct stat sbuf;
71 
72 	if (path == NULL || string == NULL)
73 		return (EINVAL);
74 
75 	if ((err = reparse_validate(string)) != 0)
76 		return (err);
77 
78 	/* check if object exists */
79 	if (lstat(path, &sbuf) == 0)
80 		return (EEXIST);
81 
82 	return (symlink(string, path) ? errno : 0);
83 }
84 
85 /*
86  * reparse_unparse()
87  *
88  * Convert an nvlist back to a string format suitable to write
89  * to the reparse point symlink body.  The string returned is in
90  * allocated memory and must be freed by the caller.
91  *
92  * return 0 if ok else return error code.
93  */
94 int
95 reparse_unparse(nvlist_t *nvl, char **stringp)
96 {
97 	int err, buflen;
98 	char *buf, *stype, *val;
99 	nvpair_t *curr;
100 
101 	if (nvl == NULL || stringp == NULL ||
102 	    ((curr = nvlist_next_nvpair(nvl, NULL)) == NULL))
103 		return (EINVAL);
104 
105 	buflen = SYMLINK_MAX;
106 	if ((buf = malloc(buflen)) == NULL)
107 		return (ENOMEM);
108 
109 	err = 0;
110 	(void) snprintf(buf, buflen, "%s", FS_REPARSE_TAG_STR);
111 	while (curr != NULL) {
112 		if (!(stype = nvpair_name(curr))) {
113 			err = EINVAL;
114 			break;
115 		}
116 		if ((strlcat(buf, FS_TOKEN_START_STR, buflen) >= buflen) ||
117 		    (strlcat(buf, stype, buflen) >= buflen) ||
118 		    (strlcat(buf, ":", buflen) >= buflen) ||
119 		    (nvpair_value_string(curr, &val) != 0) ||
120 		    (strlcat(buf, val, buflen) >= buflen) ||
121 		    (strlcat(buf, FS_TOKEN_END_STR, buflen) >= buflen)) {
122 			err = E2BIG;
123 			break;
124 		}
125 		curr = nvlist_next_nvpair(nvl, curr);
126 	}
127 	if (err != 0) {
128 		free(buf);
129 		return (err);
130 	}
131 	if (strlcat(buf, FS_REPARSE_TAG_END_STR, buflen) >= buflen) {
132 		free(buf);
133 		return (E2BIG);
134 	}
135 
136 	*stringp = buf;
137 	return (0);
138 }
139 
140 /*
141  * reparse_deref()
142  *
143  * Accepts the service-specific item from the reparse point and returns
144  * the service-specific data requested.  The caller specifies the size
145  * of the buffer provided via *bufsz.
146  *
147  * if ok return 0 and *bufsz is updated to contain the actual length of
148  * the returned results, else return error code. If the error code is
149  * EOVERFLOW; results do not fit in the buffer, *bufsz will be updated
150  * to contain the number of bytes needed to hold the results.
151  */
152 int
153 reparse_deref(const char *svc_type, const char *svc_data, char *buf,
154     size_t *bufsz)
155 {
156 	rp_plugin_ops_t *ops;
157 
158 	if ((svc_type == NULL) || (svc_data == NULL) || (buf == NULL) ||
159 	    (bufsz == NULL))
160 		return (EINVAL);
161 
162 	ops = rp_find_protocol(svc_type);
163 	if ((ops != NULL) && (ops->rpo_deref != NULL))
164 		return (ops->rpo_deref(svc_type, svc_data, buf, bufsz));
165 
166 	/* no plugin, return error */
167 	return (ENOTSUP);
168 }
169 
170 /*
171  * reparse_delete()
172  *
173  * Delete a reparse point at a given pathname.  It will fail if
174  * a reparse point does not exist at the given path or the pathname
175  * is not a symlink.
176  *
177  * return 0 if ok else return error code.
178  */
179 int
180 reparse_delete(const char *path)
181 {
182 	struct stat sbuf;
183 
184 	if (path == NULL)
185 		return (EINVAL);
186 
187 	/* check if object exists */
188 	if (lstat(path, &sbuf) != 0)
189 		return (errno);
190 
191 	if ((sbuf.st_mode & S_IFLNK) != S_IFLNK)
192 		return (EINVAL);
193 
194 	return (unlink(path) ? errno : 0);
195 }
196 
197 /*
198  * reparse_add()
199  *
200  * Add a service type entry to a nvlist with a copy of svc_data,
201  * replacing one of the same type if already present.
202  *
203  * return 0 if ok else return error code.
204  */
205 int
206 reparse_add(nvlist_t *nvl, const char *svc_type, const char *svc_data)
207 {
208 	int err;
209 	char *buf;
210 	size_t bufsz;
211 	rp_plugin_ops_t *ops;
212 
213 	if ((nvl == NULL) || (svc_type == NULL) || (svc_data == NULL))
214 		return (EINVAL);
215 
216 	bufsz = SYMLINK_MAX;		/* no need to mess around */
217 	if ((buf = malloc(bufsz)) == NULL)
218 		return (ENOMEM);
219 
220 	ops = rp_find_protocol(svc_type);
221 	if ((ops != NULL) && (ops->rpo_form != NULL))
222 		err = ops->rpo_form(svc_type, svc_data, buf, &bufsz);
223 	else
224 		err = ENOTSUP;		/* no plugin */
225 
226 	if (err != 0) {
227 		free(buf);
228 		return (err);
229 	}
230 
231 	err =  nvlist_add_string(nvl, svc_type, buf);
232 	free(buf);
233 	return (err);
234 }
235 
236 /*
237  * reparse_remove()
238  *
239  * Remove a service type entry from the nvlist, if present.
240  *
241  * return 0 if ok else return error code.
242  */
243 int
244 reparse_remove(nvlist_t *nvl, const char *svc_type)
245 {
246 	if ((nvl == NULL) || (svc_type == NULL))
247 		return (EINVAL);
248 
249 	return (nvlist_remove_all(nvl, svc_type));
250 }
251 
252 /*
253  * Returns true if name is "." or "..", otherwise returns false.
254  */
255 static boolean_t
256 rp_is_dot_or_dotdot(const char *name)
257 {
258 	if (*name != '.')
259 		return (B_FALSE);
260 
261 	if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))
262 		return (B_TRUE);
263 
264 	return (B_FALSE);
265 }
266 
267 static void
268 proto_plugin_fini()
269 {
270 	rp_proto_plugin_t *p;
271 
272 	/*
273 	 * Protocols may call this framework during _fini
274 	 */
275 	for (p = rp_proto_list; p != NULL; p = p->plugin_next) {
276 		if (p->plugin_ops->rpo_fini)
277 			p->plugin_ops->rpo_fini();
278 	}
279 	while ((p = rp_proto_list) != NULL) {
280 		rp_proto_list = p->plugin_next;
281 		if (p->plugin_handle != NULL)
282 			(void) dlclose(p->plugin_handle);
283 		free(p);
284 	}
285 
286 	if (rp_proto_handle.rp_ops != NULL) {
287 		free(rp_proto_handle.rp_ops);
288 		rp_proto_handle.rp_ops = NULL;
289 	}
290 	rp_proto_handle.rp_num_proto = 0;
291 }
292 
293 /*
294  * rp_plugin_init()
295  *
296  * Initialize the service type specific plugin modules.
297  * For each reparse service type, there should be a plugin library for it.
298  * This function walks /usr/lib/reparse directory for plugin libraries.
299  * For each plugin library found, initialize it and add it to the internal
300  * list of service type plugin. These are used for service type specific
301  * operations.
302  */
303 int
304 rp_plugin_init()
305 {
306 	int err, ret = RP_OK;
307 	char isa[MAXISALEN], dirpath[MAXPATHLEN], path[MAXPATHLEN];
308 	int num_protos = 0;
309 	rp_proto_handle_t *rp_hdl;
310 	rp_proto_plugin_t *proto, *tmp;
311 	rp_plugin_ops_t *plugin_ops;
312 	struct stat st;
313 	void *dlhandle;
314 	DIR *dir;
315 	struct dirent *dent;
316 
317 #if defined(_LP64)
318 	if (sysinfo(SI_ARCHITECTURE_64, isa, MAXISALEN) == -1)
319 		isa[0] = '\0';
320 #else
321 	isa[0] = '\0';
322 #endif
323 
324 	(void) snprintf(dirpath, MAXPATHLEN,
325 	    "%s/%s", RP_LIB_DIR, isa);
326 
327 	if ((dir = opendir(dirpath)) == NULL)
328 		return (RP_NO_PLUGIN_DIR);
329 
330 	while ((dent = readdir(dir)) != NULL) {
331 		if (rp_is_dot_or_dotdot(dent->d_name))
332 			continue;
333 
334 		(void) snprintf(path, MAXPATHLEN,
335 		    "%s/%s", dirpath, dent->d_name);
336 
337 		/*
338 		 * If file doesn't exist, don't try to map it
339 		 */
340 		if (stat(path, &st) < 0)
341 			continue;
342 		if ((dlhandle = dlopen(path, RTLD_FIRST|RTLD_LAZY)) == NULL)
343 			continue;
344 
345 		plugin_ops = (rp_plugin_ops_t *)
346 		    dlsym(dlhandle, "rp_plugin_ops");
347 		if (plugin_ops == NULL) {
348 			(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
349 			    "Error in plugin ops for service type %s\n%s\n"),
350 			    dent->d_name, dlerror());
351 			(void) dlclose(dlhandle);
352 			continue;
353 		}
354 		proto = (rp_proto_plugin_t *)
355 		    calloc(1, sizeof (rp_proto_plugin_t));
356 		if (proto == NULL) {
357 			(void) dlclose(dlhandle);
358 			(void) fprintf(stderr,
359 			    dgettext(TEXT_DOMAIN, "No memory for plugin %s\n"),
360 			    dent->d_name);
361 			ret = RP_NO_MEMORY;
362 			break;
363 		}
364 
365 		proto->plugin_ops = plugin_ops;
366 		proto->plugin_handle = dlhandle;
367 		num_protos++;
368 		proto->plugin_next = rp_proto_list;
369 		rp_proto_list = proto;
370 	}
371 
372 	(void) closedir(dir);
373 
374 	if ((num_protos == 0) && (ret == 0))
375 		ret = RP_NO_PLUGIN;
376 	/*
377 	 * There was an error, so cleanup prior to return of failure.
378 	 */
379 	if (ret != RP_OK) {
380 		proto_plugin_fini();
381 		return (ret);
382 	}
383 
384 	rp_proto_handle.rp_ops = (rp_plugin_ops_t **)calloc(num_protos,
385 	    sizeof (rp_plugin_ops_t *));
386 	if (!rp_proto_handle.rp_ops) {
387 		proto_plugin_fini();
388 		return (RP_NO_MEMORY);
389 	}
390 
391 	rp_hdl = &rp_proto_handle;
392 	rp_hdl->rp_num_proto = 0;
393 	for (tmp = rp_proto_list; rp_hdl->rp_num_proto < num_protos &&
394 	    tmp != NULL; tmp = tmp->plugin_next) {
395 
396 		err = RP_OK;
397 		if (tmp->plugin_ops->rpo_init != NULL)
398 			err = tmp->plugin_ops->rpo_init();
399 		if (err != RP_OK)
400 			continue;
401 		rp_hdl->rp_ops[rp_hdl->rp_num_proto++] = tmp->plugin_ops;
402 	}
403 
404 	return (rp_hdl->rp_num_proto > 0 ? RP_OK : RP_NO_PLUGIN);
405 }
406 
407 
408 /*
409  * find_protocol()
410  *
411  * Search the plugin list for the specified protocol and return the
412  * ops vector.  return NULL if protocol is not defined.
413  */
414 static rp_plugin_ops_t *
415 rp_find_protocol(const char *svc_type)
416 {
417 	int i;
418 	rp_plugin_ops_t *ops = NULL;
419 
420 	if (svc_type == NULL)
421 		return (NULL);
422 
423 	if (rp_plugin_inited == 0) {
424 		if (rp_plugin_init() == RP_OK)
425 			rp_plugin_inited = 1;
426 		else
427 			return (NULL);
428 	}
429 
430 	for (i = 0; i < rp_proto_handle.rp_num_proto; i++) {
431 		ops = rp_proto_handle.rp_ops[i];
432 		if (ops->rpo_supports_svc(svc_type))
433 			return (ops);
434 
435 	}
436 	return (NULL);
437 }
438