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**));
opt(p)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*));
eval_opts(opts,mapkey)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*/
free_op(p,b)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));
normalize_slash(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));
expand_op(p,sel_p)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));
expand_opts(p,sel_p)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 */
free_opts(fo)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 */
expand_key(key)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));
deslashify(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
eval_fs_opts(fo,opts,g_opts,path,key,map)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