/* $NetBSD: automountd.c,v 1.2 2018/01/11 13:44:26 christos Exp $ */ /*- * Copyright (c) 2017 The NetBSD Foundation, Inc. * Copyright (c) 2016 The DragonFly Project * Copyright (c) 2014 The FreeBSD Foundation * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Tomohiro Kusumi . * * 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 __RCSID("$NetBSD: automountd.c,v 1.2 2018/01/11 13:44:26 christos Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" static int nchildren = 0; static int autofs_fd; static int request_id; static char nfs_def_retry[] = "1"; static void done(int request_error, bool wildcards) { struct autofs_daemon_done add; int error; memset(&add, 0, sizeof(add)); add.add_id = request_id; add.add_wildcards = wildcards; add.add_error = request_error; log_debugx("completing request %d with error %d", request_id, request_error); error = ioctl(autofs_fd, AUTOFSDONE, &add); if (error != 0) log_warn("AUTOFSDONE"); } /* * Remove "fstype=whatever" from optionsp and return the "whatever" part. */ static char * pick_option(const char *option, char **optionsp) { char *tofree, *pair, *newoptions; char *picked = NULL; bool first = true; tofree = *optionsp; newoptions = calloc(1, strlen(*optionsp) + 1); if (newoptions == NULL) log_err(1, "calloc"); size_t olen = strlen(option); while ((pair = strsep(optionsp, ",")) != NULL) { /* * XXX: strncasecmp(3) perhaps? */ if (strncmp(pair, option, olen) == 0) { picked = checked_strdup(pair + olen); } else { if (first) first = false; else strcat(newoptions, ","); strcat(newoptions, pair); } } free(tofree); *optionsp = newoptions; return picked; } static void create_subtree(const struct node *node, bool incomplete) { const struct node *child; char *path; bool wildcard_found = false; /* * Skip wildcard nodes. */ if (strcmp(node->n_key, "*") == 0) return; path = node_path(node); log_debugx("creating subtree at %s", path); create_directory(path); if (incomplete) { TAILQ_FOREACH(child, &node->n_children, n_next) { if (strcmp(child->n_key, "*") == 0) { wildcard_found = true; break; } } if (wildcard_found) { log_debugx("node %s contains wildcard entry; " "not creating its subdirectories due to -d flag", path); free(path); return; } } free(path); TAILQ_FOREACH(child, &node->n_children, n_next) create_subtree(child, incomplete); } static void exit_callback(void) { done(EIO, true); } __dead static void handle_request(const struct autofs_daemon_request *adr, char *cmdline_options, bool incomplete_hierarchy) { const char *map; struct node *root, *parent, *node; FILE *f; char *key, *options, *fstype, *nobrowse, *retrycnt, *tmp; int error; bool wildcards; log_debugx("got request %d: from %s, path %s, prefix \"%s\", " "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from, adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options); /* * Try to notify the kernel about any problems. */ request_id = adr->adr_id; atexit(exit_callback); if (strncmp(adr->adr_from, "map ", 4) != 0) { log_errx(1, "invalid mountfrom \"%s\"; failing request", adr->adr_from); } map = adr->adr_from + 4; /* 4 for strlen("map "); */ root = node_new_root(); if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) { /* * Direct map. autofs(4) doesn't have a way to determine * correct map key, but since it's a direct map, we can just * use adr_path instead. */ parent = root; key = checked_strdup(adr->adr_path); } else { /* * Indirect map. */ parent = node_new_map(root, checked_strdup(adr->adr_prefix), NULL, checked_strdup(map), checked_strdup("[kernel request]"), lineno); if (adr->adr_key[0] == '\0') key = NULL; else key = checked_strdup(adr->adr_key); } /* * "Wildcards" here actually means "make autofs(4) request * automountd(8) action if the node being looked up does not * exist, even though the parent is marked as cached". This * needs to be done for maps with wildcard entries, but also * for special and executable maps. */ parse_map(parent, map, key, &wildcards); if (!wildcards) wildcards = node_has_wildcards(parent); if (wildcards) log_debugx("map may contain wildcard entries"); else log_debugx("map does not contain wildcard entries"); if (key != NULL) node_expand_wildcard(root, key); node = node_find(root, adr->adr_path); if (node == NULL) { log_errx(1, "map %s does not contain key for \"%s\"; " "failing mount", map, adr->adr_path); } options = node_options(node); /* * Append options from auto_master. */ options = concat(options, ',', adr->adr_options); /* * Prepend options passed via automountd(8) command line. */ options = concat(cmdline_options, ',', options); if (node->n_location == NULL) { log_debugx("found node defined at %s:%d; not a mountpoint", node->n_config_file, node->n_config_line); nobrowse = pick_option("nobrowse", &options); if (nobrowse != NULL && key == NULL) { log_debugx("skipping map %s due to \"nobrowse\" " "option; exiting", map); done(0, true); /* * Exit without calling exit_callback(). */ quick_exit(EXIT_SUCCESS); } /* * Not a mountpoint; create directories in the autofs mount * and complete the request. */ create_subtree(node, incomplete_hierarchy); if (incomplete_hierarchy && key != NULL) { /* * We still need to create the single subdirectory * user is trying to access. */ tmp = concat(adr->adr_path, '/', key); node = node_find(root, tmp); if (node != NULL) create_subtree(node, false); } log_debugx("nothing to mount; exiting"); done(0, wildcards); /* * Exit without calling exit_callback(). */ quick_exit(EXIT_SUCCESS); } log_debugx("found node defined at %s:%d; it is a mountpoint", node->n_config_file, node->n_config_line); if (key != NULL) node_expand_ampersand(node, key); error = node_expand_defined(node); if (error != 0) { log_errx(1, "variable expansion failed for %s; " "failing mount", adr->adr_path); } /* * Append "automounted". */ options = concat(options, ',', "automounted"); /* * Remove "nobrowse", mount(8) doesn't understand it. */ pick_option("nobrowse", &options); /* * Figure out fstype. */ fstype = pick_option("fstype=", &options); if (fstype == NULL) { log_debugx("fstype not specified in options; " "defaulting to \"nfs\""); fstype = checked_strdup("nfs"); } retrycnt = NULL; if (strcmp(fstype, "nfs") == 0) { /* * The mount_nfs(8) command defaults to retry DEF_RETRY(10000). * We do not want that behaviour, because it leaves mount_nfs(8) * instances and automountd(8) children hanging forever. * Disable retries unless the option was passed explicitly. */ retrycnt = pick_option("retrycnt=", &options); if (retrycnt == NULL) { log_debugx("retrycnt not specified in options; " "defaulting to 1"); retrycnt = nfs_def_retry; } } /* * NetBSD doesn't have -o retrycnt=... option which is available * on FreeBSD and DragonFlyBSD, so use -R if the target type is NFS * (or add -o retrycnt=... to mount_nfs(8)). */ if (retrycnt) { assert(!strcmp(fstype, "nfs")); f = auto_popen("mount_nfs", "-o", options, "-R", retrycnt, node->n_location, adr->adr_path, NULL); } else { f = auto_popen("mount", "-t", fstype, "-o", options, node->n_location, adr->adr_path, NULL); } assert(f != NULL); error = auto_pclose(f); if (error != 0) log_errx(1, "mount failed"); log_debugx("mount done; exiting"); done(0, wildcards); /* * Exit without calling exit_callback(). */ quick_exit(EXIT_SUCCESS); } static void sigchld_handler(int dummy __unused) { /* * The only purpose of this handler is to make SIGCHLD * interrupt the AUTOFSREQUEST ioctl(2), so we can call * wait_for_children(). */ } static void register_sigchld(void) { struct sigaction sa; int error; bzero(&sa, sizeof(sa)); sa.sa_handler = sigchld_handler; sigfillset(&sa.sa_mask); error = sigaction(SIGCHLD, &sa, NULL); if (error != 0) log_err(1, "sigaction"); } static int wait_for_children(bool block) { pid_t pid; int status; int num = 0; for (;;) { /* * If "block" is true, wait for at least one process. */ if (block && num == 0) pid = wait4(-1, &status, 0, NULL); else pid = wait4(-1, &status, WNOHANG, NULL); if (pid <= 0) break; if (WIFSIGNALED(status)) { log_warnx("child process %d terminated with signal %d", pid, WTERMSIG(status)); } else if (WEXITSTATUS(status) != 0) { log_debugx("child process %d terminated with exit " "status %d", pid, WEXITSTATUS(status)); } else { log_debugx("child process %d terminated gracefully", pid); } num++; } return num; } __dead static void usage_automountd(void) { fprintf(stderr, "Usage: %s [-D name=value][-m maxproc]" "[-o opts][-Tidv]\n", getprogname()); exit(EXIT_FAILURE); } static int load_autofs(void) { modctl_load_t args = { .ml_filename = "autofs", .ml_flags = MODCTL_NO_PROP, .ml_props = NULL, .ml_propslen = 0 }; int error; error = modctl(MODCTL_LOAD, &args); if (error && errno != EEXIST) log_warn("failed to load %s: %s", args.ml_filename, strerror(errno)); return error; } int main_automountd(int argc, char **argv) { pid_t pid; char *options = NULL; struct autofs_daemon_request request; int ch, debug = 0, error, maxproc = 30, saved_errno; bool dont_daemonize = false, incomplete_hierarchy = false; defined_init(); while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) { switch (ch) { case 'D': defined_parse_and_add(optarg); break; case 'T': /* * For compatibility with other implementations, * such as OS X. */ debug++; break; case 'd': dont_daemonize = true; debug++; break; case 'i': incomplete_hierarchy = true; break; case 'm': maxproc = atoi(optarg); break; case 'o': options = concat(options, ',', optarg); break; case 'v': debug++; break; case '?': default: usage_automountd(); } } argc -= optind; if (argc != 0) usage_automountd(); log_init(debug); /* * XXX: Workaround for NetBSD. * load_autofs() should be needed only if open(2) failed with ENXIO. * We attempt to load autofs before open(2) to suppress below warning * "module error: incompatible module class for `autofs' (3 != 2)", * which comes from sys/miscfs/specfs/spec_vnops.c:spec_open(). * spec_open() tries to load autofs as MODULE_CLASS_DRIVER while autofs * is of MODULE_CLASS_VFS. */ load_autofs(); /* * NetBSD needs to check ENXIO here, but might not need ENOENT. */ autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC); if (autofs_fd < 0 && (errno == ENOENT || errno == ENXIO)) { saved_errno = errno; if (!load_autofs()) autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC); else errno = saved_errno; } if (autofs_fd < 0) log_err(1, "failed to open %s", AUTOFS_PATH); if (dont_daemonize == false) { if (daemon(0, 0) == -1) { log_warn("cannot daemonize"); pidfile_clean(); exit(EXIT_FAILURE); } } else { lesser_daemon(); } /* * Call pidfile(3) after daemon(3). */ if (pidfile(NULL) == -1) { if (errno == EEXIST) log_errx(1, "daemon already running"); else if (errno == ENAMETOOLONG) log_errx(1, "pidfile name too long"); log_err(1, "cannot create pidfile"); } if (pidfile_lock(NULL) == -1) log_err(1, "cannot lock pidfile"); register_sigchld(); for (;;) { log_debugx("waiting for request from the kernel"); memset(&request, 0, sizeof(request)); error = ioctl(autofs_fd, AUTOFSREQUEST, &request); if (error != 0) { if (errno == EINTR) { nchildren -= wait_for_children(false); assert(nchildren >= 0); continue; } log_err(1, "AUTOFSREQUEST"); } if (dont_daemonize) { log_debugx("not forking due to -d flag; " "will exit after servicing a single request"); } else { nchildren -= wait_for_children(false); assert(nchildren >= 0); while (maxproc > 0 && nchildren >= maxproc) { log_debugx("maxproc limit of %d child processes" " hit; waiting for child process to exit", maxproc); nchildren -= wait_for_children(true); assert(nchildren >= 0); } log_debugx("got request; forking child process #%d", nchildren); nchildren++; pid = fork(); if (pid < 0) log_err(1, "fork"); if (pid > 0) continue; } handle_request(&request, options, incomplete_hierarchy); } pidfile_clean(); return EXIT_SUCCESS; }