xref: /openbsd-src/usr.sbin/amd/amd/opts.c (revision bf0193d8cd0ca4e683146c29a671bf62f193ec92)
1 /*-
2  * Copyright (c) 1989 Jan-Simon Pendry
3  * Copyright (c) 1989 Imperial College of Science, Technology & Medicine
4  * Copyright (c) 1989, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Jan-Simon Pendry at Imperial College, London.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include "am.h"
36 
37 /*
38  * static copy of the options with
39  * which to play
40  */
41 static struct am_opts fs_static;
42 
43 static char *opt_host = hostname;
44 static char *opt_hostd = hostd;
45 static char nullstr[] = "";
46 static char *opt_key = nullstr;
47 static char *opt_map = nullstr;
48 static char *opt_path = nullstr;
49 
50 static char *vars[8];
51 
52 /*
53  * Length of longest option name
54  */
55 #define	NLEN	16	/* conservative */
56 #define S(x) (x) , (sizeof(x)-1)
57 static struct opt {
58 	char *name;		/* Name of the option */
59 	int nlen;		/* Length of option name */
60 	char **optp;		/* Pointer to option value string */
61 	char **sel_p;		/* Pointer to selector value string */
62 } opt_fields[] = {
63 	/* Options in something corresponding to frequency of use */
64 	{ S("opts"), &fs_static.opt_opts, 0 },
65 	{ S("host"), 0, &opt_host },
66 	{ S("hostd"), 0, &opt_hostd },
67 	{ S("type"), &fs_static.opt_type, 0 },
68 	{ S("rhost"), &fs_static.opt_rhost, 0 },
69 	{ S("rfs"), &fs_static.opt_rfs, 0 },
70 	{ S("fs"), &fs_static.opt_fs, 0 },
71 	{ S("key"), 0, &opt_key },
72 	{ S("map"), 0, &opt_map },
73 	{ S("sublink"), &fs_static.opt_sublink, 0 },
74 	{ S("arch"), 0, &arch },
75 	{ S("dev"), &fs_static.opt_dev, 0 },
76 	{ S("pref"), &fs_static.opt_pref, 0 },
77 	{ S("path"), 0, &opt_path },
78 	{ S("autodir"), 0, &auto_dir },
79 	{ S("delay"), &fs_static.opt_delay, 0 },
80 	{ S("domain"), 0, &hostdomain },
81 	{ S("karch"), 0, &karch },
82 	{ S("cluster"), 0, &cluster },
83 	{ S("wire"), 0, &wire },
84 	{ S("byte"), 0, &endian },
85 	{ S("os"), 0, &op_sys },
86 	{ S("remopts"), &fs_static.opt_remopts, 0 },
87 	{ S("mount"), &fs_static.opt_mount, 0 },
88 	{ S("unmount"), &fs_static.opt_unmount, 0 },
89 	{ S("cache"), &fs_static.opt_cache, 0 },
90 	{ S("user"), &fs_static.opt_user, 0 },
91 	{ S("group"), &fs_static.opt_group, 0 },
92 	{ S("var0"), &vars[0], 0 },
93 	{ S("var1"), &vars[1], 0 },
94 	{ S("var2"), &vars[2], 0 },
95 	{ S("var3"), &vars[3], 0 },
96 	{ S("var4"), &vars[4], 0 },
97 	{ S("var5"), &vars[5], 0 },
98 	{ S("var6"), &vars[6], 0 },
99 	{ S("var7"), &vars[7], 0 },
100 	{ 0, 0, 0, 0 },
101 };
102 
103 typedef struct opt_apply opt_apply;
104 struct opt_apply {
105 	char **opt;
106 	char *val;
107 };
108 
109 /*
110  * Specially expand the remote host name first
111  */
112 static opt_apply rhost_expansion[] = {
113 	{ &fs_static.opt_rhost, "${host}" },
114 	{ 0, 0 },
115 };
116 /*
117  * List of options which need to be expanded
118  * Note that this the order here _may_ be important.
119  */
120 static opt_apply expansions[] = {
121 /*	{ &fs_static.opt_dir, 0 },	*/
122 	{ &fs_static.opt_sublink, 0 },
123 	{ &fs_static.opt_rfs, "${path}" },
124 	{ &fs_static.opt_fs, "${autodir}/${rhost}${rfs}" },
125 	{ &fs_static.opt_opts, "rw" },
126 	{ &fs_static.opt_remopts, "${opts}" },
127 	{ &fs_static.opt_mount, 0 },
128 	{ &fs_static.opt_unmount, 0 },
129 	{ 0, 0 },
130 };
131 
132 /*
133  * List of options which need to be free'ed before re-use
134  */
135 static opt_apply to_free[] = {
136 	{ &fs_static.fs_glob, 0 },
137 	{ &fs_static.fs_local, 0 },
138 	{ &fs_static.fs_mtab, 0 },
139 /*	{ &fs_static.opt_dir, 0 },	*/
140 	{ &fs_static.opt_sublink, 0 },
141 	{ &fs_static.opt_rfs, 0 },
142 	{ &fs_static.opt_fs, 0 },
143 	{ &fs_static.opt_rhost, 0 },
144 	{ &fs_static.opt_opts, 0 },
145 	{ &fs_static.opt_remopts, 0 },
146 	{ &fs_static.opt_mount, 0 },
147 	{ &fs_static.opt_unmount, 0 },
148 	{ &vars[0], 0 },
149 	{ &vars[1], 0 },
150 	{ &vars[2], 0 },
151 	{ &vars[3], 0 },
152 	{ &vars[4], 0 },
153 	{ &vars[5], 0 },
154 	{ &vars[6], 0 },
155 	{ &vars[7], 0 },
156 	{ 0, 0 },
157 };
158 
159 /*
160  * Skip to next option in the string
161  */
162 static char *
opt(char ** p)163 opt(char **p)
164 {
165 	char *cp = *p;
166 	char *dp = cp;
167 	char *s = cp;
168 
169 top:
170 	while (*cp && *cp != ';') {
171 		if (*cp == '\"') {
172 			/*
173 			 * Skip past string
174 			 */
175 			cp++;
176 			while (*cp && *cp != '\"')
177 				*dp++ = *cp++;
178 			if (*cp)
179 				cp++;
180 		} else {
181 			*dp++ = *cp++;
182 		}
183 	}
184 
185 	/*
186 	 * Skip past any remaining ';'s
187 	 */
188 	while (*cp == ';')
189 		cp++;
190 
191 	/*
192 	 * If we have a zero length string
193 	 * and there are more fields, then
194 	 * parse the next one.  This allows
195 	 * sequences of empty fields.
196 	 */
197 	if (*cp && dp == s)
198 		goto top;
199 
200 	*dp = '\0';
201 
202 	*p = cp;
203 	return s;
204 }
205 
206 static int
eval_opts(char * opts,char * mapkey)207 eval_opts(char *opts, char *mapkey)
208 {
209 	/*
210 	 * Fill in the global structure fs_static by
211 	 * cracking the string opts.  opts may be
212 	 * scribbled on at will.
213 	 */
214 	char *o = opts;
215 	char *f;
216 
217 	/*
218 	 * For each user-specified option
219 	 */
220 	while (*(f = opt(&o))) {
221 		struct opt *op;
222 		enum vs_opt { OldSyn, SelEQ, SelNE, VarAss } vs_opt;
223 		char *eq = strchr(f, '=');
224 		char *opt;
225 		if (!eq || eq[1] == '\0' || eq == f) {
226 			/*
227 			 * No value, just continue
228 			 */
229 			plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f);
230 			continue;
231 		}
232 
233 		/*
234 		 * Check what type of operation is happening
235 		 * !=, =!  is SelNE
236 		 * == is SelEQ
237 		 * := is VarAss
238 		 * = is OldSyn (either SelEQ or VarAss)
239 		 */
240 		if (eq[-1] == '!') {		/* != */
241 			vs_opt = SelNE;
242 			eq[-1] = '\0';
243 			opt = eq + 1;
244 		} else if (eq[-1] == ':') {	/* := */
245 			vs_opt = VarAss;
246 			eq[-1] = '\0';
247 			opt = eq + 1;
248 		} else if (eq[1] == '=') {	/* == */
249 			vs_opt = SelEQ;
250 			eq[0] = '\0';
251 			opt = eq + 2;
252 		} else if (eq[1] == '!') {	/* =! */
253 			vs_opt = SelNE;
254 			eq[0] = '\0';
255 			opt = eq + 2;
256 		} else {			/* = */
257 			vs_opt = OldSyn;
258 			eq[0] = '\0';
259 			opt = eq + 1;
260 		}
261 
262 		/*
263 		 * For each recognised option
264 		 */
265 		for (op = opt_fields; op->name; op++) {
266 			/*
267 			 * Check whether they match
268 			 */
269 			if (FSTREQ(op->name, f)) {
270 				switch (vs_opt) {
271 #if 1	/* XXX ancient compat */
272 				case OldSyn:
273 					plog(XLOG_WARNING, "key %s: Old syntax selector found: %s=%s", mapkey, f, opt);
274 					if (!op->sel_p) {
275 						*op->optp = opt;
276 						break;
277 					}
278 					/* fall through ... */
279 #endif
280 				case SelEQ:
281 				case SelNE:
282 					if (op->sel_p && (STREQ(*op->sel_p, opt) == (vs_opt == SelNE))) {
283 						plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s",
284 							mapkey,
285 							op->name,
286 							*op->sel_p,
287 							vs_opt == SelNE ? "not " : "",
288 							opt);
289 						return 0;
290 					}
291 					break;
292 
293 				case VarAss:
294 					if (op->sel_p) {
295 						plog(XLOG_USER, "key %s: Can't assign to a selector (%s)", mapkey, op->name);
296 						return 0;
297 					}
298 					*op->optp = opt;
299 					break;
300 				}
301 				break;
302 			}
303 		}
304 
305 		if (!op->name)
306 			plog(XLOG_USER, "key %s: Unrecognised key/option \"%s\"", mapkey, f);
307 	}
308 
309 	return 1;
310 }
311 
312 /*
313  * Free an option
314  */
315 static void
free_op(opt_apply * p,int b)316 free_op(opt_apply *p, int b)
317 {
318 	if (*p->opt) {
319 		free(*p->opt);
320 		*p->opt = 0;
321 	}
322 }
323 
324 /*
325  * Normalize slashes in the string.
326  */
327 void
normalize_slash(char * p)328 normalize_slash(char *p)
329 {
330 	char *f = strchr(p, '/');
331 	char *f0 = f;
332 	if (f) {
333 		char *t = f;
334 		do {
335 			/* assert(*f == '/'); */
336 			if (f == f0 && f[0] == '/' && f[1] == '/') {
337 				/* copy double slash iff first */
338 				*t++ = *f++;
339 				*t++ = *f++;
340 			} else {
341 				/* copy a single / across */
342 				*t++ = *f++;
343 			}
344 
345 			/* assert(f[-1] == '/'); */
346 			/* skip past more /'s */
347 			while (*f == '/')
348 				f++;
349 
350 			/* assert(*f != '/'); */
351 			/* keep copying up to next / */
352 			while (*f && *f != '/') {
353 				*t++ = *f++;
354 			}
355 
356 			/* assert(*f == 0 || *f == '/'); */
357 
358 		} while (*f);
359 		*t = 0;			/* derived from fix by Steven Glassman */
360 	}
361 }
362 
363 /*
364  * Macro-expand an option.  Note that this does not
365  * handle recursive expansions.  They will go badly wrong.
366  * If sel is true then old expand selectors, otherwise
367  * don't expand selectors.
368  */
369 static void
expand_op(opt_apply * p,int sel_p)370 expand_op(opt_apply *p, int sel_p)
371 {
372 /*
373  * The BUFSPACE macros checks that there is enough space
374  * left in the expansion buffer.  If there isn't then we
375  * give up completely.  This is done to avoid crashing the
376  * automounter itself (which would be a bad thing to do).
377  */
378 #define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+PATH_MAX)
379 static char expand_error[] = "No space to expand \"%s\"";
380 
381 	char expbuf[PATH_MAX+1];
382 	char nbuf[NLEN+1];
383 	char *ep = expbuf;
384 	char *cp = *p->opt;
385 	char *dp;
386 #ifdef DEBUG
387 	char *cp_orig = *p->opt;
388 #endif /* DEBUG */
389 	struct opt *op;
390 
391 	while ((dp = strchr(cp, '$'))) {
392 		char ch;
393 		/*
394 		 * First copy up to the $
395 		 */
396 		{ int len = dp - cp;
397 		  if (BUFSPACE(ep, len)) {
398 			strncpy(ep, cp, len);
399 			ep += len;
400 		  } else {
401 			plog(XLOG_ERROR, expand_error, *p->opt);
402 			goto out;
403 		  }
404 		}
405 		cp = dp + 1;
406 		ch = *cp++;
407 		if (ch == '$') {
408 			if (BUFSPACE(ep, 1)) {
409 				*ep++ = '$';
410 			} else {
411 				plog(XLOG_ERROR, expand_error, *p->opt);
412 				goto out;
413 			}
414 		} else if (ch == '{') {
415 			/* Expansion... */
416 			enum { E_All, E_Dir, E_File, E_Domain, E_Host } todo;
417 			/*
418 			 * Find closing brace
419 			 */
420 			char *br_p = strchr(cp, '}');
421 			int len;
422 			/*
423 			 * Check we found it
424 			 */
425 			if (!br_p) {
426 				/*
427 				 * Just give up
428 				 */
429 				plog(XLOG_USER, "No closing '}' in \"%s\"", *p->opt);
430 				goto out;
431 			}
432 			len = br_p - cp;
433 			/*
434 			 * Figure out which part of the variable to grab.
435 			 */
436 			if (*cp == '/') {
437 				/*
438 				 * Just take the last component
439 				 */
440 				todo = E_File;
441 				cp++;
442 				--len;
443 			} else if (br_p[-1] == '/') {
444 				/*
445 				 * Take all but the last component
446 				 */
447 				todo = E_Dir;
448 				--len;
449 			} else if (*cp == '.') {
450 				/*
451 				 * Take domain name
452 				 */
453 				todo = E_Domain;
454 				cp++;
455 				--len;
456 			} else if (br_p[-1] == '.') {
457 				/*
458 				 * Take host name
459 				 */
460 				todo = E_Host;
461 				--len;
462 			} else {
463 				/*
464 				 * Take the whole lot
465 				 */
466 				todo = E_All;
467 			}
468 			/*
469 			 * Truncate if too long.  Since it won't
470 			 * match anyway it doesn't matter that
471 			 * it has been cut short.
472 			 */
473 			if (len > NLEN)
474 				len = NLEN;
475 			/*
476 			 * Put the string into another buffer so
477 			 * we can do comparisons.
478 			 */
479 			strncpy(nbuf, cp, len);
480 			nbuf[len] = '\0';
481 			/*
482 			 * Advance cp
483 			 */
484 			cp = br_p + 1;
485 			/*
486 			 * Search the option array
487 			 */
488 			for (op = opt_fields; op->name; op++) {
489 				/*
490 				 * Check for match
491 				 */
492 				if (len == op->nlen && STREQ(op->name, nbuf)) {
493 					char xbuf[NLEN+3];
494 					char *val;
495 					/*
496 					 * Found expansion.  Copy
497 					 * the correct value field.
498 					 */
499 					if (!(!op->sel_p == !sel_p)) {
500 						/*
501 						 * Copy the string across unexpanded
502 						 */
503 						snprintf(xbuf, sizeof(xbuf), "${%s%s%s}",
504 							todo == E_File ? "/" :
505 								todo == E_Domain ? "." : "",
506 							nbuf,
507 							todo == E_Dir ? "/" :
508 								todo == E_Host ? "." : "");
509 						val = xbuf;
510 						/*
511 						 * Make sure expansion doesn't
512 						 * munge the value!
513 						 */
514 						todo = E_All;
515 					} else if (op->sel_p) {
516 						val = *op->sel_p;
517 					} else {
518 						val = *op->optp;
519 					}
520 					if (val) {
521 						/*
522 						 * Do expansion:
523 						 * ${/var} means take just the last part
524 						 * ${var/} means take all but the last part
525 						 * ${.var} means take all but first part
526 						 * ${var.} means take just the first part
527 						 * ${var} means take the whole lot
528 						 */
529 						int vlen = strlen(val);
530 						char *vptr = val;
531 						switch (todo) {
532 						case E_Dir:
533 							vptr = strrchr(val, '/');
534 							if (vptr)
535 								vlen = vptr - val;
536 							vptr = val;
537 							break;
538 						case E_File:
539 							vptr = strrchr(val, '/');
540 							if (vptr) {
541 								vptr++;
542 								vlen = strlen(vptr);
543 							} else
544 								vptr = val;
545 							break;
546 						case E_Domain:
547 							vptr = strchr(val, '.');
548 							if (vptr) {
549 								vptr++;
550 								vlen = strlen(vptr);
551 							} else {
552 								vptr = "";
553 								vlen = 0;
554 							}
555 							break;
556 						case E_Host:
557 							vptr = strchr(val, '.');
558 							if (vptr)
559 								vlen = vptr - val;
560 							vptr = val;
561 							break;
562 						case E_All:
563 							break;
564 						}
565 #ifdef DEBUG
566 					/*dlog("Expanding \"%s\" to \"%s\"", nbuf, val);*/
567 #endif /* DEBUG */
568 						if (BUFSPACE(ep, vlen)) {
569 							strlcpy(ep, vptr, expbuf + sizeof expbuf - ep);
570 							ep += strlen(ep);
571 						} else {
572 							plog(XLOG_ERROR, expand_error, *p->opt);
573 							goto out;
574 						}
575 					}
576 					/*
577 					 * Done with this variable
578 					 */
579 					break;
580 				}
581 			}
582 			/*
583 			 * Check that the search was successful
584 			 */
585 			if (!op->name) {
586 				/*
587 				 * If it wasn't then scan the
588 				 * environment for that name
589 				 * and use any value found
590 				 */
591 				char *env = getenv(nbuf);
592 				if (env) {
593 					int vlen = strlen(env);
594 
595 					if (BUFSPACE(ep, vlen)) {
596 						strlcpy(ep, env, expbuf + sizeof expbuf - ep);
597 						ep += strlen(ep);
598 					} else {
599 						plog(XLOG_ERROR, expand_error, *p->opt);
600 						goto out;
601 					}
602 #ifdef DEBUG
603 					Debug(D_STR)
604 					plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env);
605 #endif /* DEBUG */
606 				} else {
607 					plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf);
608 				}
609 			}
610 		} else {
611 			/*
612 			 * Error, error
613 			 */
614 			plog(XLOG_USER, "Unknown $ sequence in \"%s\"", *p->opt);
615 		}
616 	}
617 
618 out:
619 	/*
620 	 * Handle common case - no expansion
621 	 */
622 	if (cp == *p->opt) {
623 		*p->opt = strdup(cp);
624 	} else {
625 		/*
626 		 * Finish off the expansion
627 		 */
628 		if (BUFSPACE(ep, strlen(cp))) {
629 			strlcpy(ep, cp, expbuf + sizeof expbuf - ep);
630 		} else {
631 			plog(XLOG_ERROR, expand_error, *p->opt);
632 		}
633 
634 		/*
635 		 * Save the exansion
636 		 */
637 		*p->opt = strdup(expbuf);
638 	}
639 
640 	normalize_slash(*p->opt);
641 
642 #ifdef DEBUG
643 	Debug(D_STR) {
644 		plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig);
645 		plog(XLOG_DEBUG, "... is \"%s\"", *p->opt);
646 	}
647 #endif /* DEBUG */
648 }
649 
650 /*
651  * Wrapper for expand_op
652  */
653 static void
expand_opts(opt_apply * p,int sel_p)654 expand_opts(opt_apply *p, int sel_p)
655 {
656 	if (*p->opt) {
657 		expand_op(p, sel_p);
658 	} else if (p->val) {
659 		/*
660 		 * Do double expansion, remembering
661 		 * to free the string from the first
662 		 * expansion...
663 		 */
664 		char *s = *p->opt = expand_key(p->val);
665 		expand_op(p, sel_p);
666 		free(s);
667 	}
668 }
669 
670 /*
671  * Apply a function to a list of options
672  */
673 static void
apply_opts(void (* op)(),opt_apply ppp[],int b)674 apply_opts(void (*op)(), opt_apply ppp[], int b)
675 {
676 	opt_apply *pp;
677 	for (pp = ppp; pp->opt; pp++)
678 		(*op)(pp, b);
679 }
680 
681 /*
682  * Free the option table
683  */
684 void
free_opts(am_opts * fo)685 free_opts(am_opts *fo)
686 {
687 	/*
688 	 * Copy in the structure we are playing with
689 	 */
690 	fs_static = *fo;
691 
692 	/*
693 	 * Free previously allocated memory
694 	 */
695 	apply_opts(free_op, to_free, FALSE);
696 }
697 
698 /*
699  * Expand lookup key
700  */
701 char *
expand_key(char * key)702 expand_key(char *key)
703 {
704 	opt_apply oa;
705 
706 	oa.opt = &key; oa.val = 0;
707 	expand_opts(&oa, TRUE);
708 
709 	return key;
710 }
711 
712 /*
713  * Remove trailing /'s from a string
714  * unless the string is a single / (Steven Glassman)
715  */
716 void
deslashify(char * s)717 deslashify(char *s)
718 {
719 	if (s && *s) {
720 		char *sl = s + strlen(s);
721 		while (*--sl == '/' && sl > s)
722 			*sl = '\0';
723 	}
724 }
725 
726 int
eval_fs_opts(am_opts * fo,char * opts,char * g_opts,char * path,char * key,char * map)727 eval_fs_opts(am_opts *fo, char *opts, char *g_opts, char *path,
728     char *key, char *map)
729 {
730 	int ok = TRUE;
731 
732 	free_opts(fo);
733 
734 	/*
735 	 * Clear out the option table
736 	 */
737 	bzero(&fs_static, sizeof(fs_static));
738 	bzero(vars, sizeof(vars));
739 	bzero(fo, sizeof(*fo));
740 
741 	/*
742 	 * Set key, map & path before expansion
743 	 */
744 	opt_key = key;
745 	opt_map = map;
746 	opt_path = path;
747 
748 	/*
749 	 * Expand global options
750 	 */
751 	fs_static.fs_glob = expand_key(g_opts);
752 
753 	/*
754 	 * Expand local options
755 	 */
756 	fs_static.fs_local = expand_key(opts);
757 
758 	/*
759 	 * Expand default (global) options
760 	 */
761 	if (!eval_opts(fs_static.fs_glob, key))
762 		ok = FALSE;
763 
764 	/*
765 	 * Expand local options
766 	 */
767 	if (ok && !eval_opts(fs_static.fs_local, key))
768 		ok = FALSE;
769 
770 	/*
771 	 * Normalise remote host name.
772 	 * 1.  Expand variables
773 	 * 2.  Normalize relative to host tables
774 	 * 3.  Strip local domains from the remote host
775 	 *     name before using it in other expansions.
776 	 *     This makes mount point names and other things
777 	 *     much shorter, while allowing cross domain
778 	 *     sharing of mount maps.
779 	 */
780 	apply_opts(expand_opts, rhost_expansion, FALSE);
781 	if (ok && fs_static.opt_rhost && *fs_static.opt_rhost)
782 		host_normalize(&fs_static.opt_rhost);
783 
784 	/*
785 	 * Macro expand the options.
786 	 * Do this regardless of whether we are accepting
787 	 * this mount - otherwise nasty things happen
788 	 * with memory allocation.
789 	 */
790 	apply_opts(expand_opts, expansions, FALSE);
791 
792 	/*
793 	 * Strip trailing slashes from local pathname...
794 	 */
795 	deslashify(fs_static.opt_fs);
796 
797 	/*
798 	 * ok... copy the data back out.
799 	 */
800 	*fo = fs_static;
801 
802 	/*
803 	 * Clear defined options
804 	 */
805 	opt_key = opt_map = opt_path = nullstr;
806 
807 	return ok;
808 }
809