2 * Copyright (c) 1997-1999 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.6 1999/09/30 21:01:32 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 struct am_opts fs_static
; /* copy of the options to play with */
107 static char NullStr
[] = "<NULL>";
108 static char nullstr
[] = "";
109 static char *opt_dkey
= NullStr
;
110 static char *opt_host
= nullstr
; /* XXX: was the global hostname */
111 static char *opt_hostd
= hostd
;
112 static char *opt_key
= nullstr
;
113 static char *opt_keyd
= nullstr
;
114 static char *opt_map
= nullstr
;
115 static char *opt_path
= nullstr
;
116 static char *vars
[8];
120 * Options in something corresponding to frequency of use so that
121 * first-match algorithm is sped up.
123 static struct opt opt_fields
[] = {
125 Option str. Selector str. boolean fxn. flags */
127 &fs_static
.opt_opts
, 0, 0, FALSE
},
129 0, &opt_host
, 0, TRUE
},
131 0, &opt_hostd
, 0, TRUE
},
133 &fs_static
.opt_type
, 0, 0, FALSE
},
135 &fs_static
.opt_rhost
, 0, 0, TRUE
},
137 &fs_static
.opt_rfs
, 0, 0, FALSE
},
139 &fs_static
.opt_fs
, 0, 0, FALSE
},
141 0, &opt_key
, 0, FALSE
},
143 0, &opt_map
, 0, FALSE
},
145 &fs_static
.opt_sublink
, 0, 0, FALSE
},
147 0, &gopt
.arch
, 0, TRUE
},
149 &fs_static
.opt_dev
, 0, 0, FALSE
},
151 &fs_static
.opt_pref
, 0, 0, FALSE
},
153 &fs_static
.opt_autopref
,0, 0, FALSE
},
155 0, &opt_path
, 0, FALSE
},
157 0, &gopt
.auto_dir
, 0, FALSE
},
159 &fs_static
.opt_delay
, 0, 0, FALSE
},
161 0, &hostdomain
, 0, TRUE
},
163 0, &gopt
.karch
, 0, TRUE
},
165 0, &gopt
.cluster
, 0, TRUE
},
167 0, 0, f_in_network
, TRUE
},
169 0, 0, f_in_network
, TRUE
},
171 0, 0, f_in_network
, TRUE
},
173 0, &endian
, 0, TRUE
},
175 0, &gopt
.op_sys
, 0, TRUE
},
177 0, &gopt
.op_sys_ver
, 0, TRUE
},
179 0, &gopt
.op_sys_full
, 0, TRUE
},
181 0, &gopt
.op_sys_vendor
, 0, TRUE
},
183 &fs_static
.opt_remopts
, 0, 0, FALSE
},
185 &fs_static
.opt_mount
, 0, 0, FALSE
},
187 &fs_static
.opt_unmount
, 0, 0, FALSE
},
189 &fs_static
.opt_cache
, 0, 0, FALSE
},
191 &fs_static
.opt_user
, 0, 0, FALSE
},
193 &fs_static
.opt_group
, 0, 0, FALSE
},
195 0, &opt_dkey
, 0, FALSE
},
197 0, &opt_keyd
, 0, FALSE
},
199 &fs_static
.opt_maptype
, 0, 0, FALSE
},
201 &fs_static
.opt_cachedir
, 0, 0, FALSE
},
203 &fs_static
.opt_addopts
, 0, 0, FALSE
},
205 &vars
[0], 0, 0, FALSE
},
207 &vars
[1], 0, 0, FALSE
},
209 &vars
[2], 0, 0, FALSE
},
211 &vars
[3], 0, 0, FALSE
},
213 &vars
[4], 0, 0, FALSE
},
215 &vars
[5], 0, 0, FALSE
},
217 &vars
[6], 0, 0, FALSE
},
219 &vars
[7], 0, 0, FALSE
},
220 { 0, 0, 0, 0, 0, FALSE
},
223 static struct functable functable
[] = {
224 { "in_network", f_in_network
},
225 { "netgrp", f_netgrp
},
226 { "netgrpd", f_netgrpd
},
227 { "exists", f_exists
},
228 { "false", f_false
},
234 * Specially expand the remote host name first
236 static opt_apply rhost_expansion
[] =
238 {&fs_static
.opt_rhost
, "${host}"},
243 * List of options which need to be expanded
244 * Note that the order here _may_ be important.
246 static opt_apply expansions
[] =
248 {&fs_static
.opt_sublink
, 0},
249 {&fs_static
.opt_rfs
, "${path}"},
250 {&fs_static
.opt_fs
, "${autodir}/${rhost}${rfs}"},
251 {&fs_static
.opt_opts
, "rw"},
252 {&fs_static
.opt_remopts
, "${opts}"},
253 {&fs_static
.opt_mount
, 0},
254 {&fs_static
.opt_unmount
, 0},
255 {&fs_static
.opt_cachedir
, 0},
256 {&fs_static
.opt_addopts
, 0},
261 * List of options which need to be free'ed before re-use
263 static opt_apply to_free
[] =
265 {&fs_static
.fs_glob
, 0},
266 {&fs_static
.fs_local
, 0},
267 {&fs_static
.fs_mtab
, 0},
268 {&fs_static
.opt_sublink
, 0},
269 {&fs_static
.opt_rfs
, 0},
270 {&fs_static
.opt_fs
, 0},
271 {&fs_static
.opt_rhost
, 0},
272 {&fs_static
.opt_opts
, 0},
273 {&fs_static
.opt_remopts
, 0},
274 {&fs_static
.opt_mount
, 0},
275 {&fs_static
.opt_unmount
, 0},
276 {&fs_static
.opt_cachedir
, 0},
277 {&fs_static
.opt_addopts
, 0},
291 * expand backslash escape sequences
298 if ((*p
)[1] == '\0') {
299 plog(XLOG_USER
, "Empty backslash escape");
307 c
= '\007'; /* Bell */
310 c
= '\010'; /* Backspace */
313 c
= '\011'; /* Horizontal Tab */
316 c
= '\012'; /* New Line */
319 c
= '\013'; /* Vertical Tab */
322 c
= '\014'; /* Form Feed */
325 c
= '\015'; /* Carriage Return */
328 c
= '\033'; /* Escape */
341 for (cnt
= 0, val
= 0; cnt
< 3; cnt
++) {
343 if (ch
< '0' || ch
> '7') {
347 val
= (val
<< 3) | (ch
- '0');
350 if ((val
& 0xffffff00) != 0)
352 "Too large character constant %u\n",
371 * Skip to next option in the string
381 while (*cp
&& *cp
!= ';') {
386 for (cp
++; *cp
&& *cp
!= '"'; cp
++)
388 *dp
++ = backslash(&cp
);
399 * Skip past any remaining ';'s
405 * If we have a zero length string
406 * and there are more fields, then
407 * parse the next one. This allows
408 * sequences of empty fields.
421 * These routines add a new style of selector; function-style boolean
422 * operators. To add new ones, just define functions as in true, false,
423 * exists (below) and add them to the functable, above.
425 * Usage example: Some people have X11R5 local, some go to a server. I do
428 * * exists(/usr/pkg/${key});type:=link;fs:=/usr/pkg/${key} || \
429 * -type:=nfs;rfs=/usr/pkg/${key} \
433 * -Rens Troost <rens@imsi.com>
436 functable_lookup(char *key
)
438 struct functable
*fp
;
440 for (fp
= functable
; fp
->name
; fp
++)
441 if (FSTREQ(fp
->name
, key
))
443 return (IntFuncPtr
) NULL
;
448 eval_opts(char *opts
, char *mapkey
)
451 * Fill in the global structure fs_static by
452 * cracking the string opts. opts may be
453 * scribbled on at will.
459 * For each user-specified option
461 while (*(f
= opt(&o
))) {
463 enum vs_opt vs_opt
= VarAss
;
464 char *eq
= strchr(f
, '=');
469 if (!eq
|| eq
[1] == '\0' || eq
== f
) {
471 * No value, is it a function call?
473 char *arg
= strchr(f
, '(');
475 if (!arg
|| arg
[1] == '\0' || arg
== f
) {
479 plog(XLOG_USER
, "key %s: No value component in \"%s\"", mapkey
, f
);
483 /* null-terminate the argument */
485 fx
= strchr(arg
, ')');
486 if (!arg
|| fx
== arg
) {
487 plog(XLOG_USER
, "key %s: Malformed function in \"%s\"", mapkey
, f
);
493 * look up f in functable and pass it arg.
494 * func must return 0 on failure, and 1 on success.
496 if ((func
= functable_lookup(f
))) {
497 if (!(*func
) (arg
)) {
501 } else if (NSTREQ(f
, "!", 1) && (func
= functable_lookup(&f
[1]))) {
502 /* then this is a negated prefixed function such as "!exists" */
503 plog(XLOG_INFO
, "executing negated function %s", &f
[1]);
509 plog(XLOG_USER
, "key %s: unknown function \"%s\"", mapkey
, f
);
516 * Check what type of operation is happening
521 if (eq
[-1] == '!') { /* != */
525 } else if (eq
[-1] == ':') { /* := */
529 } else if (eq
[1] == '=') { /* == */
533 } else if (eq
[1] == '!') { /* =! */
540 * For each recognized option
542 for (op
= opt_fields
; op
->name
; op
++) {
544 * Check whether they match
546 if (FSTREQ(op
->name
, f
)) {
551 if ((selok
= (op
->sel_p
!= NULL
))) {
552 if (op
->case_insensitive
) {
553 selok
= (STRCEQ(*op
->sel_p
, opt
) == (vs_opt
== SelNE
));
555 selok
= (STREQ(*op
->sel_p
, opt
) == (vs_opt
== SelNE
));
559 plog(XLOG_MAP
, "key %s: map selector %s (=%s) did not %smatch %s",
563 vs_opt
== SelNE
? "mis" : "",
567 /* check if to apply a function */
569 ((*op
->fxn_p
)(opt
) == (vs_opt
== SelNE
))) {
570 plog(XLOG_MAP
, "key %s: map function %s did not %smatch %s",
573 vs_opt
== SelNE
? "mis" : "",
581 plog(XLOG_USER
, "key %s: Can't assign to a selector (%s)",
588 } /* end of "switch (vs_opt)" statement */
589 break; /* break out of for loop */
594 plog(XLOG_USER
, "key %s: Unrecognized key/option \"%s\"", mapkey
, f
);
602 * Skip to next option in the string, but don't scribble over the string.
603 * However, *p gets repointed to the start of the next string past ';'.
606 opt_no_scribble(char **p
)
613 while (*cp
&& *cp
!= ';') {
619 while (*cp
&& *cp
!= '\"')
629 * Skip past any remaining ';'s
635 * If we have a zero length string
636 * and there are more fields, then
637 * parse the next one. This allows
638 * sequences of empty fields.
649 * Strip any selectors from a string. Selectors are all assumed to be
650 * first in the string. This is used for the new /defaults method which will
651 * use selectors as well.
654 strip_selectors(char *opts
, char *mapkey
)
657 * Fill in the global structure fs_static by
658 * cracking the string opts. opts may be
659 * scribbled on at will.
666 * Scan options. Note that the opt() function scribbles on the opt string.
668 while (*(f
= opt_no_scribble(&o
))) {
669 enum vs_opt vs_opt
= VarAss
;
670 char *eq
= strchr(f
, '=');
672 if (!eq
|| eq
[1] == '\0' || eq
== f
) {
674 * No option or assignment? Return as is.
676 plog(XLOG_USER
, "key %s: No option or assignment in \"%s\"", mapkey
, f
);
680 * Check what type of operation is happening
685 if (eq
[-1] == '!') { /* != */
687 } else if (eq
[-1] == ':') { /* := */
689 } else if (eq
[1] == '=') { /* == */
691 } else if (eq
[1] == '!') { /* =! */
697 /* Skip this selector, maybe there's another one following it */
698 plog(XLOG_USER
, "skipping selector to \"%s\"", o
);
699 /* store previous match. it may have been the first assignment */
704 /* found the first assignment, return the string starting with it */
706 dlog("found first assignment past selectors \"%s\"", o
);
712 /* return the same string by default. should not happen. */
717 /*****************************************************************************
718 *** BOOLEAN FUNCTIONS (return 0 if false, 1 if true): ***
719 *****************************************************************************/
721 /* test if arg is any of this host's network names or numbers */
723 f_in_network(char *arg
)
730 status
= is_network_member(arg
);
732 plog(XLOG_USER
, "%s is %son a local network",
733 arg
, (status
? "" : "not "));
739 /* test if this host (short hostname form) is in netgroup (arg) */
745 status
= innetgr(arg
, opt_host
, NULL
, NULL
);
747 plog(XLOG_USER
, "netgrp = %s status = %d host = %s", arg
, status
, opt_host
);
753 /* test if this host (fully-qualified name) is in netgroup (arg) */
759 status
= innetgr(arg
, opt_hostd
, NULL
, NULL
);
761 plog(XLOG_USER
, "netgrp = %s status = %d hostd = %s", arg
, status
, opt_hostd
);
767 /* test if file (arg) exists via lstat */
773 if (lstat(arg
, &buf
) < 0)
800 free_op(opt_apply
*p
, int b
)
810 * Normalize slashes in the string.
813 normalize_slash(char *p
)
815 char *f
= strchr(p
, '/');
821 /* assert(*f == '/'); */
822 if (f
== f0
&& f
[0] == '/' && f
[1] == '/') {
823 /* copy double slash iff first */
827 /* copy a single / across */
831 /* assert(f[-1] == '/'); */
832 /* skip past more /'s */
836 /* assert(*f != '/'); */
837 /* keep copying up to next / */
838 while (*f
&& *f
!= '/') {
842 /* assert(*f == 0 || *f == '/'); */
845 *t
= 0; /* derived from fix by Steven Glassman */
851 * Macro-expand an option. Note that this does not
852 * handle recursive expansions. They will go badly wrong.
853 * If sel is true then old expand selectors, otherwise
854 * don't expand selectors.
857 expand_op(opt_apply
*p
, int sel_p
)
859 static char expand_error
[] = "No space to expand \"%s\"";
860 char expbuf
[MAXPATHLEN
+ 1];
867 char *cp_orig
= *p
->opt
;
870 while ((dp
= strchr(cp
, '$'))) {
873 * First copy up to the $
878 if (BUFSPACE(ep
, len
)) {
879 strncpy(ep
, cp
, len
);
882 plog(XLOG_ERROR
, expand_error
, *p
->opt
);
890 if (BUFSPACE(ep
, 1)) {
893 plog(XLOG_ERROR
, expand_error
, *p
->opt
);
896 } else if (ch
== '{') {
899 E_All
, E_Dir
, E_File
, E_Domain
, E_Host
904 char *br_p
= strchr(cp
, '}');
914 plog(XLOG_USER
, "No closing '}' in \"%s\"", *p
->opt
);
920 * Figure out which part of the variable to grab.
924 * Just take the last component
929 } else if (br_p
[-1] == '/') {
931 * Take all but the last component
935 } else if (*cp
== '.') {
942 } else if (br_p
[-1] == '.') {
956 * Truncate if too long. Since it won't
957 * match anyway it doesn't matter that
958 * it has been cut short.
964 * Put the string into another buffer so
965 * we can do comparisons.
967 strncpy(nbuf
, cp
, len
);
976 * Search the option array
978 for (op
= opt_fields
; op
->name
; op
++) {
982 if (len
== op
->nlen
&& STREQ(op
->name
, nbuf
)) {
986 * Found expansion. Copy
987 * the correct value field.
989 if (!(!op
->sel_p
== !sel_p
)) {
991 * Copy the string across unexpanded
993 sprintf(xbuf
, "${%s%s%s}",
994 todo
== E_File
? "/" :
995 todo
== E_Domain
? "." : "",
997 todo
== E_Dir
? "/" :
998 todo
== E_Host
? "." : "");
1001 * Make sure expansion doesn't
1005 } else if (op
->sel_p
) {
1014 * ${/var} means take just the last part
1015 * ${var/} means take all but the last part
1016 * ${.var} means take all but first part
1017 * ${var.} means take just the first part
1018 * ${var} means take the whole lot
1020 int vlen
= strlen(val
);
1024 vptr
= strrchr(val
, '/');
1030 vptr
= strrchr(val
, '/');
1033 vlen
= strlen(vptr
);
1038 vptr
= strchr(val
, '.');
1041 vlen
= strlen(vptr
);
1048 vptr
= strchr(val
, '.');
1057 if (BUFSPACE(ep
, vlen
)) {
1061 plog(XLOG_ERROR
, expand_error
, *p
->opt
);
1066 * Done with this variable
1073 * Check that the search was successful
1077 * If it wasn't then scan the
1078 * environment for that name
1079 * and use any value found
1081 char *env
= getenv(nbuf
);
1084 int vlen
= strlen(env
);
1086 if (BUFSPACE(ep
, vlen
)) {
1090 plog(XLOG_ERROR
, expand_error
, *p
->opt
);
1095 plog(XLOG_DEBUG
, "Environment gave \"%s\" -> \"%s\"", nbuf
, env
);
1098 plog(XLOG_USER
, "Unknown sequence \"${%s}\"", nbuf
);
1105 plog(XLOG_USER
, "Unknown $ sequence in \"%s\"", *p
->opt
);
1111 * Handle common case - no expansion
1113 if (cp
== *p
->opt
) {
1114 *p
->opt
= strdup(cp
);
1117 * Finish off the expansion
1119 if (BUFSPACE(ep
, strlen(cp
))) {
1121 /* ep += strlen(ep); */
1123 plog(XLOG_ERROR
, expand_error
, *p
->opt
);
1127 * Save the expansion
1129 *p
->opt
= strdup(expbuf
);
1132 normalize_slash(*p
->opt
);
1136 plog(XLOG_DEBUG
, "Expansion of \"%s\"...", cp_orig
);
1137 plog(XLOG_DEBUG
, "... is \"%s\"", *p
->opt
);
1144 * Wrapper for expand_op
1147 expand_opts(opt_apply
*p
, int sel_p
)
1150 expand_op(p
, sel_p
);
1151 } else if (p
->val
) {
1153 * Do double expansion, remembering
1154 * to free the string from the first
1157 char *s
= *p
->opt
= expand_key(p
->val
);
1158 expand_op(p
, sel_p
);
1165 * Apply a function to a list of options
1168 apply_opts(void (*op
) (opt_apply
*, int), opt_apply ppp
[], int b
)
1172 for (pp
= ppp
; pp
->opt
; pp
++)
1178 * Free the option table
1181 free_opts(am_opts
*fo
)
1184 * Copy in the structure we are playing with
1189 * Free previously allocated memory
1191 apply_opts(free_op
, to_free
, FALSE
);
1199 expand_key(char *key
)
1205 expand_opts(&oa
, TRUE
);
1212 * Remove trailing /'s from a string
1213 * unless the string is a single / (Steven Glassman)
1214 * or unless it is two slashes // (Kevin D. Bond)
1220 char *sl
= s
+ strlen(s
);
1222 while (*--sl
== '/' && sl
> s
)
1229 eval_fs_opts(am_opts
*fo
, char *opts
, char *g_opts
, char *path
, char *key
, char *map
)
1236 * Clear out the option table
1238 memset((voidp
) &fs_static
, 0, sizeof(fs_static
));
1239 memset((voidp
) vars
, 0, sizeof(vars
));
1240 memset((voidp
) fo
, 0, sizeof(*fo
));
1243 opt_host
= (char *) am_get_hostname();
1246 * Set key, map & path before expansion
1252 opt_dkey
= strchr(key
, '.');
1257 opt_keyd
= strnsave(key
, opt_dkey
- key
);
1259 if (*opt_dkey
== '\0') /* check for 'host.' */
1264 * Expand global options
1266 fs_static
.fs_glob
= expand_key(g_opts
);
1269 * Expand local options
1271 fs_static
.fs_local
= expand_key(opts
);
1274 * Expand default (global) options
1276 if (!eval_opts(fs_static
.fs_glob
, key
))
1280 * Expand local options
1282 if (ok
&& !eval_opts(fs_static
.fs_local
, key
))
1286 * Normalize remote host name.
1287 * 1. Expand variables
1288 * 2. Normalize relative to host tables
1289 * 3. Strip local domains from the remote host
1290 * name before using it in other expansions.
1291 * This makes mount point names and other things
1292 * much shorter, while allowing cross domain
1293 * sharing of mount maps.
1295 apply_opts(expand_opts
, rhost_expansion
, FALSE
);
1296 if (ok
&& fs_static
.opt_rhost
&& *fs_static
.opt_rhost
)
1297 host_normalize(&fs_static
.opt_rhost
);
1300 * Macro expand the options.
1301 * Do this regardless of whether we are accepting
1302 * this mount - otherwise nasty things happen
1303 * with memory allocation.
1305 apply_opts(expand_opts
, expansions
, FALSE
);
1308 * Strip trailing slashes from local pathname...
1310 deslashify(fs_static
.opt_fs
);
1313 * ok... copy the data back out.
1318 * Clear defined options
1320 if (opt_keyd
!= key
&& opt_keyd
!= nullstr
)
1324 opt_key
= opt_map
= opt_path
= nullstr
;