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