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