/*- * Copyright (c) 1989 Jan-Simon Pendry * Copyright (c) 1989 Imperial College of Science, Technology & Medicine * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Jan-Simon Pendry at Imperial College, London. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint /*static char sccsid[] = "from: @(#)opts.c 8.1 (Berkeley) 6/6/93";*/ static char *rcsid = "$Id: opts.c,v 1.9 2003/06/02 23:36:51 millert Exp $"; #endif /* not lint */ #include "am.h" /* * static copy of the options with * which to play */ static struct am_opts fs_static; static char *opt_host = hostname; static char *opt_hostd = hostd; static char nullstr[] = ""; static char *opt_key = nullstr; static char *opt_map = nullstr; static char *opt_path = nullstr; static char *vars[8]; /* * Length of longest option name */ #define NLEN 16 /* conservative */ #define S(x) (x) , (sizeof(x)-1) static struct opt { char *name; /* Name of the option */ int nlen; /* Length of option name */ char **optp; /* Pointer to option value string */ char **sel_p; /* Pointer to selector value string */ } opt_fields[] = { /* Options in something corresponding to frequency of use */ { S("opts"), &fs_static.opt_opts, 0 }, { S("host"), 0, &opt_host }, { S("hostd"), 0, &opt_hostd }, { S("type"), &fs_static.opt_type, 0 }, { S("rhost"), &fs_static.opt_rhost, 0 }, { S("rfs"), &fs_static.opt_rfs, 0 }, { S("fs"), &fs_static.opt_fs, 0 }, { S("key"), 0, &opt_key }, { S("map"), 0, &opt_map }, { S("sublink"), &fs_static.opt_sublink, 0 }, { S("arch"), 0, &arch }, { S("dev"), &fs_static.opt_dev, 0 }, { S("pref"), &fs_static.opt_pref, 0 }, { S("path"), 0, &opt_path }, { S("autodir"), 0, &auto_dir }, { S("delay"), &fs_static.opt_delay, 0 }, { S("domain"), 0, &hostdomain }, { S("karch"), 0, &karch }, { S("cluster"), 0, &cluster }, { S("wire"), 0, &wire }, { S("byte"), 0, &endian }, { S("os"), 0, &op_sys }, { S("remopts"), &fs_static.opt_remopts, 0 }, { S("mount"), &fs_static.opt_mount, 0 }, { S("unmount"), &fs_static.opt_unmount, 0 }, { S("cache"), &fs_static.opt_cache, 0 }, { S("user"), &fs_static.opt_user, 0 }, { S("group"), &fs_static.opt_group, 0 }, { S("var0"), &vars[0], 0 }, { S("var1"), &vars[1], 0 }, { S("var2"), &vars[2], 0 }, { S("var3"), &vars[3], 0 }, { S("var4"), &vars[4], 0 }, { S("var5"), &vars[5], 0 }, { S("var6"), &vars[6], 0 }, { S("var7"), &vars[7], 0 }, { 0, 0, 0, 0 }, }; typedef struct opt_apply opt_apply; struct opt_apply { char **opt; char *val; }; /* * Specially expand the remote host name first */ static opt_apply rhost_expansion[] = { { &fs_static.opt_rhost, "${host}" }, { 0, 0 }, }; /* * List of options which need to be expanded * Note that this the order here _may_ be important. */ static opt_apply expansions[] = { /* { &fs_static.opt_dir, 0 }, */ { &fs_static.opt_sublink, 0 }, { &fs_static.opt_rfs, "${path}" }, { &fs_static.opt_fs, "${autodir}/${rhost}${rfs}" }, { &fs_static.opt_opts, "rw" }, { &fs_static.opt_remopts, "${opts}" }, { &fs_static.opt_mount, 0 }, { &fs_static.opt_unmount, 0 }, { 0, 0 }, }; /* * List of options which need to be free'ed before re-use */ static opt_apply to_free[] = { { &fs_static.fs_glob, 0 }, { &fs_static.fs_local, 0 }, { &fs_static.fs_mtab, 0 }, /* { &fs_static.opt_dir, 0 }, */ { &fs_static.opt_sublink, 0 }, { &fs_static.opt_rfs, 0 }, { &fs_static.opt_fs, 0 }, { &fs_static.opt_rhost, 0 }, { &fs_static.opt_opts, 0 }, { &fs_static.opt_remopts, 0 }, { &fs_static.opt_mount, 0 }, { &fs_static.opt_unmount, 0 }, { &vars[0], 0 }, { &vars[1], 0 }, { &vars[2], 0 }, { &vars[3], 0 }, { &vars[4], 0 }, { &vars[5], 0 }, { &vars[6], 0 }, { &vars[7], 0 }, { 0, 0 }, }; /* * Skip to next option in the string */ static char * opt(char **p) { char *cp = *p; char *dp = cp; char *s = cp; top: while (*cp && *cp != ';') { if (*cp == '\"') { /* * Skip past string */ cp++; while (*cp && *cp != '\"') *dp++ = *cp++; if (*cp) cp++; } else { *dp++ = *cp++; } } /* * Skip past any remaining ';'s */ while (*cp == ';') cp++; /* * If we have a zero length string * and there are more fields, then * parse the next one. This allows * sequences of empty fields. */ if (*cp && dp == s) goto top; *dp = '\0'; *p = cp; return s; } static int eval_opts(char *opts, char *mapkey) { /* * Fill in the global structure fs_static by * cracking the string opts. opts may be * scribbled on at will. */ char *o = opts; char *f; /* * For each user-specified option */ while (*(f = opt(&o))) { struct opt *op; enum vs_opt { OldSyn, SelEQ, SelNE, VarAss } vs_opt; char *eq = strchr(f, '='); char *opt; if (!eq || eq[1] == '\0' || eq == f) { /* * No value, just continue */ plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f); continue; } /* * Check what type of operation is happening * !=, =! is SelNE * == is SelEQ * := is VarAss * = is OldSyn (either SelEQ or VarAss) */ if (eq[-1] == '!') { /* != */ vs_opt = SelNE; eq[-1] = '\0'; opt = eq + 1; } else if (eq[-1] == ':') { /* := */ vs_opt = VarAss; eq[-1] = '\0'; opt = eq + 1; } else if (eq[1] == '=') { /* == */ vs_opt = SelEQ; eq[0] = '\0'; opt = eq + 2; } else if (eq[1] == '!') { /* =! */ vs_opt = SelNE; eq[0] = '\0'; opt = eq + 2; } else { /* = */ vs_opt = OldSyn; eq[0] = '\0'; opt = eq + 1; } /* * For each recognised option */ for (op = opt_fields; op->name; op++) { /* * Check whether they match */ if (FSTREQ(op->name, f)) { switch (vs_opt) { #if AMD_COMPAT <= 5000108 case OldSyn: plog(XLOG_WARNING, "key %s: Old syntax selector found: %s=%s", mapkey, f, opt); if (!op->sel_p) { *op->optp = opt; break; } /* fall through ... */ #endif /* 5000108 */ case SelEQ: case SelNE: if (op->sel_p && (STREQ(*op->sel_p, opt) == (vs_opt == SelNE))) { plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s", mapkey, op->name, *op->sel_p, vs_opt == SelNE ? "not " : "", opt); return 0; } break; case VarAss: if (op->sel_p) { plog(XLOG_USER, "key %s: Can't assign to a selector (%s)", mapkey, op->name); return 0; } *op->optp = opt; break; } break; } } if (!op->name) plog(XLOG_USER, "key %s: Unrecognised key/option \"%s\"", mapkey, f); } return 1; } /* * Free an option */ /*ARGSUSED*/ static void free_op(opt_apply *p, int b) { if (*p->opt) { free(*p->opt); *p->opt = 0; } } /* * Normalize slashes in the string. */ void normalize_slash(char *p) { char *f = strchr(p, '/'); char *f0 = f; if (f) { char *t = f; do { /* assert(*f == '/'); */ if (f == f0 && f[0] == '/' && f[1] == '/') { /* copy double slash iff first */ *t++ = *f++; *t++ = *f++; } else { /* copy a single / across */ *t++ = *f++; } /* assert(f[-1] == '/'); */ /* skip past more /'s */ while (*f == '/') f++; /* assert(*f != '/'); */ /* keep copying up to next / */ while (*f && *f != '/') { *t++ = *f++; } /* assert(*f == 0 || *f == '/'); */ } while (*f); *t = 0; /* derived from fix by Steven Glassman */ } } /* * Macro-expand an option. Note that this does not * handle recursive expansions. They will go badly wrong. * If sel is true then old expand selectors, otherwise * don't expand selectors. */ static void expand_op(opt_apply *p, int sel_p) { /* * The BUFSPACE macros checks that there is enough space * left in the expansion buffer. If there isn't then we * give up completely. This is done to avoid crashing the * automounter itself (which would be a bad thing to do). */ #define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+MAXPATHLEN) static char expand_error[] = "No space to expand \"%s\""; char expbuf[MAXPATHLEN+1]; char nbuf[NLEN+1]; char *ep = expbuf; char *cp = *p->opt; char *dp; #ifdef DEBUG char *cp_orig = *p->opt; #endif /* DEBUG */ struct opt *op; while ((dp = strchr(cp, '$'))) { char ch; /* * First copy up to the $ */ { int len = dp - cp; if (BUFSPACE(ep, len)) { strncpy(ep, cp, len); ep += len; } else { plog(XLOG_ERROR, expand_error, *p->opt); goto out; } } cp = dp + 1; ch = *cp++; if (ch == '$') { if (BUFSPACE(ep, 1)) { *ep++ = '$'; } else { plog(XLOG_ERROR, expand_error, *p->opt); goto out; } } else if (ch == '{') { /* Expansion... */ enum { E_All, E_Dir, E_File, E_Domain, E_Host } todo; /* * Find closing brace */ char *br_p = strchr(cp, '}'); int len; /* * Check we found it */ if (!br_p) { /* * Just give up */ plog(XLOG_USER, "No closing '}' in \"%s\"", *p->opt); goto out; } len = br_p - cp; /* * Figure out which part of the variable to grab. */ if (*cp == '/') { /* * Just take the last component */ todo = E_File; cp++; --len; } else if (br_p[-1] == '/') { /* * Take all but the last component */ todo = E_Dir; --len; } else if (*cp == '.') { /* * Take domain name */ todo = E_Domain; cp++; --len; } else if (br_p[-1] == '.') { /* * Take host name */ todo = E_Host; --len; } else { /* * Take the whole lot */ todo = E_All; } /* * Truncate if too long. Since it won't * match anyway it doesn't matter that * it has been cut short. */ if (len > NLEN) len = NLEN; /* * Put the string into another buffer so * we can do comparisons. */ strncpy(nbuf, cp, len); nbuf[len] = '\0'; /* * Advance cp */ cp = br_p + 1; /* * Search the option array */ for (op = opt_fields; op->name; op++) { /* * Check for match */ if (len == op->nlen && STREQ(op->name, nbuf)) { char xbuf[NLEN+3]; char *val; /* * Found expansion. Copy * the correct value field. */ if (!(!op->sel_p == !sel_p)) { /* * Copy the string across unexpanded */ snprintf(xbuf, sizeof(xbuf), "${%s%s%s}", todo == E_File ? "/" : todo == E_Domain ? "." : "", nbuf, todo == E_Dir ? "/" : todo == E_Host ? "." : ""); val = xbuf; /* * Make sure expansion doesn't * munge the value! */ todo = E_All; } else if (op->sel_p) { val = *op->sel_p; } else { val = *op->optp; } if (val) { /* * Do expansion: * ${/var} means take just the last part * ${var/} means take all but the last part * ${.var} means take all but first part * ${var.} means take just the first part * ${var} means take the whole lot */ int vlen = strlen(val); char *vptr = val; switch (todo) { case E_Dir: vptr = strrchr(val, '/'); if (vptr) vlen = vptr - val; vptr = val; break; case E_File: vptr = strrchr(val, '/'); if (vptr) { vptr++; vlen = strlen(vptr); } else vptr = val; break; case E_Domain: vptr = strchr(val, '.'); if (vptr) { vptr++; vlen = strlen(vptr); } else { vptr = ""; vlen = 0; } break; case E_Host: vptr = strchr(val, '.'); if (vptr) vlen = vptr - val; vptr = val; break; case E_All: break; } #ifdef DEBUG /*dlog("Expanding \"%s\" to \"%s\"", nbuf, val);*/ #endif /* DEBUG */ if (BUFSPACE(ep, vlen)) { strlcpy(ep, vptr, expbuf + sizeof expbuf - ep); ep += strlen(ep); } else { plog(XLOG_ERROR, expand_error, *p->opt); goto out; } } /* * Done with this variable */ break; } } /* * Check that the search was successful */ if (!op->name) { /* * If it wasn't then scan the * environment for that name * and use any value found */ char *env = getenv(nbuf); if (env) { int vlen = strlen(env); if (BUFSPACE(ep, vlen)) { strlcpy(ep, env, expbuf + sizeof expbuf - ep); ep += strlen(ep); } else { plog(XLOG_ERROR, expand_error, *p->opt); goto out; } #ifdef DEBUG Debug(D_STR) plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env); #endif /* DEBUG */ } else { plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf); } } } else { /* * Error, error */ plog(XLOG_USER, "Unknown $ sequence in \"%s\"", *p->opt); } } out: /* * Handle common case - no expansion */ if (cp == *p->opt) { *p->opt = strdup(cp); } else { /* * Finish off the expansion */ if (BUFSPACE(ep, strlen(cp))) { strlcpy(ep, cp, expbuf + sizeof expbuf - ep); } else { plog(XLOG_ERROR, expand_error, *p->opt); } /* * Save the exansion */ *p->opt = strdup(expbuf); } normalize_slash(*p->opt); #ifdef DEBUG Debug(D_STR) { plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig); plog(XLOG_DEBUG, "... is \"%s\"", *p->opt); } #endif /* DEBUG */ } /* * Wrapper for expand_op */ static void expand_opts(opt_apply *p, int sel_p) { if (*p->opt) { expand_op(p, sel_p); } else if (p->val) { /* * Do double expansion, remembering * to free the string from the first * expansion... */ char *s = *p->opt = expand_key(p->val); expand_op(p, sel_p); free(s); } } /* * Apply a function to a list of options */ static void apply_opts(void (*op)(), opt_apply ppp[], int b) { opt_apply *pp; for (pp = ppp; pp->opt; pp++) (*op)(pp, b); } /* * Free the option table */ void free_opts(am_opts *fo) { /* * Copy in the structure we are playing with */ fs_static = *fo; /* * Free previously allocated memory */ apply_opts(free_op, to_free, FALSE); } /* * Expand lookup key */ char * expand_key(char *key) { opt_apply oa; oa.opt = &key; oa.val = 0; expand_opts(&oa, TRUE); return key; } /* * Remove trailing /'s from a string * unless the string is a single / (Steven Glassman) */ void deslashify(char *s) { if (s && *s) { char *sl = s + strlen(s); while (*--sl == '/' && sl > s) *sl = '\0'; } } int eval_fs_opts(am_opts *fo, char *opts, char *g_opts, char *path, char *key, char *map) { int ok = TRUE; free_opts(fo); /* * Clear out the option table */ bzero((void *)&fs_static, sizeof(fs_static)); bzero((void *)vars, sizeof(vars)); bzero((void *)fo, sizeof(*fo)); /* * Set key, map & path before expansion */ opt_key = key; opt_map = map; opt_path = path; /* * Expand global options */ fs_static.fs_glob = expand_key(g_opts); /* * Expand local options */ fs_static.fs_local = expand_key(opts); /* * Expand default (global) options */ if (!eval_opts(fs_static.fs_glob, key)) ok = FALSE; /* * Expand local options */ if (ok && !eval_opts(fs_static.fs_local, key)) ok = FALSE; /* * Normalise remote host name. * 1. Expand variables * 2. Normalize relative to host tables * 3. Strip local domains from the remote host * name before using it in other expansions. * This makes mount point names and other things * much shorter, while allowing cross domain * sharing of mount maps. */ apply_opts(expand_opts, rhost_expansion, FALSE); if (ok && fs_static.opt_rhost && *fs_static.opt_rhost) host_normalize(&fs_static.opt_rhost); /* * Macro expand the options. * Do this regardless of whether we are accepting * this mount - otherwise nasty things happen * with memory allocation. */ apply_opts(expand_opts, expansions, FALSE); /* * Strip trailing slashes from local pathname... */ deslashify(fs_static.opt_fs); /* * ok... copy the data back out. */ *fo = fs_static; /* * Clear defined options */ opt_key = opt_map = opt_path = nullstr; return ok; }