xref: /original-bsd/usr.sbin/amd/amd/opts.c (revision 753853ba)
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.1 1992/02/09 15:08:54 jsp beta $
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 	if (f) {
324 		char *t = f;
325 		do {
326 			/* assert(*f == '/'); */
327 			if (f == t && f[0] == '/' && f[1] == '/') {
328 				/* copy double slash iff first */
329 				*t++ = *f++;
330 				*t++ = *f++;
331 			} else {
332 				/* copy a single / across */
333 				*t++ = *f++;
334 			}
335 
336 			/* assert(f[-1] == '/'); */
337 			/* skip past more /'s */
338 			while (*f == '/')
339 				f++;
340 
341 			/* assert(*f != '/'); */
342 			/* keep copying up to next / */
343 			do {
344 				*t++ = *f++;
345 			} while (*f && *f != '/');
346 
347 			/* assert(*f == 0 || *f == '/'); */
348 
349 		} while (*f);
350 		*t = 0;			/* derived from fix by Steven Glassman */
351 	}
352 }
353 
354 /*
355  * Macro-expand an option.  Note that this does not
356  * handle recursive expansions.  They will go badly wrong.
357  * If sel is true then old expand selectors, otherwise
358  * don't expand selectors.
359  */
360 static void expand_op P((opt_apply*, int));
361 static void expand_op(p, sel_p)
362 opt_apply *p;
363 int sel_p;
364 {
365 /*
366  * The BUFSPACE macros checks that there is enough space
367  * left in the expansion buffer.  If there isn't then we
368  * give up completely.  This is done to avoid crashing the
369  * automounter itself (which would be a bad thing to do).
370  */
371 #define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+MAXPATHLEN)
372 static char expand_error[] = "No space to expand \"%s\"";
373 
374 	char expbuf[MAXPATHLEN+1];
375 	char nbuf[NLEN+1];
376 	char *ep = expbuf;
377 	char *cp = *p->opt;
378 	char *dp;
379 #ifdef DEBUG
380 	char *cp_orig = *p->opt;
381 #endif /* DEBUG */
382 	struct opt *op;
383 
384 	while (dp = strchr(cp, '$')) {
385 		char ch;
386 		/*
387 		 * First copy up to the $
388 		 */
389 		{ int len = dp - cp;
390 		  if (BUFSPACE(ep, len)) {
391 			strncpy(ep, cp, len);
392 			ep += len;
393 		  } else {
394 			plog(XLOG_ERROR, expand_error, *p->opt);
395 			goto out;
396 		  }
397 		}
398 		cp = dp + 1;
399 		ch = *cp++;
400 		if (ch == '$') {
401 			if (BUFSPACE(ep, 1)) {
402 				*ep++ = '$';
403 			} else {
404 				plog(XLOG_ERROR, expand_error, *p->opt);
405 				goto out;
406 			}
407 		} else if (ch == '{') {
408 			/* Expansion... */
409 			enum { E_All, E_Dir, E_File, E_Domain, E_Host } todo;
410 			/*
411 			 * Find closing brace
412 			 */
413 			char *br_p = strchr(cp, '}');
414 			int len;
415 			/*
416 			 * Check we found it
417 			 */
418 			if (!br_p) {
419 				/*
420 				 * Just give up
421 				 */
422 				plog(XLOG_USER, "No closing '}' in \"%s\"", *p->opt);
423 				goto out;
424 			}
425 			len = br_p - cp;
426 			/*
427 			 * Figure out which part of the variable to grab.
428 			 */
429 			if (*cp == '/') {
430 				/*
431 				 * Just take the last component
432 				 */
433 				todo = E_File;
434 				cp++;
435 				--len;
436 			} else if (br_p[-1] == '/') {
437 				/*
438 				 * Take all but the last component
439 				 */
440 				todo = E_Dir;
441 				--len;
442 			} else if (*cp == '.') {
443 				/*
444 				 * Take domain name
445 				 */
446 				todo = E_Domain;
447 				cp++;
448 				--len;
449 			} else if (br_p[-1] == '.') {
450 				/*
451 				 * Take host name
452 				 */
453 				todo = E_Host;
454 				--len;
455 			} else {
456 				/*
457 				 * Take the whole lot
458 				 */
459 				todo = E_All;
460 			}
461 			/*
462 			 * Truncate if too long.  Since it won't
463 			 * match anyway it doesn't matter that
464 			 * it has been cut short.
465 			 */
466 			if (len > NLEN)
467 				len = NLEN;
468 			/*
469 			 * Put the string into another buffer so
470 			 * we can do comparisons.
471 			 */
472 			strncpy(nbuf, cp, len);
473 			nbuf[len] = '\0';
474 			/*
475 			 * Advance cp
476 			 */
477 			cp = br_p + 1;
478 			/*
479 			 * Search the option array
480 			 */
481 			for (op = opt_fields; op->name; op++) {
482 				/*
483 				 * Check for match
484 				 */
485 				if (len == op->nlen && STREQ(op->name, nbuf)) {
486 					char xbuf[NLEN+3];
487 					char *val;
488 					/*
489 					 * Found expansion.  Copy
490 					 * the correct value field.
491 					 */
492 					if (!(!op->sel_p == !sel_p)) {
493 						/*
494 						 * Copy the string across unexpanded
495 						 */
496 						sprintf(xbuf, "${%s%s%s}",
497 							todo == E_File ? "/" :
498 								todo == E_Domain ? "." : "",
499 							nbuf,
500 							todo == E_Dir ? "/" :
501 								todo == E_Host ? "." : "");
502 						val = xbuf;
503 						/*
504 						 * Make sure expansion doesn't
505 						 * munge the value!
506 						 */
507 						todo = E_All;
508 					} else if (op->sel_p) {
509 						val = *op->sel_p;
510 					} else {
511 						val = *op->optp;
512 					}
513 					if (val) {
514 						/*
515 						 * Do expansion:
516 						 * ${/var} means take just the last part
517 						 * ${var/} means take all but the last part
518 						 * ${.var} means take all but first part
519 						 * ${var.} means take just the first part
520 						 * ${var} means take the whole lot
521 						 */
522 						int vlen = strlen(val);
523 						char *vptr = val;
524 						switch (todo) {
525 						case E_Dir:
526 							vptr = strrchr(val, '/');
527 							if (vptr)
528 								vlen = vptr - val;
529 							vptr = val;
530 							break;
531 						case E_File:
532 							vptr = strrchr(val, '/');
533 							if (vptr) {
534 								vptr++;
535 								vlen = strlen(vptr);
536 							} else
537 								vptr = val;
538 							break;
539 						case E_Domain:
540 							vptr = strchr(val, '.');
541 							if (vptr) {
542 								vptr++;
543 								vlen = strlen(vptr);
544 							} else {
545 								vptr = "";
546 								vlen = 0;
547 							}
548 							break;
549 						case E_Host:
550 							vptr = strchr(val, '.');
551 							if (vptr)
552 								vlen = vptr - val;
553 							vptr = val;
554 							break;
555 						case E_All:
556 							break;
557 						}
558 #ifdef DEBUG
559 					/*dlog("Expanding \"%s\" to \"%s\"", nbuf, val);*/
560 #endif /* DEBUG */
561 						if (BUFSPACE(ep, vlen)) {
562 							strcpy(ep, vptr);
563 							ep += vlen;
564 						} else {
565 							plog(XLOG_ERROR, expand_error, *p->opt);
566 							goto out;
567 						}
568 					}
569 					/*
570 					 * Done with this variable
571 					 */
572 					break;
573 				}
574 			}
575 			/*
576 			 * Check that the search was succesful
577 			 */
578 			if (!op->name) {
579 				/*
580 				 * If it wasn't then scan the
581 				 * environment for that name
582 				 * and use any value found
583 				 */
584 				char *env = getenv(nbuf);
585 				if (env) {
586 					int vlen = strlen(env);
587 
588 					if (BUFSPACE(ep, vlen)) {
589 						strcpy(ep, env);
590 						ep += vlen;
591 					} else {
592 						plog(XLOG_ERROR, expand_error, *p->opt);
593 						goto out;
594 					}
595 #ifdef DEBUG
596 					Debug(D_STR)
597 					plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env);
598 #endif /* DEBUG */
599 				} else {
600 					plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf);
601 				}
602 			}
603 		} else {
604 			/*
605 			 * Error, error
606 			 */
607 			plog(XLOG_USER, "Unknown $ sequence in \"%s\"", *p->opt);
608 		}
609 	}
610 
611 out:
612 	/*
613 	 * Handle common case - no expansion
614 	 */
615 	if (cp == *p->opt) {
616 		*p->opt = strdup(cp);
617 	} else {
618 		/*
619 		 * Finish off the expansion
620 		 */
621 		if (BUFSPACE(ep, strlen(cp))) {
622 			strcpy(ep, cp);
623 			/*ep += strlen(ep);*/
624 		} else {
625 			plog(XLOG_ERROR, expand_error, *p->opt);
626 		}
627 
628 		/*
629 		 * Save the exansion
630 		 */
631 		*p->opt = strdup(expbuf);
632 	}
633 
634 	normalize_slash(*p->opt);
635 
636 #ifdef DEBUG
637 	Debug(D_STR) {
638 		plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig);
639 		plog(XLOG_DEBUG, "... is \"%s\"", *p->opt);
640 	}
641 #endif /* DEBUG */
642 }
643 
644 /*
645  * Wrapper for expand_op
646  */
647 static void expand_opts P((opt_apply*, int));
648 static void expand_opts(p, sel_p)
649 opt_apply *p;
650 int sel_p;
651 {
652 	if (*p->opt) {
653 		expand_op(p, sel_p);
654 	} else if (p->val) {
655 		/*
656 		 * Do double expansion, remembering
657 		 * to free the string from the first
658 		 * expansion...
659 		 */
660 		char *s = *p->opt = expand_key(p->val);
661 		expand_op(p, sel_p);
662 		free(s);
663 	}
664 }
665 
666 /*
667  * Apply a function to a list of options
668  */
669 static void apply_opts(op, ppp, b)
670 void (*op)();
671 opt_apply ppp[];
672 int b;
673 {
674 	opt_apply *pp;
675 	for (pp = ppp; pp->opt; pp++)
676 		(*op)(pp, b);
677 }
678 
679 /*
680  * Free the option table
681  */
682 void free_opts(fo)
683 am_opts *fo;
684 {
685 	/*
686 	 * Copy in the structure we are playing with
687 	 */
688 	fs_static = *fo;
689 
690 	/*
691 	 * Free previously allocated memory
692 	 */
693 	apply_opts(free_op, to_free, FALSE);
694 }
695 
696 /*
697  * Expand lookup key
698  */
699 char *expand_key(key)
700 char *key;
701 {
702 	opt_apply oa;
703 
704 	oa.opt = &key; oa.val = 0;
705 	expand_opts(&oa, TRUE);
706 
707 	return key;
708 }
709 
710 /*
711  * Remove trailing /'s from a string
712  * unless the string is a single / (Steven Glassman)
713  */
714 void deslashify P((char *s));
715 void deslashify(s)
716 char *s;
717 {
718 	if (s && *s) {
719 		char *sl = s + strlen(s);
720 		while (*--sl == '/' && sl > s)
721 			*sl = '\0';
722 	}
723 }
724 
725 int eval_fs_opts(fo, opts, g_opts, path, key, map)
726 am_opts *fo;
727 char *opts, *g_opts, *path, *key, *map;
728 {
729 	int ok = TRUE;
730 
731 	free_opts(fo);
732 
733 	/*
734 	 * Clear out the option table
735 	 */
736 	bzero((voidp) &fs_static, sizeof(fs_static));
737 	bzero((voidp) vars, sizeof(vars));
738 	bzero((voidp) fo, sizeof(*fo));
739 
740 	/*
741 	 * Set key, map & path before expansion
742 	 */
743 	opt_key = key;
744 	opt_map = map;
745 	opt_path = path;
746 
747 	/*
748 	 * Expand global options
749 	 */
750 	fs_static.fs_glob = expand_key(g_opts);
751 
752 	/*
753 	 * Expand local options
754 	 */
755 	fs_static.fs_local = expand_key(opts);
756 
757 	/*
758 	 * Expand default (global) options
759 	 */
760 	if (!eval_opts(fs_static.fs_glob, key))
761 		ok = FALSE;
762 
763 	/*
764 	 * Expand local options
765 	 */
766 	if (ok && !eval_opts(fs_static.fs_local, key))
767 		ok = FALSE;
768 
769 	/*
770 	 * Normalise remote host name.
771 	 * 1.  Expand variables
772 	 * 2.  Normalize relative to host tables
773 	 * 3.  Strip local domains from the remote host
774 	 *     name before using it in other expansions.
775 	 *     This makes mount point names and other things
776 	 *     much shorter, while allowing cross domain
777 	 *     sharing of mount maps.
778 	 */
779 	apply_opts(expand_opts, rhost_expansion, FALSE);
780 	if (ok && fs_static.opt_rhost && *fs_static.opt_rhost)
781 		host_normalize(&fs_static.opt_rhost);
782 
783 	/*
784 	 * Macro expand the options.
785 	 * Do this regardless of whether we are accepting
786 	 * this mount - otherwise nasty things happen
787 	 * with memory allocation.
788 	 */
789 	apply_opts(expand_opts, expansions, FALSE);
790 
791 	/*
792 	 * Strip trailing slashes from local pathname...
793 	 */
794 	deslashify(fs_static.opt_fs);
795 
796 	/*
797 	 * ok... copy the data back out.
798 	 */
799 	*fo = fs_static;
800 
801 	/*
802 	 * Clear defined options
803 	 */
804 	opt_key = opt_map = opt_path = nullstr;
805 
806 	return ok;
807 }
808