diff options
Diffstat (limited to 'sys')
-rw-r--r-- | sys/conf/NOTES | 1 | ||||
-rw-r--r-- | sys/conf/files | 4 | ||||
-rw-r--r-- | sys/conf/options | 1 | ||||
-rw-r--r-- | sys/fs/autofs/autofs.c | 637 | ||||
-rw-r--r-- | sys/fs/autofs/autofs.h | 141 | ||||
-rw-r--r-- | sys/fs/autofs/autofs_ioctl.h | 89 | ||||
-rw-r--r-- | sys/fs/autofs/autofs_vfsops.c | 209 | ||||
-rw-r--r-- | sys/fs/autofs/autofs_vnops.c | 646 | ||||
-rw-r--r-- | sys/kern/vfs_mount.c | 4 | ||||
-rw-r--r-- | sys/libkern/strndup.c | 51 | ||||
-rw-r--r-- | sys/modules/Makefile | 1 | ||||
-rw-r--r-- | sys/modules/autofs/Makefile | 11 | ||||
-rw-r--r-- | sys/sys/libkern.h | 1 | ||||
-rw-r--r-- | sys/sys/mount.h | 6 |
14 files changed, 1800 insertions, 2 deletions
diff --git a/sys/conf/NOTES b/sys/conf/NOTES index 9002000..5fdc582 100644 --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -1017,6 +1017,7 @@ options FFS #Fast filesystem options NFSCLIENT #Network File System client # The rest are optional: +options AUTOFS #Automounter filesystem options CD9660 #ISO 9660 filesystem options FDESCFS #File descriptor filesystem options FUSE #FUSE support module diff --git a/sys/conf/files b/sys/conf/files index 6eed819..f884e9b 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -2589,6 +2589,9 @@ dev/xen/xenpci/xenpci.c optional xenpci dev/xen/timer/timer.c optional xen | xenhvm dev/xl/if_xl.c optional xl pci dev/xl/xlphy.c optional xl pci +fs/autofs/autofs.c optional autofs +fs/autofs/autofs_vfsops.c optional autofs +fs/autofs/autofs_vnops.c optional autofs fs/deadfs/dead_vnops.c standard fs/devfs/devfs_devs.c standard fs/devfs/devfs_dir.c standard @@ -3125,6 +3128,7 @@ libkern/strcmp.c standard libkern/strcpy.c standard libkern/strcspn.c standard libkern/strdup.c standard +libkern/strndup.c standard libkern/strlcat.c standard libkern/strlcpy.c standard libkern/strlen.c standard diff --git a/sys/conf/options b/sys/conf/options index 33ce752..a613612 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -218,6 +218,7 @@ INCLUDE_CONFIG_FILE opt_config.h # time, since the corresponding lkms cannot work if there are any static # dependencies. Unusability is enforced by hiding the defines for the # options in a never-included header. +AUTOFS opt_dontuse.h CD9660 opt_dontuse.h EXT2FS opt_dontuse.h FDESCFS opt_dontuse.h diff --git a/sys/fs/autofs/autofs.c b/sys/fs/autofs/autofs.c new file mode 100644 index 0000000..9715eea --- /dev/null +++ b/sys/fs/autofs/autofs.c @@ -0,0 +1,637 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * 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$ + */ +/*- + * Copyright (c) 1989, 1991, 1993, 1995 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Rick Macklem at The University of Guelph. + * + * 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. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/buf.h> +#include <sys/conf.h> +#include <sys/dirent.h> +#include <sys/ioccom.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/mount.h> +#include <sys/refcount.h> +#include <sys/sx.h> +#include <sys/sysctl.h> +#include <sys/syscallsubr.h> +#include <sys/vnode.h> +#include <machine/atomic.h> +#include <vm/uma.h> + +#include "autofs.h" +#include "autofs_ioctl.h" + +MALLOC_DEFINE(M_AUTOFS, "autofs", "Automounter filesystem"); + +uma_zone_t autofs_request_zone; +uma_zone_t autofs_node_zone; + +static int autofs_open(struct cdev *dev, int flags, int fmt, + struct thread *td); +static int autofs_close(struct cdev *dev, int flag, int fmt, + struct thread *td); +static int autofs_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, + int mode, struct thread *td); + +static struct cdevsw autofs_cdevsw = { + .d_version = D_VERSION, + .d_open = autofs_open, + .d_close = autofs_close, + .d_ioctl = autofs_ioctl, + .d_name = "autofs", +}; + +/* + * List of signals that can interrupt an autofs trigger. Might be a good + * idea to keep it synchronised with list in sys/fs/nfs/nfs_commonkrpc.c. + */ +int autofs_sig_set[] = { + SIGINT, + SIGTERM, + SIGHUP, + SIGKILL, + SIGQUIT +}; + +struct autofs_softc *sc; + +SYSCTL_NODE(_vfs, OID_AUTO, autofs, CTLFLAG_RD, 0, "Automounter filesystem"); +int autofs_debug = 1; +TUNABLE_INT("vfs.autofs.debug", &autofs_debug); +SYSCTL_INT(_vfs_autofs, OID_AUTO, debug, CTLFLAG_RWTUN, + &autofs_debug, 1, "Enable debug messages"); +int autofs_mount_on_stat = 0; +TUNABLE_INT("vfs.autofs.mount_on_stat", &autofs_mount_on_stat); +SYSCTL_INT(_vfs_autofs, OID_AUTO, mount_on_stat, CTLFLAG_RWTUN, + &autofs_mount_on_stat, 0, "Trigger mount on stat(2) on mountpoint"); +int autofs_timeout = 30; +TUNABLE_INT("vfs.autofs.timeout", &autofs_timeout); +SYSCTL_INT(_vfs_autofs, OID_AUTO, timeout, CTLFLAG_RWTUN, + &autofs_timeout, 30, "Number of seconds to wait for automountd(8)"); +int autofs_cache = 600; +TUNABLE_INT("vfs.autofs.cache", &autofs_cache); +SYSCTL_INT(_vfs_autofs, OID_AUTO, cache, CTLFLAG_RWTUN, + &autofs_cache, 600, "Number of seconds to wait before reinvoking " + "automountd(8) for any given file or directory"); +int autofs_retry_attempts = 3; +TUNABLE_INT("vfs.autofs.retry_attempts", &autofs_retry_attempts); +SYSCTL_INT(_vfs_autofs, OID_AUTO, retry_attempts, CTLFLAG_RWTUN, + &autofs_retry_attempts, 3, "Number of attempts before failing mount"); +int autofs_retry_delay = 1; +TUNABLE_INT("vfs.autofs.retry_delay", &autofs_retry_delay); +SYSCTL_INT(_vfs_autofs, OID_AUTO, retry_delay, CTLFLAG_RWTUN, + &autofs_retry_delay, 1, "Number of seconds before retrying"); +int autofs_interruptible = 1; +TUNABLE_INT("vfs.autofs.interruptible", &autofs_interruptible); +SYSCTL_INT(_vfs_autofs, OID_AUTO, interruptible, CTLFLAG_RWTUN, + &autofs_interruptible, 1, "Allow requests to be interrupted by signal"); + +int +autofs_init(struct vfsconf *vfsp) +{ + int error; + + sc = malloc(sizeof(*sc), M_AUTOFS, M_WAITOK | M_ZERO); + + autofs_request_zone = uma_zcreate("autofs_request", + sizeof(struct autofs_request), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, 0); + autofs_node_zone = uma_zcreate("autofs_node", + sizeof(struct autofs_node), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, 0); + + TAILQ_INIT(&sc->sc_requests); + cv_init(&sc->sc_cv, "autofscv"); + sx_init(&sc->sc_lock, "autofslk"); + + error = make_dev_p(MAKEDEV_CHECKNAME, &sc->sc_cdev, &autofs_cdevsw, + NULL, UID_ROOT, GID_WHEEL, 0600, "autofs"); + if (error != 0) { + AUTOFS_WARN("failed to create device node, error %d", error); + free(sc, M_AUTOFS); + return (error); + } + sc->sc_cdev->si_drv1 = sc; + + return (0); +} + +int +autofs_uninit(struct vfsconf *vfsp) +{ + + sx_xlock(&sc->sc_lock); + if (sc->sc_dev_opened) { + sx_xunlock(&sc->sc_lock); + return (EBUSY); + } + if (sc->sc_cdev != NULL) + destroy_dev(sc->sc_cdev); + + uma_zdestroy(autofs_request_zone); + uma_zdestroy(autofs_node_zone); + + sx_xunlock(&sc->sc_lock); + /* + * XXX: Race with open? + */ + free(sc, M_AUTOFS); + + return (0); +} + +bool +autofs_ignore_thread(const struct thread *td) +{ + struct proc *p; + + p = td->td_proc; + + if (sc->sc_dev_opened == false) + return (false); + + PROC_LOCK(p); + if (p->p_session->s_sid == sc->sc_dev_sid) { + PROC_UNLOCK(p); + return (true); + } + PROC_UNLOCK(p); + + return (false); +} + +static char * +autofs_path(struct autofs_node *anp) +{ + struct autofs_mount *amp; + char *path, *tmp; + + amp = anp->an_mount; + + path = strdup("", M_AUTOFS); + for (; anp->an_parent != NULL; anp = anp->an_parent) { + tmp = malloc(strlen(anp->an_name) + strlen(path) + 2, + M_AUTOFS, M_WAITOK); + strcpy(tmp, anp->an_name); + strcat(tmp, "/"); + strcat(tmp, path); + free(path, M_AUTOFS); + path = tmp; + } + + tmp = malloc(strlen(amp->am_mountpoint) + strlen(path) + 2, + M_AUTOFS, M_WAITOK); + strcpy(tmp, amp->am_mountpoint); + strcat(tmp, "/"); + strcat(tmp, path); + free(path, M_AUTOFS); + path = tmp; + + return (path); +} + +static void +autofs_callout(void *context) +{ + struct autofs_request *ar; + struct autofs_softc *sc; + + ar = context; + sc = ar->ar_mount->am_softc; + + sx_xlock(&sc->sc_lock); + AUTOFS_WARN("request %d for %s timed out after %d seconds", + ar->ar_id, ar->ar_path, autofs_timeout); + /* + * XXX: EIO perhaps? + */ + ar->ar_error = ETIMEDOUT; + ar->ar_done = true; + ar->ar_in_progress = false; + cv_broadcast(&sc->sc_cv); + sx_xunlock(&sc->sc_lock); +} + +bool +autofs_cached(struct autofs_node *anp, const char *component, int componentlen) +{ + int error; + struct autofs_mount *amp; + + amp = anp->an_mount; + + AUTOFS_ASSERT_UNLOCKED(amp); + + /* + * For top-level nodes we need to request automountd(8) + * assistance even if the node is marked as cached, + * but the requested subdirectory does not exist. This + * is necessary for wildcard indirect map keys to work. + */ + if (anp->an_parent == NULL && componentlen != 0) { + AUTOFS_LOCK(amp); + error = autofs_node_find(anp, component, componentlen, NULL); + AUTOFS_UNLOCK(amp); + if (error != 0) + return (false); + } + + return (anp->an_cached); +} + +static void +autofs_cache_callout(void *context) +{ + struct autofs_node *anp; + + anp = context; + anp->an_cached = false; +} + +/* + * The set/restore sigmask functions are used to (temporarily) overwrite + * the thread td_sigmask during triggering. + */ +static void +autofs_set_sigmask(sigset_t *oldset) +{ + sigset_t newset; + int i; + + SIGFILLSET(newset); + /* Remove the autofs set of signals from newset */ + PROC_LOCK(curproc); + mtx_lock(&curproc->p_sigacts->ps_mtx); + for (i = 0 ; i < sizeof(autofs_sig_set)/sizeof(int) ; i++) { + /* + * But make sure we leave the ones already masked + * by the process, i.e. remove the signal from the + * temporary signalmask only if it wasn't already + * in p_sigmask. + */ + if (!SIGISMEMBER(curthread->td_sigmask, autofs_sig_set[i]) && + !SIGISMEMBER(curproc->p_sigacts->ps_sigignore, + autofs_sig_set[i])) { + SIGDELSET(newset, autofs_sig_set[i]); + } + } + mtx_unlock(&curproc->p_sigacts->ps_mtx); + kern_sigprocmask(curthread, SIG_SETMASK, &newset, oldset, + SIGPROCMASK_PROC_LOCKED); + PROC_UNLOCK(curproc); +} + +static void +autofs_restore_sigmask(sigset_t *set) +{ + + kern_sigprocmask(curthread, SIG_SETMASK, set, NULL, 0); +} + +static int +autofs_trigger_one(struct autofs_node *anp, + const char *component, int componentlen) +{ + sigset_t oldset; + struct autofs_mount *amp; + struct autofs_softc *sc; + struct autofs_node *firstanp; + struct autofs_request *ar; + char *key, *path; + int error = 0, request_error, last; + + amp = VFSTOAUTOFS(anp->an_vnode->v_mount); + sc = amp->am_softc; + + sx_assert(&sc->sc_lock, SA_XLOCKED); + + if (anp->an_parent == NULL) { + key = strndup(component, componentlen, M_AUTOFS); + } else { + for (firstanp = anp; firstanp->an_parent->an_parent != NULL; + firstanp = firstanp->an_parent) + continue; + key = strdup(firstanp->an_name, M_AUTOFS); + } + + path = autofs_path(anp); + + TAILQ_FOREACH(ar, &sc->sc_requests, ar_next) { + if (strcmp(ar->ar_path, path) != 0) + continue; + if (strcmp(ar->ar_key, key) != 0) + continue; + + KASSERT(strcmp(ar->ar_from, amp->am_from) == 0, + ("from changed; %s != %s", ar->ar_from, amp->am_from)); + KASSERT(strcmp(ar->ar_prefix, amp->am_prefix) == 0, + ("prefix changed; %s != %s", + ar->ar_prefix, amp->am_prefix)); + KASSERT(strcmp(ar->ar_options, amp->am_options) == 0, + ("options changed; %s != %s", + ar->ar_options, amp->am_options)); + + break; + } + + if (ar != NULL) { + refcount_acquire(&ar->ar_refcount); + } else { + ar = uma_zalloc(autofs_request_zone, M_WAITOK | M_ZERO); + ar->ar_mount = amp; + + ar->ar_id = atomic_fetchadd_int(&sc->sc_last_request_id, 1); + strlcpy(ar->ar_from, amp->am_from, sizeof(ar->ar_from)); + strlcpy(ar->ar_path, path, sizeof(ar->ar_path)); + strlcpy(ar->ar_prefix, amp->am_prefix, sizeof(ar->ar_prefix)); + strlcpy(ar->ar_key, key, sizeof(ar->ar_key)); + strlcpy(ar->ar_options, + amp->am_options, sizeof(ar->ar_options)); + + callout_init(&ar->ar_callout, 1); + callout_reset(&ar->ar_callout, + autofs_timeout * hz, autofs_callout, ar); + refcount_init(&ar->ar_refcount, 1); + TAILQ_INSERT_TAIL(&sc->sc_requests, ar, ar_next); + } + + cv_broadcast(&sc->sc_cv); + while (ar->ar_done == false) { + if (autofs_interruptible != 0) { + autofs_set_sigmask(&oldset); + error = cv_wait_sig(&sc->sc_cv, &sc->sc_lock); + autofs_restore_sigmask(&oldset); + if (error != 0) { + /* + * XXX: For some reson this returns -1 + * instead of EINTR, wtf?! + */ + error = EINTR; + AUTOFS_WARN("cv_wait_sig for %s failed " + "with error %d", ar->ar_path, error); + break; + } + } else { + cv_wait(&sc->sc_cv, &sc->sc_lock); + } + } + + request_error = ar->ar_error; + if (request_error != 0) { + AUTOFS_WARN("request for %s completed with error %d", + ar->ar_path, request_error); + } + + last = refcount_release(&ar->ar_refcount); + if (last) { + TAILQ_REMOVE(&sc->sc_requests, ar, ar_next); + /* + * XXX: Is it safe? + */ + sx_xunlock(&sc->sc_lock); + callout_drain(&ar->ar_callout); + sx_xlock(&sc->sc_lock); + uma_zfree(autofs_request_zone, ar); + } + + /* + * Note that we do not do negative caching on purpose. This + * way the user can retry access at any time, e.g. after fixing + * the failure reason, without waiting for cache timer to expire. + */ + if (error == 0 && request_error == 0 && autofs_cache > 0) { + anp->an_cached = true; + callout_reset(&anp->an_callout, autofs_cache * hz, + autofs_cache_callout, anp); + } + + free(key, M_AUTOFS); + free(path, M_AUTOFS); + + if (error != 0) + return (error); + return (request_error); +} + +/* + * Send request to automountd(8) and wait for completion. + */ +int +autofs_trigger(struct autofs_node *anp, + const char *component, int componentlen) +{ + int error; + + for (;;) { + error = autofs_trigger_one(anp, component, componentlen); + if (error == 0) { + anp->an_retries = 0; + return (0); + } + if (error == EINTR) { + AUTOFS_DEBUG("trigger interrupted by signal, " + "not retrying"); + anp->an_retries = 0; + return (error); + } + anp->an_retries++; + if (anp->an_retries >= autofs_retry_attempts) { + AUTOFS_DEBUG("trigger failed %d times; returning " + "error %d", anp->an_retries, error); + anp->an_retries = 0; + return (error); + + } + AUTOFS_DEBUG("trigger failed with error %d; will retry in " + "%d seconds, %d attempts left", error, autofs_retry_delay, + autofs_retry_attempts - anp->an_retries); + sx_xunlock(&sc->sc_lock); + pause("autofs_retry", autofs_retry_delay * hz); + sx_xlock(&sc->sc_lock); + } +} + +static int +autofs_ioctl_request(struct autofs_softc *sc, struct autofs_daemon_request *adr) +{ + struct autofs_request *ar; + int error; + + sx_xlock(&sc->sc_lock); + for (;;) { + TAILQ_FOREACH(ar, &sc->sc_requests, ar_next) { + if (ar->ar_done) + continue; + if (ar->ar_in_progress) + continue; + + break; + } + + if (ar != NULL) + break; + + error = cv_wait_sig(&sc->sc_cv, &sc->sc_lock); + if (error != 0) { + /* + * XXX: For some reson this returns -1 instead + * of EINTR, wtf?! + */ + error = EINTR; + sx_xunlock(&sc->sc_lock); + AUTOFS_DEBUG("failed with error %d", error); + return (error); + } + } + + ar->ar_in_progress = true; + sx_xunlock(&sc->sc_lock); + + adr->adr_id = ar->ar_id; + strlcpy(adr->adr_from, ar->ar_from, sizeof(adr->adr_from)); + strlcpy(adr->adr_path, ar->ar_path, sizeof(adr->adr_path)); + strlcpy(adr->adr_prefix, ar->ar_prefix, sizeof(adr->adr_prefix)); + strlcpy(adr->adr_key, ar->ar_key, sizeof(adr->adr_key)); + strlcpy(adr->adr_options, ar->ar_options, sizeof(adr->adr_options)); + + PROC_LOCK(curproc); + sc->sc_dev_sid = curproc->p_session->s_sid; + PROC_UNLOCK(curproc); + + return (0); +} + +static int +autofs_ioctl_done(struct autofs_softc *sc, struct autofs_daemon_done *add) +{ + struct autofs_request *ar; + + sx_xlock(&sc->sc_lock); + TAILQ_FOREACH(ar, &sc->sc_requests, ar_next) { + if (ar->ar_id == add->add_id) + break; + } + + if (ar == NULL) { + sx_xunlock(&sc->sc_lock); + AUTOFS_DEBUG("id %d not found", add->add_id); + return (ESRCH); + } + + ar->ar_error = add->add_error; + ar->ar_done = true; + ar->ar_in_progress = false; + cv_broadcast(&sc->sc_cv); + + sx_xunlock(&sc->sc_lock); + + return (0); +} + +static int +autofs_open(struct cdev *dev, int flags, int fmt, struct thread *td) +{ + + sx_xlock(&sc->sc_lock); + if (sc->sc_dev_opened) { + sx_xunlock(&sc->sc_lock); + return (EBUSY); + } + + sc->sc_dev_opened = true; + sx_xunlock(&sc->sc_lock); + + return (0); +} + +static int +autofs_close(struct cdev *dev, int flag, int fmt, struct thread *td) +{ + + sx_xlock(&sc->sc_lock); + KASSERT(sc->sc_dev_opened, ("not opened?")); + sc->sc_dev_opened = false; + sx_xunlock(&sc->sc_lock); + + return (0); +} + +static int +autofs_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + + KASSERT(sc->sc_dev_opened, ("not opened?")); + + switch (cmd) { + case AUTOFSREQUEST: + return (autofs_ioctl_request(sc, + (struct autofs_daemon_request *)arg)); + case AUTOFSDONE: + return (autofs_ioctl_done(sc, + (struct autofs_daemon_done *)arg)); + default: + AUTOFS_DEBUG("invalid cmd %lx", cmd); + return (EINVAL); + } +} diff --git a/sys/fs/autofs/autofs.h b/sys/fs/autofs/autofs.h new file mode 100644 index 0000000..ec1ca66 --- /dev/null +++ b/sys/fs/autofs/autofs.h @@ -0,0 +1,141 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * 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 AUTOFS_H +#define AUTOFS_H + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#define VFSTOAUTOFS(mp) ((struct autofs_mount *)((mp)->mnt_data)) + +MALLOC_DECLARE(M_AUTOFS); + +extern uma_zone_t autofs_request_zone; +extern uma_zone_t autofs_node_zone; + +extern int autofs_debug; +extern int autofs_mount_on_stat; + +#define AUTOFS_DEBUG(X, ...) \ + if (autofs_debug > 1) { \ + printf("%s: " X "\n", __func__, ## __VA_ARGS__);\ + } while (0) + +#define AUTOFS_WARN(X, ...) \ + if (autofs_debug > 0) { \ + printf("WARNING: %s: " X "\n", \ + __func__, ## __VA_ARGS__); \ + } while (0) + +#define AUTOFS_LOCK(X) sx_xlock(&X->am_lock) +#define AUTOFS_UNLOCK(X) sx_xunlock(&X->am_lock) +#define AUTOFS_ASSERT_LOCKED(X) sx_assert(&X->am_lock, SA_XLOCKED) +#define AUTOFS_ASSERT_UNLOCKED(X) sx_assert(&X->am_lock, SA_UNLOCKED) + +struct autofs_node { + TAILQ_ENTRY(autofs_node) an_next; + char *an_name; + int an_fileno; + struct autofs_node *an_parent; + TAILQ_HEAD(, autofs_node) an_children; + struct autofs_mount *an_mount; + struct vnode *an_vnode; + struct sx an_vnode_lock; + bool an_cached; + struct callout an_callout; + int an_retries; + struct timespec an_ctime; +}; + +struct autofs_mount { + TAILQ_ENTRY(autofs_mount) am_next; + struct autofs_softc *am_softc; + struct autofs_node *am_root; + struct mount *am_mp; + struct sx am_lock; + char am_from[MAXPATHLEN]; + char am_mountpoint[MAXPATHLEN]; + char am_options[MAXPATHLEN]; + char am_prefix[MAXPATHLEN]; + int am_last_fileno; +}; + +struct autofs_request { + TAILQ_ENTRY(autofs_request) ar_next; + struct autofs_mount *ar_mount; + int ar_id; + bool ar_done; + int ar_error; + bool ar_in_progress; + char ar_from[MAXPATHLEN]; + char ar_path[MAXPATHLEN]; + char ar_prefix[MAXPATHLEN]; + char ar_key[MAXPATHLEN]; + char ar_options[MAXPATHLEN]; + struct callout ar_callout; + volatile u_int ar_refcount; +}; + +struct autofs_softc { + device_t sc_dev; + struct cdev *sc_cdev; + struct cv sc_cv; + struct sx sc_lock; + TAILQ_HEAD(, autofs_request) sc_requests; + bool sc_dev_opened; + pid_t sc_dev_sid; + int sc_last_request_id; +}; + +/* + * Limits and constants + */ +#define AUTOFS_NAMELEN 24 +#define AUTOFS_FSNAMELEN 16 /* equal to MFSNAMELEN */ +#define AUTOFS_DELEN (8 + AUTOFS_NAMELEN) + +int autofs_init(struct vfsconf *vfsp); +int autofs_uninit(struct vfsconf *vfsp); +int autofs_trigger(struct autofs_node *anp, const char *component, + int componentlen); +bool autofs_cached(struct autofs_node *anp, const char *component, + int componentlen); +bool autofs_ignore_thread(const struct thread *td); +int autofs_node_new(struct autofs_node *parent, struct autofs_mount *amp, + const char *name, int namelen, struct autofs_node **anpp); +int autofs_node_find(struct autofs_node *parent, + const char *name, int namelen, struct autofs_node **anpp); +void autofs_node_delete(struct autofs_node *anp); +int autofs_node_vn(struct autofs_node *anp, struct mount *mp, + struct vnode **vpp); + +#endif /* !AUTOFS_H */ diff --git a/sys/fs/autofs/autofs_ioctl.h b/sys/fs/autofs/autofs_ioctl.h new file mode 100644 index 0000000..8d03ef7 --- /dev/null +++ b/sys/fs/autofs/autofs_ioctl.h @@ -0,0 +1,89 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * All rights reserved. + * + * 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 AUTOFS_IOCTL_H +#define AUTOFS_IOCTL_H + +#define AUTOFS_PATH "/dev/autofs" + +struct autofs_daemon_request { + /* + * Request identifier. + */ + int adr_id; + + /* + * The "from" field, containing map name. For example, + * when accessing '/net/192.168.1.3/tank/vm/', that would + * be '-hosts'. + */ + char adr_from[MAXPATHLEN]; + + /* + * Full path to the node being looked up; for requests that result + * in actual mount it is the full mount path. + */ + char adr_path[MAXPATHLEN]; + + /* + * Prefix, which is basically the mountpoint from auto_master(5). + * In example above that would be "/net"; for direct maps it is "/". + */ + char adr_prefix[MAXPATHLEN]; + + /* + * Map key, also used as command argument for dynamic maps; in example + * above that would be '192.168.1.3'. + */ + char adr_key[MAXPATHLEN]; + + /* + * Mount options from auto_master(5). + */ + char adr_options[MAXPATHLEN]; +}; + +struct autofs_daemon_done { + /* + * Identifier, copied from adr_id. + */ + int add_id; + + /* + * Error number, possibly returned to userland. + */ + int add_error; +}; + +#define AUTOFSREQUEST _IOR('I', 0x01, struct autofs_daemon_request) +#define AUTOFSDONE _IOW('I', 0x02, struct autofs_daemon_done) + +#endif /* !AUTOFS_IOCTL_H */ diff --git a/sys/fs/autofs/autofs_vfsops.c b/sys/fs/autofs/autofs_vfsops.c new file mode 100644 index 0000000..0c1f36b --- /dev/null +++ b/sys/fs/autofs/autofs_vfsops.c @@ -0,0 +1,209 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * 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$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/condvar.h> +#include <sys/ioccom.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/mount.h> +#include <sys/sx.h> +#include <sys/vnode.h> + +#include "autofs.h" + +static const char *autofs_opts[] = { + "from", "master_options", "master_prefix", NULL +}; + +extern struct autofs_softc *sc; + +static int +autofs_mount(struct mount *mp) +{ + struct autofs_mount *amp; + char *from, *fspath, *options, *prefix; + int error; + + if (vfs_filteropt(mp->mnt_optnew, autofs_opts)) + return (EINVAL); + + if (mp->mnt_flag & MNT_UPDATE) + return (0); + + if (vfs_getopt(mp->mnt_optnew, "from", (void **)&from, NULL)) + return (EINVAL); + + if (vfs_getopt(mp->mnt_optnew, "fspath", (void **)&fspath, NULL)) + return (EINVAL); + + if (vfs_getopt(mp->mnt_optnew, "master_options", (void **)&options, NULL)) + return (EINVAL); + + if (vfs_getopt(mp->mnt_optnew, "master_prefix", (void **)&prefix, NULL)) + return (EINVAL); + + amp = malloc(sizeof(*amp), M_AUTOFS, M_WAITOK | M_ZERO); + mp->mnt_data = amp; + amp->am_mp = mp; + amp->am_softc = sc; + strlcpy(amp->am_from, from, sizeof(amp->am_from)); + strlcpy(amp->am_mountpoint, fspath, sizeof(amp->am_mountpoint)); + strlcpy(amp->am_options, options, sizeof(amp->am_options)); + strlcpy(amp->am_prefix, prefix, sizeof(amp->am_prefix)); + sx_init(&->am_lock, "autofslk"); + amp->am_last_fileno = 1; + + vfs_getnewfsid(mp); + + AUTOFS_LOCK(amp); + error = autofs_node_new(NULL, amp, ".", -1, &->am_root); + if (error != 0) { + AUTOFS_UNLOCK(amp); + free(amp, M_AUTOFS); + return (error); + } + AUTOFS_UNLOCK(amp); + + vfs_mountedfrom(mp, from); + + return (0); +} + +static int +autofs_unmount(struct mount *mp, int mntflags) +{ + struct autofs_mount *amp; + struct autofs_node *anp; + struct autofs_request *ar; + int error, flags; + bool found; + + amp = VFSTOAUTOFS(mp); + + flags = 0; + if (mntflags & MNT_FORCE) + flags |= FORCECLOSE; + error = vflush(mp, 0, flags, curthread); + if (error != 0) { + AUTOFS_WARN("vflush failed with error %d", error); + return (error); + } + + /* + * All vnodes are gone, and new one will not appear - so, + * no new triggerings. We can iterate over outstanding + * autofs_requests and terminate them. + */ + for (;;) { + found = false; + sx_xlock(&sc->sc_lock); + TAILQ_FOREACH(ar, &sc->sc_requests, ar_next) { + if (ar->ar_mount != amp) + continue; + ar->ar_error = ENXIO; + ar->ar_done = true; + ar->ar_in_progress = false; + found = true; + } + sx_xunlock(&sc->sc_lock); + if (found == false) + break; + + cv_broadcast(&sc->sc_cv); + pause("autofs_umount", 1); + } + + AUTOFS_LOCK(amp); + + /* + * Not terribly efficient, but at least not recursive. + */ + while (!TAILQ_EMPTY(&->am_root->an_children)) { + anp = TAILQ_FIRST(&->am_root->an_children); + while (!TAILQ_EMPTY(&anp->an_children)) + anp = TAILQ_FIRST(&anp->an_children); + autofs_node_delete(anp); + } + autofs_node_delete(amp->am_root); + + mp->mnt_data = NULL; + AUTOFS_UNLOCK(amp); + + sx_destroy(&->am_lock); + + free(amp, M_AUTOFS); + + return (0); +} + +static int +autofs_root(struct mount *mp, int flags, struct vnode **vpp) +{ + struct autofs_mount *amp; + int error; + + amp = VFSTOAUTOFS(mp); + + error = autofs_node_vn(amp->am_root, mp, vpp); + + return (error); +} + +static int +autofs_statfs(struct mount *mp, struct statfs *sbp) +{ + + sbp->f_bsize = 512; + sbp->f_iosize = 0; + sbp->f_blocks = 0; + sbp->f_bfree = 0; + sbp->f_bavail = 0; + sbp->f_files = 0; + sbp->f_ffree = 0; + + return (0); +} + +static struct vfsops autofs_vfsops = { + .vfs_fhtovp = NULL, /* XXX */ + .vfs_mount = autofs_mount, + .vfs_unmount = autofs_unmount, + .vfs_root = autofs_root, + .vfs_statfs = autofs_statfs, + .vfs_init = autofs_init, + .vfs_uninit = autofs_uninit, +}; + +VFS_SET(autofs_vfsops, autofs, VFCF_SYNTHETIC | VFCF_NETWORK); +MODULE_VERSION(autofs, 1); diff --git a/sys/fs/autofs/autofs_vnops.c b/sys/fs/autofs/autofs_vnops.c new file mode 100644 index 0000000..7623145 --- /dev/null +++ b/sys/fs/autofs/autofs_vnops.c @@ -0,0 +1,646 @@ +/*- + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * 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$ + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/condvar.h> +#include <sys/dirent.h> +#include <sys/fcntl.h> +#include <sys/lock.h> +#include <sys/mount.h> +#include <sys/mutex.h> +#include <sys/namei.h> +#include <sys/signalvar.h> +#include <sys/systm.h> +#include <sys/vnode.h> +#include <machine/atomic.h> +#include <vm/uma.h> + +#include "autofs.h" + +static int autofs_trigger_vn(struct vnode *vp, const char *path, + int pathlen, struct vnode **newvp); + +static int +autofs_access(struct vop_access_args *ap) +{ + + /* + * Nothing to do here; the only kind of access control + * needed is in autofs_mkdir(). + */ + + return (0); +} + +static int +autofs_getattr(struct vop_getattr_args *ap) +{ + struct vnode *vp, *newvp; + struct autofs_node *anp; + struct mount *mp; + struct vattr *vap; + int error; + + vp = ap->a_vp; + anp = vp->v_data; + mp = vp->v_mount; + vap = ap->a_vap; + + KASSERT(ap->a_vp->v_type == VDIR, ("!VDIR")); + + /* + * The reason we must do this is that some tree-walking software, + * namely fts(3), assumes that stat(".") results will not change + * between chdir("subdir") and chdir(".."), and fails with ENOENT + * otherwise. + */ + if (autofs_mount_on_stat && autofs_cached(anp, NULL, 0) == false && + autofs_ignore_thread(curthread) == false) { + error = autofs_trigger_vn(vp, "", 0, &newvp); + if (error != 0) + return (error); + + if (newvp != NULL) { + error = VOP_GETATTR(newvp, ap->a_vap, + ap->a_cred); + vput(newvp); + return (error); + } + } + + vap->va_type = VDIR; + vap->va_mode = 0755; + vap->va_nlink = 3; /* XXX */ + vap->va_uid = 0; + vap->va_gid = 0; + vap->va_rdev = NODEV; + vap->va_fsid = mp->mnt_stat.f_fsid.val[0]; + vap->va_fileid = anp->an_fileno; + vap->va_size = 512; /* XXX */ + vap->va_blocksize = 512; + vap->va_mtime = anp->an_ctime; + vap->va_atime = anp->an_ctime; + vap->va_ctime = anp->an_ctime; + vap->va_birthtime = anp->an_ctime; + vap->va_gen = 0; + vap->va_flags = 0; + vap->va_rdev = 0; + vap->va_bytes = 512; /* XXX */ + vap->va_filerev = 0; + vap->va_spare = 0; + + return (0); +} + +/* + * Unlock the vnode, request automountd(8) action, and then lock it back. + * If anything got mounted on top of the vnode, return the new filesystem's + * root vnode in 'newvp', locked. + */ +static int +autofs_trigger_vn(struct vnode *vp, const char *path, int pathlen, + struct vnode **newvp) +{ + struct autofs_node *anp; + struct autofs_mount *amp; + struct autofs_softc *sc; + int error, lock_flags; + + anp = vp->v_data; + amp = VFSTOAUTOFS(vp->v_mount); + sc = amp->am_softc; + + /* + * Release the vnode lock, so that other operations, in partcular + * mounting a filesystem on top of it, can proceed. Increase use + * count, to prevent the vnode from being deallocated and to prevent + * filesystem from being unmounted. + */ + lock_flags = VOP_ISLOCKED(vp); + vref(vp); + VOP_UNLOCK(vp, 0); + + sx_xlock(&sc->sc_lock); + + /* + * XXX: Workaround for mounting the same thing multiple times; revisit. + */ + if (vp->v_mountedhere != NULL) { + error = 0; + goto mounted; + } + + error = autofs_trigger(anp, path, pathlen); +mounted: + sx_xunlock(&sc->sc_lock); + vn_lock(vp, lock_flags | LK_RETRY); + vunref(vp); + if ((vp->v_iflag & VI_DOOMED) != 0) { + AUTOFS_DEBUG("VI_DOOMED"); + return (ENOENT); + } + + if (error != 0) + return (error); + + if (vp->v_mountedhere == NULL) { + *newvp = NULL; + return (0); + } else { + /* + * If the operation that succeeded was mount, then mark + * the node as non-cached. Otherwise, if someone unmounts + * the filesystem before the cache times out, we will fail + * to trigger. + */ + anp->an_cached = false; + } + + error = VFS_ROOT(vp->v_mountedhere, lock_flags, newvp); + if (error != 0) { + AUTOFS_WARN("VFS_ROOT() failed with error %d", error); + return (error); + } + + return (0); +} + +static int +autofs_lookup(struct vop_lookup_args *ap) +{ + struct vnode *dvp, *newvp, **vpp; + struct mount *mp; + struct autofs_mount *amp; + struct autofs_node *anp, *child; + struct componentname *cnp; + int error, lock_flags; + + dvp = ap->a_dvp; + vpp = ap->a_vpp; + mp = dvp->v_mount; + amp = VFSTOAUTOFS(mp); + anp = dvp->v_data; + cnp = ap->a_cnp; + + if (cnp->cn_flags & ISDOTDOT) { + KASSERT(anp->an_parent != NULL, ("NULL parent")); + /* + * Note that in this case, dvp is the child vnode, and we are + * looking up the parent vnode - exactly reverse from normal + * operation. To preserve lock order, we unlock the child + * (dvp), obtain the lock on parent (*vpp) in autofs_node_vn(), + * then relock the child. We use vhold()/vdrop() to prevent + * dvp from being freed in the meantime. + */ + lock_flags = VOP_ISLOCKED(dvp); + vhold(dvp); + VOP_UNLOCK(dvp, 0); + error = autofs_node_vn(anp->an_parent, mp, vpp); + if (error != 0) { + AUTOFS_WARN("autofs_node_vn() failed with error %d", + error); + } + vn_lock(dvp, lock_flags | LK_RETRY); + vdrop(dvp); + + return (error); + } + + if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') { + vref(dvp); + *vpp = dvp; + + return (0); + } + + if (autofs_cached(anp, cnp->cn_nameptr, cnp->cn_namelen) == false && + autofs_ignore_thread(cnp->cn_thread) == false) { + error = autofs_trigger_vn(dvp, + cnp->cn_nameptr, cnp->cn_namelen, &newvp); + if (error != 0) + return (error); + + if (newvp != NULL) { + error = VOP_LOOKUP(newvp, ap->a_vpp, ap->a_cnp); + + /* + * Instead of figuring out whether our vnode should + * be locked or not given the error and cnp flags, + * just "copy" the lock status from vnode returned + * by mounted filesystem's VOP_LOOKUP(). Get rid + * of that new vnode afterwards. + */ + lock_flags = VOP_ISLOCKED(newvp); + if (lock_flags == 0) { + VOP_UNLOCK(dvp, 0); + vrele(newvp); + } else { + vput(newvp); + } + return (error); + } + } + + if (cnp->cn_nameiop == RENAME) + return (EOPNOTSUPP); + + AUTOFS_LOCK(amp); + error = autofs_node_find(anp, cnp->cn_nameptr, cnp->cn_namelen, &child); + if (error != 0) { + if ((cnp->cn_flags & ISLASTCN) && cnp->cn_nameiop == CREATE) { + AUTOFS_UNLOCK(amp); + return (EJUSTRETURN); + } + + AUTOFS_UNLOCK(amp); + return (ENOENT); + } + + /* + * XXX: Dropping the node here is ok, because we never remove nodes. + */ + AUTOFS_UNLOCK(amp); + + error = autofs_node_vn(child, mp, vpp); + if (error != 0) { + if ((cnp->cn_flags & ISLASTCN) && cnp->cn_nameiop == CREATE) + return (EJUSTRETURN); + + return (error); + } + + return (0); +} + +static int +autofs_mkdir(struct vop_mkdir_args *ap) +{ + struct vnode *vp; + struct autofs_node *anp; + struct autofs_mount *amp; + struct autofs_node *child; + int error; + + vp = ap->a_dvp; + anp = vp->v_data; + amp = VFSTOAUTOFS(vp->v_mount); + + /* + * Do not allow mkdir() if the calling thread is not + * automountd(8) descendant. + */ + if (autofs_ignore_thread(curthread) == false) + return (EPERM); + + AUTOFS_LOCK(amp); + error = autofs_node_new(anp, amp, ap->a_cnp->cn_nameptr, + ap->a_cnp->cn_namelen, &child); + if (error != 0) { + AUTOFS_UNLOCK(amp); + return (error); + } + AUTOFS_UNLOCK(amp); + + error = autofs_node_vn(child, vp->v_mount, ap->a_vpp); + + return (error); +} + +static int +autofs_readdir_one(struct uio *uio, const char *name, int fileno) +{ + struct dirent dirent; + int error, i; + + memset(&dirent, 0, sizeof(dirent)); + dirent.d_type = DT_DIR; + dirent.d_reclen = AUTOFS_DELEN; + dirent.d_fileno = fileno; + /* PFS_DELEN was picked to fit PFS_NAMLEN */ + for (i = 0; i < AUTOFS_NAMELEN - 1 && name[i] != '\0'; ++i) + dirent.d_name[i] = name[i]; + dirent.d_name[i] = 0; + dirent.d_namlen = i; + + error = uiomove(&dirent, AUTOFS_DELEN, uio); + return (error); +} + +static int +autofs_readdir(struct vop_readdir_args *ap) +{ + struct vnode *vp, *newvp; + struct autofs_mount *amp; + struct autofs_node *anp, *child; + struct uio *uio; + off_t offset; + int error, i, resid; + + vp = ap->a_vp; + amp = VFSTOAUTOFS(vp->v_mount); + anp = vp->v_data; + uio = ap->a_uio; + + KASSERT(vp->v_type == VDIR, ("!VDIR")); + + if (autofs_cached(anp, NULL, 0) == false && + autofs_ignore_thread(curthread) == false) { + error = autofs_trigger_vn(vp, "", 0, &newvp); + if (error != 0) + return (error); + + if (newvp != NULL) { + error = VOP_READDIR(newvp, ap->a_uio, ap->a_cred, + ap->a_eofflag, ap->a_ncookies, ap->a_cookies); + vput(newvp); + return (error); + } + } + + /* only allow reading entire entries */ + offset = uio->uio_offset; + resid = uio->uio_resid; + if (offset < 0 || offset % AUTOFS_DELEN != 0 || + (resid && resid < AUTOFS_DELEN)) + return (EINVAL); + if (resid == 0) + return (0); + + if (ap->a_eofflag != NULL) + *ap->a_eofflag = TRUE; + + if (offset == 0 && resid >= AUTOFS_DELEN) { + error = autofs_readdir_one(uio, ".", anp->an_fileno); + if (error != 0) + return (error); + offset += AUTOFS_DELEN; + resid -= AUTOFS_DELEN; + } + + if (offset == AUTOFS_DELEN && resid >= AUTOFS_DELEN) { + if (anp->an_parent == NULL) { + /* + * XXX: Right? + */ + error = autofs_readdir_one(uio, "..", anp->an_fileno); + } else { + error = autofs_readdir_one(uio, "..", + anp->an_parent->an_fileno); + } + if (error != 0) + return (error); + offset += AUTOFS_DELEN; + resid -= AUTOFS_DELEN; + } + + i = 2; /* Account for "." and "..". */ + AUTOFS_LOCK(amp); + TAILQ_FOREACH(child, &anp->an_children, an_next) { + if (resid < AUTOFS_DELEN) { + if (ap->a_eofflag != NULL) + *ap->a_eofflag = 0; + break; + } + + /* + * Skip entries returned by previous call to getdents(). + */ + i++; + if (i * AUTOFS_DELEN <= offset) + continue; + + error = autofs_readdir_one(uio, child->an_name, + child->an_fileno); + if (error != 0) { + AUTOFS_UNLOCK(amp); + return (error); + } + offset += AUTOFS_DELEN; + resid -= AUTOFS_DELEN; + } + + AUTOFS_UNLOCK(amp); + return (0); +} + +static int +autofs_reclaim(struct vop_reclaim_args *ap) +{ + struct vnode *vp = ap->a_vp; + struct autofs_node *anp = vp->v_data; + + vp = ap->a_vp; + anp = vp->v_data; + + /* + * We do not free autofs_node here; instead we are + * destroying them in autofs_node_delete(). + */ + sx_xlock(&anp->an_vnode_lock); + anp->an_vnode = NULL; + vp->v_data = NULL; + sx_xunlock(&anp->an_vnode_lock); + + return (0); +} + +struct vop_vector autofs_vnodeops = { + .vop_default = &default_vnodeops, + + .vop_access = autofs_access, + .vop_lookup = autofs_lookup, + .vop_create = VOP_EOPNOTSUPP, + .vop_getattr = autofs_getattr, + .vop_link = VOP_EOPNOTSUPP, + .vop_mkdir = autofs_mkdir, + .vop_mknod = VOP_EOPNOTSUPP, + .vop_read = VOP_EOPNOTSUPP, + .vop_readdir = autofs_readdir, + .vop_remove = VOP_EOPNOTSUPP, + .vop_rename = VOP_EOPNOTSUPP, + .vop_rmdir = VOP_EOPNOTSUPP, + .vop_setattr = VOP_EOPNOTSUPP, + .vop_symlink = VOP_EOPNOTSUPP, + .vop_write = VOP_EOPNOTSUPP, + .vop_reclaim = autofs_reclaim, +}; + +int +autofs_node_new(struct autofs_node *parent, struct autofs_mount *amp, + const char *name, int namelen, struct autofs_node **anpp) +{ + struct autofs_node *anp; + + if (parent != NULL) + AUTOFS_ASSERT_LOCKED(parent->an_mount); + + anp = uma_zalloc(autofs_node_zone, M_WAITOK | M_ZERO); + if (namelen >= 0) + anp->an_name = strndup(name, namelen, M_AUTOFS); + else + anp->an_name = strdup(name, M_AUTOFS); + anp->an_fileno = atomic_fetchadd_int(&->am_last_fileno, 1); + callout_init(&anp->an_callout, 1); + /* + * The reason for SX_NOWITNESS here is that witness(4) + * cannot tell vnodes apart, so the following perfectly + * valid lock order... + * + * vnode lock A -> autofsvlk B -> vnode lock B + * + * ... gets reported as a LOR. + */ + sx_init_flags(&anp->an_vnode_lock, "autofsvlk", SX_NOWITNESS); + getnanotime(&anp->an_ctime); + anp->an_parent = parent; + anp->an_mount = amp; + if (parent != NULL) + TAILQ_INSERT_TAIL(&parent->an_children, anp, an_next); + TAILQ_INIT(&anp->an_children); + + *anpp = anp; + return (0); +} + +int +autofs_node_find(struct autofs_node *parent, const char *name, + int namelen, struct autofs_node **anpp) +{ + struct autofs_node *anp; + + AUTOFS_ASSERT_LOCKED(parent->an_mount); + + TAILQ_FOREACH(anp, &parent->an_children, an_next) { + if (namelen >= 0) { + if (strncmp(anp->an_name, name, namelen) != 0) + continue; + } else { + if (strcmp(anp->an_name, name) != 0) + continue; + } + + if (anpp != NULL) + *anpp = anp; + return (0); + } + + return (ENOENT); +} + +void +autofs_node_delete(struct autofs_node *anp) +{ + struct autofs_node *parent; + + AUTOFS_ASSERT_LOCKED(anp->an_mount); + KASSERT(TAILQ_EMPTY(&anp->an_children), ("have children")); + + callout_drain(&anp->an_callout); + + parent = anp->an_parent; + if (parent != NULL) + TAILQ_REMOVE(&parent->an_children, anp, an_next); + sx_destroy(&anp->an_vnode_lock); + free(anp->an_name, M_AUTOFS); + uma_zfree(autofs_node_zone, anp); +} + +int +autofs_node_vn(struct autofs_node *anp, struct mount *mp, struct vnode **vpp) +{ + struct vnode *vp; + int error; + + AUTOFS_ASSERT_UNLOCKED(anp->an_mount); + + sx_xlock(&anp->an_vnode_lock); + + vp = anp->an_vnode; + if (vp != NULL) { + error = vget(vp, LK_EXCLUSIVE | LK_RETRY, curthread); + if (error != 0) { + AUTOFS_WARN("vget failed with error %d", error); + sx_xunlock(&anp->an_vnode_lock); + return (error); + } + if (vp->v_iflag & VI_DOOMED) { + /* + * We got forcibly unmounted. + */ + AUTOFS_DEBUG("doomed vnode"); + sx_xunlock(&anp->an_vnode_lock); + vput(vp); + + return (ENOENT); + } + + *vpp = vp; + sx_xunlock(&anp->an_vnode_lock); + return (0); + } + + error = getnewvnode("autofs", mp, &autofs_vnodeops, &vp); + if (error != 0) { + sx_xunlock(&anp->an_vnode_lock); + return (error); + } + + error = vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + if (error != 0) { + sx_xunlock(&anp->an_vnode_lock); + vdrop(vp); + return (error); + } + + vp->v_type = VDIR; + if (anp->an_parent == NULL) + vp->v_vflag |= VV_ROOT; + vp->v_data = anp; + + error = insmntque(vp, mp); + if (error != 0) { + AUTOFS_WARN("insmntque() failed with error %d", error); + sx_xunlock(&anp->an_vnode_lock); + return (error); + } + + KASSERT(anp->an_vnode == NULL, ("lost race")); + anp->an_vnode = vp; + + sx_xunlock(&anp->an_vnode_lock); + + *vpp = vp; + return (0); +} diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c index 674e526..c407699 100644 --- a/sys/kern/vfs_mount.c +++ b/sys/kern/vfs_mount.c @@ -649,6 +649,10 @@ vfs_donmount(struct thread *td, uint64_t fsflags, struct uio *fsoptions) fsflags |= MNT_SYNCHRONOUS; else if (strcmp(opt->name, "union") == 0) fsflags |= MNT_UNION; + else if (strcmp(opt->name, "automounted") == 0) { + fsflags |= MNT_AUTOMOUNTED; + vfs_freeopt(optlist, opt); + } } /* diff --git a/sys/libkern/strndup.c b/sys/libkern/strndup.c new file mode 100644 index 0000000..43983d5 --- /dev/null +++ b/sys/libkern/strndup.c @@ -0,0 +1,51 @@ +/*- + * Copyright (c) 2003 Networks Associates Technology, Inc. + * All rights reserved. + * + * This software was developed for the FreeBSD Project by Network + * Associates Laboratories, the Security Research Division of Network + * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 + * ("CBOSS"), as part of the DARPA CHATS research program. + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/libkern.h> +#include <sys/malloc.h> + +char * +strndup(const char *string, size_t maxlen, struct malloc_type *type) +{ + size_t len; + char *copy; + + len = strnlen(string, maxlen) + 1; + copy = malloc(len, type, M_WAITOK); + bcopy(string, copy, len); + copy[len - 1] = '\0'; + return (copy); +} diff --git a/sys/modules/Makefile b/sys/modules/Makefile index 1e26093..429901f 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -44,6 +44,7 @@ SUBDIR= \ ata \ ath \ ath_pci \ + autofs \ ${_auxio} \ ${_bce} \ bfe \ diff --git a/sys/modules/autofs/Makefile b/sys/modules/autofs/Makefile new file mode 100644 index 0000000..5aaa892 --- /dev/null +++ b/sys/modules/autofs/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../fs/autofs + +KMOD= autofs +SRCS= vnode_if.h \ + autofs.c \ + autofs_vnops.c \ + autofs_vfsops.c + +.include <bsd.kmod.mk> diff --git a/sys/sys/libkern.h b/sys/sys/libkern.h index 3665b3a..5f850fb 100644 --- a/sys/sys/libkern.h +++ b/sys/sys/libkern.h @@ -117,6 +117,7 @@ int strcmp(const char *, const char *); char *strcpy(char * __restrict, const char * __restrict); size_t strcspn(const char * __restrict, const char * __restrict) __pure; char *strdup(const char *__restrict, struct malloc_type *); +char *strndup(const char *__restrict, size_t, struct malloc_type *); size_t strlcat(char *, const char *, size_t); size_t strlcpy(char *, const char *, size_t); size_t strlen(const char *); diff --git a/sys/sys/mount.h b/sys/sys/mount.h index e0c2696..0c9a1da 100644 --- a/sys/sys/mount.h +++ b/sys/sys/mount.h @@ -260,6 +260,7 @@ void __mnt_vnode_markerfree_active(struct vnode **mvp, struct mount *); #define MNT_NOCLUSTERR 0x0000000040000000ULL /* disable cluster read */ #define MNT_NOCLUSTERW 0x0000000080000000ULL /* disable cluster write */ #define MNT_SUJ 0x0000000100000000ULL /* using journaled soft updates */ +#define MNT_AUTOMOUNTED 0x0000000200000000ULL /* mounted by automountd(8) */ /* * NFS export related mount flags. @@ -296,7 +297,7 @@ void __mnt_vnode_markerfree_active(struct vnode **mvp, struct mount *); MNT_NOCLUSTERW | MNT_SUIDDIR | MNT_SOFTDEP | \ MNT_IGNORE | MNT_EXPUBLIC | MNT_NOSYMFOLLOW | \ MNT_GJOURNAL | MNT_MULTILABEL | MNT_ACLS | \ - MNT_NFS4ACLS) + MNT_NFS4ACLS | MNT_AUTOMOUNTED) /* Mask of flags that can be updated. */ #define MNT_UPDATEMASK (MNT_NOSUID | MNT_NOEXEC | \ @@ -304,7 +305,8 @@ void __mnt_vnode_markerfree_active(struct vnode **mvp, struct mount *); MNT_NOATIME | \ MNT_NOSYMFOLLOW | MNT_IGNORE | \ MNT_NOCLUSTERR | MNT_NOCLUSTERW | MNT_SUIDDIR | \ - MNT_ACLS | MNT_USER | MNT_NFS4ACLS) + MNT_ACLS | MNT_USER | MNT_NFS4ACLS | \ + MNT_AUTOMOUNTED) /* * External filesystem command modifier flags. |