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
51static rp_proto_handle_t rp_proto_handle;
52static rp_proto_plugin_t *rp_proto_list;
53
54int rp_plugin_init(void);
55static void proto_plugin_fini(void);
56static rp_plugin_ops_t *rp_find_protocol(const char *svctype);
57
58extern int errno;
59static 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 */
70int
71reparse_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 */
98int
99reparse_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 */
156int
157reparse_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 */
183int
184reparse_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 */
209int
210reparse_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 */
247int
248reparse_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 */
259static boolean_t
260rp_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
271static void
272proto_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 */
307int
308rp_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 */
418static rp_plugin_ops_t *
419rp_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