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