/* ----------------------------------------------------------------------- * * * automount.c - Linux automounter daemon * * Copyright 1997 Transmeta Corporation - All Rights Reserved * Copyright 1999-2000 Jeremy Fitzhardinge * Copyright 2001-2005 Ian Kent * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139, * USA; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ----------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_SYSTEMD #include #endif #include "automount.h" #if defined(LIBXML2_WORKAROUND) || defined(TIRPC_WORKAROUND) #include #ifdef WITH_LDAP #include #endif #endif const char *program; /* Initialized with argv[0] */ const char *version = VERSION_STRING; /* Program version */ const char *libdir = AUTOFS_LIB_DIR; /* Location of library modules */ const char *mapdir = AUTOFS_MAP_DIR; /* Location of mount maps */ const char *confdir = AUTOFS_CONF_DIR; /* Location of autofs config file */ unsigned int mp_mode = 0755; unsigned int nfs_mount_uses_string_options = 0; static struct nfs_mount_vers vers, check = {1, 1, 1}; /* autofs fifo name prefix */ #define FIFO_NAME_PREFIX "autofs.fifo" const char *fifodir = AUTOFS_FIFO_DIR "/" FIFO_NAME_PREFIX; const char *global_options; /* Global option, from command line */ static char *pid_file = NULL; /* File in which to keep pid */ unsigned int global_selection_options; long global_negative_timeout = -1; int do_force_unlink = 0; /* Forceably unlink mount tree at startup */ static int start_pipefd[2] = {-1, -1}; static int st_stat = 1; static int *pst_stat = &st_stat; static pthread_t state_mach_thid; static sigset_t block_sigs; /* Pre-calculated kernel packet length */ static size_t kpkt_len; /* Attributes for creating detached and joinable threads */ pthread_attr_t th_attr; pthread_attr_t th_attr_detached; size_t detached_thread_stack_size = PTHREAD_STACK_MIN * 144; struct master_readmap_cond mrc = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0, NULL, 0, 0, 0, 0}; struct startup_cond suc = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0, 0}; pthread_key_t key_thread_stdenv_vars; pthread_key_t key_thread_attempt_id = (pthread_key_t) 0L; #define MAX_OPEN_FILES 10240 int aquire_flag_file(void); void release_flag_file(void); static int umount_all(struct autofs_point *ap, int force); extern struct master *master_list; /* simple string hash based on public domain sdbm library */ static unsigned long sdbm_hash(const char *str, unsigned long seed) { unsigned long hash = seed; char c; while ((c = *str++)) hash = c + (hash << 6) + (hash << 16) - hash; return hash; } void set_thread_mount_request_log_id(struct pending_args *mt) { char attempt_id_comp[20]; unsigned long *attempt_id; int status; if (!defaults_get_use_mount_request_log_id()) return; attempt_id = pthread_getspecific(key_thread_attempt_id); if (attempt_id == NULL) { attempt_id = (unsigned long *) calloc(1, sizeof(unsigned long)); if (attempt_id == NULL) fatal(ENOMEM); snprintf(attempt_id_comp, 20, "%ld", mt->wait_queue_token); *attempt_id = sdbm_hash(attempt_id_comp, 0); snprintf(attempt_id_comp, 20, "%u", mt->pid); *attempt_id = sdbm_hash(attempt_id_comp, *attempt_id); *attempt_id = sdbm_hash(mt->name, *attempt_id); status = pthread_setspecific(key_thread_attempt_id, attempt_id); if (status != 0) fatal(status); } } static int is_remote_fstype(unsigned int fs_type) { int ret = 0; switch (fs_type) { case SMB_SUPER_MAGIC: case CIFS_MAGIC_NUMBER: case NCP_SUPER_MAGIC: case NFS_SUPER_MAGIC: ret = 1; break; }; return ret; } static int do_mkdir(const char *parent, const char *path, mode_t mode) { int status; mode_t mask; struct stat st, root; struct statfs fs; /* If path exists we're done */ status = stat(path, &st); if (status == 0) { errno = EEXIST; if (!S_ISDIR(st.st_mode)) errno = ENOTDIR; return 0; } /* * We don't want to create the path on a remote file system * unless it's the root file system. * An empty parent means it's the root directory and always ok. */ if (*parent) { status = statfs(parent, &fs); if (status == -1) goto fail; if (is_remote_fstype(fs.f_type)) { status = stat(parent, &st); if (status == -1) goto fail; status = stat("/", &root); if (status == -1) goto fail; if (st.st_dev != root.st_dev) goto fail; } } mask = umask(0022); status = mkdir(path, mode); (void) umask(mask); if (status == -1) goto fail; return 1; fail: errno = EACCES; return 0; } int mkdir_path(const char *path, mode_t mode) { char buf[PATH_MAX]; char parent[PATH_MAX]; const char *cp = path, *lcp = path; char *bp = buf, *pp = parent; *parent = '\0'; do { if (cp != path && (*cp == '/' || *cp == '\0')) { memcpy(bp, lcp, cp - lcp); bp += cp - lcp; *bp = '\0'; if (!do_mkdir(parent, buf, mode)) { if (*cp != '\0') { memcpy(pp, lcp, cp - lcp); pp += cp - lcp; *pp = '\0'; lcp = cp; continue; } return -1; } memcpy(pp, lcp, cp - lcp); pp += cp - lcp; *pp = '\0'; lcp = cp; } } while (*cp++ != '\0'); return 0; } /* Remove as much as possible of a path */ int rmdir_path(struct autofs_point *ap, const char *path, dev_t dev) { int len = strlen(path); char buf[PATH_MAX]; char *cp; int first = 1; struct stat st; struct statfs fs; strcpy(buf, path); cp = buf + len; do { *cp = '\0'; /* * Before removing anything, perform some sanity checks to * ensure that we are looking at files in the automount * file system. */ memset(&st, 0, sizeof(st)); if (lstat(buf, &st) != 0) { crit(ap->logopt, "lstat of %s failed", buf); return -1; } /* Termination condition removing full path within autofs fs */ if (st.st_dev != dev) return 0; if (statfs(buf, &fs) != 0) { error(ap->logopt, "could not stat fs of %s", buf); return -1; } if (fs.f_type != (__SWORD_TYPE) AUTOFS_SUPER_MAGIC) { crit(ap->logopt, "attempt to remove directory from a " "non-autofs filesystem!"); crit(ap->logopt, "requester dev == %llu, \"%s\" owner dev == %llu", dev, buf, st.st_dev); return -1; } /* * Last element of path may be a symbolic link; all others * are directories (and the last directory element is * processed first, hence the variable name) */ if (rmdir(buf) == -1) { if (first && errno == ENOTDIR) { /* * Ensure that we will only remove * symbolic links. */ if (S_ISLNK(st.st_mode)) { if (unlink(buf) == -1) return -1; } else { crit(ap->logopt, "file \"%s\" is neither a directory" " nor a symbolic link. mode %d", buf, st.st_mode); return -1; } } /* * If we fail to remove a directory for any reason, * we need to return an error. */ return -1; } first = 0; } while ((cp = strrchr(buf, '/')) != NULL && cp != buf); return 0; } /* Like ftw, except fn gets called twice: before a directory is entered, and after. If the before call returns 0, the directory isn't entered. */ static int walk_tree(const char *base, int (*fn) (struct autofs_point *ap, const char *file, const struct stat * st, int, void *), int incl, struct autofs_point *ap, void *arg) { char buf[PATH_MAX + 1]; struct stat st, *pst = &st; int ret; if (!is_mounted(base, MNTS_REAL)) ret = lstat(base, pst); else { pst = NULL; ret = 0; } if (ret != -1 && (fn) (ap, base, pst, 0, arg)) { if (S_ISDIR(st.st_mode)) { struct dirent **de; int n; n = scandir(base, &de, 0, alphasort); if (n < 0) return -1; while (n--) { if (strcmp(de[n]->d_name, ".") == 0 || strcmp(de[n]->d_name, "..") == 0) { free(de[n]); continue; } if (!cat_path(buf, sizeof(buf), base, de[n]->d_name)) { do { free(de[n]); } while (n--); free(de); return -1; } walk_tree(buf, fn, 1, ap, arg); free(de[n]); } free(de); } if (incl) (fn) (ap, base, pst, 1, arg); } return 0; } static int rm_unwanted_fn(struct autofs_point *ap, const char *file, const struct stat *st, int when, void *arg) { dev_t dev = *(dev_t *) arg; char buf[MAX_ERR_BUF]; struct stat newst; if (!st) return 0; if (when == 0) { if (st->st_dev != dev) return 0; return 1; } if (lstat(file, &newst)) { crit(ap->logopt, "unable to stat file, possible race condition"); return 0; } if (newst.st_dev != dev) { crit(ap->logopt, "file %s has the wrong device, possible race condition", file); return 0; } if (S_ISDIR(newst.st_mode)) { debug(ap->logopt, "removing directory %s", file); if (rmdir(file)) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); warn(ap->logopt, "unable to remove directory %s: %s", file, estr); return 0; } } else if (S_ISREG(newst.st_mode)) { crit(ap->logopt, "attempting to remove files from a mounted " "directory. file %s", file); return 0; } else if (S_ISLNK(newst.st_mode)) { debug(ap->logopt, "removing symlink %s", file); unlink(file); } return 1; } void rm_unwanted(struct autofs_point *ap, const char *path, int incl) { walk_tree(path, rm_unwanted_fn, incl, ap, &ap->dev); } struct counter_args { unsigned int count; dev_t dev; }; static int counter_fn(struct autofs_point *ap, const char *file, const struct stat *st, int when, void *arg) { struct counter_args *counter = (struct counter_args *) arg; if (!st || (S_ISLNK(st->st_mode) || (S_ISDIR(st->st_mode) && st->st_dev != counter->dev))) { counter->count++; return 0; } return 1; } /* Count mounted filesystems and symlinks */ int count_mounts(struct autofs_point *ap, const char *path, dev_t dev) { struct counter_args counter; counter.count = 0; counter.dev = dev; if (walk_tree(path, counter_fn, 1, ap, &counter) == -1) return -1; return counter.count; } static void check_rm_dirs(struct autofs_point *ap, const char *path, int incl) { /* * If we're a submount the kernel can't know we're trying to * shutdown and so cannot block processes walking into the * mount point directory. If this is the call to umount_multi() * made during shutdown (incl == 0) we have to leave any mount * point directories in place so we can recover if needed. The * umount itself will clean these directories up for us * automagically. */ if (!incl && ap->submount) return; if ((!(ap->flags & MOUNT_FLAG_GHOST)) || (ap->state == ST_SHUTDOWN_PENDING || ap->state == ST_SHUTDOWN_FORCE || ap->state == ST_SHUTDOWN)) rm_unwanted(ap, path, incl); else if ((ap->flags & MOUNT_FLAG_GHOST) && (ap->type == LKP_INDIRECT)) rm_unwanted(ap, path, 0); } /* Try to purge cache entries kept around due to existing mounts */ static void update_map_cache(struct autofs_point *ap, const char *path) { struct map_source *map; struct mapent_cache *mc; const char *key; if (ap->type == LKP_INDIRECT) key = strrchr(path, '/') + 1; else key = path; map = ap->entry->maps; while (map) { struct mapent *me = NULL; /* Skip current, in-use cache */ if (ap->entry->age <= map->age) { map = map->next; continue; } mc = map->mc; /* If the lock is busy try later */ if (cache_try_writelock(mc)) { me = cache_lookup_distinct(mc, key); if (me && me->ioctlfd == -1) cache_delete(mc, key); cache_unlock(mc); } map = map->next; } return; } static int umount_subtree_mounts(struct autofs_point *ap, const char *path, unsigned int is_autofs_fs) { struct mapent_cache *mc; struct mapent *me; unsigned int is_mm_root = 0; int left; me = lookup_source_mapent(ap, path, LKP_DISTINCT); if (!me) { char *ind_key; ind_key = strrchr(path, '/'); if (ind_key) ind_key++; me = lookup_source_mapent(ap, ind_key, LKP_NORMAL); } if (me) { mc = me->mc; is_mm_root = (me->multi == me); } left = 0; if (me && me->multi) { char root[PATH_MAX]; char *base; int cur_state; pthread_cleanup_push(cache_lock_cleanup, mc); if (!strchr(me->multi->key, '/')) /* Indirect multi-mount root */ /* sprintf okay - if it's mounted, it's * PATH_MAX or less bytes */ sprintf(root, "%s/%s", ap->path, me->multi->key); else strcpy(root, me->multi->key); if (is_mm_root) base = NULL; else base = me->key + strlen(root); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); /* Lock the closest parent nesting point for umount */ cache_multi_writelock(me->parent); if (umount_multi_triggers(ap, me, root, base)) { warn(ap->logopt, "some offset mounts still present under %s", path); left++; } cache_multi_unlock(me->parent); if (ap->entry->maps && (ap->entry->maps->flags & MAP_FLAG_FORMAT_AMD)) cache_pop_mapent(me); pthread_setcancelstate(cur_state, NULL); pthread_cleanup_pop(0); } if (me) cache_unlock(mc); if (left || is_autofs_fs) return left; /* * If this is the root of a multi-mount we've had to umount * it already to ensure it's ok to remove any offset triggers. */ if (!is_mm_root && is_mounted(path, MNTS_REAL)) { struct amd_entry *entry; debug(ap->logopt, "unmounting dir = %s", path); if (umount_ent(ap, path) && is_mounted(path, MNTS_REAL)) { warn(ap->logopt, "could not umount dir %s", path); left++; goto done; } /* Check for an external mount and umount if possible */ mounts_mutex_lock(ap); entry = __master_find_amdmount(ap, path); if (!entry) { mounts_mutex_unlock(ap); goto done; } list_del(&entry->entries); mounts_mutex_unlock(ap); umount_amd_ext_mount(ap, entry); free_amd_entry(entry); } done: return left; } /* umount all filesystems mounted under path. If incl is true, then it also tries to umount path itself */ int umount_multi(struct autofs_point *ap, const char *path, int incl) { int is_autofs_fs; struct stat st; int left; debug(ap->logopt, "path %s incl %d", path, incl); if (lstat(path, &st)) { warn(ap->logopt, "failed to stat directory or symlink %s", path); return 1; } /* if this is a symlink we can handle it now */ if (S_ISLNK(st.st_mode)) { struct amd_entry *entry; if (st.st_dev != ap->dev) { crit(ap->logopt, "symlink %s has the wrong device, " "possible race condition", path); return 1; } debug(ap->logopt, "removing symlink %s", path); if (unlink(path)) { error(ap->logopt, "failed to remove symlink %s", path); return 1; } /* Check for an external mount and attempt umount if needed */ mounts_mutex_lock(ap); entry = __master_find_amdmount(ap, path); if (!entry) { mounts_mutex_unlock(ap); return 0; } list_del(&entry->entries); mounts_mutex_unlock(ap); umount_amd_ext_mount(ap, entry); free_amd_entry(entry); return 0; } is_autofs_fs = 0; if (master_find_submount(ap, path)) is_autofs_fs = 1; left = 0; /* * If we are a submount we need to umount any offsets our * parent may have mounted over top of us. */ if (ap->submount) left += umount_subtree_mounts(ap->parent, path, 1); left += umount_subtree_mounts(ap, path, is_autofs_fs); /* Delete detritus like unwanted mountpoints and symlinks */ if (left == 0 && ap->state != ST_READMAP && !count_mounts(ap, path, ap->dev)) { update_map_cache(ap, path); check_rm_dirs(ap, path, incl); } return left; } static int umount_all(struct autofs_point *ap, int force) { int left; left = umount_multi(ap, ap->path, 0); if (force && left) warn(ap->logopt, "could not unmount %d dirs under %s", left, ap->path); return left; } int umount_autofs(struct autofs_point *ap, const char *root, int force) { int ret = 0; if (ap->state == ST_INIT) return -1; /* * Since lookup.c is lazy about closing lookup modules * to prevent unneeded opens, we need to clean them up * before umount. */ lookup_close_lookup(ap); if (ap->type == LKP_INDIRECT) { if (umount_all(ap, force) && !force) return -1; ret = umount_autofs_indirect(ap, root); } else ret = umount_autofs_direct(ap); return ret; } static size_t get_kpkt_len(void) { size_t pkt_len = sizeof(struct autofs_v5_packet); struct utsname un; int kern_vers; kern_vers = linux_version_code(); if (kern_vers >= KERNEL_VERSION(3, 3, 0)) return pkt_len; uname(&un); if (pkt_len % 8) { if (strcmp(un.machine, "alpha") == 0 || strcmp(un.machine, "ia64") == 0 || strcmp(un.machine, "x86_64") == 0 || strcmp(un.machine, "parisc64") == 0 || strcmp(un.machine, "ppc64") == 0) pkt_len += 4; } return pkt_len; } static int fullread(int fd, void *ptr, size_t len) { char *buf = (char *) ptr; while (len > 0) { ssize_t r = read(fd, buf, len); if (r == -1) { if (errno == EINTR) continue; break; } buf += r; len -= r; } return len; } static char *automount_path_to_fifo(unsigned logopt, const char *path) { char *fifo_name, *p; int name_len = strlen(path) + strlen(fifodir) + 1; int ret; fifo_name = malloc(name_len); if (!fifo_name) return NULL; ret = snprintf(fifo_name, name_len, "%s%s", fifodir, path); if (ret >= name_len) { info(logopt, "fifo path for \"%s\" truncated to \"%s\". This may " "lead to --set-log-priority commands being sent to the " "wrong automount daemon.", path, fifo_name); } /* * An automount path can be made up of subdirectories. So, to * create the fifo name, we will just replace instances of '/' with * '-'. */ p = fifo_name + strlen(fifodir); while (*p != '\0') { if (*p == '/') *p = '-'; p++; } debug(logopt, "fifo name %s",fifo_name); return fifo_name; } static int create_logpri_fifo(struct autofs_point *ap) { int ret = -1; int fd; char *fifo_name; char buf[MAX_ERR_BUF]; fifo_name = automount_path_to_fifo(ap->logopt, ap->path); if (!fifo_name) { crit(ap->logopt, "Failed to allocate memory!"); goto out_free; /* free(NULL) is okay */ } ret = unlink(fifo_name); if (ret != 0 && errno != ENOENT) { crit(ap->logopt, "Failed to unlink FIFO. Is the automount daemon " "already running?"); goto out_free; } ret = mkfifo(fifo_name, S_IRUSR|S_IWUSR); if (ret != 0) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); crit(ap->logopt, "mkfifo for %s failed: %s", fifo_name, estr); goto out_free; } fd = open_fd(fifo_name, O_RDWR|O_NONBLOCK); if (fd < 0) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); crit(ap->logopt, "Failed to open %s: %s", fifo_name, estr); unlink(fifo_name); ret = -1; goto out_free; } ap->logpri_fifo = fd; out_free: free(fifo_name); return ret; } int destroy_logpri_fifo(struct autofs_point *ap) { int ret = -1; int fd = ap->logpri_fifo; char *fifo_name; char buf[MAX_ERR_BUF]; if (fd == -1) return 0; fifo_name = automount_path_to_fifo(ap->logopt, ap->path); if (!fifo_name) { crit(ap->logopt, "Failed to allocate memory!"); goto out_free; /* free(NULL) is okay */ } ap->logpri_fifo = -1; ret = close(fd); if (ret != 0) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); warn(ap->logopt, "close for fifo %s: %s", fifo_name, estr); } ret = unlink(fifo_name); if (ret != 0) { warn(ap->logopt, "Failed to unlink FIFO. Was the fifo created OK?"); } out_free: free(fifo_name); return ret; } static void cleanup_stale_logpri_fifo_pipes(void) { size_t prefix_len = strlen(FIFO_NAME_PREFIX); char *dir = AUTOFS_FIFO_DIR; size_t dir_len = strlen(dir); struct dirent *dent; DIR *dfd; int ret; dfd = opendir(dir); if (!dfd) { warn(LOGOPT_ANY, "failed to open fifo dir %s", dir); return; } while ((dent = readdir(dfd))) { char fifo_path[PATH_MAX]; if (!(dent->d_type & DT_FIFO)) continue; if (strncmp(FIFO_NAME_PREFIX, dent->d_name, prefix_len)) continue; if ((dir_len + 1 + strlen(dent->d_name)) >= PATH_MAX) { warn(LOGOPT_ANY, "fifo path too long for buffer"); continue; } strcpy(fifo_path, dir); strcat(fifo_path, "/"); strcat(fifo_path, dent->d_name); ret = unlink(fifo_path); if (ret == -1) { char buf[MAX_ERR_BUF]; char *estr = strerror_r(errno, buf, MAX_ERR_BUF); warn(LOGOPT_ANY, "unlink of fifo failed: %s", estr); } } closedir(dfd); } static void handle_fifo_message(struct autofs_point *ap, int fd) { int ret; char buffer[PIPE_BUF]; char *end; long pri; char buf[MAX_ERR_BUF]; memset(buffer, 0, sizeof(buffer)); ret = read(fd, &buffer, sizeof(buffer)); if (ret < 0) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); warn(ap->logopt, "read on fifo returned error: %s", estr); return; } if (ret != 2) { debug(ap->logopt, "expected 2 bytes, received %d.", ret); return; } errno = 0; pri = strtol(buffer, &end, 10); if ((pri == LONG_MIN || pri == LONG_MAX) && errno == ERANGE) { debug(ap->logopt, "strtol reported an %s. Failed to set " "log priority.", pri == LONG_MIN ? "underflow" : "overflow"); return; } if ((pri == 0 && errno == EINVAL) || end == buffer) { debug(ap->logopt, "priority is expected to be an integer " "in the range 0-7 inclusive."); return; } if (pri > LOG_DEBUG || pri < LOG_EMERG) { debug(ap->logopt, "invalid log priority (%ld) received " "on fifo", pri); return; } /* * OK, the message passed all of the sanity checks. The * automounter actually only supports three log priorities. * Everything is logged at log level debug, deamon messages * and everything except debug messages are logged with the * verbose setting and only error and critical messages are * logged when debugging isn't enabled. */ if (pri >= LOG_WARNING) { if (pri == LOG_DEBUG) { set_log_debug_ap(ap); info(ap->logopt, "Debug logging set for %s", ap->path); } else { set_log_verbose_ap(ap); info(ap->logopt, "Verbose logging set for %s", ap->path); } } else { if (ap->logopt & LOGOPT_ANY) info(ap->logopt, "Basic logging set for %s", ap->path); set_log_norm_ap(ap); } } static int set_log_priority(const char *path, int priority) { int fd; char *fifo_name; char buf[2]; if (priority > LOG_DEBUG || priority < LOG_EMERG) { fprintf(stderr, "Log priority %d is invalid.\n", priority); fprintf(stderr, "Please specify a number in the range 0-7.\n"); return -1; } /* * This is an ascii based protocol, so we want the string * representation of the integer log priority. */ snprintf(buf, sizeof(buf), "%d", priority); fifo_name = automount_path_to_fifo(LOGOPT_NONE, path); if (!fifo_name) { fprintf(stderr, "%s: Failed to allocate memory!\n", __FUNCTION__); return -1; } /* * Specify O_NONBLOCK so that the open will fail if there is no * daemon reading from the other side of the FIFO. */ fd = open_fd(fifo_name, O_WRONLY|O_NONBLOCK); if (fd < 0) { fprintf(stderr, "%s: open of %s failed with %s\n", __FUNCTION__, fifo_name, strerror(errno)); fprintf(stderr, "%s: perhaps the fifo wasn't setup," " please check your log for more information\n", __FUNCTION__); free(fifo_name); return -1; } if (write(fd, buf, sizeof(buf)) != sizeof(buf)) { fprintf(stderr, "Failed to change logging priority. "); fprintf(stderr, "write to fifo failed: %s.\n", strerror(errno)); close(fd); free(fifo_name); return -1; } close(fd); free(fifo_name); fprintf(stdout, "Successfully set log priority for %s.\n", path); return 0; } static int get_pkt(struct autofs_point *ap, union autofs_v5_packet_union *pkt) { struct pollfd fds[3]; int pollfds = 3; char buf[MAX_ERR_BUF]; size_t read; char *estr; fds[0].fd = ap->pipefd; fds[0].events = POLLIN; fds[1].fd = ap->state_pipe[0]; fds[1].events = POLLIN; fds[2].fd = ap->logpri_fifo; fds[2].events = POLLIN; if (fds[2].fd == -1) pollfds--; for (;;) { if (poll(fds, pollfds, -1) == -1) { if (errno == EINTR) continue; estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr("poll failed: %s", estr); return -1; } if (fds[1].revents & POLLIN) { enum states next_state; size_t read_size = sizeof(next_state); int state_pipe; next_state = ST_INVAL; st_mutex_lock(); state_pipe = ap->state_pipe[0]; read = fullread(state_pipe, &next_state, read_size); if (read) { estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, "read error on state pipe, " "read %u, error %s", read, estr); st_mutex_unlock(); continue; } st_mutex_unlock(); if (next_state == ST_SHUTDOWN) return -1; } if (fds[0].revents & POLLIN) { read = fullread(ap->pipefd, pkt, kpkt_len); if (read) { estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, "read error on request pipe, " "read %u, expected %u error %s", read, kpkt_len, estr); } return read; } if (fds[2].fd != -1 && fds[2].revents & POLLIN) { debug(ap->logopt, "message pending on control fifo."); handle_fifo_message(ap, fds[2].fd); } } } int do_expire(struct autofs_point *ap, const char *name, int namelen) { char buf[PATH_MAX]; int len, ret; if (*name != '/') { len = ncat_path(buf, sizeof(buf), ap->path, name, namelen); } else { len = snprintf(buf, PATH_MAX, "%s", name); if (len >= PATH_MAX) len = 0; } if (!len) { crit(ap->logopt, "path too long for buffer"); return 1; } info(ap->logopt, "expiring path %s", buf); pthread_cleanup_push(master_source_lock_cleanup, ap->entry); master_source_readlock(ap->entry); ret = umount_multi(ap, buf, 1); if (ret == 0) info(ap->logopt, "expired %s", buf); else warn(ap->logopt, "couldn't complete expire of %s", buf); pthread_cleanup_pop(1); return ret; } static int autofs_init_ap(struct autofs_point *ap) { int pipefd[2]; if ((ap->state != ST_INIT)) { /* This can happen if an autofs process is already running*/ error(ap->logopt, "bad state %d", ap->state); return -1; } ap->pipefd = ap->kpipefd = ap->ioctlfd = -1; /* Pipe for kernel communications */ if (open_pipe(pipefd) < 0) { crit(ap->logopt, "failed to create commumication pipe for autofs path %s", ap->path); return -1; } ap->pipefd = pipefd[0]; ap->kpipefd = pipefd[1]; /* Pipe state changes from signal handler to main loop */ if (open_pipe(ap->state_pipe) < 0) { crit(ap->logopt, "failed create state pipe for autofs path %s", ap->path); close(ap->pipefd); close(ap->kpipefd); /* Close kernel pipe end */ return -1; } if (create_logpri_fifo(ap) < 0) { logmsg("could not create FIFO for path %s\n", ap->path); logmsg("dynamic log level changes not available for %s", ap->path); } return 0; } static int mount_autofs(struct autofs_point *ap, const char *root) { int status = 0; /* No need to create comms fds and command fifo if * unlinking mounts and exiting. */ if (!(do_force_unlink & UNLINK_AND_EXIT)) { if (autofs_init_ap(ap) != 0) return -1; } if (ap->type == LKP_DIRECT) status = mount_autofs_direct(ap); else status = mount_autofs_indirect(ap, root); if (status < 0) { destroy_logpri_fifo(ap); return -1; } st_add_task(ap, ST_READY); return 0; } static int handle_packet(struct autofs_point *ap) { union autofs_v5_packet_union pkt; if (get_pkt(ap, &pkt)) return -1; debug(ap->logopt, "type = %d", pkt.hdr.type); switch (pkt.hdr.type) { case autofs_ptype_missing_indirect: return handle_packet_missing_indirect(ap, &pkt.v5_packet); case autofs_ptype_missing_direct: return handle_packet_missing_direct(ap, &pkt.v5_packet); case autofs_ptype_expire_indirect: return handle_packet_expire_indirect(ap, &pkt.v5_packet); case autofs_ptype_expire_direct: return handle_packet_expire_direct(ap, &pkt.v5_packet); } error(ap->logopt, "unknown packet type %d", pkt.hdr.type); return -1; } static void become_daemon(unsigned int flags) { FILE *pidfp; char buf[MAX_ERR_BUF]; int res; pid_t pid; /* Don't BUSY any directories unnecessarily */ if (chdir("/")) { fprintf(stderr, "%s: failed change working directory.\n", program); exit(0); } /* Detach from foreground process */ if (flags & DAEMON_FLAGS_FOREGROUND && !(flags & DAEMON_FLAGS_SYSTEMD_SERVICE)) { if ((flags & DAEMON_FLAGS_CHECK_DAEMON) && !aquire_flag_file()) { fprintf(stderr, "%s: program is already running.\n", program); exit(1); } log_to_stderr(); } else if (flags & DAEMON_FLAGS_SYSTEMD_SERVICE) { if ((flags & DAEMON_FLAGS_CHECK_DAEMON) && !aquire_flag_file()) { fprintf(stderr, "%s: program is already running.\n", program); exit(1); } open_log(); } else { int nullfd; if (open_pipe(start_pipefd) < 0) { fprintf(stderr, "%s: failed to create start_pipefd.\n", program); exit(0); } pid = fork(); if (pid > 0) { close(start_pipefd[1]); res = read(start_pipefd[0], pst_stat, sizeof(*pst_stat)); if (res < 0) exit(1); exit(*pst_stat); } else if (pid < 0) { fprintf(stderr, "%s: Could not detach process\n", program); exit(1); } close(start_pipefd[0]); if ((flags & DAEMON_FLAGS_CHECK_DAEMON) && !aquire_flag_file()) { fprintf(stderr, "%s: program is already running.\n", program); /* Return success if already running */ st_stat = 0; res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); if (res < 0) exit(1); close(start_pipefd[1]); exit(*pst_stat); } /* * Make our own process group for "magic" reason: processes that share * our pgrp see the raw filesystem behind the magic. */ if (setsid() == -1) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); fprintf(stderr, "setsid: %s", estr); res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); exit(*pst_stat); } /* Redirect all our file descriptors to /dev/null */ nullfd = open("/dev/null", O_RDWR); if (nullfd < 0) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); fprintf(stderr, "cannot open /dev/null: %s", estr); res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); exit(*pst_stat); } if (dup2(nullfd, STDIN_FILENO) < 0 || dup2(nullfd, STDOUT_FILENO) < 0 || dup2(nullfd, STDERR_FILENO) < 0) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); fprintf(stderr, "redirecting file descriptors failed: %s", estr); res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); exit(*pst_stat); } open_log(); close(nullfd); } /* Write pid file if requested */ if (pid_file) { if ((pidfp = fopen(pid_file, "wt"))) { fprintf(pidfp, "%lu\n", (unsigned long) getpid()); fclose(pidfp); } else { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr("failed to write pid file %s: %s", pid_file, estr); pid_file = NULL; } } } static unsigned long getnumopt(char *str, char option) { unsigned long val; char *end; val = strtoul(str, &end, 0); if (!*str || *end) { fprintf(stderr, "%s: option -%c requires a numeric argument, got %s\n", program, option, str); exit(1); } return val; } static void do_master_cleanup_unlock(void *arg) { int status; status = pthread_mutex_unlock(&mrc.mutex); if (status) fatal(status); return; } static void *do_notify_state(void *arg) { struct master *master; int sig; int status; sig = *(int *) arg; status = pthread_mutex_lock(&mrc.mutex); if (status) fatal(status); master = mrc.master; debug(master->logopt, "signal %d", sig); mrc.signaled = 1; status = pthread_cond_signal(&mrc.cond); if (status) { error(master->logopt, "failed to signal state notify condition"); status = pthread_mutex_unlock(&mrc.mutex); if (status) fatal(status); pthread_exit(NULL); } status = pthread_mutex_unlock(&mrc.mutex); if (status) fatal(status); master_notify_state_change(master, sig); return NULL; } static pthread_t do_signals(struct master *master, int sig) { pthread_t thid; int r_sig = sig; int status; status = pthread_mutex_lock(&mrc.mutex); if (status) fatal(status); status = pthread_create(&thid, &th_attr_detached, do_notify_state, &r_sig); if (status) { error(master->logopt, "mount state notify thread create failed"); status = pthread_mutex_unlock(&mrc.mutex); if (status) fatal(status); return 0; } mrc.thid = thid; mrc.master = master; pthread_cleanup_push(do_master_cleanup_unlock, NULL); mrc.signaled = 0; while (!mrc.signaled) { status = pthread_cond_wait(&mrc.cond, &mrc.mutex); if (status) fatal(status); } pthread_cleanup_pop(1); return thid; } static void *do_read_master(void *arg) { struct master *master; unsigned int logopt; time_t age; int readall = 1; int status; status = pthread_mutex_lock(&mrc.mutex); if (status) fatal(status); master = mrc.master; age = mrc.age; logopt = master->logopt; mrc.signaled = 1; status = pthread_cond_signal(&mrc.cond); if (status) { error(logopt, "failed to signal master read map condition"); master->reading = 0; status = pthread_mutex_unlock(&mrc.mutex); if (status) fatal(status); pthread_exit(NULL); } status = pthread_mutex_unlock(&mrc.mutex); if (status) fatal(status); defaults_read_config(1); info(logopt, "re-reading master map %s", master->name); status = master_read_master(master, age, readall); master->reading = 0; return NULL; } static int do_hup_signal(struct master *master) { unsigned int logopt = master->logopt; time_t age = monotonic_time(NULL); pthread_t thid; int status; status = pthread_mutex_lock(&mrc.mutex); if (status) fatal(status); nfs_mount_uses_string_options = check_nfs_mount_version(&vers, &check); master_mutex_lock(); /* Already doing a map read or shutdown or no mounts */ if (master->reading) { status = pthread_mutex_unlock(&mrc.mutex); if (status) fatal(status); master_mutex_unlock(); return 1; } master->reading = 1; master_mutex_unlock(); status = pthread_create(&thid, &th_attr_detached, do_read_master, NULL); if (status) { error(logopt, "master read map thread create failed"); master->reading = 0; status = pthread_mutex_unlock(&mrc.mutex); if (status) fatal(status); return 0; } mrc.thid = thid; mrc.master = master; mrc.age = age; pthread_cleanup_push(do_master_cleanup_unlock, NULL); mrc.signaled = 0; while (!mrc.signaled) { status = pthread_cond_wait(&mrc.cond, &mrc.mutex); if (status) fatal(status); } pthread_cleanup_pop(1); return 1; } /* Deal with all the signal-driven events in the state machine */ static void *statemachine(void *arg) { sigset_t signalset; int sig; memcpy(&signalset, &block_sigs, sizeof(signalset)); sigdelset(&signalset, SIGCHLD); sigdelset(&signalset, SIGCONT); while (1) { sigwait(&signalset, &sig); switch (sig) { case SIGTERM: case SIGINT: case SIGUSR2: master_mutex_lock(); if (list_empty(&master_list->completed)) { if (list_empty(&master_list->mounts)) { master_mutex_unlock(); return NULL; } } else { if (master_done(master_list)) { master_mutex_unlock(); return NULL; } master_mutex_unlock(); break; } master_mutex_unlock(); case SIGUSR1: do_signals(master_list, sig); break; case SIGHUP: do_hup_signal(master_list); break; default: logerr("got unexpected signal %d!", sig); continue; } } } static void return_start_status(void *arg) { struct startup_cond *sc; int status; sc = (struct startup_cond *) arg; sc->done = 1; /* * Startup condition mutex must be locked during * the startup process. */ status = pthread_cond_signal(&sc->cond); if (status) fatal(status); status = pthread_mutex_unlock(&sc->mutex); if (status) fatal(status); } int handle_mounts_startup_cond_init(struct startup_cond *suc) { int status; status = pthread_mutex_init(&suc->mutex, NULL); if (status) return status; status = pthread_cond_init(&suc->cond, NULL); if (status) { status = pthread_mutex_destroy(&suc->mutex); if (status) fatal(status); return status; } status = pthread_mutex_lock(&suc->mutex); if (status) { status = pthread_mutex_destroy(&suc->mutex); if (status) fatal(status); status = pthread_cond_destroy(&suc->cond); if (status) fatal(status); } return 0; } void handle_mounts_startup_cond_destroy(void *arg) { struct startup_cond *suc = (struct startup_cond *) arg; int status; status = pthread_mutex_unlock(&suc->mutex); if (status) fatal(status); status = pthread_mutex_destroy(&suc->mutex); if (status) fatal(status); status = pthread_cond_destroy(&suc->cond); if (status) fatal(status); return; } static void handle_mounts_cleanup(void *arg) { struct autofs_point *ap; char path[PATH_MAX + 1]; char buf[MAX_ERR_BUF]; unsigned int clean = 0, submount, logopt; unsigned int pending = 0; ap = (struct autofs_point *) arg; logopt = ap->logopt; submount = ap->submount; strcpy(path, ap->path); if (!submount && strcmp(ap->path, "/-") && ap->flags & MOUNT_FLAG_DIR_CREATED) clean = 1; if (submount) { struct amd_entry *am; /* We are finishing up */ ap->parent->submnt_count--; list_del_init(&ap->mounts); am = __master_find_amdmount(ap->parent, ap->path); if (am) { list_del_init(&am->entries); free_amd_entry(am); } } /* Don't signal the handler if we have already done so */ if (!list_empty(&master_list->completed)) pending = 1; master_remove_mapent(ap->entry); master_source_unlock(ap->entry); destroy_logpri_fifo(ap); /* * Submounts are detached threads and don't belong to the * master map entry list so we need to free their resources * here. */ if (submount) { mounts_mutex_unlock(ap->parent); master_source_unlock(ap->parent->entry); master_free_mapent_sources(ap->entry, 1); master_free_mapent(ap->entry); } if (clean) { if (rmdir(path) == -1) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); warn(logopt, "failed to remove dir %s: %s", path, estr); } } info(logopt, "shut down path %s", path); /* * If we are not a submount send a signal to the signal handler * so it can join with any completed handle_mounts() threads and * perform final cleanup. */ if (!submount && !pending) pthread_kill(state_mach_thid, SIGTERM); master_mutex_unlock(); return; } static int submount_source_writelock_nested(struct autofs_point *ap) { struct autofs_point *parent = ap->parent; int status; status = pthread_rwlock_trywrlock(&parent->entry->source_lock); if (status) goto done; mounts_mutex_lock(parent); status = pthread_rwlock_trywrlock(&ap->entry->source_lock); if (status) { mounts_mutex_unlock(parent); master_source_unlock(parent->entry); } done: if (status && status != EBUSY) { logmsg("submount nested master_mapent source write lock failed"); fatal(status); } return status; } static void submount_source_unlock_nested(struct autofs_point *ap) { struct autofs_point *parent = ap->parent; master_source_unlock(ap->entry); mounts_mutex_unlock(parent); master_source_unlock(parent->entry); } int handle_mounts_exit(struct autofs_point *ap) { int ret, cur_state; /* * If we're a submount we need to ensure our parent * doesn't try to mount us again until our shutdown * is complete and that any outstanding mounts are * completed before we try to shutdown. */ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); master_mutex_lock(); if (!ap->submount) master_source_writelock(ap->entry); else { /* * If a mount request arrives before the locks are * aquired just return to ready state. */ ret = submount_source_writelock_nested(ap); if (ret) { warn(ap->logopt, "can't shutdown submount: mount in progress"); /* Return to ST_READY is done immediately */ st_add_task(ap, ST_READY); master_mutex_unlock(); pthread_setcancelstate(cur_state, NULL); return 0; } } if (ap->state != ST_SHUTDOWN) { if (!ap->submount) alarm_add(ap, ap->exp_runfreq); /* Return to ST_READY is done immediately */ st_add_task(ap, ST_READY); if (ap->submount) submount_source_unlock_nested(ap); else master_source_unlock(ap->entry); master_mutex_unlock(); pthread_setcancelstate(cur_state, NULL); return 0; } alarm_delete(ap); st_remove_tasks(ap); st_wait_task(ap, ST_ANY, 0); /* * For a direct mount map all mounts have already gone * by the time we get here and since we only ever * umount direct mounts at shutdown there is no need * to check for possible recovery. */ if (ap->type == LKP_DIRECT) { umount_autofs(ap, NULL, 1); handle_mounts_cleanup(ap); return 1; } /* * If umount_autofs returns non-zero it wasn't able * to complete the umount and has left the mount intact * so we can continue. This can happen if a lookup * occurs while we're trying to umount. */ ret = umount_autofs(ap, NULL, 1); if (!ret) { set_indirect_mount_tree_catatonic(ap); handle_mounts_cleanup(ap); return 1; } /* Failed shutdown returns to ready */ warn(ap->logopt, "can't shutdown: filesystem %s still busy", ap->path); if (!ap->submount) alarm_add(ap, ap->exp_runfreq); /* Return to ST_READY is done immediately */ st_add_task(ap, ST_READY); if (ap->submount) submount_source_unlock_nested(ap); else master_source_unlock(ap->entry); master_mutex_unlock(); pthread_setcancelstate(cur_state, NULL); return 0; } void *handle_mounts(void *arg) { struct startup_cond *suc; struct autofs_point *ap; int cancel_state, status = 0; char *root; suc = (struct startup_cond *) arg; ap = suc->ap; root = strdup(suc->root); pthread_cleanup_push(return_start_status, suc); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); status = pthread_mutex_lock(&suc->mutex); if (status) { logerr("failed to lock startup condition mutex!"); fatal(status); } if (!root) { crit(ap->logopt, "failed to alloc string root"); suc->status = 1; pthread_setcancelstate(cancel_state, NULL); pthread_exit(NULL); } if (mount_autofs(ap, root) < 0) { if (!(do_force_unlink & UNLINK_AND_EXIT)) crit(ap->logopt, "mount of %s failed!", ap->path); suc->status = 1; umount_autofs(ap, root, 1); free(root); pthread_setcancelstate(cancel_state, NULL); pthread_exit(NULL); } free(root); if (ap->flags & MOUNT_FLAG_NOBIND) info(ap->logopt, "bind mounts disabled"); if (ap->flags & MOUNT_FLAG_GHOST && ap->type != LKP_DIRECT) info(ap->logopt, "ghosting enabled"); suc->status = 0; pthread_cleanup_pop(1); /* We often start several automounters at the same time. Add some randomness so we don't all expire at the same time. */ if (!ap->submount && ap->exp_runfreq) alarm_add(ap, ap->exp_runfreq + rand() % ap->exp_runfreq); pthread_setcancelstate(cancel_state, NULL); while (1) { if (handle_packet(ap)) { if (handle_mounts_exit(ap)) break; } /* If we get here a packet has been received and handled * and the autofs mount point has not been shutdown. But * if the autofs mount point has been set to ST_SHUTDOWN * we should attempt to perform the shutdown cleanup and * exit if successful. */ if (ap->state == ST_SHUTDOWN) { if (handle_mounts_exit(ap)) break; } } return NULL; } static void key_thread_stdenv_vars_destroy(void *arg) { struct thread_stdenv_vars *tsv; tsv = (struct thread_stdenv_vars *) arg; if (tsv->user) free(tsv->user); if (tsv->group) free(tsv->group); if (tsv->home) free(tsv->home); free(tsv); return; } static void usage(void) { fprintf(stderr, "Usage: %s [options] [master_map_name]\n" " -h --help this text\n" " -p --pid-file f write process id to file f\n" " -t --timeout n auto-unmount in n seconds (0-disable)\n" " -M --master-wait n\n" " maximum wait time (seconds) for master\n" " map to become available\n" " -v --verbose be verbose\n" " -d --debug log debuging info\n" " -Dvariable=value, --define variable=value\n" " define global macro variable\n" " -S --systemd-service\n" " run automounter as a systemd service\n" " -f --foreground do not fork into background\n" " -r --random-multimount-selection\n" " use ramdom replicated server selection\n" " -m --dumpmaps [ ]\n" " dump automounter maps and exit\n" " -n --negative-timeout n\n" " set the timeout for failed key lookups.\n" " -O --global-options\n" " specify global mount options\n" " -l --set-log-priority priority path [path,...]\n" " set daemon log verbosity\n" " -C --dont-check-daemon\n" " don't check if daemon is already running\n" " -F --force forceably clean up known automounts at start\n" " -U --force-exit forceably clean up known automounts and exit\n" " -V --version print version, build config and exit\n" , program); } static void show_build_info(void) { int count = 0; printf("\nLinux automount version %s\n", version); printf("\nDirectories:\n"); printf("\tconfig dir:\t%s\n", confdir); printf("\tmaps dir:\t%s\n", mapdir); printf("\tmodules dir:\t%s\n", libdir); printf("\nCompile options:\n "); #ifndef ENABLE_MOUNT_LOCKING printf("DISABLE_MOUNT_LOCKING "); count = 22; #endif #ifdef ENABLE_FORCED_SHUTDOWN printf("ENABLE_FORCED_SHUTDOWN "); count = count + 23; #endif #ifdef ENABLE_IGNORE_BUSY_MOUNTS printf("ENABLE_IGNORE_BUSY_MOUNTS "); count = count + 26; if (count > 60) { printf("\n "); count = 0; } #endif #ifdef WITH_SYSTEMD printf("WITH_SYSTEMD "); count = count + 13; if (count > 60) { printf("\n "); count = 0; } #endif #ifdef WITH_HESIOD printf("WITH_HESIOD "); count = count + 12; if (count > 60) { printf("\n "); count = 0; } #endif #ifdef WITH_LDAP printf("WITH_LDAP "); count = count + 10; if (count > 60) { printf("\n "); count = 0; } #endif #ifdef WITH_SASL printf("WITH_SASL "); count = count + 10; if (count > 60) { printf("\n "); count = 0; } #endif #ifdef WITH_DMALLOC printf("WITH_DMALLOC "); count = count + 13; if (count > 60) { printf("\n "); count = 0; } #endif #ifdef LIBXML2_WORKAROUND printf("LIBXML2_WORKAROUND "); count = count + 19; if (count > 60) { printf("\n "); count = 0; } #endif #ifdef WITH_LIBTIRPC printf("WITH_LIBTIRPC "); count = count + 14; #endif printf("\n\n"); return; } typedef struct _code { char *c_name; int c_val; } CODE; CODE prioritynames[] = { { "alert", LOG_ALERT }, { "crit", LOG_CRIT }, { "debug", LOG_DEBUG }, { "emerg", LOG_EMERG }, { "err", LOG_ERR }, { "error", LOG_ERR }, /* DEPRECATED */ { "info", LOG_INFO }, { "notice", LOG_NOTICE }, { "panic", LOG_EMERG }, /* DEPRECATED */ { "warn", LOG_WARNING }, /* DEPRECATED */ { "warning", LOG_WARNING }, { NULL, -1 }, }; static int convert_log_priority(char *priority_name) { CODE *priority_mapping; for (priority_mapping = prioritynames; priority_mapping->c_name != NULL; priority_mapping++) { if (!strcasecmp(priority_name, priority_mapping->c_name)) return priority_mapping->c_val; } return -1; } static void remove_empty_args(char **argv, int *argc) { int next_to_last = *argc - 1; int i, j; for (i = j = 1; i < *argc; i++) { if (*argv[i]) { j++; continue; } while (i < *argc && argv[i] && !*argv[i]) i++; if (i == *argc) break; if (i == next_to_last) { if (*argv[i]) argv[j++] = argv[i]; break; } else { argv[j++] = argv[i]; argv[i--] = ""; } } *argc = j; } static void do_master_list_reset(struct master *master) { struct list_head *head, *p, *n; master_mutex_lock(); head = &master->mounts; n = head->next; while (n != head) { struct master_mapent *entry; p = n; n = p->next; entry = list_entry(p, struct master_mapent, list); if (!list_empty(&entry->list)) list_del(&entry->list); master_free_mapent_sources(entry, 1); master_free_mapent(entry); } master_mutex_unlock(); } static int do_master_read_master(struct master *master, int wait) { sigset_t signalset; /* Wait must be at least 1 second */ unsigned int retry_wait = 2; unsigned int elapsed = 0; int max_wait = wait; int ret = 0; time_t age; sigemptyset(&signalset); sigaddset(&signalset, SIGTERM); sigaddset(&signalset, SIGINT); sigaddset(&signalset, SIGHUP); sigprocmask(SIG_UNBLOCK, &signalset, NULL); while (1) { struct timespec t = { retry_wait, 0 }; do_master_list_reset(master); age = monotonic_time(NULL); if (master_read_master(master, age, 0)) { ret = 1; break; } if (nanosleep(&t, NULL) == -1) break; if (max_wait > 0) { elapsed += retry_wait; if (elapsed >= max_wait) { logmsg("problem reading master map, " "maximum wait exceeded"); break; } } } sigprocmask(SIG_BLOCK, &signalset, NULL); return ret; } int main(int argc, char *argv[]) { int res, opt, status; int logpri = -1; unsigned int flags; unsigned int logging; unsigned master_read; int master_wait; time_t timeout; time_t age = monotonic_time(NULL); struct rlimit rlim; const char *options = "+hp:t:vmdD:SfVrO:l:n:CFUM"; static const struct option long_options[] = { {"help", 0, 0, 'h'}, {"pid-file", 1, 0, 'p'}, {"timeout", 1, 0, 't'}, {"verbose", 0, 0, 'v'}, {"debug", 0, 0, 'd'}, {"define", 1, 0, 'D'}, {"systemd-service", 0, 0, 'S'}, {"foreground", 0, 0, 'f'}, {"random-multimount-selection", 0, 0, 'r'}, {"negative-timeout", 1, 0, 'n'}, {"dumpmaps", 0, 0, 'm'}, {"global-options", 1, 0, 'O'}, {"version", 0, 0, 'V'}, {"set-log-priority", 1, 0, 'l'}, {"dont-check-daemon", 0, 0, 'C'}, {"force", 0, 0, 'F'}, {"force-exit", 0, 0, 'U'}, {"master-wait", 1, 0, 'M'}, {0, 0, 0, 0} }; sigfillset(&block_sigs); /* allow for the dropping of core files */ sigdelset(&block_sigs, SIGABRT); sigdelset(&block_sigs, SIGBUS); sigdelset(&block_sigs, SIGSEGV); sigdelset(&block_sigs, SIGILL); sigdelset(&block_sigs, SIGFPE); sigdelset(&block_sigs, SIGTRAP); sigprocmask(SIG_BLOCK, &block_sigs, NULL); program = argv[0]; defaults_read_config(0); nfs_mount_uses_string_options = check_nfs_mount_version(&vers, &check); flags = defaults_get_browse_mode() ? DAEMON_FLAGS_GHOST : 0; flags |= DAEMON_FLAGS_CHECK_DAEMON; kpkt_len = get_kpkt_len(); master_wait = defaults_get_master_wait(); timeout = defaults_get_timeout(); logging = defaults_get_logging(); global_selection_options = 0; global_options = NULL; remove_empty_args(argv, &argc); opterr = 0; while ((opt = getopt_long(argc, argv, options, long_options, NULL)) != EOF) { switch (opt) { case 'h': usage(); exit(0); case 'p': pid_file = optarg; break; case 't': timeout = getnumopt(optarg, opt); break; case 'v': logging |= LOGOPT_VERBOSE; break; case 'd': logging |= LOGOPT_DEBUG; break; case 'D': macro_parse_globalvar(optarg); break; case 'S': flags |= DAEMON_FLAGS_SYSTEMD_SERVICE; break; case 'f': flags |= DAEMON_FLAGS_FOREGROUND; break; case 'V': show_build_info(); exit(0); case 'r': global_selection_options |= MOUNT_FLAG_RANDOM_SELECT; break; case 'n': global_negative_timeout = getnumopt(optarg, opt); break; case 'm': flags |= DAEMON_FLAGS_DUMP_MAPS; break; case 'M': master_wait = getnumopt(optarg, opt); break; case 'O': if (!(flags & DAEMON_FLAGS_HAVE_GLOBAL_OPTIONS)) { global_options = strdup(optarg); flags |= DAEMON_FLAGS_HAVE_GLOBAL_OPTIONS; break; } printf("%s: global options already specified.\n", program); break; case 'l': if (isalpha(*optarg)) { logpri = convert_log_priority(optarg); if (logpri < 0) { fprintf(stderr, "Invalid log priority:" " %s\n", optarg); exit(1); } } else if (isdigit(*optarg)) { logpri = getnumopt(optarg, opt); } else { fprintf(stderr, "non-alphanumeric character " "found in log priority. Aborting.\n"); exit(1); } break; case 'C': flags &= ~DAEMON_FLAGS_CHECK_DAEMON; break; case 'F': do_force_unlink = UNLINK_AND_CONT; break; case 'U': flags |= DAEMON_FLAGS_FOREGROUND; do_force_unlink = UNLINK_AND_EXIT; break; case '?': case ':': printf("%s: Ambiguous or unknown options\n", program); exit(1); } } if (logging & LOGOPT_VERBOSE) set_log_verbose(); if (logging & LOGOPT_DEBUG) set_log_debug(); if (geteuid() != 0) { fprintf(stderr, "%s: this program must be run by root.\n", program); exit(1); } /* Remove the options */ argv += optind; argc -= optind; if (logpri >= 0) { int exit_code = 0; int i; /* * The remaining argv elements are the paths for which * log priorities must be changed. */ for (i = 0; i < argc; i++) { if (set_log_priority(argv[i], logpri) < 0) exit_code = 1; } if (argc < 1) { fprintf(stderr, "--set-log-priority requires a path.\n"); exit_code = 1; } exit(exit_code); } /* Don't need the kernel module just to look at the configured maps */ if (!(flags & DAEMON_FLAGS_DUMP_MAPS) && (!query_kproto_ver() || get_kver_major() < 5)) { fprintf(stderr, "%s: test mount forbidden or " "incorrect kernel protocol version, " "kernel protocol version 5.00 or above required.\n", program); exit(1); } res = getrlimit(RLIMIT_NOFILE, &rlim); if (res == -1 || rlim.rlim_max <= MAX_OPEN_FILES) { rlim.rlim_cur = MAX_OPEN_FILES; rlim.rlim_max = MAX_OPEN_FILES; } res = setrlimit(RLIMIT_NOFILE, &rlim); if (res) printf("%s: can't increase open file limit - continuing", program); #if ENABLE_CORES rlim.rlim_cur = RLIM_INFINITY; rlim.rlim_max = RLIM_INFINITY; res = setrlimit(RLIMIT_CORE, &rlim); if (res) printf("%s: can't increase core file limit - continuing", program); #endif /* Get processor information for predefined escapes */ macro_init(); if (flags & DAEMON_FLAGS_DUMP_MAPS) { struct master_mapent *entry; struct list_head *head, *p; struct mapent_cache *nc; const char *type = NULL; const char *name = NULL; const char *master = NULL; if (argc > 0) { if (argc >= 2) { type = argv[0]; name = argv[1]; } if (argc == 3) master = argv[2]; } if (master) master_list = master_new(NULL, timeout, flags); else master_list = master_new(master, timeout, flags); if (!master_list) { printf("%s: can't create master map", program); macro_free_global_table(); exit(1); } log_to_stderr(); master_init_scan(); nc = cache_init_null_cache(master_list); if (!nc) { printf("%s: failed to init null map cache for %s", program, master_list->name); macro_free_global_table(); exit(1); } master_list->nc = nc; lookup_nss_read_master(master_list, 0); if (type) { const char *map = basename(name); if (!map) printf("%s: invalid map name %s\n", program, name); else dump_map(master_list, type, map); } else master_show_mounts(master_list); head = &master_list->mounts; p = head->next; while (p != head) { entry = list_entry(p, struct master_mapent, list); p = p->next; master_free_mapent_sources(entry, 1); master_free_mapent(entry); } master_kill(master_list); macro_free_global_table(); exit(0); } if (argc == 0) master_list = master_new(NULL, timeout, flags); else master_list = master_new(argv[0], timeout, flags); if (!master_list) { printf("%s: can't create master map %s", program, argv[0]); macro_free_global_table(); exit(1); } become_daemon(flags); if (pthread_attr_init(&th_attr)) { logerr("%s: failed to init thread attribute struct!", program); if (start_pipefd[1] != -1) { res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); } release_flag_file(); macro_free_global_table(); exit(1); } if (pthread_attr_init(&th_attr_detached)) { logerr("%s: failed to init thread attribute struct!", program); if (start_pipefd[1] != -1) { res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); } release_flag_file(); macro_free_global_table(); exit(1); } if (pthread_attr_setdetachstate( &th_attr_detached, PTHREAD_CREATE_DETACHED)) { logerr("%s: failed to set detached thread attribute!", program); if (start_pipefd[1] != -1) { res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); } release_flag_file(); macro_free_global_table(); exit(1); } #ifdef _POSIX_THREAD_ATTR_STACKSIZE if (pthread_attr_setstacksize( &th_attr_detached, detached_thread_stack_size)) { logerr("%s: failed to set stack size thread attribute!", program); if (start_pipefd[1] != -1) { res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); } release_flag_file(); macro_free_global_table(); exit(1); } #endif if (pthread_attr_getstacksize( &th_attr_detached, &detached_thread_stack_size)) { logerr("%s: failed to get detached thread stack size!", program); if (start_pipefd[1] != -1) { res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); } release_flag_file(); macro_free_global_table(); exit(1); } info(logging, "Starting automounter version %s, master map %s", version, master_list->name); info(logging, "using kernel protocol version %d.%02d", get_kver_major(), get_kver_minor()); status = pthread_key_create(&key_thread_stdenv_vars, key_thread_stdenv_vars_destroy); if (status) { logerr("%s: failed to create thread data key for std env vars!", program); master_kill(master_list); if (start_pipefd[1] != -1) { res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); } release_flag_file(); macro_free_global_table(); exit(1); } status = pthread_key_create(&key_thread_attempt_id, free); if (status) { logerr("%s: failed to create thread data key for attempt ID!", program); master_kill(master_list); if (start_pipefd[1] != -1) { res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); } release_flag_file(); macro_free_global_table(); exit(1); } init_ioctl_ctl(); if (!alarm_start_handler()) { logerr("%s: failed to create alarm handler thread!", program); master_kill(master_list); if (start_pipefd[1] != -1) { res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); } release_flag_file(); macro_free_global_table(); exit(1); } if (!st_start_handler()) { logerr("%s: failed to create FSM handler thread!", program); master_kill(master_list); if (start_pipefd[1] != -1) { res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); } release_flag_file(); macro_free_global_table(); exit(1); } #if defined(WITH_LDAP) && defined(LIBXML2_WORKAROUND) void *dh_xml2 = dlopen("libxml2.so", RTLD_NOW); if (!dh_xml2) dh_xml2 = dlopen("libxml2.so.2", RTLD_NOW); if (dh_xml2) xmlInitParser(); #endif #ifdef TIRPC_WORKAROUND void *dh_tirpc = dlopen("libtirpc.so", RTLD_NOW); if (!dh_tirpc) dh_tirpc = dlopen("libtirpc.so.1", RTLD_NOW); if (!dh_tirpc) dh_tirpc = dlopen("libtirpc.so.3", RTLD_NOW); #endif master_read = master_read_master(master_list, age, 0); if (!master_read) { /* * Read master map, waiting until it is available, unless * a signal is received, in which case exit returning an * error. */ if (!do_master_read_master(master_list, master_wait)) { logmsg("%s: warning: could not read at least one " "map source after waiting, continuing ...", program); /* * Failed to read master map, continue with what * we have anyway. */ do_master_list_reset(master_list); age = monotonic_time(NULL); master_read_master(master_list, age, 1); } } /* If the option to unlink all autofs mounts and exit has * been given remove logpri fifo files as all the mounts * will be detached leaving them stale. */ if (do_force_unlink & UNLINK_AND_EXIT) cleanup_stale_logpri_fifo_pipes(); else { /* * Mmm ... reset force unlink umount so we don't also do * this in future when we receive a HUP signal. */ do_force_unlink = 0; if (start_pipefd[1] != -1) { st_stat = 0; res = write(start_pipefd[1], pst_stat, sizeof(*pst_stat)); close(start_pipefd[1]); } #ifdef WITH_SYSTEMD if (flags & DAEMON_FLAGS_SYSTEMD_SERVICE) sd_notify(1, "READY=1"); #endif state_mach_thid = pthread_self(); statemachine(NULL); } master_kill(master_list); if (pid_file) { unlink(pid_file); pid_file = NULL; } defaults_conf_release(); closelog(); release_flag_file(); macro_free_global_table(); #ifdef TIRPC_WORKAROUND if (dh_tirpc) dlclose(dh_tirpc); #endif #if defined(WITH_LDAP) && defined( LIBXML2_WORKAROUND) if (dh_xml2) { xmlCleanupParser(); dlclose(dh_xml2); } #endif close_ioctl_ctl(); info(logging, "autofs stopped"); exit(0); }