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