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 <unistd.h>
29#include <strings.h>
30#include <string.h>
31#include <limits.h>
32#include <libnvpair.h>
33#include <locale.h>
34#include <sys/stat.h>
35#include <sys/fs_reparse.h>
36#include <rp_plugin.h>
37#include <uuid/uuid.h>
38#include <sys/types.h>
39#include <sys/stat.h>
40#include <fcntl.h>
41#include <priv.h>
42#include <nfs/nfs4.h>
43#include <rpcsvc/nfs4_prot.h>
44#include "ref_subr.h"
45
46#ifndef TEXT_DOMAIN
47#define	TEXT_DOMAIN	"SUNW_OST_OSCMD"
48#endif /* TEXT_DOMAIN */
49
50extern int errno;
51
52void
53usage()
54{
55	fprintf(stderr, gettext("Usage:\n"));
56	fprintf(stderr,
57	    gettext("\tnfsref [-t type] add path location [location ...]\n"));
58	fprintf(stderr, gettext("\tnfsref [-t type] remove path\n"));
59	fprintf(stderr, gettext("\tnfsref [-t type] lookup path\n"));
60}
61
62/*
63 * Copy a string from source to destination, escaping space
64 * with a backslash and escaping the escape character as well.
65 */
66int
67add_escape(char *src, char *dest, int limit)
68{
69	char *sp, *dp;
70	int destlen = 0;
71
72	sp = src;
73	dp = dest;
74
75	while (*sp && destlen < limit) {
76		if (*sp == '\\') {
77			*dp++ = '\\';
78			*dp++ = '\\';
79			destlen++;
80		} else if (*sp == ' ') {
81			*dp++ = '\\';
82			*dp++ = ' ';
83			destlen++;
84		} else
85			*dp++ = *sp;
86		destlen++;
87		sp++;
88	}
89	if (limit <= 0)
90		return (-1);
91	return (destlen);
92}
93
94int
95addref(char *sl_path, char *svc_type, int optind, int argc, char *argv[])
96{
97	int err, fd, i, len, oldlen, notfound = 0;
98	char *text, *location;
99	nvlist_t *nvl = NULL;
100	char buf[SYMLINK_MAX];
101	struct stat sbuf;
102
103	/* Get an nvlist */
104	nvl = reparse_init();
105	if (nvl == NULL)
106		return (ENOMEM);
107
108	/* Get the reparse point data, if the RP exists */
109	err = readlink(sl_path, buf, SYMLINK_MAX);
110	if (err == -1) {
111		if (errno == ENOENT) {
112			notfound = 1;
113			err = 0;
114		} else {
115			reparse_free(nvl);
116			return (errno);
117		}
118	} else {
119		buf[err] = '\0';
120	}
121
122	/* Get any data into nvlist */
123	if (notfound == 0)
124		err = reparse_parse(buf, nvl);
125	if (err != 0) {
126		reparse_free(nvl);
127		return (err);
128	}
129
130	/*
131	 * Accumulate multiple locations on the command line into 'buf'
132	 */
133	oldlen = len = 0;
134	location = NULL;
135	for (i = optind; i < argc; i++) {
136		bzero(buf, sizeof (buf));
137		len += add_escape(argv[i], buf, SYMLINK_MAX) + 2;
138		location = realloc(location, len);
139		location[oldlen] = '\0';
140		oldlen = len;
141		strlcat(location, buf, len);
142		strlcat(location, " ", len);
143	}
144	location[len - 2] = '\0';
145
146	/* Add to the list */
147	err = reparse_add(nvl, svc_type, location);
148	if (err) {
149		reparse_free(nvl);
150		return (err);
151	}
152
153	/* Get the new or modified symlink contents */
154	err = reparse_unparse(nvl, &text);
155	reparse_free(nvl);
156	if (err)
157		return (err);
158
159	/* Delete first if found */
160	if (notfound == 0) {
161		err =  reparse_delete(sl_path);
162		if (err) {
163			free(text);
164			return (err);
165		}
166	}
167
168	/* Finally, write out the reparse point */
169	err = reparse_create(sl_path, text);
170	free(text);
171	if (err)
172		return (err);
173
174	err = lstat(sl_path, &sbuf);
175	if (err == 0 && strcasecmp(sbuf.st_fstype, "ZFS") != 0)
176		printf(gettext(
177		    "Warning: referrals do not work on this filesystem\n"));
178
179	if (notfound)
180		printf(gettext("Created reparse point %s\n"), sl_path);
181	else
182		printf(gettext("Added to reparse point %s\n"), sl_path);
183
184	return (0);
185}
186
187int
188delref(char *sl_path, char *svc_type)
189{
190	char *cp;
191	char *svc_data;
192	int err;
193	nvlist_t *nvl;
194	nvpair_t *curr;
195	char buf[SYMLINK_MAX];
196	int fd, fd2;
197	FILE *fp, *fp2;
198	char uuid[UUID_PRINTABLE_STRING_LENGTH], path[256], loc[2048];
199
200	/* Get an nvlist */
201	if (!(nvl = reparse_init()))
202		return (ENOMEM);
203
204	/* Get the symlink data (should be there) */
205	err = readlink(sl_path, buf, SYMLINK_MAX);
206	if (err == -1) {
207		reparse_free(nvl);
208		return (errno);
209	}
210	buf[err] = '\0';
211
212	/* Get the records into the nvlist */
213	err = reparse_parse(buf, nvl);
214	if (err) {
215		reparse_free(nvl);
216		return (err);
217	}
218
219	/* Remove from nvlist */
220	err = reparse_remove(nvl, svc_type);
221	if (err) {
222		reparse_free(nvl);
223		return (err);
224	}
225
226	/* Any list entries left? If so, turn nvlist back to string. */
227	curr = nvlist_next_nvpair(nvl, NULL);
228	if (curr != NULL) {
229		err = reparse_unparse(nvl, &cp);
230		reparse_free(nvl);
231		if (err)
232			return (err);
233	} else {
234		reparse_free(nvl);
235		cp = NULL;
236	}
237
238	/* Finally, delete and perhaps recreate the reparse point */
239	err = reparse_delete(sl_path);
240	if (err) {
241		free(cp);
242		return (err);
243	}
244
245	if (cp != NULL) {
246		err = reparse_create(sl_path, cp);
247		free(cp);
248		if (err)
249			return (err);
250	}
251	printf(gettext("Removed svc_type '%s' from %s\n"), svc_type, sl_path);
252	return (err);
253}
254
255int
256lookup(char *sl_path, char *svc_type, int type_set)
257{
258	int err;
259	size_t bufsize;
260	char buf[1024];
261	char *type, *svc_data;
262	nvlist_t *nvl;
263	nvpair_t *curr;
264	fs_locations4 fsl;
265	XDR xdr;
266
267	if (!(nvl = reparse_init()))
268		return (-1);
269
270	/* Get reparse point data */
271	err = readlink(sl_path, buf, SYMLINK_MAX);
272	if (err == -1)
273		return (errno);
274	buf[err] = '\0';
275
276	/* Parse it to an nvlist */
277	err = reparse_parse(buf, nvl);
278	if (err) {
279		reparse_free(nvl);
280		return (err);
281	}
282
283	/* Look for entries of the requested service type */
284	curr = NULL;
285	while ((curr = nvlist_next_nvpair(nvl, curr)) != NULL) {
286		type = nvpair_name(curr);
287		if (type_set && strcasecmp(type, svc_type) == 0)
288			break;
289		if (!type_set && strncasecmp(type, "nfs", 3) == 0)
290			break;
291	}
292	if (curr == NULL) {
293		reparse_free(nvl);
294		return (ENOENT);
295	}
296
297	/* Get the service data and look it up */
298	nvpair_value_string(curr, &svc_data);
299
300	bufsize = sizeof (buf);
301	err = reparse_deref(type, svc_data, buf, &bufsize);
302	reparse_free(nvl);
303	if (err)
304		return (err);
305
306	xdrmem_create(&xdr, buf, bufsize, XDR_DECODE);
307	err = xdr_fs_locations4(&xdr, &fsl);
308	XDR_DESTROY(&xdr);
309	if (err != TRUE)
310		return (ENOENT);
311	printf(gettext("%s points to: "), sl_path);
312	print_referral_summary(&fsl);
313	return (0);
314}
315
316extern char *optarg;
317extern int optind, optopt;
318
319int
320main(int argc, char *argv[])
321{
322	char c, *command, *sl_path, *svc_type;
323	int type_set, err;
324
325	(void) setlocale(LC_ALL, "");
326	(void) textdomain(TEXT_DOMAIN);
327
328	svc_type = "nfs-basic";		/* Default from SMF some day */
329	type_set = 0;			/* Lookup any nfs type */
330
331	/* Look for options (just the service type now) */
332	while ((c = getopt(argc, argv, "t:")) != -1) {
333		switch (c) {
334		case 't':
335			svc_type = optarg;
336			type_set = 1;
337			break;
338
339		default:
340			usage();
341			exit(1);
342		}
343	}
344
345	/* Make sure there's at least a command and one argument */
346	if (optind + 1 >= argc) {
347		usage();
348		exit(1);
349	}
350
351	err = rp_plugin_init();
352	switch (err) {
353	case RP_OK:
354		break;
355	case RP_NO_PLUGIN_DIR:
356		fprintf(stderr,
357		    gettext("Warning: no plugin directory, continuing...\n"));
358		break;
359	case RP_NO_PLUGIN:
360		fprintf(stderr,
361		    gettext("Warning: no plugin found, continuing...\n"));
362		break;
363	case RP_NO_MEMORY:
364		fprintf(stderr,
365		    gettext("rp_plugin_init failed, no memory\n"));
366		exit(0);
367	default:
368		fprintf(stderr,
369		    gettext("rp_plugin_init failed, error %d\n"), err);
370		exit(0);
371	}
372
373	command = argv[optind++];
374	sl_path = argv[optind++];
375
376	if (strcmp(command, "add") == 0) {
377
378		if (optind >= argc) {
379			usage();
380			exit(1);
381		}
382
383		err = addref(sl_path, svc_type, optind, argc, argv);
384
385	} else if (strcmp(command, "remove") == 0) {
386
387		err = delref(sl_path, svc_type);
388
389	} else if (strcmp(command, "lookup") == 0) {
390
391		err = lookup(sl_path, svc_type, type_set);
392
393	} else {
394		usage();
395		exit(1);
396	}
397	if (err != 0)
398		fprintf(stderr, gettext("Command %s failed: %s\n"), command,
399		    strerror(err));
400	return (err);
401}
402