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