2 * Copyright (c) 1997-2004 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.
8 * This code is derived from software contributed to Berkeley by
9 * Jan-Simon Pendry at Imperial College, London.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
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.
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
41 * $Id: opts.c,v 1.8.2.7 2004/01/06 03:15:16 ezk Exp $
47 #endif /* HAVE_CONFIG_H */
54 #define NLEN 16 /* Length of longest option name (conservative) */
55 #define S(x) (x) , (sizeof(x)-1)
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).
62 #define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+MAXPATHLEN)
67 typedef int (*IntFuncPtr) (char *);
68 typedef struct opt_apply opt_apply;
69 enum vs_opt { SelEQ, SelNE, VarAss };
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 */
96 static int f_in_network(char *);
97 static int f_netgrp(char *);
98 static int f_netgrpd(char *);
99 static int f_exists(char *);
100 static int f_false(char *);
101 static int f_true(char *);
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 uid_str[12], gid_str[12];
116 char *opt_uid = uid_str;
117 char *opt_gid = gid_str;
118 static char *vars[8];
119 static char *literal_dollar = "$"; /* ${dollar}: a literal '$' in maps */
124 struct am_opts fs_static; /* copy of the options to play with */
128 * Options in something corresponding to frequency of use so that
129 * first-match algorithm is sped up.
131 static struct opt opt_fields[] = {
133 Option str. Selector str. boolean fxn. case sensitive */
135 &fs_static.opt_opts, 0, 0, FALSE },
137 0, &opt_host, 0, TRUE },
139 0, &opt_hostd, 0, TRUE },
141 &fs_static.opt_type, 0, 0, FALSE },
143 &fs_static.opt_rhost, 0, 0, TRUE },
145 &fs_static.opt_rfs, 0, 0, FALSE },
147 &fs_static.opt_fs, 0, 0, FALSE },
149 0, &opt_key, 0, FALSE },
151 0, &opt_map, 0, FALSE },
153 &fs_static.opt_sublink, 0, 0, FALSE },
155 0, &gopt.arch, 0, TRUE },
157 &fs_static.opt_dev, 0, 0, FALSE },
159 &fs_static.opt_pref, 0, 0, FALSE },
161 0, &opt_path, 0, FALSE },
163 0, &gopt.auto_dir, 0, FALSE },
165 &fs_static.opt_delay, 0, 0, FALSE },
167 0, &hostdomain, 0, TRUE },
169 0, &gopt.karch, 0, TRUE },
171 0, &gopt.cluster, 0, TRUE },
173 0, 0, f_in_network, TRUE },
175 0, 0, f_in_network, TRUE },
177 0, 0, f_in_network, TRUE },
179 0, &endian, 0, TRUE },
181 0, &gopt.op_sys, 0, TRUE },
183 0, &gopt.op_sys_ver, 0, TRUE },
185 0, &gopt.op_sys_full, 0, TRUE },
187 0, &gopt.op_sys_vendor, 0, TRUE },
189 &fs_static.opt_remopts, 0, 0, FALSE },
191 &fs_static.opt_mount, 0, 0, FALSE },
193 &fs_static.opt_unmount, 0, 0, FALSE },
195 &fs_static.opt_cache, 0, 0, FALSE },
197 &fs_static.opt_user, 0, 0, FALSE },
199 &fs_static.opt_group, 0, 0, FALSE },
201 0, &opt_dkey, 0, FALSE },
203 0, &opt_keyd, 0, FALSE },
204 /* XXX: should maptype really be a variable? I think selector. -Erez */
206 &fs_static.opt_maptype, 0, 0, FALSE },
208 &fs_static.opt_cachedir, 0, 0, FALSE },
210 &fs_static.opt_addopts, 0, 0, FALSE },
212 0, &opt_uid, 0, FALSE },
214 0, &opt_gid, 0, FALSE },
216 &literal_dollar, 0, 0, FALSE },
218 &vars[0], 0, 0, FALSE },
220 &vars[1], 0, 0, FALSE },
222 &vars[2], 0, 0, FALSE },
224 &vars[3], 0, 0, FALSE },
226 &vars[4], 0, 0, FALSE },
228 &vars[5], 0, 0, FALSE },
230 &vars[6], 0, 0, FALSE },
232 &vars[7], 0, 0, FALSE },
233 { 0, 0, 0, 0, 0, FALSE },
236 static struct functable functable[] = {
237 { "in_network", f_in_network },
238 { "netgrp", f_netgrp },
239 { "netgrpd", f_netgrpd },
240 { "exists", f_exists },
241 { "false", f_false },
247 * Specially expand the remote host name first
249 static opt_apply rhost_expansion[] =
251 {&fs_static.opt_rhost, "${host}"},
256 * List of options which need to be expanded
257 * Note that the order here _may_ be important.
259 static opt_apply expansions[] =
261 {&fs_static.opt_sublink, 0},
262 {&fs_static.opt_rfs, "${path}"},
263 {&fs_static.opt_fs, "${autodir}/${rhost}${rfs}"},
264 {&fs_static.opt_opts, "rw"},
265 {&fs_static.opt_remopts, "${opts}"},
266 {&fs_static.opt_mount, 0},
267 {&fs_static.opt_unmount, 0},
268 {&fs_static.opt_cachedir, 0},
269 {&fs_static.opt_addopts, 0},
274 * List of options which need to be free'ed before re-use
276 static opt_apply to_free[] =
278 {&fs_static.fs_glob, 0},
279 {&fs_static.fs_local, 0},
280 {&fs_static.fs_mtab, 0},
281 {&fs_static.opt_sublink, 0},
282 {&fs_static.opt_rfs, 0},
283 {&fs_static.opt_fs, 0},
284 {&fs_static.opt_rhost, 0},
285 {&fs_static.opt_opts, 0},
286 {&fs_static.opt_remopts, 0},
287 {&fs_static.opt_mount, 0},
288 {&fs_static.opt_unmount, 0},
289 {&fs_static.opt_cachedir, 0},
290 {&fs_static.opt_addopts, 0},
304 * expand backslash escape sequences
311 if ((*p)[1] == '\0') {
312 plog(XLOG_USER, "Empty backslash escape");
320 c = '\007'; /* Bell */
323 c = '\010'; /* Backspace */
326 c = '\011'; /* Horizontal Tab */
329 c = '\012'; /* New Line */
332 c = '\013'; /* Vertical Tab */
335 c = '\014'; /* Form Feed */
338 c = '\015'; /* Carriage Return */
341 c = '\033'; /* Escape */
354 for (cnt = 0, val = 0; cnt < 3; cnt++) {
356 if (ch < '0' || ch > '7') {
360 val = (val << 3) | (ch - '0');
363 if ((val & 0xffffff00) != 0)
365 "Too large character constant %u\n",
384 * Skip to next option in the string
394 while (*cp && *cp != ';') {
399 for (cp++; *cp && *cp != '"'; cp++)
401 *dp++ = backslash(&cp);
412 * Skip past any remaining ';'s
418 * If we have a zero length string
419 * and there are more fields, then
420 * parse the next one. This allows
421 * sequences of empty fields.
434 * These routines add a new style of selector; function-style boolean
435 * operators. To add new ones, just define functions as in true, false,
436 * exists (below) and add them to the functable, above.
438 * Usage example: Some people have X11R5 local, some go to a server. I do
441 * * exists(/usr/pkg/${key});type:=link;fs:=/usr/pkg/${key} || \
442 * -type:=nfs;rfs=/usr/pkg/${key} \
446 * -Rens Troost <rens@imsi.com>
449 functable_lookup(char *key)
451 struct functable *fp;
453 for (fp = functable; fp->name; fp++)
454 if (FSTREQ(fp->name, key))
456 return (IntFuncPtr) NULL;
461 eval_opts(char *opts, char *mapkey)
464 * Fill in the global structure fs_static by
465 * cracking the string opts. opts may be
466 * scribbled on at will.
472 * For each user-specified option
474 while (*(f = opt(&o))) {
476 enum vs_opt vs_opt = VarAss;
477 char *eq = strchr(f, '=');
482 if (!eq || eq[1] == '\0' || eq == f) {
484 * No value, is it a function call?
486 char *arg = strchr(f, '(');
488 if (!arg || arg[1] == '\0' || arg == f) {
492 plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f);
496 /* null-terminate the argument */
498 fx = strchr(arg, ')');
499 if (!arg || fx == arg) {
500 plog(XLOG_USER, "key %s: Malformed function in \"%s\"", mapkey, f);
506 * look up f in functable and pass it arg.
507 * func must return 0 on failure, and 1 on success.
509 if ((func = functable_lookup(f))) {
510 if (!(*func) (arg)) {
514 } else if (NSTREQ(f, "!", 1) && (func = functable_lookup(&f[1]))) {
515 /* then this is a negated prefixed function such as "!exists" */
516 plog(XLOG_INFO, "executing negated function %s", &f[1]);
522 plog(XLOG_USER, "key %s: unknown function \"%s\"", mapkey, f);
529 * Check what type of operation is happening
534 if (eq[-1] == '!') { /* != */
538 } else if (eq[-1] == ':') { /* := */
542 } else if (eq[1] == '=') { /* == */
546 } else if (eq[1] == '!') { /* =! */
553 * For each recognized option
555 for (op = opt_fields; op->name; op++) {
557 * Check whether they match
559 if (FSTREQ(op->name, f)) {
564 if ((selok = (op->sel_p != NULL))) {
565 if (op->case_insensitive) {
566 selok = (STRCEQ(*op->sel_p, opt) == (vs_opt == SelNE));
568 selok = (STREQ(*op->sel_p, opt) == (vs_opt == SelNE));
572 plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s",
576 vs_opt == SelNE ? "mis" : "",
580 /* check if to apply a function */
582 ((*op->fxn_p)(opt) == (vs_opt == SelNE))) {
583 plog(XLOG_MAP, "key %s: map function %s did not %smatch %s",
586 vs_opt == SelNE ? "mis" : "",
594 plog(XLOG_USER, "key %s: Can't assign to a selector (%s)",
601 } /* end of "switch (vs_opt)" statement */
602 break; /* break out of for loop */
607 plog(XLOG_USER, "key %s: Unrecognized key/option \"%s\"", mapkey, f);
615 * Skip to next option in the string, but don't scribble over the string.
616 * However, *p gets repointed to the start of the next string past ';'.
619 opt_no_scribble(char **p)
626 while (*cp && *cp != ';') {
632 while (*cp && *cp != '\"')
642 * Skip past any remaining ';'s
648 * If we have a zero length string
649 * and there are more fields, then
650 * parse the next one. This allows
651 * sequences of empty fields.
662 * Strip any selectors from a string. Selectors are all assumed to be
663 * first in the string. This is used for the new /defaults method which will
664 * use selectors as well.
667 strip_selectors(char *opts, char *mapkey)
670 * Fill in the global structure fs_static by
671 * cracking the string opts. opts may be
672 * scribbled on at will.
679 * Scan options. Note that the opt() function scribbles on the opt string.
681 while (*(f = opt_no_scribble(&o))) {
682 enum vs_opt vs_opt = VarAss;
683 char *eq = strchr(f, '=');
685 if (!eq || eq[1] == '\0' || eq == f) {
687 * No option or assignment? Return as is.
689 plog(XLOG_USER, "key %s: No option or assignment in \"%s\"", mapkey, f);
693 * Check what type of operation is happening
698 if (eq[-1] == '!') { /* != */
700 } else if (eq[-1] == ':') { /* := */
702 } else if (eq[1] == '=') { /* == */
704 } else if (eq[1] == '!') { /* =! */
710 /* Skip this selector, maybe there's another one following it */
711 plog(XLOG_USER, "skipping selector to \"%s\"", o);
712 /* store previous match. it may have been the first assignment */
717 /* found the first assignment, return the string starting with it */
719 dlog("found first assignment past selectors \"%s\"", o);
725 /* return the same string by default. should not happen. */
730 /*****************************************************************************
731 *** BOOLEAN FUNCTIONS (return 0 if false, 1 if true): ***
732 *****************************************************************************/
734 /* test if arg is any of this host's network names or numbers */
736 f_in_network(char *arg)
743 status = is_network_member(arg);
745 plog(XLOG_USER, "%s is %son a local network",
746 arg, (status ? "" : "not "));
752 /* test if this host (short hostname form) is in netgroup (arg) */
758 status = innetgr(arg, opt_host, NULL, NULL);
760 plog(XLOG_USER, "netgrp = %s status = %d host = %s", arg, status, opt_host);
766 /* test if this host (fully-qualified name) is in netgroup (arg) */
772 status = innetgr(arg, opt_hostd, NULL, NULL);
774 plog(XLOG_USER, "netgrp = %s status = %d hostd = %s", arg, status, opt_hostd);
780 /* test if file (arg) exists via lstat */
786 if (lstat(arg, &buf) < 0)
813 free_op(opt_apply *p, int b)
823 * Normalize slashes in the string.
826 normalize_slash(char *p)
828 char *f = strchr(p, '/');
834 /* assert(*f == '/'); */
835 if (f == f0 && f[0] == '/' && f[1] == '/') {
836 /* copy double slash iff first */
840 /* copy a single / across */
844 /* assert(f[-1] == '/'); */
845 /* skip past more /'s */
849 /* assert(*f != '/'); */
850 /* keep copying up to next / */
851 while (*f && *f != '/') {
855 /* assert(*f == 0 || *f == '/'); */
858 *t = 0; /* derived from fix by Steven Glassman */
864 * Macro-expand an option. Note that this does not
865 * handle recursive expansions. They will go badly wrong.
866 * If sel is true then old expand selectors, otherwise
867 * don't expand selectors.
870 expand_op(opt_apply *p, int sel_p)
872 static const char expand_error[] = "No space to expand \"%s\"";
873 char expbuf[MAXPATHLEN + 1];
880 char *cp_orig = *p->opt;
883 while ((dp = strchr(cp, '$'))) {
886 * First copy up to the $
891 if (BUFSPACE(ep, len)) {
892 strncpy(ep, cp, len);
895 plog(XLOG_ERROR, expand_error, *p->opt);
903 if (BUFSPACE(ep, 1)) {
906 plog(XLOG_ERROR, expand_error, *p->opt);
909 } else if (ch == '{') {
912 E_All, E_Dir, E_File, E_Domain, E_Host
917 char *br_p = strchr(cp, '}');
927 plog(XLOG_USER, "No closing '}' in \"%s\"", *p->opt);
933 * Figure out which part of the variable to grab.
937 * Just take the last component
942 } else if (br_p[-1] == '/') {
944 * Take all but the last component
948 } else if (*cp == '.') {
955 } else if (br_p[-1] == '.') {
969 * Truncate if too long. Since it won't
970 * match anyway it doesn't matter that
971 * it has been cut short.
977 * Put the string into another buffer so
978 * we can do comparisons.
980 strncpy(nbuf, cp, len);
989 * Search the option array
991 for (op = opt_fields; op->name; op++) {
995 if (len == op->nlen && STREQ(op->name, nbuf)) {
999 * Found expansion. Copy
1000 * the correct value field.
1002 if (!(!op->sel_p == !sel_p)) {
1004 * Copy the string across unexpanded
1006 sprintf(xbuf, "${%s%s%s}",
1007 todo == E_File ? "/" :
1008 todo == E_Domain ? "." : "",
1010 todo == E_Dir ? "/" :
1011 todo == E_Host ? "." : "");
1014 * Make sure expansion doesn't
1018 } else if (op->sel_p) {
1027 * ${/var} means take just the last part
1028 * ${var/} means take all but the last part
1029 * ${.var} means take all but first part
1030 * ${var.} means take just the first part
1031 * ${var} means take the whole lot
1033 int vlen = strlen(val);
1037 vptr = strrchr(val, '/');
1043 vptr = strrchr(val, '/');
1046 vlen = strlen(vptr);
1051 vptr = strchr(val, '.');
1054 vlen = strlen(vptr);
1061 vptr = strchr(val, '.');
1070 if (BUFSPACE(ep, vlen)) {
1074 plog(XLOG_ERROR, expand_error, *p->opt);
1079 * Done with this variable
1086 * Check that the search was successful
1090 * If it wasn't then scan the
1091 * environment for that name
1092 * and use any value found
1094 char *env = getenv(nbuf);
1097 int vlen = strlen(env);
1099 if (BUFSPACE(ep, vlen)) {
1103 plog(XLOG_ERROR, expand_error, *p->opt);
1108 plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env);
1111 plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf);
1118 plog(XLOG_USER, "Unknown $ sequence in \"%s\"", *p->opt);
1124 * Handle common case - no expansion
1126 if (cp == *p->opt) {
1127 *p->opt = strdup(cp);
1130 * Finish off the expansion
1132 if (BUFSPACE(ep, strlen(cp))) {
1134 /* ep += strlen(ep); */
1136 plog(XLOG_ERROR, expand_error, *p->opt);
1140 * Save the expansion
1142 *p->opt = strdup(expbuf);
1145 normalize_slash(*p->opt);
1149 plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig);
1150 plog(XLOG_DEBUG, "... is \"%s\"", *p->opt);
1157 * Wrapper for expand_op
1160 expand_opts(opt_apply *p, int sel_p)
1163 expand_op(p, sel_p);
1164 } else if (p->val) {
1166 * Do double expansion, remembering
1167 * to free the string from the first
1170 char *s = *p->opt = expand_key(p->val);
1171 expand_op(p, sel_p);
1178 * Apply a function to a list of options
1181 apply_opts(void (*op) (opt_apply *, int), opt_apply ppp[], int b)
1185 for (pp = ppp; pp->opt; pp++)
1191 * Free the option table
1194 free_opts(am_opts *fo)
1197 * Copy in the structure we are playing with
1202 * Free previously allocated memory
1204 apply_opts(free_op, to_free, FALSE);
1212 expand_key(char *key)
1218 expand_opts(&oa, TRUE);
1225 * Remove trailing /'s from a string
1226 * unless the string is a single / (Steven Glassman)
1227 * or unless it is two slashes // (Kevin D. Bond)
1233 char *sl = s + strlen(s);
1235 while (*--sl == '/' && sl > s)
1242 eval_fs_opts(am_opts *fo, char *opts, char *g_opts, char *path, char *key, char *map)
1249 * Clear out the option table
1251 memset((voidp) &fs_static, 0, sizeof(fs_static));
1252 memset((voidp) vars, 0, sizeof(vars));
1253 memset((voidp) fo, 0, sizeof(*fo));
1256 opt_host = (char *) am_get_hostname();
1259 * Set key, map & path before expansion
1265 opt_dkey = strchr(key, '.');
1270 opt_keyd = strnsave(key, opt_dkey - key);
1272 if (*opt_dkey == '\0') /* check for 'host.' */
1277 * Expand global options
1279 fs_static.fs_glob = expand_key(g_opts);
1282 * Expand local options
1284 fs_static.fs_local = expand_key(opts);
1287 * Expand default (global) options
1289 if (!eval_opts(fs_static.fs_glob, key))
1293 * Expand local options
1295 if (ok && !eval_opts(fs_static.fs_local, key))
1299 * Normalize remote host name.
1300 * 1. Expand variables
1301 * 2. Normalize relative to host tables
1302 * 3. Strip local domains from the remote host
1303 * name before using it in other expansions.
1304 * This makes mount point names and other things
1305 * much shorter, while allowing cross domain
1306 * sharing of mount maps.
1308 apply_opts(expand_opts, rhost_expansion, FALSE);
1309 if (ok && fs_static.opt_rhost && *fs_static.opt_rhost)
1310 host_normalize(&fs_static.opt_rhost);
1313 * Macro expand the options.
1314 * Do this regardless of whether we are accepting
1315 * this mount - otherwise nasty things happen
1316 * with memory allocation.
1318 apply_opts(expand_opts, expansions, FALSE);
1321 * Strip trailing slashes from local pathname...
1323 deslashify(fs_static.opt_fs);
1326 * ok... copy the data back out.
1331 * Clear defined options
1333 if (opt_keyd != key && opt_keyd != nullstr)
1337 opt_key = opt_map = opt_path = nullstr;