]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/amd/amd/opts.c
This commit was generated by cvs2svn to compensate for changes in r49182,
[FreeBSD/FreeBSD.git] / contrib / amd / amd / opts.c
1 /*
2  * Copyright (c) 1997-1998 Erez Zadok
3  * Copyright (c) 1989 Jan-Simon Pendry
4  * Copyright (c) 1989 Imperial College of Science, Technology & Medicine
5  * Copyright (c) 1989 The Regents of the University of California.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Jan-Simon Pendry at Imperial College, London.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgment:
21  *      This product includes software developed by the University of
22  *      California, Berkeley and its contributors.
23  * 4. Neither the name of the University nor the names of its contributors
24  *    may be used to endorse or promote products derived from this software
25  *    without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37  * SUCH DAMAGE.
38  *
39  *      %W% (Berkeley) %G%
40  *
41  * $Id: opts.c,v 1.2 1998/12/27 06:24:48 ezk Exp $
42  *
43  */
44
45 #ifdef HAVE_CONFIG_H
46 # include <config.h>
47 #endif /* HAVE_CONFIG_H */
48 #include <am_defs.h>
49 #include <amd.h>
50
51 /*
52  * MACROS:
53  */
54 #define NLEN    16      /* Length of longest option name (conservative) */
55 #define S(x) (x) , (sizeof(x)-1)
56 /*
57  * The BUFSPACE macros checks that there is enough space
58  * left in the expansion buffer.  If there isn't then we
59  * give up completely.  This is done to avoid crashing the
60  * automounter itself (which would be a bad thing to do).
61  */
62 #define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+MAXPATHLEN)
63
64 /*
65  * TYPEDEFS:
66  */
67 typedef int (*IntFuncPtr) (char *);
68 typedef struct opt_apply opt_apply;
69 enum vs_opt { SelEQ, SelNE, VarAss };
70
71 /*
72  * STRUCTURES
73  */
74 struct opt {
75   char *name;                   /* Name of the option */
76   int nlen;                     /* Length of option name */
77   char **optp;                  /* Pointer to option value string */
78   char **sel_p;                 /* Pointer to selector value string */
79   int (*fxn_p)(char *);         /* Pointer to boolean function */
80   int case_insensitive;         /* How to do selector comparisons */
81 };
82
83 struct opt_apply {
84   char **opt;
85   char *val;
86 };
87
88 struct functable {
89   char *name;
90   IntFuncPtr func;
91 };
92
93 /*
94  * FORWARD DEFINITION:
95  */
96 static int f_in_network(char *);
97 static int f_netgrp(char *);
98 static int f_exists(char *);
99 static int f_false(char *);
100 static int f_true(char *);
101
102 /*
103  * STATICS:
104  */
105 static struct am_opts fs_static; /* copy of the options to play with */
106 static char NullStr[] = "<NULL>";
107 static char nullstr[] = "";
108 static char *opt_dkey = NullStr;
109 static char *opt_host = nullstr; /* XXX: was the global hostname */
110 static char *opt_hostd = hostd;
111 static char *opt_key = nullstr;
112 static char *opt_keyd = nullstr;
113 static char *opt_map = nullstr;
114 static char *opt_path = nullstr;
115 static char *vars[8];
116
117
118 /*
119  * Options in something corresponding to frequency of use so that
120  * first-match algorithm is sped up.
121  */
122 static struct opt opt_fields[] = {
123   /* Name and length.
124         Option str.             Selector str.   boolean fxn.    flags */
125   { S("opts"),
126        &fs_static.opt_opts,     0,              0,              FALSE   },
127   { S("host"),
128         0,                      &opt_host,      0,              TRUE    },
129   { S("hostd"),
130         0,                      &opt_hostd,     0,              TRUE    },
131   { S("type"),
132         &fs_static.opt_type,    0,              0,              FALSE   },
133   { S("rhost"),
134         &fs_static.opt_rhost,   0,              0,              TRUE    },
135   { S("rfs"),
136         &fs_static.opt_rfs,     0,              0,              FALSE   },
137   { S("fs"),
138         &fs_static.opt_fs,      0,              0,              FALSE   },
139   { S("key"),
140         0,                      &opt_key,       0,              FALSE   },
141   { S("map"),
142         0,                      &opt_map,       0,              FALSE   },
143   { S("sublink"),
144         &fs_static.opt_sublink, 0,              0,              FALSE   },
145   { S("arch"),
146         0,                      &gopt.arch,     0,              TRUE    },
147   { S("dev"),
148         &fs_static.opt_dev,     0,              0,              FALSE   },
149   { S("pref"),
150         &fs_static.opt_pref,    0,              0,              FALSE   },
151   { S("autopref"),
152         &fs_static.opt_autopref,0,              0,              FALSE   },
153   { S("path"),
154         0,                      &opt_path,      0,              FALSE   },
155   { S("autodir"),
156         0,                      &gopt.auto_dir, 0,              FALSE   },
157   { S("delay"),
158         &fs_static.opt_delay,   0,              0,              FALSE   },
159   { S("domain"),
160         0,                      &hostdomain,    0,              TRUE    },
161   { S("karch"),
162         0,                      &gopt.karch,    0,              TRUE    },
163   { S("cluster"),
164         0,                      &gopt.cluster,  0,              TRUE    },
165   { S("wire"),
166         0,                      0,              f_in_network,   TRUE    },
167   { S("network"),
168         0,                      0,              f_in_network,   TRUE    },
169   { S("netnumber"),
170         0,                      0,              f_in_network,   TRUE    },
171   { S("byte"),
172         0,                      &endian,        0,              TRUE    },
173   { S("os"),
174         0,                      &gopt.op_sys,   0,              TRUE    },
175   { S("osver"),
176         0,                      &gopt.op_sys_ver,       0,      TRUE    },
177   { S("remopts"),
178         &fs_static.opt_remopts, 0,              0,              FALSE   },
179   { S("mount"),
180         &fs_static.opt_mount,   0,              0,              FALSE   },
181   { S("unmount"),
182         &fs_static.opt_unmount, 0,              0,              FALSE   },
183   { S("cache"),
184         &fs_static.opt_cache,   0,              0,              FALSE   },
185   { S("user"),
186         &fs_static.opt_user,    0,              0,              FALSE   },
187   { S("group"),
188         &fs_static.opt_group,   0,              0,              FALSE   },
189   { S(".key"),
190         0,                      &opt_dkey,      0,              FALSE   },
191   { S("key."),
192         0,                      &opt_keyd,      0,              FALSE   },
193   { S("maptype"),
194         &fs_static.opt_maptype, 0,              0,              FALSE   },
195   { S("cachedir"),
196         &fs_static.opt_cachedir, 0,             0,              FALSE   },
197   { S("addopts"),
198        &fs_static.opt_addopts,  0,              0,              FALSE   },
199   { S("var0"),
200         &vars[0],               0,              0,              FALSE   },
201   { S("var1"),
202         &vars[1],               0,              0,              FALSE   },
203   { S("var2"),
204         &vars[2],               0,              0,              FALSE   },
205   { S("var3"),
206         &vars[3],               0,              0,              FALSE   },
207   { S("var4"),
208         &vars[4],               0,              0,              FALSE   },
209   { S("var5"),
210         &vars[5],               0,              0,              FALSE   },
211   { S("var6"),
212         &vars[6],               0,              0,              FALSE   },
213   { S("var7"),
214         &vars[7],               0,              0,              FALSE   },
215   { 0, 0, 0, 0, 0, FALSE },
216 };
217
218 static struct functable functable[] = {
219   { "in_network",       f_in_network },
220   { "netgrp",           f_netgrp },
221   { "exists",           f_exists },
222   { "false",            f_false },
223   { "true",             f_true },
224   { 0, 0 },
225 };
226
227 /*
228  * Specially expand the remote host name first
229  */
230 static opt_apply rhost_expansion[] =
231 {
232   {&fs_static.opt_rhost, "${host}"},
233   {0, 0},
234 };
235
236 /*
237  * List of options which need to be expanded
238  * Note that the order here _may_ be important.
239  */
240 static opt_apply expansions[] =
241 {
242   {&fs_static.opt_sublink, 0},
243   {&fs_static.opt_rfs, "${path}"},
244   {&fs_static.opt_fs, "${autodir}/${rhost}${rfs}"},
245   {&fs_static.opt_opts, "rw"},
246   {&fs_static.opt_remopts, "${opts}"},
247   {&fs_static.opt_mount, 0},
248   {&fs_static.opt_unmount, 0},
249   {&fs_static.opt_cachedir, 0},
250   {&fs_static.opt_addopts, 0},
251   {0, 0},
252 };
253
254 /*
255  * List of options which need to be free'ed before re-use
256  */
257 static opt_apply to_free[] =
258 {
259   {&fs_static.fs_glob, 0},
260   {&fs_static.fs_local, 0},
261   {&fs_static.fs_mtab, 0},
262   {&fs_static.opt_sublink, 0},
263   {&fs_static.opt_rfs, 0},
264   {&fs_static.opt_fs, 0},
265   {&fs_static.opt_rhost, 0},
266   {&fs_static.opt_opts, 0},
267   {&fs_static.opt_remopts, 0},
268   {&fs_static.opt_mount, 0},
269   {&fs_static.opt_unmount, 0},
270   {&fs_static.opt_cachedir, 0},
271   {&fs_static.opt_addopts, 0},
272   {&vars[0], 0},
273   {&vars[1], 0},
274   {&vars[2], 0},
275   {&vars[3], 0},
276   {&vars[4], 0},
277   {&vars[5], 0},
278   {&vars[6], 0},
279   {&vars[7], 0},
280   {0, 0},
281 };
282
283
284 /*
285  * expand backslash escape sequences
286  */
287 static char
288 backslash(char **p)
289 {
290   char c;
291
292   if ((*p)[1] == '\0') {
293     plog(XLOG_USER, "Empty backslash escape");
294     return **p;
295   }
296
297   if (**p == '\\') {
298     (*p)++;
299     switch (**p) {
300     case 'a':
301       c = '\007';               /* Bell */
302       break;
303     case 'b':
304       c = '\010';               /* Backspace */
305       break;
306     case 't':
307       c = '\011';               /* Horizontal Tab */
308       break;
309     case 'n':
310       c = '\012';               /* New Line */
311       break;
312     case 'v':
313       c = '\013';               /* Vertical Tab */
314       break;
315     case 'f':
316       c = '\014';               /* Form Feed */
317       break;
318     case 'r':
319       c = '\015';               /* Carriage Return */
320       break;
321     case 'e':
322       c = '\033';               /* Escape */
323       break;
324     case '0':
325     case '1':
326     case '2':
327     case '3':
328     case '4':
329     case '5':
330     case '6':
331     case '7':
332       {
333         int cnt, val, ch;
334
335         for (cnt = 0, val = 0; cnt < 3; cnt++) {
336           ch = *(*p)++;
337           if (ch < '0' || ch > '7') {
338             (*p)--;
339             break;
340           }
341           val = (val << 3) | (ch - '0');
342         }
343
344         if ((val & 0xffffff00) != 0)
345           plog(XLOG_USER,
346                "Too large character constant %u\n",
347                val);
348         c = (char) val;
349         --(*p);
350       }
351       break;
352
353     default:
354       c = **p;
355       break;
356     }
357   } else
358     c = **p;
359
360   return c;
361 }
362
363
364 /*
365  * Skip to next option in the string
366  */
367 static char *
368 opt(char **p)
369 {
370   char *cp = *p;
371   char *dp = cp;
372   char *s = cp;
373
374 top:
375   while (*cp && *cp != ';') {
376     if (*cp == '"') {
377       /*
378        * Skip past string
379        */
380       for (cp++; *cp && *cp != '"'; cp++)
381         if (*cp == '\\')
382           *dp++ = backslash(&cp);
383         else
384           *dp++ = *cp;
385       if (*cp)
386         cp++;
387     } else {
388       *dp++ = *cp++;
389     }
390   }
391
392   /*
393    * Skip past any remaining ';'s
394    */
395   while (*cp == ';')
396     cp++;
397
398   /*
399    * If we have a zero length string
400    * and there are more fields, then
401    * parse the next one.  This allows
402    * sequences of empty fields.
403    */
404   if (*cp && dp == s)
405     goto top;
406
407   *dp = '\0';
408
409   *p = cp;
410   return s;
411 }
412
413
414 /*
415  * These routines add a new style of selector; function-style boolean
416  * operators.  To add new ones, just define functions as in true, false,
417  * exists (below) and add them to the functable, above.
418  *
419  * Usage example: Some people have X11R5 local, some go to a server. I do
420  * this:
421  *
422  *    *       exists(/usr/pkg/${key});type:=link;fs:=/usr/pkg/${key} || \
423  *            -type:=nfs;rfs=/usr/pkg/${key} \
424  *            rhost:=server1 \
425  *            rhost:=server2
426  *
427  * -Rens Troost <rens@imsi.com>
428  */
429 static IntFuncPtr
430 functable_lookup(char *key)
431 {
432   struct functable *fp;
433
434   for (fp = functable; fp->name; fp++)
435     if (FSTREQ(fp->name, key))
436         return (fp->func);
437   return (IntFuncPtr) NULL;
438 }
439
440
441 static int
442 eval_opts(char *opts, char *mapkey)
443 {
444   /*
445    * Fill in the global structure fs_static by
446    * cracking the string opts.  opts may be
447    * scribbled on at will.
448    */
449   char *o = opts;
450   char *f;
451
452   /*
453    * For each user-specified option
454    */
455   while (*(f = opt(&o))) {
456     struct opt *op;
457     enum vs_opt vs_opt = VarAss;
458     char *eq = strchr(f, '=');
459     char *fx;
460     IntFuncPtr func;
461     char *opt = NULL;
462
463     if (!eq || eq[1] == '\0' || eq == f) {
464       /*
465        * No value, is it a function call?
466        */
467       char *arg = strchr(f, '(');
468
469       if (!arg || arg[1] == '\0' || arg == f) {
470         /*
471          * No, just continue
472          */
473         plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f);
474         continue;
475       }
476
477       /* null-terminate the argument  */
478       *arg++ = '\0';
479       fx = strchr(arg, ')');
480       if (!arg || fx == arg) {
481         plog(XLOG_USER, "key %s: Malformed function in \"%s\"", mapkey, f);
482         continue;
483       }
484       *fx = '\0';
485
486       /*
487        * look up f in functable and pass it arg.
488        * func must return 0 on failure, and 1 on success.
489        */
490       if ((func = functable_lookup(f))) {
491         if (!(*func) (arg)) {
492           return (0);
493         }
494         continue;
495       } else if (NSTREQ(f, "!", 1) && (func = functable_lookup(&f[1]))) {
496         /* then this is a negated prefixed function such as "!exists" */
497         plog(XLOG_USER, "executing negated function %s", &f[1]);
498         if ((*func) (arg)) {
499           return (0);
500         }
501         continue;
502       } else {
503         plog(XLOG_USER, "key %s: unknown function \"%s\"", mapkey, f);
504         return (0);
505       }
506
507     }
508
509     /*
510      * Check what type of operation is happening
511      * !=, =!  is SelNE
512      * == is SelEQ
513      * := is VarAss
514      */
515     if (eq[-1] == '!') {        /* != */
516       vs_opt = SelNE;
517       eq[-1] = '\0';
518       opt = eq + 1;
519     } else if (eq[-1] == ':') { /* := */
520       vs_opt = VarAss;
521       eq[-1] = '\0';
522       opt = eq + 1;
523     } else if (eq[1] == '=') {  /* == */
524       vs_opt = SelEQ;
525       eq[0] = '\0';
526       opt = eq + 2;
527     } else if (eq[1] == '!') {  /* =! */
528       vs_opt = SelNE;
529       eq[0] = '\0';
530       opt = eq + 2;
531     }
532
533     /*
534      * For each recognized option
535      */
536     for (op = opt_fields; op->name; op++) {
537       /*
538        * Check whether they match
539        */
540       if (FSTREQ(op->name, f)) {
541         int selok;
542         switch (vs_opt) {
543         case SelEQ:
544         case SelNE:
545           if ((selok = (op->sel_p != NULL))) {
546             if (op->case_insensitive) {
547               selok = (STRCEQ(*op->sel_p, opt) == (vs_opt == SelNE));
548             } else {
549               selok = (STREQ(*op->sel_p, opt) == (vs_opt == SelNE));
550             }
551           }
552           if (selok) {
553             plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s",
554                  mapkey,
555                  op->name,
556                  *op->sel_p,
557                  vs_opt == SelNE ? "mis" : "",
558                  opt);
559             return 0;
560           }
561           /* check if to apply a function */
562           if (op->fxn_p &&
563               ((*op->fxn_p)(opt) == (vs_opt == SelNE))) {
564             plog(XLOG_MAP, "key %s: map function %s did not %smatch %s",
565                  mapkey,
566                  op->name,
567                  vs_opt == SelNE ? "mis" : "",
568                  opt);
569             return 0;
570           }
571           break;
572
573         case VarAss:
574           if (op->sel_p) {
575             plog(XLOG_USER, "key %s: Can't assign to a selector (%s)",
576                  mapkey, op->name);
577             return 0;
578           }
579           *op->optp = opt;
580           break;
581
582         } /* end of "switch (vs_opt)" statement */
583         break;                  /* break out of for loop */
584       }
585     }
586
587     if (!op->name)
588       plog(XLOG_USER, "key %s: Unrecognized key/option \"%s\"", mapkey, f);
589   }
590
591   return 1;
592 }
593
594
595 /*
596  * Skip to next option in the string, but don't scribble over the string.
597  * However, *p gets repointed to the start of the next string past ';'.
598  */
599 static char *
600 opt_no_scribble(char **p)
601 {
602   char *cp = *p;
603   char *dp = cp;
604   char *s = cp;
605
606 top:
607   while (*cp && *cp != ';') {
608     if (*cp == '\"') {
609       /*
610        * Skip past string
611        */
612       cp++;
613       while (*cp && *cp != '\"')
614         *dp++ = *cp++;
615       if (*cp)
616         cp++;
617     } else {
618       *dp++ = *cp++;
619     }
620   }
621
622   /*
623    * Skip past any remaining ';'s
624    */
625   while (*cp == ';')
626     cp++;
627
628   /*
629    * If we have a zero length string
630    * and there are more fields, then
631    * parse the next one.  This allows
632    * sequences of empty fields.
633    */
634   if (*cp && dp == s)
635     goto top;
636
637   *p = cp;
638   return s;
639 }
640
641
642 /*
643  * Strip any selectors from a string.  Selectors are all assumed to be
644  * first in the string.  This is used for the new /defaults method which will
645  * use selectors as well.
646  */
647 char *
648 strip_selectors(char *opts, char *mapkey)
649 {
650   /*
651    * Fill in the global structure fs_static by
652    * cracking the string opts.  opts may be
653    * scribbled on at will.
654    */
655   char *o = opts;
656   char *oo = opts;
657   char *f;
658
659   /*
660    * Scan options.  Note that the opt() function scribbles on the opt string.
661    */
662   while (*(f = opt_no_scribble(&o))) {
663     enum vs_opt vs_opt = VarAss;
664     char *eq = strchr(f, '=');
665
666     if (!eq || eq[1] == '\0' || eq == f) {
667       /*
668        * No option or assignment?  Return as is.
669        */
670       plog(XLOG_USER, "key %s: No option or assignment in \"%s\"", mapkey, f);
671       return o;
672     }
673     /*
674      * Check what type of operation is happening
675      * !=, =!  is SelNE
676      * == is SelEQ
677      * := is VarAss
678      */
679     if (eq[-1] == '!') {        /* != */
680       vs_opt = SelNE;
681     } else if (eq[-1] == ':') { /* := */
682       vs_opt = VarAss;
683     } else if (eq[1] == '=') {  /* == */
684       vs_opt = SelEQ;
685     } else if (eq[1] == '!') {  /* =! */
686       vs_opt = SelNE;
687     }
688     switch (vs_opt) {
689     case SelEQ:
690     case SelNE:
691       /* Skip this selector, maybe there's another one following it */
692       plog(XLOG_USER, "skipping selector to \"%s\"", o);
693       /* store previous match. it may have been the first assignment */
694       oo = o;
695       break;
696
697     case VarAss:
698       /* found the first assignment, return the string starting with it */
699 #ifdef DEBUG
700       dlog("found first assignment past selectors \"%s\"", o);
701 #endif /* DEBUG */
702       return oo;
703     }
704   }
705
706   /* return the same string by default. should not happen. */
707   return oo;
708 }
709
710
711 /*****************************************************************************
712  *** BOOLEAN FUNCTIONS (return 0 if false, 1 if true):                     ***
713  *****************************************************************************/
714
715 /* test if arg is any of this host's network names or numbers */
716 static int
717 f_in_network(char *arg)
718 {
719   int status;
720
721   if (!arg)
722     return FALSE;
723
724   status = is_network_member(arg);
725 #ifdef DEBUG
726     plog(XLOG_USER, "%s is %son a local network",
727          arg, (status ? "" : "not "));
728 #endif /* DEBUG */
729   return status;
730 }
731
732
733 /* test if this host is in netgroup (arg) */
734 static int
735 f_netgrp(char *arg)
736 {
737   int status;
738
739   status = innetgr(arg, opt_host, NULL, NULL);
740 #ifdef DEBUG
741   plog(XLOG_USER, "netgrp = %s status = %d host = %s", arg, status, opt_host);
742 #endif /* DEBUG */
743   return status;
744 }
745
746
747 /* test if file (arg) exists via lstat */
748 static int
749 f_exists(char *arg)
750 {
751   struct stat buf;
752
753   if (lstat(arg, &buf) < 0)
754     return (0);
755   else
756     return (1);
757 }
758
759
760 /* always false */
761 static int
762 f_false(char *arg)
763 {
764   return (0);
765 }
766
767
768 /* always true */
769 static int
770 f_true(char *arg)
771 {
772   return (1);
773 }
774
775
776 /*
777  * Free an option
778  */
779 static void
780 free_op(opt_apply *p, int b)
781 {
782   if (*p->opt) {
783     XFREE(*p->opt);
784     *p->opt = 0;
785   }
786 }
787
788
789 /*
790  * Normalize slashes in the string.
791  */
792 void
793 normalize_slash(char *p)
794 {
795   char *f = strchr(p, '/');
796   char *f0 = f;
797
798   if (f) {
799     char *t = f;
800     do {
801       /* assert(*f == '/'); */
802       if (f == f0 && f[0] == '/' && f[1] == '/') {
803         /* copy double slash iff first */
804         *t++ = *f++;
805         *t++ = *f++;
806       } else {
807         /* copy a single / across */
808         *t++ = *f++;
809       }
810
811       /* assert(f[-1] == '/'); */
812       /* skip past more /'s */
813       while (*f == '/')
814         f++;
815
816       /* assert(*f != '/'); */
817       /* keep copying up to next / */
818       while (*f && *f != '/') {
819         *t++ = *f++;
820       }
821
822       /* assert(*f == 0 || *f == '/'); */
823
824     } while (*f);
825     *t = 0;                     /* derived from fix by Steven Glassman */
826   }
827 }
828
829
830 /*
831  * Macro-expand an option.  Note that this does not
832  * handle recursive expansions.  They will go badly wrong.
833  * If sel is true then old expand selectors, otherwise
834  * don't expand selectors.
835  */
836 static void
837 expand_op(opt_apply *p, int sel_p)
838 {
839   static char expand_error[] = "No space to expand \"%s\"";
840   char expbuf[MAXPATHLEN + 1];
841   char nbuf[NLEN + 1];
842   char *ep = expbuf;
843   char *cp = *p->opt;
844   char *dp;
845   struct opt *op;
846 #ifdef DEBUG
847   char *cp_orig = *p->opt;
848 #endif /* DEBUG */
849
850   while ((dp = strchr(cp, '$'))) {
851     char ch;
852     /*
853      * First copy up to the $
854      */
855     {
856       int len = dp - cp;
857
858       if (BUFSPACE(ep, len)) {
859         strncpy(ep, cp, len);
860         ep += len;
861       } else {
862         plog(XLOG_ERROR, expand_error, *p->opt);
863         goto out;
864       }
865     }
866
867     cp = dp + 1;
868     ch = *cp++;
869     if (ch == '$') {
870       if (BUFSPACE(ep, 1)) {
871         *ep++ = '$';
872       } else {
873         plog(XLOG_ERROR, expand_error, *p->opt);
874         goto out;
875       }
876     } else if (ch == '{') {
877       /* Expansion... */
878       enum {
879         E_All, E_Dir, E_File, E_Domain, E_Host
880       } todo;
881       /*
882        * Find closing brace
883        */
884       char *br_p = strchr(cp, '}');
885       int len;
886
887       /*
888        * Check we found it
889        */
890       if (!br_p) {
891         /*
892          * Just give up
893          */
894         plog(XLOG_USER, "No closing '}' in \"%s\"", *p->opt);
895         goto out;
896       }
897       len = br_p - cp;
898
899       /*
900        * Figure out which part of the variable to grab.
901        */
902       if (*cp == '/') {
903         /*
904          * Just take the last component
905          */
906         todo = E_File;
907         cp++;
908         --len;
909       } else if (br_p[-1] == '/') {
910         /*
911          * Take all but the last component
912          */
913         todo = E_Dir;
914         --len;
915       } else if (*cp == '.') {
916         /*
917          * Take domain name
918          */
919         todo = E_Domain;
920         cp++;
921         --len;
922       } else if (br_p[-1] == '.') {
923         /*
924          * Take host name
925          */
926         todo = E_Host;
927         --len;
928       } else {
929         /*
930          * Take the whole lot
931          */
932         todo = E_All;
933       }
934
935       /*
936        * Truncate if too long.  Since it won't
937        * match anyway it doesn't matter that
938        * it has been cut short.
939        */
940       if (len > NLEN)
941         len = NLEN;
942
943       /*
944        * Put the string into another buffer so
945        * we can do comparisons.
946        */
947       strncpy(nbuf, cp, len);
948       nbuf[len] = '\0';
949
950       /*
951        * Advance cp
952        */
953       cp = br_p + 1;
954
955       /*
956        * Search the option array
957        */
958       for (op = opt_fields; op->name; op++) {
959         /*
960          * Check for match
961          */
962         if (len == op->nlen && STREQ(op->name, nbuf)) {
963           char xbuf[NLEN + 3];
964           char *val;
965           /*
966            * Found expansion.  Copy
967            * the correct value field.
968            */
969           if (!(!op->sel_p == !sel_p)) {
970             /*
971              * Copy the string across unexpanded
972              */
973             sprintf(xbuf, "${%s%s%s}",
974                     todo == E_File ? "/" :
975                     todo == E_Domain ? "." : "",
976                     nbuf,
977                     todo == E_Dir ? "/" :
978                     todo == E_Host ? "." : "");
979             val = xbuf;
980             /*
981              * Make sure expansion doesn't
982              * munge the value!
983              */
984             todo = E_All;
985           } else if (op->sel_p) {
986             val = *op->sel_p;
987           } else {
988             val = *op->optp;
989           }
990
991           if (val) {
992             /*
993              * Do expansion:
994              * ${/var} means take just the last part
995              * ${var/} means take all but the last part
996              * ${.var} means take all but first part
997              * ${var.} means take just the first part
998              * ${var} means take the whole lot
999              */
1000             int vlen = strlen(val);
1001             char *vptr = val;
1002             switch (todo) {
1003             case E_Dir:
1004               vptr = strrchr(val, '/');
1005               if (vptr)
1006                 vlen = vptr - val;
1007               vptr = val;
1008               break;
1009             case E_File:
1010               vptr = strrchr(val, '/');
1011               if (vptr) {
1012                 vptr++;
1013                 vlen = strlen(vptr);
1014               } else
1015                 vptr = val;
1016               break;
1017             case E_Domain:
1018               vptr = strchr(val, '.');
1019               if (vptr) {
1020                 vptr++;
1021                 vlen = strlen(vptr);
1022               } else {
1023                 vptr = "";
1024                 vlen = 0;
1025               }
1026               break;
1027             case E_Host:
1028               vptr = strchr(val, '.');
1029               if (vptr)
1030                 vlen = vptr - val;
1031               vptr = val;
1032               break;
1033             case E_All:
1034               break;
1035             }
1036
1037             if (BUFSPACE(ep, vlen)) {
1038               strcpy(ep, vptr);
1039               ep += vlen;
1040             } else {
1041               plog(XLOG_ERROR, expand_error, *p->opt);
1042               goto out;
1043             }
1044           }
1045           /*
1046            * Done with this variable
1047            */
1048           break;
1049         }
1050       }
1051
1052       /*
1053        * Check that the search was successful
1054        */
1055       if (!op->name) {
1056         /*
1057          * If it wasn't then scan the
1058          * environment for that name
1059          * and use any value found
1060          */
1061         char *env = getenv(nbuf);
1062
1063         if (env) {
1064           int vlen = strlen(env);
1065
1066           if (BUFSPACE(ep, vlen)) {
1067             strcpy(ep, env);
1068             ep += vlen;
1069           } else {
1070             plog(XLOG_ERROR, expand_error, *p->opt);
1071             goto out;
1072           }
1073 #ifdef DEBUG
1074           amuDebug(D_STR)
1075             plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env);
1076 #endif /* DEBUG */
1077         } else {
1078           plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf);
1079         }
1080       }
1081     } else {
1082       /*
1083        * Error, error
1084        */
1085       plog(XLOG_USER, "Unknown $ sequence in \"%s\"", *p->opt);
1086     }
1087   }
1088
1089 out:
1090   /*
1091    * Handle common case - no expansion
1092    */
1093   if (cp == *p->opt) {
1094     *p->opt = strdup(cp);
1095   } else {
1096     /*
1097      * Finish off the expansion
1098      */
1099     if (BUFSPACE(ep, strlen(cp))) {
1100       strcpy(ep, cp);
1101       /* ep += strlen(ep); */
1102     } else {
1103       plog(XLOG_ERROR, expand_error, *p->opt);
1104     }
1105
1106     /*
1107      * Save the expansion
1108      */
1109     *p->opt = strdup(expbuf);
1110   }
1111
1112   normalize_slash(*p->opt);
1113
1114 #ifdef DEBUG
1115   amuDebug(D_STR) {
1116     plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig);
1117     plog(XLOG_DEBUG, "... is \"%s\"", *p->opt);
1118   }
1119 #endif /* DEBUG */
1120 }
1121
1122
1123 /*
1124  * Wrapper for expand_op
1125  */
1126 static void
1127 expand_opts(opt_apply *p, int sel_p)
1128 {
1129   if (*p->opt) {
1130     expand_op(p, sel_p);
1131   } else if (p->val) {
1132     /*
1133      * Do double expansion, remembering
1134      * to free the string from the first
1135      * expansion...
1136      */
1137     char *s = *p->opt = expand_key(p->val);
1138     expand_op(p, sel_p);
1139     XFREE(s);
1140   }
1141 }
1142
1143
1144 /*
1145  * Apply a function to a list of options
1146  */
1147 static void
1148 apply_opts(void (*op) (opt_apply *, int), opt_apply ppp[], int b)
1149 {
1150   opt_apply *pp;
1151
1152   for (pp = ppp; pp->opt; pp++)
1153     (*op) (pp, b);
1154 }
1155
1156
1157 /*
1158  * Free the option table
1159  */
1160 void
1161 free_opts(am_opts *fo)
1162 {
1163   /*
1164    * Copy in the structure we are playing with
1165    */
1166   fs_static = *fo;
1167
1168   /*
1169    * Free previously allocated memory
1170    */
1171   apply_opts(free_op, to_free, FALSE);
1172 }
1173
1174
1175 /*
1176  * Expand lookup key
1177  */
1178 char *
1179 expand_key(char *key)
1180 {
1181   opt_apply oa;
1182
1183   oa.opt = &key;
1184   oa.val = 0;
1185   expand_opts(&oa, TRUE);
1186
1187   return key;
1188 }
1189
1190
1191 /*
1192  * Remove trailing /'s from a string
1193  * unless the string is a single / (Steven Glassman)
1194  * or unless it is two slashes // (Kevin D. Bond)
1195  */
1196 void
1197 deslashify(char *s)
1198 {
1199   if (s && *s) {
1200     char *sl = s + strlen(s);
1201
1202     while (*--sl == '/' && sl > s)
1203       *sl = '\0';
1204   }
1205 }
1206
1207
1208 int
1209 eval_fs_opts(am_opts *fo, char *opts, char *g_opts, char *path, char *key, char *map)
1210 {
1211   int ok = TRUE;
1212
1213   free_opts(fo);
1214
1215   /*
1216    * Clear out the option table
1217    */
1218   memset((voidp) &fs_static, 0, sizeof(fs_static));
1219   memset((voidp) vars, 0, sizeof(vars));
1220   memset((voidp) fo, 0, sizeof(*fo));
1221
1222   /* set hostname */
1223   opt_host = (char *) am_get_hostname();
1224
1225   /*
1226    * Set key, map & path before expansion
1227    */
1228   opt_key = key;
1229   opt_map = map;
1230   opt_path = path;
1231
1232   opt_dkey = strchr(key, '.');
1233   if (!opt_dkey) {
1234     opt_dkey = NullStr;
1235     opt_keyd = key;
1236   } else {
1237     opt_keyd = strnsave(key, opt_dkey - key);
1238     opt_dkey++;
1239     if (*opt_dkey == '\0')      /* check for 'host.' */
1240       opt_dkey = NullStr;
1241   }
1242
1243   /*
1244    * Expand global options
1245    */
1246   fs_static.fs_glob = expand_key(g_opts);
1247
1248   /*
1249    * Expand local options
1250    */
1251   fs_static.fs_local = expand_key(opts);
1252
1253   /*
1254    * Expand default (global) options
1255    */
1256   if (!eval_opts(fs_static.fs_glob, key))
1257     ok = FALSE;
1258
1259   /*
1260    * Expand local options
1261    */
1262   if (ok && !eval_opts(fs_static.fs_local, key))
1263     ok = FALSE;
1264
1265   /*
1266    * Normalize remote host name.
1267    * 1.  Expand variables
1268    * 2.  Normalize relative to host tables
1269    * 3.  Strip local domains from the remote host
1270    *     name before using it in other expansions.
1271    *     This makes mount point names and other things
1272    *     much shorter, while allowing cross domain
1273    *     sharing of mount maps.
1274    */
1275   apply_opts(expand_opts, rhost_expansion, FALSE);
1276   if (ok && fs_static.opt_rhost && *fs_static.opt_rhost)
1277     host_normalize(&fs_static.opt_rhost);
1278
1279   /*
1280    * Macro expand the options.
1281    * Do this regardless of whether we are accepting
1282    * this mount - otherwise nasty things happen
1283    * with memory allocation.
1284    */
1285   apply_opts(expand_opts, expansions, FALSE);
1286
1287   /*
1288    * Strip trailing slashes from local pathname...
1289    */
1290   deslashify(fs_static.opt_fs);
1291
1292   /*
1293    * ok... copy the data back out.
1294    */
1295   *fo = fs_static;
1296
1297   /*
1298    * Clear defined options
1299    */
1300   if (opt_keyd != key && opt_keyd != nullstr)
1301     XFREE(opt_keyd);
1302   opt_keyd = nullstr;
1303   opt_dkey = NullStr;
1304   opt_key = opt_map = opt_path = nullstr;
1305
1306   return ok;
1307 }