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