xref: /illumos-gate/usr/src/cmd/fs.d/autofs/ns_fnmount.c (revision e8d80663e4f91871f843bb8ad9108dc0b76dfcf3)
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  * ns_fnmount.c
23  *
24  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <ctype.h>
32 #include <syslog.h>
33 #include <rpc/rpc.h>
34 #include <rpcsvc/nis.h>
35 #include <xfn/xfn.h>
36 #include "automount.h"
37 #include "ns_fnutils.h"
38 
39 
40 /*
41  * The maximum sizes of map names, key names, composite names, and status
42  * descriptions, including the trailing '\0'.
43  */
44 #define	MAPNAMESZ	(size_t)(AUTOFS_MAXCOMPONENTLEN + 1)
45 #define	KEYNAMESZ	(size_t)(AUTOFS_MAXCOMPONENTLEN + 1)
46 #define	COMPNAMESZ	(size_t)(MAPNAMESZ - FNPREFIXLEN + KEYNAMESZ - 2)
47 #define	DESCSZ		(size_t)512
48 
49 typedef struct mapent	mapent;
50 typedef struct mapline	mapline;
51 
52 
53 /*
54  * The name of an attribute.
55  */
56 static const FN_identifier_t attr_exported = {FN_ID_STRING, 8, "exported"};
57 
58 
59 /*
60  * Given a request by a particular user to mount the name "key" under
61  * map/context "map", and a set of default mount options, return (in
62  * "res") either a list of mapents giving the mounts that need to be
63  * performed, or a symbolic link to be created for a user-relative
64  * context.  If "shallow" is true return, in place of the list of
65  * mapents, a single mapent representing an indirect mount point.
66  *
67  *	void
68  *	getmapent_fn(char *key, char *map, char *opts, uid_t uid,
69  *	      bool_t shallow, getmapent_fn_res *res);
70  */
71 
72 /*
73  * Given a reference, its composite name, default mount options, and a
74  * mapent root, return a list of mapents to mount.  If "shallow" is
75  * true return, in place of the list of mapents, a single mapent
76  * representing an indirect mount point.  The map and key strings are
77  * pieces of the composite name such that:
78  * "FNPREFIX/cname" == "map/key".
79  */
80 static mapent *
81 process_ref(const FN_ref_t *ref, const char *cname, char *map, char *key,
82     char *opts, char *root, bool_t shallow, FN_status_t *status);
83 
84 /*
85  * Traverse the namespace to find a frontier below ref along which
86  * future mounts may need to be triggered.  Add to mapents the
87  * corresponding direct autofs mount points.
88  *     map:	map name for ref
89  *     maplen:	strlen(map)
90  *     mntpnt:	suffix of map where the current mount request begins
91  *		(starts off as "", and grows as we traverse the namespace)
92  *     opts:	default mount options
93  *     status:	passed from above to avoid having to allocate one on each call
94  * Works by calling frontier_aux() on each name bound under ref.
95  * Return the new mapents, or free mapents and return NULL on failure.
96  */
97 static mapent *
98 frontier(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
99     char *mntpnt, char *opts, FN_status_t *status);
100 
101 /*
102  * Called by frontier(), once for each "name" that it finds.  map is
103  * passed unchanged from frontier().  ref is the reference named by
104  * "map/name".  If ref is found to be along the frontier, add the
105  * corresponding direct autofs mount point to mapents.  Otherwise
106  * continue traversing the namespace to find the frontier.  Other
107  * arguments and the return value are as for frontier().
108  */
109 static mapent *
110 frontier_aux(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
111     char *mntpnt, const char *name, char *opts, FN_status_t *status);
112 
113 /*
114  * Given a reference with an address type of ADDR_HOST and its
115  * composite name, check the attr_exported attribute to determine if
116  * the corresponding directory is exported.  Return FALSE on error.
117  */
118 static bool_t
119 exported(const FN_ref_t *ref, const char *cname, FN_status_t *status);
120 
121 /*
122  * Find a reference's address type and, if "data" is not NULL, its
123  * data string.  If there is no address of a known type, set *typep to
124  * NUM_ADDRTYPES; if there are several, stop after finding the first.
125  * Return 0 on success.
126  */
127 static int
128 addr_from_ref(const FN_ref_t *ref, const char *cname, addrtype_t *typep,
129     char *data, size_t datasz);
130 
131 /*
132  * Decode an address's data into a string.  Return 0 on success.
133  */
134 static int
135 str_from_addr(const char *cname, const FN_ref_addr_t *addr, char str[],
136     size_t strsz);
137 
138 /*
139  * Given a map name and its current length, append "/name".  Return
140  * the new length.  On error, syslog a warning and return 0.
141  */
142 static size_t
143 append_mapname(char *map, size_t maplen, const char *name);
144 
145 /*
146  * Concatenate two strings using the given separator.  The result is a
147  * newly-allocated string, or NULL on error.
148  */
149 static char *
150 concat(const char *s1, char sep, const char *s2);
151 
152 /*
153  * Add the "nosuid" option to a mapent.  Also check for a sneaky
154  * hacker trying to override this option by manually inserting a
155  * multiple mount entry into the XFN namespace.  Return FALSE on error.
156  */
157 static bool_t
158 safe_mapent(mapent *me);
159 
160 /*
161  * Append "nosuid" to a list of options.  The result is a
162  * newly-allocated string, or NULL on error.
163  */
164 static char *
165 safe_opts(const char *opts);
166 
167 /*
168  * Trim comments and trailing whitespace from ml->linebuf, then
169  * unquote it and leave the result in ml.  Return 0 on success.
170  */
171 static int
172 trim_line(mapline *ml);
173 
174 /*
175  * Determine whether ml contains an option string (such as "-ro") and
176  * nothing else.
177  */
178 static bool_t
179 opts_only(const mapline *ml);
180 
181 /*
182  * Allocate a new mapent structure.  The arguments must have been
183  * malloc'ed, and are owned by the mapent; they are freed if
184  * new_mapent() fails.  If any argument is NULL, the call fails and a
185  * memory allocation failure is logged.  A root argument of 'noroot'
186  * indicates that the map_root field does not need to be set (it's
187  * only needed in the first of a list of mapents).
188  */
189 static char *noroot = "[no root]";
190 static mapent *
191 new_mapent(char *root, char *mntpnt, char *fstype, char *mntopts, char *host,
192     char *dir);
193 
194 /*
195  * Determine whether cname is a user-relative binding -- such as "myself" --
196  * in the initial context.
197  */
198 static bool_t
199 is_user_relative(const char *cname);
200 
201 /*
202  * Given the name of a user-relative binding, return an equivalent
203  * name that is not user-relative.
204  */
205 static char *
206 equiv_name(FN_ctx_t *, const char *cname, FN_status_t *);
207 
208 void
209 getmapent_fn(char *key, char *map, char *opts, uid_t uid, bool_t shallow,
210     getmapent_fn_res *res)
211 {
212 	size_t			maplen;
213 	FN_status_t		*status;
214 	FN_ctx_t		*init_ctx = NULL;
215 	int			statcode;
216 	char			cname[COMPNAMESZ];
217 	FN_composite_name_t	*compname;
218 	FN_ref_t		*ref;
219 	char			mapname[MAPNAMESZ];
220 	char			*root;
221 
222 	res->type = FN_NONE;
223 	res->m_or_l.mapents = NULL;
224 
225 	if (init_fn() != 0) {
226 		return;
227 	}
228 
229 	/*
230 	 * For direct mounts, the key is the entire path, and the map
231 	 * name already has the final key component appended.  Split
232 	 * apart the map name and key.  The "root" of the mapent is
233 	 * "/key" for indirect mounts, and "" for direct mounts.
234 	 */
235 	strcpy(mapname, map);
236 	if (key[0] == '/') {
237 		key = strrchr(key, '/') + 1;
238 		*strrchr(mapname, '/') = '\0';
239 		root = strdup("");
240 	} else {
241 		root = concat("", '/', key);
242 	}
243 	map = mapname;
244 	maplen = strlen(map);
245 
246 	if ((maplen - FNPREFIXLEN + strlen(key)) >= COMPNAMESZ) {
247 		if (verbose) {
248 			syslog(LOG_ERR, "name %s/%s too long", map, key);
249 		}
250 		return;
251 	}
252 	if (maplen == FNPREFIXLEN) {
253 		strcpy(cname, key);
254 	} else {
255 		sprintf(cname, "%s/%s", map + FNPREFIXLEN + 1, key);
256 	}
257 
258 	status = fn_status_create();
259 	if (status == NULL) {
260 		if (verbose) {
261 			syslog(LOG_ERR, "Could not create FNS status object");
262 		}
263 		return;
264 	}
265 	init_ctx = _fn_ctx_handle_from_initial_with_uid(uid, 0, status);
266 	if (init_ctx == NULL) {
267 		logstat(status, "", "No initial context");
268 		goto done;
269 	}
270 
271 #ifndef XFN1ENV
272 	if (is_user_relative(cname)) {
273 		res->type = FN_SYMLINK;
274 		res->m_or_l.symlink = equiv_name(init_ctx, cname, status);
275 		goto done;
276 	}
277 #endif
278 
279 	if ((compname = new_cname(cname)) == NULL) {
280 		goto done;
281 	}
282 	ref = fn_ctx_lookup(init_ctx, compname, status);
283 	statcode = fn_status_code(status);
284 	fn_composite_name_destroy(compname);
285 
286 	if (trace > 1 && !shallow) {
287 		trace_prt(1, "  FNS traversal: %s\n", cname);
288 	}
289 
290 	if (ref == NULL) {
291 		if ((statcode != FN_E_NAME_NOT_FOUND) &&
292 		    (statcode != FN_E_NOT_A_CONTEXT)) {
293 			logstat(status, "lookup failed on", cname);
294 		}
295 		goto done;
296 	}
297 
298 	res->type = FN_MAPENTS;
299 	res->m_or_l.mapents =
300 	    process_ref(ref, cname, map, key, opts, root, shallow, status);
301 	fn_ref_destroy(ref);
302 done:
303 	fn_ctx_handle_destroy(init_ctx);
304 	fn_status_destroy(status);
305 }
306 
307 
308 static mapent *
309 process_ref(const FN_ref_t *ref, const char *cname, char *map, char *key,
310     char *opts, char *root, bool_t shallow, FN_status_t *status)
311 {
312 	addrtype_t	addrtype;
313 	mapline		ml;
314 	char		*addrdata = ml.linebuf;
315 	mapent		*mapents;
316 	bool_t		self;
317 	char		*homedir;
318 	size_t		maplen;
319 	char		*colon;
320 	char		*nfshost;
321 	char		*nfsdir;
322 
323 	if ((reftype(ref) < NUM_REFTYPES) &&
324 	    (addr_from_ref(ref, cname, &addrtype, addrdata, LINESZ) == 0)) {
325 
326 		switch (addrtype) {
327 		case ADDR_MOUNT:
328 			if (trim_line(&ml) != 0) {
329 				return (NULL);
330 			}
331 			if (opts_only(&ml)) {
332 				/* parse_entry() can't handle such lines */
333 				if (macro_expand("&", ml.linebuf,
334 				    ml.lineqbuf, LINESZ)) {
335 					syslog(LOG_ERR,
336 					"%s/%s: opts too long (max %d chars)",
337 					    FNPREFIX, cname, LINESZ - 1);
338 					return (NULL);
339 				}
340 				opts = ml.linebuf + 1;	/* skip '-' */
341 				goto indirect;
342 			}
343 			mapents = parse_entry(key, map, opts, &ml, NULL, 0,
344 			    TRUE);
345 			if (mapents == NULL || !safe_mapent(mapents)) {
346 				free_mapent(mapents);
347 				return (NULL);
348 			}
349 			free(mapents->map_root);
350 			mapents->map_root = root;
351 			break;
352 
353 		case ADDR_HOST:
354 			/*
355 			 * Address is of the form "host:dir".
356 			 * If "dir" is not supplied, it defaults to "/".
357 			 */
358 			colon = strchr(addrdata, ':');
359 			if (colon == NULL || colon[1] == '\0') {
360 				nfsdir = strdup("/");
361 			} else {
362 				*colon = '\0';
363 				nfsdir = strdup(colon + 1);
364 			}
365 			nfshost = strdup(addrdata);
366 			/*
367 			 * If nfshost is the local host, the NFS mount
368 			 * request will be converted to a loopback
369 			 * mount.  Otherwise check that the file system
370 			 * is exported.
371 			 */
372 			if (nfshost != NULL) {
373 				self = self_check(nfshost);
374 				if (!self && !exported(ref, cname, status)) {
375 					if (transient(status)) {
376 						return (NULL);
377 					} else {
378 						goto indirect;
379 					}
380 				}
381 			}
382 			mapents = new_mapent(root, strdup(""), strdup("nfs"),
383 			    safe_opts(opts), nfshost, nfsdir);
384 			if (self && !shallow) {
385 				return (mapents);
386 			}
387 			break;
388 
389 		case ADDR_USER:
390 			homedir = strdup(addrdata);
391 			homedir[strcspn(homedir, " \t\r\n")] = '\0';
392 			mapents = new_mapent(root, strdup(""), strdup("lofs"),
393 			    strdup(opts), strdup(""), homedir);
394 			break;
395 		}
396 
397 		if (mapents == NULL) {
398 			return (NULL);
399 		}
400 		if (shallow) {
401 			mapents->map_root = NULL;	/* don't free "root" */
402 			free_mapent(mapents);
403 			goto indirect;
404 		}
405 
406 		/* "map" => "map/key" */
407 		if ((maplen = append_mapname(map, strlen(map), key)) == 0) {
408 			return (mapents);
409 		}
410 		return (frontier(mapents, ref, map, maplen, map + maplen,
411 		    opts, status));
412 	}
413 
414 	/* Ref type wasn't recognized. */
415 
416 indirect:
417 	/* Install an indirect autofs mount point. */
418 	return (new_mapent(root, strdup(""), strdup("autofs"), strdup(opts),
419 	    strdup(""), concat(map, '/', key)));
420 }
421 
422 
423 /*
424  * All that this function really does is call frontier_aux() on every
425  * name bound under ref.  The rest is error checking(!)
426  *
427  * The error handling strategy is to reject the entire mount request
428  * (by freeing mapents) if any (potentially) transient error occurs,
429  * and to treat nontransient errors as holes in the affected portions
430  * of the namespace.
431  */
432 static mapent *
433 frontier(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
434     char *mntpnt, char *opts, FN_status_t *status)
435 {
436 	FN_ctx_t		*ctx;
437 	FN_bindinglist_t	*bindings = NULL;
438 	FN_ref_t		*child_ref;
439 	FN_string_t		*child_s;
440 	const char		*child;
441 	unsigned int		statcode;
442 
443 	ctx = fn_ctx_handle_from_ref(ref, XFN2(0) status);
444 	if (ctx == NULL) {
445 		if (fn_status_code(status) != FN_E_NO_SUPPORTED_ADDRESS) {
446 			logstat(status, "from_ref failed for", map);
447 		}
448 		goto checkerr_return;
449 	}
450 
451 	bindings = fn_ctx_list_bindings(ctx, empty_cname, status);
452 	fn_ctx_handle_destroy(ctx);
453 	if (bindings == NULL) {
454 		logstat(status, "list_bindings failed for", map);
455 		goto checkerr_return;
456 	}
457 
458 	while ((child_s = fn_bindinglist_next(bindings, &child_ref, status))
459 	    != NULL) {
460 		child = (const char *)fn_string_str(child_s, &statcode);
461 		if (child == NULL) {
462 			if (verbose) {
463 				syslog(LOG_ERR,
464 				    "FNS string error listing %s", map);
465 			}
466 			fn_string_destroy(child_s);
467 			goto err_return;
468 		}
469 		mapents = frontier_aux(mapents, child_ref, map, maplen,
470 		    mntpnt, child, opts, status);
471 		fn_string_destroy(child_s);
472 		fn_ref_destroy(child_ref);
473 		if (mapents == NULL) {
474 			goto noerr_return;
475 		}
476 	}
477 	if (fn_status_is_success(status)) {
478 		goto noerr_return;
479 	} else {
480 		logstat(status, "error while listing", map);
481 		/* Fall through to checkerr_return. */
482 	}
483 
484 checkerr_return:
485 	if (!transient(status)) {
486 		goto noerr_return;
487 	}
488 err_return:
489 	free_mapent(mapents);
490 	mapents = NULL;
491 noerr_return:
492 	fn_bindinglist_destroy(bindings XFN1(status));
493 	return (mapents);
494 }
495 
496 
497 static mapent *
498 frontier_aux(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
499     char *mntpnt, const char *name, char *opts, FN_status_t *status)
500 {
501 	addrtype_t	addrtype;
502 	bool_t		at_frontier;
503 	mapent		*me;
504 	size_t		maplen_save = maplen;
505 	char		*cname = map + FNPREFIXLEN + 1;	/* for error msgs */
506 
507 	if (reftype(ref) >= NUM_REFTYPES) {
508 		/*
509 		 * We could instead install an indirect autofs mount point
510 		 * here.  That would allow, for example, a user to be bound
511 		 * beneath a file system.
512 		 */
513 		return (mapents);
514 	}
515 
516 	/* "map" => "map/name" */
517 	if ((maplen = append_mapname(map, maplen, name)) == 0) {
518 		return (mapents);
519 	}
520 	if (trace > 1) {
521 		trace_prt(1, "  FNS traversal: %s/\n", cname);
522 	}
523 
524 	/*
525 	 * If this is an address type that we know how to mount, then
526 	 * we have reached the frontier.
527 	 */
528 	at_frontier = (addr_from_ref(ref, cname, &addrtype, NULL, 0) == 0);
529 	/*
530 	 * For an ADDR_HOST address, treat a non-exported directory as
531 	 * if the address type were not known:  continue searching for
532 	 * exported subdirectories.
533 	 */
534 	if (at_frontier && (addrtype == ADDR_HOST)) {
535 		if (!exported(ref, cname, status)) {
536 			if (transient(status)) {
537 				free_mapent(mapents);
538 				return (NULL);
539 			} else {
540 				at_frontier = FALSE;
541 			}
542 		}
543 	}
544 	/*
545 	 * If we have reached the frontier, install a direct autofs
546 	 * mount point (which will trigger the actual mount if the
547 	 * user steps on it later).  Otherwise, continue traversing
548 	 * the namespace looking for known address types.
549 	 */
550 	if (at_frontier) {
551 		opts = (opts[0] != '\0')
552 		    ? concat(opts, ',', "direct")
553 		    : strdup("direct");
554 		me = new_mapent(noroot, strdup(mntpnt), strdup("autofs"), opts,
555 		    strdup(""), strdup(map));
556 		if (me != NULL) {
557 			/* Link new mapent into list (not at the head). */
558 			me->map_next = mapents->map_next;
559 			mapents->map_next = me;
560 		} else {
561 			free_mapent(mapents);
562 			mapents = NULL;
563 		}
564 	} else {
565 		mapents =
566 		    frontier(mapents, ref, map, maplen, mntpnt, opts, status);
567 	}
568 	map[maplen_save] = '\0';	/* "map/name" => "map" */
569 	return (mapents);
570 }
571 
572 
573 static bool_t
574 exported(const FN_ref_t *ref, const char *cname, FN_status_t *status)
575 {
576 	FN_ctx_t		*ctx;
577 	FN_attribute_t		*attr;
578 
579 	ctx = fn_ctx_handle_from_ref(ref, XFN2(0) status);
580 	if (ctx == NULL) {
581 		logstat(status, "from_ref failed for", cname);
582 		return (FALSE);
583 	}
584 	attr = fn_attr_get(ctx, empty_cname, &attr_exported, XFN2(1) status);
585 	fn_ctx_handle_destroy(ctx);
586 
587 	switch (fn_status_code(status)) {
588 	case FN_SUCCESS:
589 		fn_attribute_destroy(attr);
590 		break;
591 	case FN_E_NO_SUCH_ATTRIBUTE:
592 		break;
593 	default:
594 		logstat(status, "could not get attributes for", cname);
595 	}
596 	return (attr != NULL);
597 }
598 
599 
600 static int
601 addr_from_ref(const FN_ref_t *ref, const char *cname, addrtype_t *typep,
602     char *data, size_t datasz)
603 {
604 	const FN_ref_addr_t	*addr;
605 	void			*iter_pos;
606 
607 	addr = fn_ref_first(ref, &iter_pos);
608 	if (addr == NULL) {
609 		if (verbose) {
610 			syslog(LOG_ERR, "FNS ref with no address: %s", cname);
611 		}
612 		return (-1);
613 	}
614 	while (addr != NULL) {
615 		*typep = addrtype(addr);
616 		if (*typep < NUM_ADDRTYPES) {
617 			return ((data != NULL)
618 			    ? str_from_addr(cname, addr, data, datasz)
619 			    : 0);
620 		}
621 		addr = fn_ref_next(ref, &iter_pos);
622 	}
623 	return (-1);
624 }
625 
626 
627 static int
628 str_from_addr(const char *cname, const FN_ref_addr_t *addr, char str[],
629     size_t strsz)
630 {
631 	XDR	xdr;
632 	int	res;
633 
634 	xdrmem_create(&xdr, (caddr_t)fn_ref_addr_data(addr),
635 	    fn_ref_addr_length(addr), XDR_DECODE);
636 	if (!xdr_string(&xdr, &str, strsz)) {
637 		if (verbose) {
638 			syslog(LOG_ERR,
639 			    "Could not decode FNS address for %s", cname);
640 		}
641 		res = -1;
642 	} else {
643 		res = 0;
644 	}
645 	xdr_destroy(&xdr);
646 	return (res);
647 }
648 
649 static size_t
650 append_mapname(char *map, size_t maplen, const char *name)
651 {
652 	size_t namelen = strlen(name);
653 
654 	if (maplen + 1 + namelen >= MAPNAMESZ) {
655 		if (verbose) {
656 			syslog(LOG_ERR, "FNS name %s/%s too long",
657 			    map + FNPREFIXLEN + 1, name);
658 		}
659 		return (0);
660 	}
661 	sprintf(map + maplen, "/%s", name);
662 	return (maplen + 1 + namelen);
663 }
664 
665 
666 static char *
667 concat(const char *s1, char sep, const char *s2)
668 {
669 	char *s = malloc(strlen(s1) + 1 + strlen(s2) + 1);
670 
671 	if (s != NULL) {
672 		sprintf(s, "%s%c%s", s1, sep, s2);
673 	}
674 	return (s);
675 }
676 
677 
678 static bool_t
679 safe_mapent(mapent *me)
680 {
681 	char	*opts;
682 
683 	if (me->map_next != NULL) {
684 		/* Multiple mounts don't belong in XFN namespace. */
685 		return (NULL);
686 	}
687 	opts = me->map_mntopts;
688 	me->map_mntopts = safe_opts(opts);
689 	free(opts);
690 	return (me->map_mntopts != NULL);
691 }
692 
693 
694 static char *
695 safe_opts(const char *opts)
696 {
697 	char	*start;
698 	size_t	len;
699 
700 	if (opts[0] == '\0') {
701 		return (strdup(MNTOPT_NOSUID));
702 	}
703 
704 	/* A quick-and-dirty check to see if "nosuid" is already there. */
705 	start = strstr(opts, MNTOPT_NOSUID);
706 	len = sizeof (MNTOPT_NOSUID) - 1;	/* "-1" for trailing '\0' */
707 	if (start != NULL) {
708 		while (start > opts && isspace(*(start - 1))) {
709 			start--;
710 		}
711 		if ((start == opts || *(start - 1) == ',') &&
712 		    opts[len] == ',' || opts[len] == '\0') {
713 			return (strdup(opts));
714 		}
715 	}
716 	return (concat(opts, ',', MNTOPT_NOSUID));
717 }
718 
719 
720 static int
721 trim_line(mapline *ml)
722 {
723 	char	*end;	/* pointer to '\0' at end of linebuf */
724 
725 	end = ml->linebuf + strcspn(ml->linebuf, "#");
726 	while ((end > ml->linebuf) && isspace(end[-1])) {
727 		end--;
728 	}
729 	if (end <= ml->linebuf) {
730 		return (-1);
731 	}
732 	*end = '\0';
733 	unquote(ml->linebuf, ml->lineqbuf);
734 	return (0);
735 }
736 
737 
738 static bool_t
739 opts_only(const mapline *ml)
740 {
741 	const char *s = ml->linebuf;
742 	const char *q = ml->lineqbuf;
743 
744 	if (*s != '-') {
745 		return (FALSE);
746 	}
747 	for (; *s != '\0'; s++, q++) {
748 		if (isspace(*s) && (*q == ' ')) {
749 			return (FALSE);
750 		}
751 	}
752 	return (TRUE);
753 }
754 
755 
756 static mapent *
757 new_mapent(char *root, char *mntpnt, char *fstype, char *mntopts, char *host,
758     char *dir)
759 {
760 	mapent		*me;
761 	struct mapfs	*mfs;
762 	char		*mounter = NULL;
763 
764 	me = calloc(1, sizeof (*me));
765 	mfs = calloc(1, sizeof (*mfs));
766 	if (fstype != NULL) {
767 		mounter = strdup(fstype);
768 	}
769 	if ((mntpnt == NULL) || (fstype == NULL) || (mntopts == NULL) ||
770 	    (host == NULL) || (dir == NULL) || (me == NULL) || (mfs == NULL) ||
771 	    (mounter == NULL) || (root == NULL)) {
772 		log_mem_failure();
773 		free(me);
774 		free(mfs);
775 		free(mounter);
776 		free(root);
777 		free(mntpnt);
778 		free(fstype);
779 		free(mntopts);
780 		free(host);
781 		free(dir);
782 		return (NULL);
783 	}
784 	me->map_root	= (root != noroot) ? root : NULL;
785 	me->map_fstype	= fstype;
786 	me->map_mounter	= mounter;
787 	me->map_mntpnt	= mntpnt;
788 	me->map_mntopts	= mntopts;
789 	me->map_fsw	= NULL;
790 	me->map_fswq    = NULL;
791 	me->map_fs	= mfs;
792 	mfs->mfs_host	= host;
793 	mfs->mfs_dir	= dir;
794 	me->map_mntlevel = -1;
795 	me->map_modified = FALSE;
796 	me->map_faked = FALSE;
797 	me->map_err = 0;		/* MAPENT_NOERR */
798 	return (me);
799 }
800 
801 
802 #ifndef XFN1ENV
803 
804 /*
805  * User-relative bindings in the initial context, and the leading components
806  * of their non-user-relative equivalents.  Leading components are listed in
807  * the order in which they should be tried.  Each list is NULL-terminated
808  * (the compiler generously does this for us).
809  * For "myorgunit", for example, we first check if it is equivalent to
810  * "thisorgunit".  If not, we translate it into "org/<something>".
811  */
812 #define	MAX_LEADS 3
813 
814 static struct {
815 	const char	*binding;
816 	const char	*leads[MAX_LEADS + 1];
817 } user_rel[] = {
818 	{"thisuser",	{"user", "thisorgunit", "org"}},
819 	{"myself",	{"user", "thisorgunit", "org"}},
820 	{"_myself",	{"_user", "_thisorgunit", "_orgunit"}},
821 	{"myorgunit",	{"thisorgunit", "org"}},
822 	{"_myorgunit",	{"_thisorgunit", "_orgunit"}},
823 	{"myens",	{"thisens"}},
824 	{"_myens",	{"_thisens"}}
825 };
826 
827 
828 static bool_t
829 is_user_relative(const char *cname)
830 {
831 	int	i;
832 
833 	for (i = 0; i < sizeof (user_rel) / sizeof (user_rel[0]); i++) {
834 		if (strcmp(cname, user_rel[i].binding) == 0) {
835 			return (TRUE);
836 		}
837 	}
838 	return (FALSE);
839 }
840 
841 
842 static char *
843 equiv_name(FN_ctx_t *ctx, const char *cname, FN_status_t *status)
844 {
845 	FN_composite_name_t	*name;
846 	FN_string_t		*leading_name;
847 	FN_composite_name_t	*equiv;
848 	FN_string_t		*equiv_string;
849 	const char		*equiv_str;
850 	char			*equiv_str_dup;
851 	const char		**leads;
852 	unsigned int		stat;
853 	int			i;
854 
855 	for (i = 0; i < sizeof (user_rel) / sizeof (user_rel[0]); i++) {
856 		if (strcmp(cname, user_rel[i].binding) == 0) {
857 			break;
858 		}
859 	}
860 	if ((name = new_cname(cname)) == NULL) {
861 		return (NULL);
862 	}
863 	leads = user_rel[i].leads;	/* array of leading names to try */
864 	do {
865 		leading_name = fn_string_from_str((unsigned char *)*leads);
866 		if (leading_name == NULL) {
867 			log_mem_failure();
868 			fn_composite_name_destroy(name);
869 			return (NULL);
870 		}
871 		equiv = prelim_fn_ctx_equivalent_name(ctx, name, leading_name,
872 		    status);
873 		fn_string_destroy(leading_name);
874 	} while (equiv == NULL && *++leads != NULL);
875 
876 	fn_composite_name_destroy(name);
877 
878 	if (equiv == NULL) {
879 		if (transient(status)) {
880 			logstat(status, "could not find equivalent of", cname);
881 		}
882 		return (NULL);
883 	}
884 	equiv_string = fn_string_from_composite_name(equiv, &stat);
885 	fn_composite_name_destroy(equiv);
886 	if (equiv_string == NULL) {
887 		log_mem_failure();
888 		return (NULL);
889 	}
890 	equiv_str = (const char *)fn_string_str(equiv_string, &stat);
891 	if (equiv_str == NULL ||
892 	    (equiv_str_dup = strdup(equiv_str)) == NULL) {
893 		log_mem_failure();
894 		fn_string_destroy(equiv_string);
895 		return (NULL);
896 	}
897 	fn_string_destroy(equiv_string);
898 	return (equiv_str_dup);
899 }
900 
901 #endif	/* XFN1ENV */
902