diff --git a/usr.sbin/autofs/automount.c b/usr.sbin/autofs/automount.c index e28129eee5b8..cd29c910bce2 100644 --- a/usr.sbin/autofs/automount.c +++ b/usr.sbin/autofs/automount.c @@ -1,396 +1,398 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2014 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "mntopts.h" static int unmount_by_statfs(const struct statfs *sb, bool force) { char *fsid_str; int error, ret, flags; ret = asprintf(&fsid_str, "FSID:%d:%d", sb->f_fsid.val[0], sb->f_fsid.val[1]); if (ret < 0) log_err(1, "asprintf"); log_debugx("unmounting %s using %s", sb->f_mntonname, fsid_str); flags = MNT_BYFSID; if (force) flags |= MNT_FORCE; error = unmount(fsid_str, flags); free(fsid_str); if (error != 0) log_warn("cannot unmount %s", sb->f_mntonname); + else + rpc_umntall(); return (error); } static const struct statfs * find_statfs(const struct statfs *mntbuf, int nitems, const char *mountpoint) { int i; for (i = 0; i < nitems; i++) { if (strcmp(mntbuf[i].f_mntonname, mountpoint) == 0) return (mntbuf + i); } return (NULL); } static void mount_autofs(const char *from, const char *fspath, const char *options, const char *prefix) { struct iovec *iov = NULL; char errmsg[255]; int error, iovlen = 0; create_directory(fspath); log_debugx("mounting %s on %s, prefix \"%s\", options \"%s\"", from, fspath, prefix, options); memset(errmsg, 0, sizeof(errmsg)); build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "autofs"), (size_t)-1); build_iovec(&iov, &iovlen, "fspath", __DECONST(void *, fspath), (size_t)-1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, from), (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); /* * Append the options and mountpoint defined in auto_master(5); * this way automountd(8) does not need to parse it. */ build_iovec(&iov, &iovlen, "master_options", __DECONST(void *, options), (size_t)-1); build_iovec(&iov, &iovlen, "master_prefix", __DECONST(void *, prefix), (size_t)-1); error = nmount(iov, iovlen, 0); if (error != 0) { if (*errmsg != '\0') { log_err(1, "cannot mount %s on %s: %s", from, fspath, errmsg); } else { log_err(1, "cannot mount %s on %s", from, fspath); } } } static void mount_if_not_already(const struct node *n, const char *map, const char *options, const char *prefix, const struct statfs *mntbuf, int nitems) { const struct statfs *sb; char *mountpoint; char *from; int ret; ret = asprintf(&from, "map %s", map); if (ret < 0) log_err(1, "asprintf"); mountpoint = node_path(n); sb = find_statfs(mntbuf, nitems, mountpoint); if (sb != NULL) { if (strcmp(sb->f_fstypename, "autofs") != 0) { log_debugx("unknown filesystem mounted " "on %s; mounting", mountpoint); /* * XXX: Compare options and 'from', * and update the mount if necessary. */ } else { log_debugx("autofs already mounted " "on %s", mountpoint); free(from); free(mountpoint); return; } } else { log_debugx("nothing mounted on %s; mounting", mountpoint); } mount_autofs(from, mountpoint, options, prefix); free(from); free(mountpoint); } static void mount_unmount(struct node *root) { struct statfs *mntbuf; struct node *n, *n2; int i, nitems; nitems = getmntinfo(&mntbuf, MNT_WAIT); if (nitems <= 0) log_err(1, "getmntinfo"); log_debugx("unmounting stale autofs mounts"); for (i = 0; i < nitems; i++) { if (strcmp(mntbuf[i].f_fstypename, "autofs") != 0) { log_debugx("skipping %s, filesystem type is not autofs", mntbuf[i].f_mntonname); continue; } n = node_find(root, mntbuf[i].f_mntonname); if (n != NULL) { log_debugx("leaving autofs mounted on %s", mntbuf[i].f_mntonname); continue; } log_debugx("autofs mounted on %s not found " "in new configuration; unmounting", mntbuf[i].f_mntonname); unmount_by_statfs(&(mntbuf[i]), false); } log_debugx("mounting new autofs mounts"); TAILQ_FOREACH(n, &root->n_children, n_next) { if (!node_is_direct_map(n)) { mount_if_not_already(n, n->n_map, n->n_options, n->n_key, mntbuf, nitems); continue; } TAILQ_FOREACH(n2, &n->n_children, n_next) { mount_if_not_already(n2, n->n_map, n->n_options, "/", mntbuf, nitems); } } } static void flush_autofs(const char *fspath) { struct iovec *iov = NULL; char errmsg[255]; int error, iovlen = 0; log_debugx("flushing %s", fspath); memset(errmsg, 0, sizeof(errmsg)); build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "autofs"), (size_t)-1); build_iovec(&iov, &iovlen, "fspath", __DECONST(void *, fspath), (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); error = nmount(iov, iovlen, MNT_UPDATE); if (error != 0) { if (*errmsg != '\0') { log_err(1, "cannot flush %s: %s", fspath, errmsg); } else { log_err(1, "cannot flush %s", fspath); } } } static void flush_caches(void) { struct statfs *mntbuf; int i, nitems; nitems = getmntinfo(&mntbuf, MNT_WAIT); if (nitems <= 0) log_err(1, "getmntinfo"); log_debugx("flushing autofs caches"); for (i = 0; i < nitems; i++) { if (strcmp(mntbuf[i].f_fstypename, "autofs") != 0) { log_debugx("skipping %s, filesystem type is not autofs", mntbuf[i].f_mntonname); continue; } flush_autofs(mntbuf[i].f_mntonname); } } static void unmount_automounted(bool force) { struct statfs *mntbuf; int i, nitems; nitems = getmntinfo(&mntbuf, MNT_WAIT); if (nitems <= 0) log_err(1, "getmntinfo"); log_debugx("unmounting automounted filesystems"); for (i = 0; i < nitems; i++) { if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) { log_debugx("skipping %s, filesystem type is autofs", mntbuf[i].f_mntonname); continue; } if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) { log_debugx("skipping %s, not automounted", mntbuf[i].f_mntonname); continue; } unmount_by_statfs(&(mntbuf[i]), force); } } static void usage_automount(void) { fprintf(stderr, "usage: automount [-D name=value][-o opts][-Lcfuv]\n"); exit(1); } int main_automount(int argc, char **argv) { struct node *root; int ch, debug = 0, show_maps = 0; char *options = NULL; bool do_unmount = false, force_unmount = false, flush = false; /* * Note that in automount(8), the only purpose of variable * handling is to aid in debugging maps (automount -L). */ defined_init(); while ((ch = getopt(argc, argv, "D:Lfco:uv")) != -1) { switch (ch) { case 'D': defined_parse_and_add(optarg); break; case 'L': show_maps++; break; case 'c': flush = true; break; case 'f': force_unmount = true; break; case 'o': options = concat(options, ',', optarg); break; case 'u': do_unmount = true; break; case 'v': debug++; break; case '?': default: usage_automount(); } } argc -= optind; if (argc != 0) usage_automount(); if (force_unmount && !do_unmount) usage_automount(); log_init(debug); if (flush) { flush_caches(); return (0); } if (do_unmount) { unmount_automounted(force_unmount); return (0); } root = node_new_root(); parse_master(root, AUTO_MASTER_PATH); if (show_maps) { if (show_maps > 1) { node_expand_indirect_maps(root); node_expand_ampersand(root, NULL); } node_expand_defined(root); node_print(root, options); return (0); } mount_unmount(root); return (0); } diff --git a/usr.sbin/autofs/autounmountd.c b/usr.sbin/autofs/autounmountd.c index 92bc8a03c96f..33b3a366cb8d 100644 --- a/usr.sbin/autofs/autounmountd.c +++ b/usr.sbin/autofs/autounmountd.c @@ -1,355 +1,356 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2014 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #define AUTOUNMOUNTD_PIDFILE "/var/run/autounmountd.pid" struct automounted_fs { TAILQ_ENTRY(automounted_fs) af_next; time_t af_mount_time; bool af_mark; fsid_t af_fsid; char af_mountpoint[MNAMELEN]; }; static TAILQ_HEAD(, automounted_fs) automounted; static struct automounted_fs * automounted_find(fsid_t fsid) { struct automounted_fs *af; TAILQ_FOREACH(af, &automounted, af_next) { if (fsidcmp(&af->af_fsid, &fsid) == 0) return (af); } return (NULL); } static struct automounted_fs * automounted_add(fsid_t fsid, const char *mountpoint) { struct automounted_fs *af; af = calloc(1, sizeof(*af)); if (af == NULL) log_err(1, "calloc"); af->af_mount_time = time(NULL); af->af_fsid = fsid; strlcpy(af->af_mountpoint, mountpoint, sizeof(af->af_mountpoint)); TAILQ_INSERT_TAIL(&automounted, af, af_next); return (af); } static void automounted_remove(struct automounted_fs *af) { TAILQ_REMOVE(&automounted, af, af_next); free(af); } static void refresh_automounted(void) { struct automounted_fs *af, *tmpaf; struct statfs *mntbuf; int i, nitems; nitems = getmntinfo(&mntbuf, MNT_WAIT); if (nitems <= 0) log_err(1, "getmntinfo"); log_debugx("refreshing list of automounted filesystems"); TAILQ_FOREACH(af, &automounted, af_next) af->af_mark = false; for (i = 0; i < nitems; i++) { if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) { log_debugx("skipping %s, filesystem type is autofs", mntbuf[i].f_mntonname); continue; } if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) { log_debugx("skipping %s, not automounted", mntbuf[i].f_mntonname); continue; } af = automounted_find(mntbuf[i].f_fsid); if (af == NULL) { log_debugx("new automounted filesystem found on %s " "(FSID:%d:%d)", mntbuf[i].f_mntonname, mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]); af = automounted_add(mntbuf[i].f_fsid, mntbuf[i].f_mntonname); } else { log_debugx("already known automounted filesystem " "found on %s (FSID:%d:%d)", mntbuf[i].f_mntonname, mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]); } af->af_mark = true; } TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) { if (af->af_mark) continue; log_debugx("lost filesystem mounted on %s (FSID:%d:%d)", af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1]); automounted_remove(af); } } static int unmount_by_fsid(const fsid_t fsid, const char *mountpoint) { char *fsid_str; int error, ret; ret = asprintf(&fsid_str, "FSID:%d:%d", fsid.val[0], fsid.val[1]); if (ret < 0) log_err(1, "asprintf"); error = unmount(fsid_str, MNT_NONBUSY | MNT_BYFSID); if (error != 0) { if (errno == EBUSY) { log_debugx("cannot unmount %s (%s): %s", mountpoint, fsid_str, strerror(errno)); } else { log_warn("cannot unmount %s (%s)", mountpoint, fsid_str); } - } + } else + rpc_umntall(); free(fsid_str); return (error); } static time_t expire_automounted(time_t expiration_time) { struct automounted_fs *af, *tmpaf; time_t now; time_t mounted_for, mounted_max = -1; int error; now = time(NULL); log_debugx("expiring automounted filesystems"); TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) { mounted_for = difftime(now, af->af_mount_time); if (mounted_for < expiration_time) { log_debugx("skipping %s (FSID:%d:%d), mounted " "for %jd seconds", af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1], (intmax_t)mounted_for); if (mounted_for > mounted_max) mounted_max = mounted_for; continue; } log_debugx("filesystem mounted on %s (FSID:%d:%d), " "was mounted for %ld seconds; unmounting", af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1], (long)mounted_for); error = unmount_by_fsid(af->af_fsid, af->af_mountpoint); if (error != 0) { if (mounted_for > mounted_max) mounted_max = mounted_for; } } return (mounted_max); } static void usage_autounmountd(void) { fprintf(stderr, "usage: autounmountd [-r time][-t time][-dv]\n"); exit(1); } static void do_wait(int kq, time_t sleep_time) { struct timespec timeout; struct kevent unused; int nevents; if (sleep_time != -1) { assert(sleep_time > 0); timeout.tv_sec = sleep_time; timeout.tv_nsec = 0; log_debugx("waiting for filesystem event for %ld seconds", (long)sleep_time); nevents = kevent(kq, NULL, 0, &unused, 1, &timeout); } else { log_debugx("waiting for filesystem event"); nevents = kevent(kq, NULL, 0, &unused, 1, NULL); } if (nevents < 0) { if (errno == EINTR) return; log_err(1, "kevent"); } if (nevents == 0) { log_debugx("timeout reached"); assert(sleep_time > 0); } else { log_debugx("got filesystem event"); } } int main_autounmountd(int argc, char **argv) { struct kevent event; struct pidfh *pidfh; pid_t otherpid; const char *pidfile_path = AUTOUNMOUNTD_PIDFILE; int ch, debug = 0, error, kq; time_t expiration_time = 600, retry_time = 600, mounted_max, sleep_time; bool dont_daemonize = false; while ((ch = getopt(argc, argv, "dr:t:v")) != -1) { switch (ch) { case 'd': dont_daemonize = true; debug++; break; case 'r': retry_time = atoi(optarg); break; case 't': expiration_time = atoi(optarg); break; case 'v': debug++; break; case '?': default: usage_autounmountd(); } } argc -= optind; if (argc != 0) usage_autounmountd(); if (retry_time <= 0) log_errx(1, "retry time must be greater than zero"); if (expiration_time <= 0) log_errx(1, "expiration time must be greater than zero"); log_init(debug); pidfh = pidfile_open(pidfile_path, 0600, &otherpid); if (pidfh == NULL) { if (errno == EEXIST) { log_errx(1, "daemon already running, pid: %jd.", (intmax_t)otherpid); } log_err(1, "cannot open or create pidfile \"%s\"", pidfile_path); } if (dont_daemonize == false) { if (daemon(0, 0) == -1) { log_warn("cannot daemonize"); pidfile_remove(pidfh); exit(1); } } pidfile_write(pidfh); TAILQ_INIT(&automounted); kq = kqueue(); if (kq < 0) log_err(1, "kqueue"); EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, NULL); error = kevent(kq, &event, 1, NULL, 0, NULL); if (error < 0) log_err(1, "kevent"); for (;;) { refresh_automounted(); mounted_max = expire_automounted(expiration_time); if (mounted_max == -1) { sleep_time = mounted_max; log_debugx("no filesystems to expire"); } else if (mounted_max < expiration_time) { sleep_time = difftime(expiration_time, mounted_max); log_debugx("some filesystems expire in %ld seconds", (long)sleep_time); } else { sleep_time = retry_time; log_debugx("some expired filesystems remain mounted, " "will retry in %ld seconds", (long)sleep_time); } do_wait(kq, sleep_time); } return (0); } diff --git a/usr.sbin/autofs/common.c b/usr.sbin/autofs/common.c index 7c8df4205a86..4581e5c4f2f9 100644 --- a/usr.sbin/autofs/common.c +++ b/usr.sbin/autofs/common.c @@ -1,1226 +1,1239 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2014 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "autofs_ioctl.h" #include "common.h" extern FILE *yyin; extern char *yytext; extern int yylex(void); static void parse_master_yyin(struct node *root, const char *master); static void parse_map_yyin(struct node *parent, const char *map, const char *executable_key); char * checked_strdup(const char *s) { char *c; assert(s != NULL); c = strdup(s); if (c == NULL) log_err(1, "strdup"); return (c); } /* * Concatenate two strings, inserting separator between them, unless not needed. */ char * concat(const char *s1, char separator, const char *s2) { char *result; char s1last, s2first; int ret; if (s1 == NULL) s1 = ""; if (s2 == NULL) s2 = ""; if (s1[0] == '\0') s1last = '\0'; else s1last = s1[strlen(s1) - 1]; s2first = s2[0]; if (s1last == separator && s2first == separator) { /* * If s1 ends with the separator and s2 begins with * it - skip the latter; otherwise concatenating "/" * and "/foo" would end up returning "//foo". */ ret = asprintf(&result, "%s%s", s1, s2 + 1); } else if (s1last == separator || s2first == separator || s1[0] == '\0' || s2[0] == '\0') { ret = asprintf(&result, "%s%s", s1, s2); } else { ret = asprintf(&result, "%s%c%s", s1, separator, s2); } if (ret < 0) log_err(1, "asprintf"); //log_debugx("%s: got %s and %s, returning %s", __func__, s1, s2, result); return (result); } void create_directory(const char *path) { char *component, *copy, *tofree, *partial, *tmp; int error; assert(path[0] == '/'); /* * +1 to skip the leading slash. */ copy = tofree = checked_strdup(path + 1); partial = checked_strdup("/"); for (;;) { component = strsep(©, "/"); if (component == NULL) break; tmp = concat(partial, '/', component); free(partial); partial = tmp; //log_debugx("creating \"%s\"", partial); error = mkdir(partial, 0755); if (error != 0 && errno != EEXIST) { log_warn("cannot create %s", partial); return; } } free(tofree); } struct node * node_new_root(void) { struct node *n; n = calloc(1, sizeof(*n)); if (n == NULL) log_err(1, "calloc"); // XXX n->n_key = checked_strdup("/"); n->n_options = checked_strdup(""); TAILQ_INIT(&n->n_children); return (n); } struct node * node_new(struct node *parent, char *key, char *options, char *location, const char *config_file, int config_line) { struct node *n; n = calloc(1, sizeof(*n)); if (n == NULL) log_err(1, "calloc"); TAILQ_INIT(&n->n_children); assert(key != NULL); assert(key[0] != '\0'); n->n_key = key; if (options != NULL) n->n_options = options; else n->n_options = strdup(""); n->n_location = location; assert(config_file != NULL); n->n_config_file = config_file; assert(config_line >= 0); n->n_config_line = config_line; assert(parent != NULL); n->n_parent = parent; TAILQ_INSERT_TAIL(&parent->n_children, n, n_next); return (n); } struct node * node_new_map(struct node *parent, char *key, char *options, char *map, const char *config_file, int config_line) { struct node *n; n = calloc(1, sizeof(*n)); if (n == NULL) log_err(1, "calloc"); TAILQ_INIT(&n->n_children); assert(key != NULL); assert(key[0] != '\0'); n->n_key = key; if (options != NULL) n->n_options = options; else n->n_options = strdup(""); n->n_map = map; assert(config_file != NULL); n->n_config_file = config_file; assert(config_line >= 0); n->n_config_line = config_line; assert(parent != NULL); n->n_parent = parent; TAILQ_INSERT_TAIL(&parent->n_children, n, n_next); return (n); } static struct node * node_duplicate(const struct node *o, struct node *parent) { const struct node *child; struct node *n; if (parent == NULL) parent = o->n_parent; n = node_new(parent, o->n_key, o->n_options, o->n_location, o->n_config_file, o->n_config_line); TAILQ_FOREACH(child, &o->n_children, n_next) node_duplicate(child, n); return (n); } static void node_delete(struct node *n) { struct node *child, *tmp; assert (n != NULL); TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) node_delete(child); if (n->n_parent != NULL) TAILQ_REMOVE(&n->n_parent->n_children, n, n_next); free(n); } /* * Move (reparent) node 'n' to make it sibling of 'previous', placed * just after it. */ static void node_move_after(struct node *n, struct node *previous) { TAILQ_REMOVE(&n->n_parent->n_children, n, n_next); n->n_parent = previous->n_parent; TAILQ_INSERT_AFTER(&previous->n_parent->n_children, previous, n, n_next); } static void node_expand_includes(struct node *root, bool is_master) { struct node *n, *n2, *tmp, *tmp2, *tmproot; int error; TAILQ_FOREACH_SAFE(n, &root->n_children, n_next, tmp) { if (n->n_key[0] != '+') continue; error = access(AUTO_INCLUDE_PATH, F_OK); if (error != 0) { log_errx(1, "directory services not configured; " "%s does not exist", AUTO_INCLUDE_PATH); } /* * "+1" to skip leading "+". */ yyin = auto_popen(AUTO_INCLUDE_PATH, n->n_key + 1, NULL); assert(yyin != NULL); tmproot = node_new_root(); if (is_master) parse_master_yyin(tmproot, n->n_key); else parse_map_yyin(tmproot, n->n_key, NULL); error = auto_pclose(yyin); yyin = NULL; if (error != 0) { log_errx(1, "failed to handle include \"%s\"", n->n_key); } /* * Entries to be included are now in tmproot. We need to merge * them with the rest, preserving their place and ordering. */ TAILQ_FOREACH_REVERSE_SAFE(n2, &tmproot->n_children, nodehead, n_next, tmp2) { node_move_after(n2, n); } node_delete(n); node_delete(tmproot); } } static char * expand_ampersand(char *string, const char *key) { char c, *expanded; int i, ret, before_len = 0; bool backslashed = false; assert(key[0] != '\0'); expanded = checked_strdup(string); for (i = 0; string[i] != '\0'; i++) { c = string[i]; if (c == '\\' && backslashed == false) { backslashed = true; continue; } if (backslashed) { backslashed = false; continue; } backslashed = false; if (c != '&') continue; /* * The 'before_len' variable contains the number * of characters before the '&'. */ before_len = i; //assert(i < (int)strlen(string)); ret = asprintf(&expanded, "%.*s%s%s", before_len, string, key, string + before_len + 1); if (ret < 0) log_err(1, "asprintf"); //log_debugx("\"%s\" expanded with key \"%s\" to \"%s\"", // string, key, expanded); /* * Figure out where to start searching for next variable. */ string = expanded; i = before_len + strlen(key); if (i == (int)strlen(string)) break; backslashed = false; //assert(i < (int)strlen(string)); } return (expanded); } /* * Expand "&" in n_location. If the key is NULL, try to use * key from map entries themselves. Keep in mind that maps * consist of tho levels of node structures, the key is one * level up. * * Variant with NULL key is for "automount -LL". */ void node_expand_ampersand(struct node *n, const char *key) { struct node *child; if (n->n_location != NULL) { if (key == NULL) { if (n->n_parent != NULL && strcmp(n->n_parent->n_key, "*") != 0) { n->n_location = expand_ampersand(n->n_location, n->n_parent->n_key); } } else { n->n_location = expand_ampersand(n->n_location, key); } } TAILQ_FOREACH(child, &n->n_children, n_next) node_expand_ampersand(child, key); } /* * Expand "*" in n_key. */ void node_expand_wildcard(struct node *n, const char *key) { struct node *child, *expanded; assert(key != NULL); if (strcmp(n->n_key, "*") == 0) { expanded = node_duplicate(n, NULL); expanded->n_key = checked_strdup(key); node_move_after(expanded, n); } TAILQ_FOREACH(child, &n->n_children, n_next) node_expand_wildcard(child, key); } int node_expand_defined(struct node *n) { struct node *child; int error, cumulated_error = 0; if (n->n_location != NULL) { n->n_location = defined_expand(n->n_location); if (n->n_location == NULL) { log_warnx("failed to expand location for %s", node_path(n)); return (EINVAL); } } TAILQ_FOREACH(child, &n->n_children, n_next) { error = node_expand_defined(child); if (error != 0 && cumulated_error == 0) cumulated_error = error; } return (cumulated_error); } static bool node_is_direct_key(const struct node *n) { if (n->n_parent != NULL && n->n_parent->n_parent == NULL && strcmp(n->n_key, "/-") == 0) { return (true); } return (false); } bool node_is_direct_map(const struct node *n) { for (;;) { assert(n->n_parent != NULL); if (n->n_parent->n_parent == NULL) break; n = n->n_parent; } return (node_is_direct_key(n)); } bool node_has_wildcards(const struct node *n) { const struct node *child; TAILQ_FOREACH(child, &n->n_children, n_next) { if (strcmp(child->n_key, "*") == 0) return (true); } return (false); } static void node_expand_maps(struct node *n, bool indirect) { struct node *child, *tmp; TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) { if (node_is_direct_map(child)) { if (indirect) continue; } else { if (indirect == false) continue; } /* * This is the first-level map node; the one that contains * the key and subnodes with mountpoints and actual map names. */ if (child->n_map == NULL) continue; if (indirect) { log_debugx("map \"%s\" is an indirect map, parsing", child->n_map); } else { log_debugx("map \"%s\" is a direct map, parsing", child->n_map); } parse_map(child, child->n_map, NULL, NULL); } } static void node_expand_direct_maps(struct node *n) { node_expand_maps(n, false); } void node_expand_indirect_maps(struct node *n) { node_expand_maps(n, true); } static char * node_path_x(const struct node *n, char *x) { char *path; if (n->n_parent == NULL) return (x); /* * Return "/-" for direct maps only if we were asked for path * to the "/-" node itself, not to any of its subnodes. */ if (node_is_direct_key(n) && x[0] != '\0') return (x); assert(n->n_key[0] != '\0'); path = concat(n->n_key, '/', x); free(x); return (node_path_x(n->n_parent, path)); } /* * Return full path for node, consisting of concatenated * paths of node itself and all its parents, up to the root. */ char * node_path(const struct node *n) { char *path; size_t len; path = node_path_x(n, checked_strdup("")); /* * Strip trailing slash, unless the whole path is "/". */ len = strlen(path); if (len > 1 && path[len - 1] == '/') path[len - 1] = '\0'; return (path); } static char * node_options_x(const struct node *n, char *x) { char *options; if (n == NULL) return (x); options = concat(x, ',', n->n_options); free(x); return (node_options_x(n->n_parent, options)); } /* * Return options for node, consisting of concatenated * options from the node itself and all its parents, * up to the root. */ char * node_options(const struct node *n) { return (node_options_x(n, checked_strdup(""))); } static void node_print_indent(const struct node *n, const char *cmdline_options, int indent) { const struct node *child, *first_child; char *path, *options, *tmp; path = node_path(n); tmp = node_options(n); options = concat(cmdline_options, ',', tmp); free(tmp); /* * Do not show both parent and child node if they have the same * mountpoint; only show the child node. This means the typical, * "key location", map entries are shown in a single line; * the "key mountpoint1 location2 mountpoint2 location2" entries * take multiple lines. */ first_child = TAILQ_FIRST(&n->n_children); if (first_child == NULL || TAILQ_NEXT(first_child, n_next) != NULL || strcmp(path, node_path(first_child)) != 0) { assert(n->n_location == NULL || n->n_map == NULL); printf("%*.s%-*s %s%-*s %-*s # %s map %s at %s:%d\n", indent, "", 25 - indent, path, options[0] != '\0' ? "-" : " ", 20, options[0] != '\0' ? options : "", 20, n->n_location != NULL ? n->n_location : n->n_map != NULL ? n->n_map : "", node_is_direct_map(n) ? "direct" : "indirect", indent == 0 ? "referenced" : "defined", n->n_config_file, n->n_config_line); } free(path); free(options); TAILQ_FOREACH(child, &n->n_children, n_next) node_print_indent(child, cmdline_options, indent + 2); } /* * Recursively print node with all its children. The cmdline_options * argument is used for additional options to be prepended to all the * others - usually those are the options passed by command line. */ void node_print(const struct node *n, const char *cmdline_options) { const struct node *child; TAILQ_FOREACH(child, &n->n_children, n_next) node_print_indent(child, cmdline_options, 0); } static struct node * node_find_x(struct node *node, const char *path) { struct node *child, *found; char *tmp; size_t tmplen; //log_debugx("looking up %s in %s", path, node_path(node)); if (!node_is_direct_key(node)) { tmp = node_path(node); tmplen = strlen(tmp); if (strncmp(tmp, path, tmplen) != 0) { free(tmp); return (NULL); } if (path[tmplen] != '/' && path[tmplen] != '\0') { /* * If we have two map entries like 'foo' and 'foobar', make * sure the search for 'foobar' won't match 'foo' instead. */ free(tmp); return (NULL); } free(tmp); } TAILQ_FOREACH(child, &node->n_children, n_next) { found = node_find_x(child, path); if (found != NULL) return (found); } if (node->n_parent == NULL || node_is_direct_key(node)) return (NULL); return (node); } struct node * node_find(struct node *root, const char *path) { struct node *node; assert(root->n_parent == NULL); node = node_find_x(root, path); if (node != NULL) assert(node != root); return (node); } /* * Canonical form of a map entry looks like this: * * key [-options] [ [/mountpoint] [-options2] location ... ] * * Entries for executable maps are slightly different, as they * lack the 'key' field and are always single-line; the key field * for those maps is taken from 'executable_key' argument. * * We parse it in such a way that a map always has two levels - first * for key, and the second, for the mountpoint. */ static void parse_map_yyin(struct node *parent, const char *map, const char *executable_key) { char *key = NULL, *options = NULL, *mountpoint = NULL, *options2 = NULL, *location = NULL; int ret; struct node *node; lineno = 1; if (executable_key != NULL) key = checked_strdup(executable_key); for (;;) { ret = yylex(); if (ret == 0 || ret == NEWLINE) { /* * In case of executable map, the key is always * non-NULL, even if the map is empty. So, make sure * we don't fail empty maps here. */ if ((key != NULL && executable_key == NULL) || options != NULL) { log_errx(1, "truncated entry at %s, line %d", map, lineno); } if (ret == 0 || executable_key != NULL) { /* * End of file. */ break; } else { key = options = NULL; continue; } } if (key == NULL) { key = checked_strdup(yytext); if (key[0] == '+') { node_new(parent, key, NULL, NULL, map, lineno); key = options = NULL; continue; } continue; } else if (yytext[0] == '-') { if (options != NULL) { log_errx(1, "duplicated options at %s, line %d", map, lineno); } /* * +1 to skip leading "-". */ options = checked_strdup(yytext + 1); continue; } /* * We cannot properly handle a situation where the map key * is "/". Ignore such entries. * * XXX: According to Piete Brooks, Linux automounter uses * "/" as a wildcard character in LDAP maps. Perhaps * we should work around this braindamage by substituting * "*" for "/"? */ if (strcmp(key, "/") == 0) { log_warnx("nonsensical map key \"/\" at %s, line %d; " "ignoring map entry ", map, lineno); /* * Skip the rest of the entry. */ do { ret = yylex(); } while (ret != 0 && ret != NEWLINE); key = options = NULL; continue; } //log_debugx("adding map node, %s", key); node = node_new(parent, key, options, NULL, map, lineno); key = options = NULL; for (;;) { if (yytext[0] == '/') { if (mountpoint != NULL) { log_errx(1, "duplicated mountpoint " "in %s, line %d", map, lineno); } if (options2 != NULL || location != NULL) { log_errx(1, "mountpoint out of order " "in %s, line %d", map, lineno); } mountpoint = checked_strdup(yytext); goto again; } if (yytext[0] == '-') { if (options2 != NULL) { log_errx(1, "duplicated options " "in %s, line %d", map, lineno); } if (location != NULL) { log_errx(1, "options out of order " "in %s, line %d", map, lineno); } options2 = checked_strdup(yytext + 1); goto again; } if (location != NULL) { log_errx(1, "too many arguments " "in %s, line %d", map, lineno); } /* * If location field starts with colon, e.g. ":/dev/cd0", * then strip it. */ if (yytext[0] == ':') { location = checked_strdup(yytext + 1); if (location[0] == '\0') { log_errx(1, "empty location in %s, " "line %d", map, lineno); } } else { location = checked_strdup(yytext); } if (mountpoint == NULL) mountpoint = checked_strdup("/"); if (options2 == NULL) options2 = checked_strdup(""); #if 0 log_debugx("adding map node, %s %s %s", mountpoint, options2, location); #endif node_new(node, mountpoint, options2, location, map, lineno); mountpoint = options2 = location = NULL; again: ret = yylex(); if (ret == 0 || ret == NEWLINE) { if (mountpoint != NULL || options2 != NULL || location != NULL) { log_errx(1, "truncated entry " "in %s, line %d", map, lineno); } break; } } } } /* * Parse output of a special map called without argument. It is a list * of keys, separated by newlines. They can contain whitespace, so use * getline(3) instead of lexer used for maps. */ static void parse_map_keys_yyin(struct node *parent, const char *map) { char *line = NULL, *key; size_t linecap = 0; ssize_t linelen; lineno = 1; for (;;) { linelen = getline(&line, &linecap, yyin); if (linelen < 0) { /* * End of file. */ break; } if (linelen <= 1) { /* * Empty line, consisting of just the newline. */ continue; } /* * "-1" to strip the trailing newline. */ key = strndup(line, linelen - 1); log_debugx("adding key \"%s\"", key); node_new(parent, key, NULL, NULL, map, lineno); lineno++; } free(line); } static bool file_is_executable(const char *path) { struct stat sb; int error; error = stat(path, &sb); if (error != 0) log_err(1, "cannot stat %s", path); if ((sb.st_mode & S_IXUSR) || (sb.st_mode & S_IXGRP) || (sb.st_mode & S_IXOTH)) return (true); return (false); } /* * Parse a special map, e.g. "-hosts". */ static void parse_special_map(struct node *parent, const char *map, const char *key) { char *path; int error, ret; assert(map[0] == '-'); /* * +1 to skip leading "-" in map name. */ ret = asprintf(&path, "%s/special_%s", AUTO_SPECIAL_PREFIX, map + 1); if (ret < 0) log_err(1, "asprintf"); yyin = auto_popen(path, key, NULL); assert(yyin != NULL); if (key == NULL) { parse_map_keys_yyin(parent, map); } else { parse_map_yyin(parent, map, key); } error = auto_pclose(yyin); yyin = NULL; if (error != 0) log_errx(1, "failed to handle special map \"%s\"", map); node_expand_includes(parent, false); node_expand_direct_maps(parent); free(path); } /* * Retrieve and parse map from directory services, e.g. LDAP. * Note that it is different from executable maps, in that * the include script outputs the whole map to standard output * (as opposed to executable maps that only output a single * entry, without the key), and it takes the map name as an * argument, instead of key. */ static void parse_included_map(struct node *parent, const char *map) { int error; assert(map[0] != '-'); assert(map[0] != '/'); error = access(AUTO_INCLUDE_PATH, F_OK); if (error != 0) { log_errx(1, "directory services not configured;" " %s does not exist", AUTO_INCLUDE_PATH); } yyin = auto_popen(AUTO_INCLUDE_PATH, map, NULL); assert(yyin != NULL); parse_map_yyin(parent, map, NULL); error = auto_pclose(yyin); yyin = NULL; if (error != 0) log_errx(1, "failed to handle remote map \"%s\"", map); node_expand_includes(parent, false); node_expand_direct_maps(parent); } void parse_map(struct node *parent, const char *map, const char *key, bool *wildcards) { char *path = NULL; int error, ret; bool executable; assert(map != NULL); assert(map[0] != '\0'); log_debugx("parsing map \"%s\"", map); if (wildcards != NULL) *wildcards = false; if (map[0] == '-') { if (wildcards != NULL) *wildcards = true; return (parse_special_map(parent, map, key)); } if (map[0] == '/') { path = checked_strdup(map); } else { ret = asprintf(&path, "%s/%s", AUTO_MAP_PREFIX, map); if (ret < 0) log_err(1, "asprintf"); log_debugx("map \"%s\" maps to \"%s\"", map, path); /* * See if the file exists. If not, try to obtain the map * from directory services. */ error = access(path, F_OK); if (error != 0) { log_debugx("map file \"%s\" does not exist; falling " "back to directory services", path); return (parse_included_map(parent, map)); } } executable = file_is_executable(path); if (executable) { log_debugx("map \"%s\" is executable", map); if (wildcards != NULL) *wildcards = true; if (key != NULL) { yyin = auto_popen(path, key, NULL); } else { yyin = auto_popen(path, NULL); } assert(yyin != NULL); } else { yyin = fopen(path, "r"); if (yyin == NULL) log_err(1, "unable to open \"%s\"", path); } free(path); path = NULL; parse_map_yyin(parent, map, executable ? key : NULL); if (executable) { error = auto_pclose(yyin); yyin = NULL; if (error != 0) { log_errx(1, "failed to handle executable map \"%s\"", map); } } else { fclose(yyin); } yyin = NULL; log_debugx("done parsing map \"%s\"", map); node_expand_includes(parent, false); node_expand_direct_maps(parent); } static void parse_master_yyin(struct node *root, const char *master) { char *mountpoint = NULL, *map = NULL, *options = NULL; int ret; /* * XXX: 1 gives incorrect values; wtf? */ lineno = 0; for (;;) { ret = yylex(); if (ret == 0 || ret == NEWLINE) { if (mountpoint != NULL) { //log_debugx("adding map for %s", mountpoint); node_new_map(root, mountpoint, options, map, master, lineno); } if (ret == 0) { break; } else { mountpoint = map = options = NULL; continue; } } if (mountpoint == NULL) { mountpoint = checked_strdup(yytext); } else if (map == NULL) { map = checked_strdup(yytext); } else if (options == NULL) { /* * +1 to skip leading "-". */ options = checked_strdup(yytext + 1); } else { log_errx(1, "too many arguments at %s, line %d", master, lineno); } } } void parse_master(struct node *root, const char *master) { log_debugx("parsing auto_master file at \"%s\"", master); yyin = fopen(master, "r"); if (yyin == NULL) err(1, "unable to open %s", master); parse_master_yyin(root, master); fclose(yyin); yyin = NULL; log_debugx("done parsing \"%s\"", master); node_expand_includes(root, true); node_expand_direct_maps(root); } /* * Two things daemon(3) does, that we actually also want to do * when running in foreground, is closing the stdin and chdiring * to "/". This is what we do here. */ void lesser_daemon(void) { int error, fd; error = chdir("/"); if (error != 0) log_warn("chdir"); fd = open(_PATH_DEVNULL, O_RDWR, 0); if (fd < 0) { log_warn("cannot open %s", _PATH_DEVNULL); return; } error = dup2(fd, STDIN_FILENO); if (error != 0) log_warn("dup2"); error = close(fd); if (error != 0) { /* Bloody hell. */ log_warn("close"); } } +/* + * Applicable to NFSv3 only, see rpc.umntall(8). + */ +void +rpc_umntall(void) +{ + FILE *f; + + f = auto_popen("rpc.umntall", "-k", NULL); + assert(f != NULL); + auto_pclose(f); +} + int main(int argc, char **argv) { char *cmdname; if (argv[0] == NULL) log_errx(1, "NULL command name"); cmdname = basename(argv[0]); if (strcmp(cmdname, "automount") == 0) return (main_automount(argc, argv)); else if (strcmp(cmdname, "automountd") == 0) return (main_automountd(argc, argv)); else if (strcmp(cmdname, "autounmountd") == 0) return (main_autounmountd(argc, argv)); else log_errx(1, "binary name should be either \"automount\", " "\"automountd\", or \"autounmountd\""); } diff --git a/usr.sbin/autofs/common.h b/usr.sbin/autofs/common.h index 34257c1caeff..e68a0be5f7c8 100644 --- a/usr.sbin/autofs/common.h +++ b/usr.sbin/autofs/common.h @@ -1,115 +1,116 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2014 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ #ifndef AUTOMOUNTD_H #define AUTOMOUNTD_H #include #include #define AUTO_MASTER_PATH "/etc/auto_master" #define AUTO_MAP_PREFIX "/etc" #define AUTO_SPECIAL_PREFIX "/etc/autofs" #define AUTO_INCLUDE_PATH AUTO_SPECIAL_PREFIX "/include" struct node { TAILQ_ENTRY(node) n_next; TAILQ_HEAD(nodehead, node) n_children; struct node *n_parent; char *n_key; char *n_options; char *n_location; char *n_map; const char *n_config_file; int n_config_line; }; struct defined_value { TAILQ_ENTRY(defined_value) d_next; char *d_name; char *d_value; }; void log_init(int level); void log_set_peer_name(const char *name); void log_set_peer_addr(const char *addr); void log_err(int, const char *, ...) __dead2 __printf0like(2, 3); void log_errx(int, const char *, ...) __dead2 __printf0like(2, 3); void log_warn(const char *, ...) __printf0like(1, 2); void log_warnx(const char *, ...) __printflike(1, 2); void log_debugx(const char *, ...) __printf0like(1, 2); char *checked_strdup(const char *); char *concat(const char *s1, char separator, const char *s2); void create_directory(const char *path); struct node *node_new_root(void); struct node *node_new(struct node *parent, char *key, char *options, char *location, const char *config_file, int config_line); struct node *node_new_map(struct node *parent, char *key, char *options, char *map, const char *config_file, int config_line); struct node *node_find(struct node *root, const char *mountpoint); bool node_is_direct_map(const struct node *n); bool node_has_wildcards(const struct node *n); char *node_path(const struct node *n); char *node_options(const struct node *n); void node_expand_ampersand(struct node *root, const char *key); void node_expand_wildcard(struct node *root, const char *key); int node_expand_defined(struct node *root); void node_expand_indirect_maps(struct node *n); void node_print(const struct node *n, const char *cmdline_options); void parse_master(struct node *root, const char *path); void parse_map(struct node *parent, const char *map, const char *args, bool *wildcards); char *defined_expand(const char *string); void defined_init(void); void defined_parse_and_add(char *def); void lesser_daemon(void); +void rpc_umntall(void); int main_automount(int argc, char **argv); int main_automountd(int argc, char **argv); int main_autounmountd(int argc, char **argv); FILE *auto_popen(const char *argv0, ...); int auto_pclose(FILE *iop); /* * lex(1) stuff. */ extern int lineno; #define STR 1 #define NEWLINE 2 #endif /* !AUTOMOUNTD_H */