Index: etc/Makefile =================================================================== --- etc/Makefile (revision 269195) +++ etc/Makefile (working copy) @@ -11,7 +11,8 @@ SUBDIR= sendmail SUBDIR+=tests .endif -BIN1= crontab \ +BIN1= auto_master \ + crontab \ devd.conf \ devfs.conf \ ddb.conf \ @@ -225,6 +226,7 @@ distribution: echo "./etc/spwd.db type=file mode=0600 uname=root gname=wheel"; \ ) | ${METALOG.add} .endif + ${_+_}cd ${.CURDIR}/autofs; ${MAKE} install .if ${MK_BLUETOOTH} != "no" ${_+_}cd ${.CURDIR}/bluetooth; ${MAKE} install .endif Index: etc/auto_master =================================================================== --- etc/auto_master (revision 0) +++ etc/auto_master (working copy) @@ -0,0 +1,5 @@ +# $FreeBSD$ +# +# Automounter master map, see auto_master(5) for details. +# +/net -hosts -nosuid Index: etc/autofs/Makefile =================================================================== --- etc/autofs/Makefile (revision 0) +++ etc/autofs/Makefile (working copy) @@ -0,0 +1,9 @@ +# $FreeBSD$ + +FILES= include_ldap special_hosts special_null + +NO_OBJ= +FILESDIR= /etc/autofs +FILESMODE= 755 + +.include Index: etc/autofs/include_ldap =================================================================== --- etc/autofs/include_ldap (revision 0) +++ etc/autofs/include_ldap (working copy) @@ -0,0 +1,35 @@ +#!/bin/sh + +# Modify this to suit your needs. The "$1" is the map name, eg. "auto_master". +# To debug, simply run this script with map name as the only parameter. It's +# supposed to output map contents ("key location" pairs) to standard output. +SEARCHBASE="ou=$1,dc=example,dc=com" +ENTRY_ATTRIBUTE="cn" +VALUE_ATTRIBUTE="automountInformation" + +/usr/local/bin/ldapsearch -LLL -x -o ldif-wrap=no -b "$SEARCHBASE" "$ENTRY_ATTRIBUTE" "$VALUE_ATTRIBUTE" | awk ' +$1 == "'$ENTRY_ATTRIBUTE':" { + key = $2 +} + +$1 == "'$VALUE_ATTRIBUTE':" && key { + printf "%s%s", key, OFS + key = "" + for (i=2; i 1 { printf "%s\t%s:%s ", $1, host, $1 } END { printf "\n" }' + Index: etc/autofs/special_null =================================================================== --- etc/autofs/special_null (revision 0) +++ etc/autofs/special_null (working copy) @@ -0,0 +1 @@ +#!/usr/bin/true Index: etc/defaults/rc.conf =================================================================== --- etc/defaults/rc.conf (revision 269195) +++ etc/defaults/rc.conf (working copy) @@ -306,6 +306,7 @@ amd_enable="NO" # Run amd service with $amd_flag amd_program="/usr/sbin/amd" # path to amd, if you want a different one. amd_flags="-a /.amd_mnt -l syslog /host /etc/amd.map /net /etc/amd.map" amd_map_program="NO" # Can be set to "ypcat -k amd.master" +autofs_enable="NO" # Run automountd(8) nfs_client_enable="NO" # This host is an NFS client (or NO). nfs_access_cache="60" # Client cache timeout in seconds nfs_server_enable="NO" # This host is an NFS server (or NO). Index: etc/mtree/BSD.root.dist =================================================================== --- etc/mtree/BSD.root.dist (revision 269195) +++ etc/mtree/BSD.root.dist (working copy) @@ -24,6 +24,8 @@ etc X11 .. + autofs + .. bluetooth .. casper Index: etc/rc.d/Makefile =================================================================== --- etc/rc.d/Makefile (revision 269195) +++ etc/rc.d/Makefile (working copy) @@ -20,6 +20,9 @@ FILES= DAEMON \ atm3 \ auditd \ auditdistd \ + automount \ + automountd \ + autounmountd \ bgfsck \ ${_bluetooth} \ bootparams \ Index: etc/rc.d/automount =================================================================== --- etc/rc.d/automount (revision 0) +++ etc/rc.d/automount (working copy) @@ -0,0 +1,31 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: automount +# REQUIRE: nfsclient +# KEYWORD: nojail shutdown + +. /etc/rc.subr + +name="automount" +rcvar="autofs_enable" +start_cmd="automount_start" +stop_cmd="automount_stop" +required_modules="autofs" + +automount_start() +{ + + /usr/sbin/automount +} + +automount_stop() +{ + + /sbin/umount -At autofs +} + +load_rc_config $name +run_rc_command "$1" Index: etc/rc.d/automountd =================================================================== --- etc/rc.d/automountd (revision 0) +++ etc/rc.d/automountd (working copy) @@ -0,0 +1,19 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: automountd +# REQUIRE: automount +# KEYWORD: nojail + +. /etc/rc.subr + +name="automountd" +rcvar="autofs_enable" +pidfile="/var/run/${name}.pid" +command="/usr/sbin/${name}" +required_modules="autofs" + +load_rc_config $name +run_rc_command "$1" Index: etc/rc.d/autounmountd =================================================================== --- etc/rc.d/autounmountd (revision 0) +++ etc/rc.d/autounmountd (working copy) @@ -0,0 +1,18 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: autounmountd +# REQUIRE: nfsclient +# KEYWORD: nojail + +. /etc/rc.subr + +name="autounmountd" +rcvar="autofs_enable" +pidfile="/var/run/${name}.pid" +command="/usr/sbin/${name}" + +load_rc_config $name +run_rc_command "$1" Index: sbin/mount/mount.c =================================================================== --- sbin/mount/mount.c (revision 269195) +++ sbin/mount/mount.c (working copy) @@ -114,6 +114,7 @@ static struct opt { { MNT_ACLS, "acls" }, { MNT_NFS4ACLS, "nfsv4acls" }, { MNT_GJOURNAL, "gjournal" }, + { MNT_AUTOMOUNTED, "automounted" }, { 0, NULL } }; Index: share/man/man5/Makefile =================================================================== --- share/man/man5/Makefile (revision 269195) +++ share/man/man5/Makefile (working copy) @@ -7,6 +7,7 @@ MAN= acct.5 \ ar.5 \ a.out.5 \ + autofs.5 \ bluetooth.device.conf.5 \ bluetooth.hosts.5 \ bluetooth.protocols.5 \ Index: share/man/man5/autofs.5 =================================================================== --- share/man/man5/autofs.5 (revision 0) +++ share/man/man5/autofs.5 (working copy) @@ -0,0 +1,100 @@ +.\" 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 AUTHORS 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 AUTHORS 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$ +.\" +.Dd July 14, 2014 +.Dt AUTOFS 5 +.Os +.Sh NAME +.Nm autofs +.Nd "automounter filesystem" +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following line in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "options AUTOFS" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +autofs_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver is the kernel component of automounter infrastructure. +Its job is to pass mount requests to the +.Xr automountd 8 +daemon, and pause offending processes until the mount is completed. +It is usually mounted by the +.Xr automount 8 +utility. +.Sh OPTIONS +The following options are available when +mounting +.Nm +file systems: +.Bl -tag -width indent +.It Cm master_options +Mount options for all filesystems specified in the map entry. +.It Cm master_prefix +Filesystem mountpoint prefix. +.El +.Sh EXAMPLES +To unmount all mounted +.Nm +filesystems: +.Pp +.Dl "umount -At autofs" +.Pp +To mount +.Nm +filesystems as specified by +.Xr auto_master 5 +file: +.Pp +.Dl "automount" +.Sh SEE ALSO +.Xr auto_master 5 , +.Xr automount 8 , +.Xr automountd 8 , +.Xr autounmountd 8 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 10.2 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. Index: sys/conf/NOTES =================================================================== --- sys/conf/NOTES (revision 269195) +++ sys/conf/NOTES (working copy) @@ -1012,6 +1012,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 Index: sys/conf/files =================================================================== --- sys/conf/files (revision 269195) +++ sys/conf/files (working copy) @@ -2595,6 +2595,9 @@ dev/xen/timer/timer.c optional xen | xenhvm dev/xen/pvcpu/pvcpu.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 @@ -3132,6 +3135,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 Index: sys/conf/options =================================================================== --- sys/conf/options (revision 269195) +++ sys/conf/options (working copy) @@ -221,6 +221,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 Index: sys/fs/autofs/autofs.c =================================================================== --- sys/fs/autofs/autofs.c (revision 0) +++ sys/fs/autofs/autofs.c (working copy) @@ -0,0 +1,543 @@ +/*- + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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", +}; + +struct autofs_softc *sc; + +SYSCTL_NODE(_vfs, OID_AUTO, autofs, CTLFLAG_RD, 0, "Automounter filesystem"); +int autofs_debug = 2; +TUNABLE_INT("vfs.autofs.debug", &autofs_debug); +SYSCTL_INT(_vfs_autofs, OID_AUTO, debug, CTLFLAG_RWTUN, + &autofs_debug, 2, "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) { + //AUTOFS_DEBUG("removing device node"); + destroy_dev(sc->sc_cdev); + //AUTOFS_DEBUG("device node removed"); + } + + 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 = td->td_proc; + + if (sc->sc_dev_opened == false) + return (false); + + PROC_LOCK(p); + if (p->p_session->s_sid == sc->sc_dev_sid) { + //AUTOFS_DEBUG("must pass pid %d (%s)", p->p_pid, p->p_comm); + PROC_UNLOCK(p); + return (true); + } + //AUTOFS_DEBUG("must hold pid %d (%s), sid %d not %d", + // p->p_pid, p->p_comm, p->p_session->s_sid, sc->sc_dev_sid); + PROC_UNLOCK(p); + + return (false); +} + +static char * +autofs_path(struct autofs_node *anp) +{ + struct autofs_mount *amp = anp->an_mount; + char *path, *tmp; + + 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 = NULL; + } + + 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; + tmp = NULL; + + return (path); +} + +static void +autofs_callout(void *context) +{ + struct autofs_request *ar = context; + struct autofs_softc *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 = anp->an_mount; + + AUTOFS_LOCK_ASSERT_NOT(amp); + + /* + * For top-level nodes we need to request automountd(8) + * assistance even if the node is marked as cached, + * but the requested subdirectory doesn't exist. This + * is neccessary 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 = context; + + anp->an_cached = false; +} + +static int +autofs_trigger_one(struct autofs_node *anp, + const char *component, int componentlen) +{ + struct autofs_mount *amp = VFSTOAUTOFS(anp->an_vnode->v_mount); + struct autofs_softc *sc = amp->am_softc; + struct autofs_node *firstanp; + struct autofs_request *ar; + char *key, *path; + int error = 0, request_error, last; + + 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) { + //AUTOFS_DEBUG("found existing request for %s %s %s", ar->ar_from, ar->ar_key, ar->ar_path); + 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)); + + //AUTOFS_DEBUG("new request for %s %s %s", ar->ar_from, ar->ar_key, ar->ar_path); + 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) { + 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; + 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); + } + + //AUTOFS_DEBUG("done with %s %s %s", ar->ar_from, ar->ar_key, ar->ar_path); + 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 don't do negative caching on purpose. This + * way the user can retry access at any time, eg. after fixing + * the failure reason, without waiting for cache timer to expire. + */ + if (error == 0 && request_error == 0 && autofs_cache > 0) { + //AUTOFS_DEBUG("disabling trigger for %s", anp->an_name); + anp->an_cached = true; + callout_reset(&anp->an_callout, autofs_cache * hz, + autofs_cache_callout, anp); + } else { + //AUTOFS_DEBUG("not disabling %s, key '%s', error %d", anp->an_name, key, error); + } + + 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); + } +} Index: sys/fs/autofs/autofs.h =================================================================== --- sys/fs/autofs/autofs.h (revision 0) +++ sys/fs/autofs/autofs.h (working copy) @@ -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 +__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_LOCK_ASSERT(X) sx_assert(&X->am_lock, SA_XLOCKED) +#define AUTOFS_LOCK_ASSERT_NOT(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 */ Index: sys/fs/autofs/autofs_ioctl.h =================================================================== --- sys/fs/autofs/autofs_ioctl.h (revision 0) +++ sys/fs/autofs/autofs_ioctl.h (working copy) @@ -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's 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's "/". + */ + 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 */ Index: sys/fs/autofs/autofs_vfsops.c =================================================================== --- sys/fs/autofs/autofs_vfsops.c (revision 0) +++ sys/fs/autofs/autofs_vfsops.c (working copy) @@ -0,0 +1,205 @@ +/*- + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = VFSTOAUTOFS(mp); + struct autofs_node *anp; + struct autofs_request *ar; + int error, flags; + bool found; + + 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 won't 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 = VFSTOAUTOFS(mp); + int error; + + 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); Index: sys/fs/autofs/autofs_vnops.c =================================================================== --- sys/fs/autofs/autofs_vnops.c (revision 0) +++ sys/fs/autofs/autofs_vnops.c (working copy) @@ -0,0 +1,639 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = ap->a_vp; + struct vnode *newvp; + struct autofs_node *anp = vp->v_data; + struct mount *mp = vp->v_mount; + struct vattr *vap = ap->a_vap; + int error; + + 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 won't 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 = vp->v_data; + struct autofs_mount *amp = VFSTOAUTOFS(vp->v_mount); + struct autofs_softc *sc = amp->am_softc; + int error, lock_flags; + + /* + * Release the vnode lock, so that other operations, in partcular + * mounting a filesystem on top of it, can proceed. Increase hold + * count, to prevent the vnode from being deallocated. + */ + lock_flags = VOP_ISLOCKED(vp); + vhold(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); + vdrop(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'll fail + * to trigger. + */ + anp->an_cached = false; + //AUTOFS_DEBUG("node %s covered by mount; uncaching", + // anp->an_name); + } + + error = vfs_busy(vp->v_mountedhere, 0); + if (error != 0) { + AUTOFS_WARN("vfs_busy failed"); + *newvp = NULL; + return (0); + } + + error = VFS_ROOT(vp->v_mountedhere, lock_flags, newvp); + if (error != 0) { + vfs_unbusy(vp->v_mountedhere); + return (error); + } + + vfs_unbusy(vp->v_mountedhere); + return (0); +} + +static int +autofs_lookup(struct vop_lookup_args *ap) +{ + struct vnode *dvp = ap->a_dvp; + struct vnode **vpp = ap->a_vpp; + struct vnode *newvp; + struct mount *mp = dvp->v_mount; + struct autofs_mount *amp = VFSTOAUTOFS(mp); + struct autofs_node *anp = dvp->v_data; + struct autofs_node *child; + struct componentname *cnp = ap->a_cnp; + int error, lock_flags; + + if (cnp->cn_flags & ISDOTDOT) { + //AUTOFS_DEBUG(".."); + KASSERT(anp->an_parent != NULL, ("NULL parent")); + /* + * Note that in this case, dvp is the child vnode, and we're + * 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] == '.') { + //AUTOFS_DEBUG("."); + 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) { + //AUTOFS_DEBUG("autofs_trigger_vn failed, error %d", error); + 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) { + //AUTOFS_DEBUG("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_DEBUG("JUSTRETURN"); + AUTOFS_UNLOCK(amp); + return (EJUSTRETURN); + } + + //AUTOFS_DEBUG("ENOENT"); + 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) { + //AUTOFS_DEBUG("JUSTRETURN"); + return (EJUSTRETURN); + } + + //AUTOFS_DEBUG("autofs_node_vn failed with error %d", error); + return (error); + } + + return (0); +} + +static int +autofs_mkdir(struct vop_mkdir_args *ap) +{ + struct vnode *vp = ap->a_dvp; + struct autofs_node *anp = vp->v_data; + struct autofs_mount *amp = VFSTOAUTOFS(vp->v_mount); + struct autofs_node *child; + int error; + + /* + * Don't 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 = ap->a_vp; + struct vnode *newvp; + struct autofs_mount *amp = VFSTOAUTOFS(vp->v_mount); + struct autofs_node *anp = vp->v_data; + struct autofs_node *child; + struct uio *uio = ap->a_uio; + off_t offset; + int error, i, resid; + + 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; + + /* + * We don't free autofs_node here; instead we're + * 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_LOCK_ASSERT(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) + * can't 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_LOCK_ASSERT(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_LOCK_ASSERT(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_LOCK_ASSERT_NOT(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); +} Index: sys/kern/vfs_mount.c =================================================================== --- sys/kern/vfs_mount.c (revision 269195) +++ sys/kern/vfs_mount.c (working copy) @@ -649,6 +649,10 @@ vfs_donmount(struct thread *td, uint64_t fsflags, 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); + } } /* Index: sys/libkern/strndup.c =================================================================== --- sys/libkern/strndup.c (revision 0) +++ sys/libkern/strndup.c (working copy) @@ -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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +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); +} Index: sys/modules/Makefile =================================================================== --- sys/modules/Makefile (revision 269195) +++ sys/modules/Makefile (working copy) @@ -44,6 +44,7 @@ SUBDIR= \ ata \ ath \ ath_pci \ + autofs \ ${_auxio} \ ${_bce} \ bfe \ Index: sys/modules/autofs/Makefile =================================================================== --- sys/modules/autofs/Makefile (revision 0) +++ sys/modules/autofs/Makefile (working copy) @@ -0,0 +1,11 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../fs/autofs + +KMOD= autofs +SRCS= vnode_if.h \ + autofs.c \ + autofs_vnops.c \ + autofs_vfsops.c + +.include Index: sys/sys/libkern.h =================================================================== --- sys/sys/libkern.h (revision 269195) +++ sys/sys/libkern.h (working copy) @@ -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 *); Index: sys/sys/mount.h =================================================================== --- sys/sys/mount.h (revision 269195) +++ sys/sys/mount.h (working copy) @@ -259,6 +259,7 @@ void __mnt_vnode_markerfree_active(struct #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. @@ -295,7 +296,7 @@ void __mnt_vnode_markerfree_active(struct 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 | \ @@ -303,7 +304,8 @@ void __mnt_vnode_markerfree_active(struct 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. Index: usr.sbin/Makefile =================================================================== --- usr.sbin/Makefile (revision 269195) +++ usr.sbin/Makefile (working copy) @@ -5,6 +5,7 @@ SUBDIR= adduser \ arp \ + automountd \ binmiscctl \ bootparamd \ bsdconfig \ Index: usr.sbin/automountd/Makefile =================================================================== --- usr.sbin/automountd/Makefile (revision 0) +++ usr.sbin/automountd/Makefile (working copy) @@ -0,0 +1,33 @@ +# $FreeBSD$ + +PROG= automountd +SRCS= automount.c +SRCS+= automountd.c +SRCS+= autounmountd.c +SRCS+= common.c +SRCS+= defined.c +SRCS+= getmntopts.c +SRCS+= log.c +SRCS+= popen.c +SRCS+= token.l + +CFLAGS+=-I${.CURDIR} +CFLAGS+=-I${.CURDIR}/../../sys/fs/autofs + +MAN= automount.8 automountd.8 autounmountd.8 auto_master.5 + +DPADD= ${LIBUTIL} +LDADD= -lutil + +# Needed for getmntopts.c +MOUNT= ${.CURDIR}/../../sbin/mount +CFLAGS+=-I${MOUNT} + +WARNS= 6 + +LINKS= ${BINDIR}/automountd ${BINDIR}/automount +LINKS+= ${BINDIR}/automountd ${BINDIR}/autounmountd + +.PATH: ${MOUNT} + +.include Index: usr.sbin/automountd/auto_master.5 =================================================================== --- usr.sbin/automountd/auto_master.5 (revision 0) +++ usr.sbin/automountd/auto_master.5 (working copy) @@ -0,0 +1,199 @@ +.\" 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 AUTHORS 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 AUTHORS 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$ +.\" +.Dd April 20, 2014 +.Dt AUTO_MASTER 5 +.Os +.Sh NAME +.Nm auto_master +.Nd auto_master and map file format +.Sh DESCRIPTION +The +.Nm +configuration file is used by the +.Xr automount 8 +command. +Map files are read by the +.Xr automountd 8 +daemon. +.Sh AUTO_MASTER SYNTAX +The +.Nm +file consists of lines with two or three entries, separated by whitespace, +terminated by newline, as follows: +.Bd -literal -offset indent +mountpoint map_name [-options] +.Ed +.Pp +The "mountpoint" is either a fully specified path, or "/-". +In the former case the "map_name" must reference an indirect map; otherwise +it must reference a direct map. +.Pp +The "map_name" specifies map to use. +If the map name begins with "-", it specifies a special map, see below. +Otherwise, if it is not a fully specified path (ie. it doesn't start with "/"), +.Xr automountd 8 +will search for it in the /etc directory; otherwise it will use the path +specified. +If the file is executable, +.Xr automountd 8 +will assume it's an executable map, see below. +Otherwise it will open the file and parse its contents. +.Pp +The "options" is an optional field that starts with "-" and can contain generic +filesystem mount options. +.Pp +The following example specifies that the /etc/auto_example indirect map +will be mounted on /example. +.Bd -literal -offset indent +/example auto_example +.Ed +.Sh MAP SYNTAX +Map files consist of lines with a number of entries, separated by whitespace, +terminated by newline, as follows: +.Bd -literal -offset indent +key [-options] [mountpoint [-options]] location [...] +.Ed +.Pp +In most cases it's simplified to: +.Bd -literal -offset indent +key [-options] location +.Ed +.Pp +The "key" specifies path component used by +.Xr automountd 8 +to find the right map entry to use. +It is also used to form the final mountpoint. +.Pp +The "options" field, if present, must begin with "-". +When mounting the filesystem, all the options - from +.Nm , +and from the map entry - get concatenated together. +.Pp +The optional "mountpoint" field is used to specify multiple mount points +for a single key. +.Pp +The location field specifies the filesystem to be mounted. +.Pp +The following example, when used with +.Nm +example above, specifies that the NFS share "192.168.1.1:/share/example/x" +will get mounted on /example/x/ when any process attempts to access that +mountpoint, with "intr" and "nfsv4" mount options: +.Bd -literal -offset indent +x -intr,nfsv4 192.168.1.1:/share/example/x +.Ed +.Sh SPECIAL MAPS +Special maps have names beginning with "-". +Supported special maps are: +.Pp +.Bl -tag -width "-hosts" -compact +.It -hosts +This map queries the remote NFS server and maps exported volumes. +It's traditionally mounted on /net. +It enables access to files on a remote NFS server by accessing +/net/nfs-server-ip/share-name/ directory, without need for any +further configuration. +.It -null +This map prevents the +.Xr automountd 8 +daemon from mounting anything on the mountpoint. +.El +.Sh EXECUTABLE MAPS +If the map file specified in auto_master has execute bit set, the +.Xr automountd 8 +daemon will execute it and parse its standard output instead of parsing +its contents. +.Sh INDIRECT VS DIRECT MAPS +Indirect maps are referred to in +.Nm +by entries with a fully qualified path as a mountpoint, and must contain only +relative paths as keys. +Direct maps are referred to in +.Nm +by entries with "/-" as the mountpoint, and must contain only fully qualified +paths as keys. +For indirect maps, the final mount point is determined by concatenating +.Nm +mountpoint, the map entry key, and optional map entry mountpoint. +For direct maps, the final mount point is determined by concatenating map entry key and optional map entry mountpoint. +.Pp +The example above could be rewritten using direct map, by placing the following +in +.Nm : +.Bd -literal -offset indent +/- auto_example +.Ed +.Pp +and the following in /etc/auto_example map file: +.Bd -literal -offset indent +/example/x -intr,nfsv4 192.168.1.1:/share/example/x +.Ed +.Sh DIRECTORY SERVICES +Both +.Nm +and maps may contain entries consisting of plus sign and map name: +.Bd -literal -offset indent ++auto_master +.Ed +.Pp +Those entries cause +.Xr automountd 8 +daemon to retrieve named map from directory services (eg. LDAP) +and include it where the entry was. +.Pp +If the file containing map referenced in +.Nm +is not found, the map will be retrieved from directory services instead. +.Pp +To retrieve entries from directory services, the +.Xr automountd 8 +daemon runs /etc/autofs/include - which is usually a shell script - with map +name as the only command line parameter. +The script should output entries, formatted according to +.Nm +or automounter map syntax, to its standard output. +Example script to use LDAP is included in /etc/autofs/include_ldap; one can +simply symlink it to /etc/autofs/include. +.Sh FILES +.Bl -tag -width ".Pa /etc/auto_master" -compact +.It Pa /etc/auto_master +The default location of the auto_master file +.El +.Sh SEE ALSO +.Xr autofs 5 , +.Xr automount 8 , +.Xr automountd 8 , +.Xr autounmountd 8 +.Sh AUTHORS +The +.Nm +configuration file functionality was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. Index: usr.sbin/automountd/automount.8 =================================================================== --- usr.sbin/automountd/automount.8 (revision 0) +++ usr.sbin/automountd/automount.8 (working copy) @@ -0,0 +1,104 @@ +.\" 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 AUTHORS 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 AUTHORS 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$ +.\" +.Dd April 20, 2014 +.Dt AUTOMOUNT 8 +.Os +.Sh NAME +.Nm automount +.Nd update autofs mounts +.Sh SYNOPSIS +.Nm +.Op Fl D Ar name=value +.Op Fl L +.Op Fl f +.Op Fl o Ar options +.Op Fl v +.Op Fl u +.Sh DESCRIPTION +When called without options, the +.Nm +command parses the +.Xr auto_master 5 +configuration file and any direct maps it references, and mounts or unmounts +.Xr autofs 4 +filesystems to match. +The following options are available: +.Bl -tag -width ".Fl v" +.It Fl D +Define a variable. +Note that in case of +.Nm , +it is only useful with -L. +.It Fl L +Do not mount or unmount anything; instead parse +.Xr auto_master 5 +and direct maps and print them to standard output. +When specified more than once, all the maps, including indirect ones, +will be parsed and shown. +This is useful when debugging configuration problems. +.It Fl f +Force unmount, to be used with -u. +.It Fl o +Specify mount options to be used along with ones specified in maps. +Note that in case of +.Nm , +it is only useful with -L. +.It Fl u +Try to unmount filesystems mounted by +.Xr automountd 8 +daemon. +Note that this does not include autofs mounts done by +.Nm . +To unmount all autofs mounts, use "umount -At autofs". +.It Fl v +Increase verbosity. +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Unmount all filesystems mounted by +.Xr automountd 8 : +.Dl Nm Fl u +.Sh SEE ALSO +.Xr auto_master 5 , +.Xr autofs 5 , +.Xr automountd 8 , +.Xr autounmountd 8 +.Sh HISTORY +The +.Nm +command appeared in +.Fx 10.2 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. Index: usr.sbin/automountd/automount.c =================================================================== --- usr.sbin/automountd/automount.c (revision 0) +++ usr.sbin/automountd/automount.c (working copy) @@ -0,0 +1,338 @@ +/*- + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "mntopts.h" + +static int +unmount_by_statfs(const struct statfs *sb, bool force) +{ + char *fsid_str; + int error, ret, flags; + + ret = asprintf(&fsid_str, "FSID:%d:%d", + sb->f_fsid.val[0], sb->f_fsid.val[1]); + if (ret < 0) + log_err(1, "asprintf"); + + log_debugx("unmounting %s using %s", sb->f_mntonname, fsid_str); + + flags = MNT_BYFSID; + if (force) + flags |= MNT_FORCE; + error = unmount(fsid_str, flags); + free(fsid_str); + if (error != 0) + log_warn("cannot unmount %s", sb->f_mntonname); + + return (error); +} + +static const struct statfs * +find_statfs(const struct statfs *mntbuf, int nitems, const char *mountpoint) +{ + int i; + + for (i = 0; i < nitems; i++) { + if (strcmp(mntbuf[i].f_mntonname, mountpoint) == 0) + return (mntbuf + i); + } + + return (NULL); +} + +static void +mount_autofs(const char *from, const char *fspath, const char *options, const char *prefix) +{ + struct iovec *iov = NULL; + char errmsg[255]; + int error, iovlen = 0; + + create_directory(fspath); + + log_debugx("mounting %s on %s, prefix \"%s\", options \"%s\"", + from, fspath, prefix, options); + memset(errmsg, 0, sizeof(errmsg)); + + build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "autofs"), (size_t)-1); + build_iovec(&iov, &iovlen, "fspath", __DECONST(void *, fspath), (size_t)-1); + build_iovec(&iov, &iovlen, "from", __DECONST(void *, from), (size_t)-1); + build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); + + /* + * Append the options and mountpoint defined in auto_master(5); + * this way automountd(8) doesn't need to parse it. + */ + build_iovec(&iov, &iovlen, "master_options", __DECONST(void *, options), (size_t)-1); + build_iovec(&iov, &iovlen, "master_prefix", __DECONST(void *, prefix), (size_t)-1); + + error = nmount(iov, iovlen, 0); + if (error != 0) { + if (*errmsg != '\0') { + log_err(1, "cannot mount %s on %s: %s", + from, fspath, errmsg); + } else { + log_err(1, "cannot mount %s on %s", from, fspath); + } + } +} + +static void +mount_if_not_already(const struct node *n, const char *map, + const struct statfs *mntbuf, int nitems) +{ + const struct statfs *sb; + char *mountpoint; + char *from; + int ret; + + ret = asprintf(&from, "map %s", map); + if (ret < 0) + log_err(1, "asprintf"); + + mountpoint = node_path(n); + sb = find_statfs(mntbuf, nitems, mountpoint); + if (sb != NULL) { + if (strcmp(sb->f_fstypename, "autofs") != 0) { + log_debugx("unknown filesystem mounted " + "on %s; mounting", mountpoint); + /* + * XXX: Compare options and 'from', + * and update the mount if neccessary. + */ + } else { + log_debugx("autofs already mounted " + "on %s", mountpoint); + free(from); + free(mountpoint); + return; + } + } else { + log_debugx("nothing mounted on %s; mounting", + mountpoint); + } + + mount_autofs(from, mountpoint, n->n_options, n->n_key); + free(from); + free(mountpoint); +} + +static void +mount_unmount(struct node *root) +{ + struct statfs *mntbuf; + struct node *n, *n2, *n3; + int i, nitems; + + nitems = getmntinfo(&mntbuf, MNT_WAIT); + if (nitems <= 0) + log_err(1, "getmntinfo"); + + log_debugx("unmounting stale autofs mounts"); + + for (i = 0; i < nitems; i++) { + if (strcmp(mntbuf[i].f_fstypename, "autofs") != 0) { + log_debugx("skipping %s, filesystem type is not autofs", + mntbuf[i].f_mntonname); + continue; + } + + n = node_find(root, mntbuf[i].f_mntonname); + if (n != NULL) { + log_debugx("leaving autofs mounted on %s", + mntbuf[i].f_mntonname); + continue; + } + + log_debugx("autofs mounted on %s not found " + "in new configuration; unmounting", mntbuf[i].f_mntonname); + unmount_by_statfs(&(mntbuf[i]), false); + } + + log_debugx("mounting new autofs mounts"); + + TAILQ_FOREACH(n, &root->n_children, n_next) { + if (!node_is_direct_map(n)) { + mount_if_not_already(n, n->n_map, mntbuf, nitems); + continue; + } + + TAILQ_FOREACH(n2, &n->n_children, n_next) { + TAILQ_FOREACH(n3, &n2->n_children, n_next) { + mount_if_not_already(n3, n->n_map, + mntbuf, nitems); + } + } + } +} + +static void +unmount_automounted(bool force) +{ + struct statfs *mntbuf; + int i, nitems; + + nitems = getmntinfo(&mntbuf, MNT_WAIT); + if (nitems <= 0) + log_err(1, "getmntinfo"); + + log_debugx("unmounting automounted filesystems"); + + for (i = 0; i < nitems; i++) { + if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) { + log_debugx("skipping %s, filesystem type is autofs", + mntbuf[i].f_mntonname); + continue; + } + + if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) { + log_debugx("skipping %s, not automounted", + mntbuf[i].f_mntonname); + continue; + } + + unmount_by_statfs(&(mntbuf[i]), force); + } +} + +static void +usage_automount(void) +{ + + fprintf(stderr, "usage: automount [-D name=value][-o opts][-Lfuv]\n"); + exit(1); +} + +int +main_automount(int argc, char **argv) +{ + struct node *root; + int ch, debug = 0, show_maps = 0; + char *options = NULL; + bool do_unmount = false, force_unmount = false; + + /* + * Note that in automount(8), the only purpose of variable + * handling is to aid in debugging maps (automount -L). + */ + defined_init(); + + while ((ch = getopt(argc, argv, "D:Lfo:uv")) != -1) { + switch (ch) { + case 'D': + defined_parse_and_add(optarg); + break; + case 'L': + show_maps++; + break; + case 'f': + force_unmount = true; + break; + case 'o': + if (options == NULL) { + options = checked_strdup(optarg); + } else { + options = + separated_concat(options, optarg, ','); + } + break; + case 'u': + do_unmount = true; + break; + case 'v': + debug++; + break; + case '?': + default: + usage_automount(); + } + } + argc -= optind; + if (argc != 0) + usage_automount(); + + if (force_unmount && !do_unmount) + usage_automount(); + + log_init(debug); + + if (do_unmount) { + unmount_automounted(force_unmount); + return (0); + } + + root = node_new_root(); + parse_master(root, AUTO_MASTER_PATH); + + if (show_maps) { + if (options != NULL) { + root->n_options = separated_concat(options, + root->n_options, ','); + } + if (show_maps > 1) { + node_expand_indirect_maps(root); + node_expand_ampersand(root, NULL); + } + node_expand_defined(root); + node_print(root); + return (0); + } + + mount_unmount(root); + + return (0); +} Index: usr.sbin/automountd/automountd.8 =================================================================== --- usr.sbin/automountd/automountd.8 (revision 0) +++ usr.sbin/automountd/automountd.8 (working copy) @@ -0,0 +1,100 @@ +.\" 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 AUTHORS 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 AUTHORS 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$ +.\" +.Dd April 20, 2014 +.Dt AUTOMOUNTD 8 +.Os +.Sh NAME +.Nm automountd +.Nd daemon handling autofs mount requests +.Sh SYNOPSIS +.Nm +.Op Fl D Ar name=value +.Op Fl i +.Op Fl m maxproc +.Op Fl o Ar options +.Op Fl d +.Op Fl v +.Sh DESCRIPTION +The +.Nm +daemon is responsible for handling autofs(4) mount requests, parsing maps, +and mounting appropriate filesystems. +Upon startup the +.Nm +daemon forks into background and waits for kernel requests. +When a request is received, +.Nm +forks a child process, which then parses appropriate map, mounts filesystems +accordingly, and signals the kernel to release blocked processes that were +waiting for mount. +.Bl -tag -width ".Fl v" +.It Fl D +Define a variable. +.It Fl i +For indirect mounts, only create subdirectories if there are no wildcard +entries. +Without this option, +.Nm +will create all the subdirectories it can, and it might not be +immediately obvious to the user that, because of the wildcard map entry, +she can also access a directory that has not yet been created. +.It Fl m +Set limit for the number of forked automountd processes, and thus the number +of mount requests being handled in parallel. +The default is 30. +.It Fl d +Debug mode - increase verbosity and do not daemonize. +.It Fl o +Specify mount options. +Options passed here are prepended to the rest, which means the ones +specified in maps or +.Xr auto_master 5 +file can override them. +.It Fl v +Increase verbosity. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr auto_master 5 , +.Xr autofs 5 , +.Xr automount 8 , +.Xr autounmountd 8 +.Sh HISTORY +The +.Nm +daemon appeared in +.Fx 10.2 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. Index: usr.sbin/automountd/automountd.c =================================================================== --- usr.sbin/automountd/automountd.c (revision 0) +++ usr.sbin/automountd/automountd.c (working copy) @@ -0,0 +1,498 @@ +/*- + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "autofs_ioctl.h" + +#include "common.h" + +#define AUTOMOUNTD_PIDFILE "/var/run/automountd.pid" + +static int nchildren = 0; +static int autofs_fd; +static int request_id; + +static void +done(int request_error) +{ + struct autofs_daemon_done add; + int error; + + memset(&add, 0, sizeof(add)); + add.add_id = request_id; + add.add_error = request_error; + + log_debugx("completing request %d with error %d", + request_id, request_error); + + error = ioctl(autofs_fd, AUTOFSDONE, &add); + if (error != 0) { + /* + * Do this instead of log_err() to avoid calling + * done() again with error, from atexit handler. + */ + log_warn("AUTOFSDONE"); + } + quick_exit(1); +} + +/* + * Remove "fstype=whatever" from optionsp and return the "whatever" part. + */ +static char * +pick_option(const char *option, char **optionsp) +{ + char *tofree, *pair, *newoptions; + char *picked = NULL; + bool first = true; + + tofree = *optionsp; + + newoptions = calloc(strlen(*optionsp) + 1, 1); + if (newoptions == NULL) + log_err(1, "calloc"); + + while ((pair = strsep(optionsp, ",")) != NULL) { + /* + * XXX: strncasecmp(3) perhaps? + */ + if (strncmp(pair, option, strlen(option)) == 0) { + picked = checked_strdup(pair + strlen(option)); + } else { + if (first == false) + strcat(newoptions, ","); + else + first = false; + strcat(newoptions, pair); + } + } + + free(tofree); + *optionsp = newoptions; + + return (picked); +} + +static void +create_subtree(const struct node *node, bool incomplete) +{ + const struct node *child; + char *path; + bool wildcard_found = false; + + /* + * Skip wildcard nodes. + */ + if (strcmp(node->n_key, "*") == 0) + return; + + path = node_path(node); + log_debugx("creating subtree at %s", path); + create_directory(path); + + if (incomplete) { + TAILQ_FOREACH(child, &node->n_children, n_next) { + if (strcmp(child->n_key, "*") == 0) { + wildcard_found = true; + break; + } + } + + if (wildcard_found) { + log_debugx("node %s contains wildcard entry; " + "not creating its subdirectories due to -d flag", + path); + free(path); + return; + } + } + + free(path); + + TAILQ_FOREACH(child, &node->n_children, n_next) + create_subtree(child, incomplete); +} + +static void +exit_callback(void) +{ + + done(EIO); +} + +static void +handle_request(const struct autofs_daemon_request *adr, char *cmdline_options, + bool incomplete_hierarchy) +{ + const char *map; + struct node *root, *parent, *node; + FILE *f; + char *options, *fstype, *retrycnt, *tmp; + int error; + + log_debugx("got request %d: from %s, path %s, prefix \"%s\", " + "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from, + adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options); + + /* + * Try to notify the kernel about any problems. + */ + request_id = adr->adr_id; + atexit(exit_callback); + + if (strncmp(adr->adr_from, "map ", 4) != 0) { + log_errx(1, "invalid mountfrom \"%s\"; failing request", + adr->adr_from); + } + + map = adr->adr_from + 4; /* 4 for strlen("map "); */ + root = node_new_root(); + if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) { + parent = root; + } else { + parent = node_new_map(root, checked_strdup(adr->adr_prefix), + checked_strdup(adr->adr_options), checked_strdup(map), + checked_strdup("[kernel request]"), lineno); + } + parse_map(parent, map, adr->adr_key[0] != '\0' ? adr->adr_key : NULL); + if (adr->adr_key[0] != '\0') + node_expand_wildcard(root, adr->adr_key); + node = node_find(root, adr->adr_path); + if (node == NULL) { + log_errx(1, "map %s does not contain key for \"%s\"; " + "failing mount", map, adr->adr_path); + } + + if (node->n_location == NULL) { + log_debugx("found node defined at %s:%d; not a mountpoint", + node->n_config_file, node->n_config_line); + + /* + * Not a mountpoint; create directories in the autofs mount + * and complete the request. + */ + create_subtree(node, incomplete_hierarchy); + + if (incomplete_hierarchy && adr->adr_key[0] != '\0') { + /* + * We still need to create the single subdirectory + * user is trying to access. + */ + tmp = separated_concat(adr->adr_path, + adr->adr_key, '/'); + node = node_find(root, tmp); + if (node != NULL) + create_subtree(node, false); + } + done(0); + + log_debugx("nothing to mount; exiting"); + + /* + * Exit without calling exit_callback(). + */ + quick_exit(0); + } + + log_debugx("found node defined at %s:%d; it is a mountpoint", + node->n_config_file, node->n_config_line); + + node_expand_ampersand(node, + adr->adr_key[0] != '\0' ? adr->adr_key : NULL); + error = node_expand_defined(node); + if (error != 0) { + log_errx(1, "variable expansion failed for %s; " + "failing mount", adr->adr_path); + } + + options = node_options(node); + + /* + * Prepend options passed via automountd(8) command line. + */ + if (cmdline_options != NULL) + options = separated_concat(cmdline_options, options, ','); + + /* + * Append "automounted". + */ + options = separated_concat(options, "automounted", ','); + + /* + * Figure out fstype. + */ + fstype = pick_option("fstype=", &options); + if (fstype == NULL) { + log_debugx("fstype not specified in options; " + "defaulting to \"nfs\""); + fstype = checked_strdup("nfs"); + } + + if (strcmp(fstype, "nfs") == 0) { + /* + * The mount_nfs(8) command defaults to retry undefinitely. + * We don't want that behaviour, because it leaves mount_nfs(8) + * instances and automountd(8) children hanging forever. + * Disable retries unless the option was passed explicitly. + */ + retrycnt = pick_option("retrycnt=", &options); + if (retrycnt == NULL) { + log_debugx("retrycnt not specified in options; " + "defaulting to 1"); + options = separated_concat(options, + separated_concat("retrycnt", "1", '='), ','); + } else { + options = separated_concat(options, + separated_concat("retrycnt", retrycnt, '='), ','); + } + } + + f = auto_popen("mount", "-t", fstype, "-o", options, + node->n_location, adr->adr_path, NULL); + assert(f != NULL); + error = auto_pclose(f); + if (error != 0) + log_errx(1, "mount failed"); + + done(0); + log_debugx("mount done; exiting"); + + /* + * Exit without calling exit_callback(). + */ + quick_exit(0); +} + +static int +wait_for_children(bool block) +{ + pid_t pid; + int status; + int num = 0; + + for (;;) { + /* + * If "block" is true, wait for at least one process. + */ + if (block && num == 0) + pid = wait4(-1, &status, 0, NULL); + else + pid = wait4(-1, &status, WNOHANG, NULL); + if (pid <= 0) + break; + if (WIFSIGNALED(status)) { + log_warnx("child process %d terminated with signal %d", + pid, WTERMSIG(status)); + } else if (WEXITSTATUS(status) != 0) { + log_warnx("child process %d terminated with exit status %d", + pid, WEXITSTATUS(status)); + } else { + log_debugx("child process %d terminated gracefully", pid); + } + num++; + } + + return (num); +} + +static void +usage_automountd(void) +{ + + fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]" + "[-o opts][-Tidv]\n"); + exit(1); +} + +int +main_automountd(int argc, char **argv) +{ + struct pidfh *pidfh; + pid_t pid, otherpid; + const char *pidfile_path = AUTOMOUNTD_PIDFILE; + char *options = NULL; + struct autofs_daemon_request request; + int ch, debug = 0, error, maxproc = 30, retval, saved_errno; + bool dont_daemonize = false, incomplete_hierarchy = false; + + defined_init(); + + while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) { + switch (ch) { + case 'D': + defined_parse_and_add(optarg); + break; + case 'T': + /* + * For compatibility with other implementations, + * such as OS X. + */ + debug++; + break; + case 'd': + dont_daemonize = true; + debug++; + break; + case 'i': + incomplete_hierarchy = true; + break; + case 'm': + maxproc = atoi(optarg); + break; + case 'o': + if (options == NULL) { + options = checked_strdup(optarg); + } else { + options = + separated_concat(options, optarg, ','); + } + break; + case 'v': + debug++; + break; + case '?': + default: + usage_automountd(); + } + } + argc -= optind; + if (argc != 0) + usage_automountd(); + + log_init(debug); + + pidfh = pidfile_open(pidfile_path, 0600, &otherpid); + if (pidfh == NULL) { + if (errno == EEXIST) { + log_errx(1, "daemon already running, pid: %jd.", + (intmax_t)otherpid); + } + log_err(1, "cannot open or create pidfile \"%s\"", + pidfile_path); + } + + autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC); + if (autofs_fd < 0 && errno == ENOENT) { + saved_errno = errno; + retval = kldload("autofs"); + if (retval != -1) + autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC); + else + errno = saved_errno; + } + if (autofs_fd < 0) + log_err(1, "failed to open %s", AUTOFS_PATH); + + if (dont_daemonize == false) { + if (daemon(0, 0) == -1) { + log_warn("cannot daemonize"); + pidfile_remove(pidfh); + exit(1); + } + } else { + lesser_daemon(); + } + + pidfile_write(pidfh); + + for (;;) { + log_debugx("waiting for request from the kernel"); + + memset(&request, 0, sizeof(request)); + error = ioctl(autofs_fd, AUTOFSREQUEST, &request); + if (error != 0) { + if (errno == EINTR) { + nchildren -= wait_for_children(false); + assert(nchildren >= 0); + continue; + } + + log_err(1, "AUTOFSREQUEST"); + } + + if (dont_daemonize) { + log_debugx("not forking due to -d flag; " + "will exit after servicing a single request"); + } else { + nchildren -= wait_for_children(false); + assert(nchildren >= 0); + + while (maxproc > 0 && nchildren >= maxproc) { + log_debugx("maxproc limit of %d child processes hit; " + "waiting for child process to exit", maxproc); + nchildren -= wait_for_children(true); + assert(nchildren >= 0); + } + log_debugx("got request; forking child process #%d", + nchildren); + nchildren++; + + pid = fork(); + if (pid < 0) + log_err(1, "fork"); + if (pid > 0) + continue; + } + + pidfile_close(pidfh); + handle_request(&request, options, incomplete_hierarchy); + } + + pidfile_close(pidfh); + + return (0); +} + Index: usr.sbin/automountd/autounmountd.8 =================================================================== --- usr.sbin/automountd/autounmountd.8 (revision 0) +++ usr.sbin/automountd/autounmountd.8 (working copy) @@ -0,0 +1,85 @@ +.\" 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 AUTHORS 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 AUTHORS 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$ +.\" +.Dd April 20, 2014 +.Dt AUTOUNMOUNTD 8 +.Os +.Sh NAME +.Nm autounmountd +.Nd daemon unmounting automounted filesystems +.Sh SYNOPSIS +.Nm +.Op Fl d +.Op Fl r time +.Op Fl t time +.Op Fl v +.Sh DESCRIPTION +The +.Nm +daemon is responsible for unmounting filesystems mounted by automountd(8). +Upon startup it retrieves a list of filesystems that have the "automounted" +mount option set. +The list gets refreshed every time a filesystem gets mounted or unmounted. +After specified time passes, +.Nm +attempts to unmount a filesystem, retrying after some time if neccessary. +.Pp +The following options are available: +.Bl -tag -width ".Fl v" +.It Fl d +Debug mode - increase verbosity and do not daemonize. +.It Fl r +Number of seconds to wait before reattempting to unmount expired +filesystem, after the previous attempt failed due to eg. filesystem +being busy. +Default is 600, which amounts to 10 minutes. +.It Fl t +Number of seconds to wait before trying to unmount a filesystem. +Default is 600, which amounts to 10 minutes. +.It Fl v +Increase verbosity. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr auto_master 5 , +.Xr autofs 5 , +.Xr automount 8 , +.Xr automountd 8 +.Sh HISTORY +The +.Nm +daemon appeared in +.Fx 10.2 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. Index: usr.sbin/automountd/autounmountd.c =================================================================== --- usr.sbin/automountd/autounmountd.c (revision 0) +++ usr.sbin/automountd/autounmountd.c (working copy) @@ -0,0 +1,351 @@ +/*- + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define AUTOUNMOUNTD_PIDFILE "/var/run/autounmountd.pid" + +struct automounted_fs { + TAILQ_ENTRY(automounted_fs) af_next; + time_t af_mount_time; + bool af_mark; + fsid_t af_fsid; + char af_mountpoint[MNAMELEN]; +}; + +static TAILQ_HEAD(, automounted_fs) automounted; + +static struct automounted_fs * +automounted_find(fsid_t fsid) +{ + struct automounted_fs *af; + + TAILQ_FOREACH(af, &automounted, af_next) { + if (af->af_fsid.val[0] == fsid.val[0] && + af->af_fsid.val[1] == fsid.val[1]) + return (af); + } + + return (NULL); +} + +static struct automounted_fs * +automounted_add(fsid_t fsid, const char *mountpoint) +{ + struct automounted_fs *af; + + af = calloc(sizeof(*af), 1); + if (af == NULL) + log_err(1, "calloc"); + af->af_mount_time = time(NULL); + af->af_fsid = fsid; + strlcpy(af->af_mountpoint, mountpoint, sizeof(af->af_mountpoint)); + + TAILQ_INSERT_TAIL(&automounted, af, af_next); + + return (af); +} + +static void +automounted_remove(struct automounted_fs *af) +{ + + TAILQ_REMOVE(&automounted, af, af_next); + free(af); +} + +static void +refresh_automounted(void) +{ + struct automounted_fs *af, *tmpaf; + struct statfs *mntbuf; + int i, nitems; + + nitems = getmntinfo(&mntbuf, MNT_WAIT); + if (nitems <= 0) + log_err(1, "getmntinfo"); + + log_debugx("refreshing list of automounted filesystems"); + + TAILQ_FOREACH(af, &automounted, af_next) + af->af_mark = false; + + for (i = 0; i < nitems; i++) { + if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) { + log_debugx("skipping %s, filesystem type is autofs", + mntbuf[i].f_mntonname); + continue; + } + + if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) { + log_debugx("skipping %s, not automounted", + mntbuf[i].f_mntonname); + continue; + } + + af = automounted_find(mntbuf[i].f_fsid); + if (af == NULL) { + log_debugx("new automounted filesystem found on %s " + "(FSID:%d:%d)", mntbuf[i].f_mntonname, + mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]); + af = automounted_add(mntbuf[i].f_fsid, + mntbuf[i].f_mntonname); + } else { + log_debugx("already known automounted filesystem " + "found on %s (FSID:%d:%d)", mntbuf[i].f_mntonname, + mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]); + } + af->af_mark = true; + } + + TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) { + if (af->af_mark) + continue; + log_debugx("lost filesystem mounted on %s (FSID:%d:%d)", + af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1]); + automounted_remove(af); + } +} + +static int +unmount_by_fsid(const fsid_t fsid, const char *mountpoint) +{ + char *fsid_str; + int error, ret; + + ret = asprintf(&fsid_str, "FSID:%d:%d", fsid.val[0], fsid.val[1]); + if (ret < 0) + log_err(1, "asprintf"); + + error = unmount(fsid_str, MNT_BYFSID); + if (error != 0) { + if (errno == EBUSY) { + log_debugx("cannot unmount %s (%s): %s", + mountpoint, fsid_str, strerror(errno)); + } else { + log_warn("cannot unmount %s (%s)", + mountpoint, fsid_str); + } + } + + free(fsid_str); + + return (error); +} + +static double +expire_automounted(double expiration_time) +{ + struct automounted_fs *af, *tmpaf; + time_t now; + double mounted_for, mounted_max = 0; + int error; + bool unmounted = false; + + now = time(NULL); + + log_debugx("expiring automounted filesystems"); + + TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) { + mounted_for = difftime(now, af->af_mount_time); + + if (mounted_for < expiration_time) { + log_debugx("skipping %s (FSID:%d:%d), mounted " + "for %.0f seconds", af->af_mountpoint, + af->af_fsid.val[0], af->af_fsid.val[1], + mounted_for); + + if (mounted_for > mounted_max) + mounted_max = mounted_for; + + continue; + } + + log_debugx("filesystem mounted on %s (FSID:%d:%d), " + "was mounted for %.0f seconds; unmounting", + af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1], + mounted_for); + error = unmount_by_fsid(af->af_fsid, af->af_mountpoint); + if (error != 0) { + if (mounted_for > mounted_max) + mounted_max = mounted_for; + } else { + unmounted = true; + } + } + + if (unmounted) { + /* + * Successful unmount of a filesystem could unbusy its parent + * filesystem that can now be unmounted. + */ + log_debugx("filesystem got unmounted; go around"); + return (expire_automounted(expiration_time)); + } + + return (mounted_max); +} + +static void +usage_autounmountd(void) +{ + + fprintf(stderr, "usage: autounmountd [-r time][-t time][-dv]\n"); + exit(1); +} + +static void +do_wait(int kq, double sleep_time) +{ + struct timespec timeout; + struct kevent unused; + int error; + + assert(sleep_time > 0); + timeout.tv_sec = sleep_time; + timeout.tv_nsec = 0; + + log_debugx("waiting for filesystem event for %.0f seconds", sleep_time); + error = kevent(kq, NULL, 0, &unused, 1, &timeout); + if (error < 0) + log_err(1, "kevent"); + + if (error == 0) + log_debugx("timeout reached"); + else + log_debugx("got filesystem event"); +} + +int +main_autounmountd(int argc, char **argv) +{ + struct kevent event; + struct pidfh *pidfh; + pid_t otherpid; + const char *pidfile_path = AUTOUNMOUNTD_PIDFILE; + int ch, debug = 0, error, kq; + double expiration_time = 600, retry_time = 600, mounted_max, sleep_time; + bool dont_daemonize = false; + + while ((ch = getopt(argc, argv, "dr:t:v")) != -1) { + switch (ch) { + case 'd': + dont_daemonize = true; + debug++; + break; + case 'r': + retry_time = atoi(optarg); + break; + case 't': + expiration_time = atoi(optarg); + break; + case 'v': + debug++; + break; + case '?': + default: + usage_autounmountd(); + } + } + argc -= optind; + if (argc != 0) + usage_autounmountd(); + + if (retry_time <= 0) + log_errx(1, "retry time must be greater than zero"); + if (expiration_time <= 0) + log_errx(1, "expiration time must be greater than zero"); + + log_init(debug); + + pidfh = pidfile_open(pidfile_path, 0600, &otherpid); + if (pidfh == NULL) { + if (errno == EEXIST) { + log_errx(1, "daemon already running, pid: %jd.", + (intmax_t)otherpid); + } + log_err(1, "cannot open or create pidfile \"%s\"", + pidfile_path); + } + + if (dont_daemonize == false) { + if (daemon(0, 0) == -1) { + log_warn("cannot daemonize"); + pidfile_remove(pidfh); + exit(1); + } + } + + pidfile_write(pidfh); + + TAILQ_INIT(&automounted); + + kq = kqueue(); + if (kq < 0) + log_err(1, "kqueue"); + + EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, NULL); + error = kevent(kq, &event, 1, NULL, 0, NULL); + if (error < 0) + log_err(1, "kevent"); + + for (;;) { + refresh_automounted(); + mounted_max = expire_automounted(expiration_time); + if (mounted_max < expiration_time) { + sleep_time = difftime(expiration_time, mounted_max); + log_debugx("some filesystems expire in %.0f seconds", + sleep_time); + } else { + sleep_time = retry_time; + log_debugx("some expired filesystems remain mounted, " + "will retry in %.0f seconds", sleep_time); + } + + do_wait(kq, sleep_time); + } + + return (0); +} Index: usr.sbin/automountd/common.c =================================================================== --- usr.sbin/automountd/common.c (revision 0) +++ usr.sbin/automountd/common.c (working copy) @@ -0,0 +1,1115 @@ +/*- + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "autofs_ioctl.h" + +#include "common.h" + +extern FILE *yyin; +extern char *yytext; +extern int yylex(void); + +static void parse_master_yyin(struct node *root, const char *master); +static void parse_map_yyin(struct node *parent, const char *map, + const char *executable_key); + +char * +checked_strdup(const char *s) +{ + char *c; + + assert(s != NULL); + + c = strdup(s); + if (c == NULL) + log_err(1, "strdup"); + return (c); +} + +/* + * Take two pointers to strings, concatenate the contents with "/" in the + * middle, make the first pointer point to the result, the second pointer + * to NULL, and free the old strings. + * + * Concatenate pathnames, basically. + */ +static void +concat(char **p1, char **p2) +{ + int ret; + char *path; + + assert(p1 != NULL); + assert(p2 != NULL); + + if (*p1 == NULL) + *p1 = checked_strdup(""); + + if (*p2 == NULL) + *p2 = checked_strdup(""); + + ret = asprintf(&path, "%s/%s", *p1, *p2); + if (ret < 0) + log_err(1, "asprintf"); + + /* + * XXX + */ + //free(*p1); + //free(*p2); + + *p1 = path; + *p2 = NULL; +} + +/* + * Concatenate two strings, inserting separator between them, unless not needed. + * + * This function is very convenient to use when you don't care about freeing + * memory, which we generally don't - because we're a short running process. + */ +char * +separated_concat(const char *s1, const char *s2, char separator) +{ + char *result; + int ret; + + assert(s1 != NULL); + assert(s2 != NULL); + + if (s1[0] == '\0' || s2[0] == '\0' || + s1[strlen(s1) - 1] == separator || s2[0] == separator) { + ret = asprintf(&result, "%s%s", s1, s2); + } else { + ret = asprintf(&result, "%s%c%s", s1, separator, s2); + } + if (ret < 0) + log_err(1, "asprintf"); + + //log_debugx("separated_concat: got %s and %s, returning %s", s1, s2, result); + + return (result); +} + +void +create_directory(const char *path) +{ + char *component, *copy, *tofree, *partial; + int error; + + assert(path[0] == '/'); + + /* + * +1 to skip the leading slash. + */ + copy = tofree = checked_strdup(path + 1); + + partial = NULL; + for (;;) { + component = strsep(©, "/"); + if (component == NULL) + break; + concat(&partial, &component); + //log_debugx("checking \"%s\" for existence", partial); + error = access(partial, F_OK); + if (error == 0) + continue; + if (errno != ENOENT) + log_err(1, "cannot access %s", partial); + log_debugx("directory %s does not exist, creating", + partial); + error = mkdir(partial, 0755); + if (error != 0) + log_err(1, "cannot create %s", partial); + } + + free(tofree); +} + +struct node * +node_new_root(void) +{ + struct node *n; + + n = calloc(1, sizeof(*n)); + if (n == NULL) + log_err(1, "calloc"); + // XXX + n->n_key = checked_strdup("/"); + n->n_options = checked_strdup(""); + + TAILQ_INIT(&n->n_children); + + return (n); +} + +struct node * +node_new(struct node *parent, char *key, char *options, char *location, + const char *config_file, int config_line) +{ + struct node *n; + + n = calloc(1, sizeof(*n)); + if (n == NULL) + log_err(1, "calloc"); + + TAILQ_INIT(&n->n_children); + assert(key != NULL); + n->n_key = key; + if (options != NULL) + n->n_options = options; + else + n->n_options = strdup(""); + n->n_location = location; + assert(config_file != NULL); + n->n_config_file = config_file; + assert(config_line >= 0); + n->n_config_line = config_line; + + assert(parent != NULL); + n->n_parent = parent; + TAILQ_INSERT_TAIL(&parent->n_children, n, n_next); + + return (n); +} + +struct node * +node_new_map(struct node *parent, char *key, char *options, char *map, + const char *config_file, int config_line) +{ + struct node *n; + + n = calloc(1, sizeof(*n)); + if (n == NULL) + log_err(1, "calloc"); + + TAILQ_INIT(&n->n_children); + assert(key != NULL); + n->n_key = key; + if (options != NULL) + n->n_options = options; + else + n->n_options = strdup(""); + n->n_map = map; + assert(config_file != NULL); + n->n_config_file = config_file; + assert(config_line >= 0); + n->n_config_line = config_line; + + assert(parent != NULL); + n->n_parent = parent; + TAILQ_INSERT_TAIL(&parent->n_children, n, n_next); + + return (n); +} + +static struct node * +node_duplicate(const struct node *o, struct node *parent) +{ + const struct node *child; + struct node *n; + + if (parent == NULL) + parent = o->n_parent; + + n = node_new(parent, o->n_key, o->n_options, o->n_location, + o->n_config_file, o->n_config_line); + + TAILQ_FOREACH(child, &o->n_children, n_next) + node_duplicate(child, n); + + return (n); +} + +static void +node_delete(struct node *n) +{ + struct node *child, *tmp; + + assert (n != NULL); + + TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) + node_delete(child); + + if (n->n_parent != NULL) + TAILQ_REMOVE(&n->n_parent->n_children, n, n_next); + + free(n); +} + +/* + * Move (reparent) node 'n' to make it sibling of 'previous', placed + * just after it. + */ +static void +node_move_after(struct node *n, struct node *previous) +{ + + TAILQ_REMOVE(&n->n_parent->n_children, n, n_next); + n->n_parent = previous->n_parent; + TAILQ_INSERT_AFTER(&previous->n_parent->n_children, previous, n, n_next); +} + +static void +node_expand_includes(struct node *root, bool is_master) +{ + struct node *n, *n2, *tmp, *tmp2, *tmproot; + int error; + + TAILQ_FOREACH_SAFE(n, &root->n_children, n_next, tmp) { + if (n->n_key[0] != '+') + continue; + + error = access(AUTO_INCLUDE_PATH, F_OK); + if (error != 0) { + log_errx(1, "directory services not configured; " + "%s does not exist", AUTO_INCLUDE_PATH); + } + + /* + * "+1" to skip leading "+". + */ + yyin = auto_popen(AUTO_INCLUDE_PATH, n->n_key + 1, NULL); + assert(yyin != NULL); + + tmproot = node_new_root(); + if (is_master) + parse_master_yyin(tmproot, n->n_key); + else + parse_map_yyin(tmproot, n->n_key, NULL); + + error = auto_pclose(yyin); + yyin = NULL; + if (error != 0) { + log_errx(1, "failed to handle include \"%s\"", + n->n_key); + } + + /* + * Entries to be included are now in tmproot. We need to merge + * them with the rest, preserving their place and ordering. + */ + TAILQ_FOREACH_REVERSE_SAFE(n2, + &tmproot->n_children, nodehead, n_next, tmp2) { + node_move_after(n2, n); + } + + node_delete(n); + node_delete(tmproot); + } +} + +static char * +expand_ampersand(char *string, const char *key) +{ + char c, *expanded; + int i, ret, before_len = 0; + bool backslashed = false; + + assert(key[0] != '\0'); + + expanded = checked_strdup(string); + + for (i = 0; string[i] != '\0'; i++) { + c = string[i]; + if (c == '\\' && backslashed == false) { + backslashed = true; + continue; + } + if (backslashed) { + backslashed = false; + continue; + } + backslashed = false; + if (c != '&') + continue; + + /* + * The 'before_len' variable contains the number + * of characters before the '&'. + */ + before_len = i; + //assert(i + 1 < (int)strlen(string)); + + ret = asprintf(&expanded, "%.*s%s%s", + before_len, string, key, string + before_len + 1); + if (ret < 0) + log_err(1, "asprintf"); + + //log_debugx("\"%s\" expanded with key \"%s\" to \"%s\"", string, key, expanded); + + /* + * Figure out where to start searching for next variable. + */ + string = expanded; + i = before_len + strlen(key); + backslashed = false; + //assert(i < (int)strlen(string)); + } + + return (expanded); +} + +/* + * Expand "&" in n_location. If the key is NULL, try to use + * key from map entries themselves. Keep in mind that maps + * consist of tho levels of node structures, the key is one + * level up. + * + * Variant with NULL key is for "automount -LL". + */ +void +node_expand_ampersand(struct node *n, const char *key) +{ + struct node *child; + + if (n->n_location != NULL) { + if (key == NULL) { + if (n->n_parent != NULL && + strcmp(n->n_parent->n_key, "*") != 0) { + n->n_location = expand_ampersand(n->n_location, + n->n_parent->n_key); + } + } else { + n->n_location = expand_ampersand(n->n_location, key); + } + } + + TAILQ_FOREACH(child, &n->n_children, n_next) + node_expand_ampersand(child, key); +} + +/* + * Expand "*" in n_key. + */ +void +node_expand_wildcard(struct node *n, const char *key) +{ + struct node *child, *expanded; + + assert(key != NULL); + + if (strcmp(n->n_key, "*") == 0) { + expanded = node_duplicate(n, NULL); + expanded->n_key = checked_strdup(key); + node_move_after(expanded, n); + } + + TAILQ_FOREACH(child, &n->n_children, n_next) + node_expand_wildcard(child, key); +} + +int +node_expand_defined(struct node *n) +{ + struct node *child; + int error, cumulated_error = 0; + + if (n->n_location != NULL) { + n->n_location = defined_expand(n->n_location); + if (n->n_location == NULL) { + log_warnx("failed to expand location for %s", + node_path(n)); + return (EINVAL); + } + } + + TAILQ_FOREACH(child, &n->n_children, n_next) { + error = node_expand_defined(child); + if (error != 0 && cumulated_error == 0) + cumulated_error = error; + } + + return (cumulated_error); +} + +bool +node_is_direct_map(const struct node *n) +{ + + for (;;) { + assert(n->n_parent != NULL); + if (n->n_parent->n_parent == NULL) + break; + n = n->n_parent; + } + + assert(n->n_key != NULL); + if (strcmp(n->n_key, "/-") != 0) + return (false); + + return (true); +} + +static void +node_expand_maps(struct node *n, bool indirect) +{ + struct node *child, *tmp; + + TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) { + if (node_is_direct_map(child)) { + if (indirect) + continue; + } else { + if (indirect == false) + continue; + } + + /* + * This is the first-level map node; the one that contains + * the key and subnodes with mountpoints and actual map names. + */ + if (child->n_map == NULL) + continue; + + if (indirect) { + log_debugx("map \"%s\" is an indirect map, parsing", + child->n_map); + } else { + log_debugx("map \"%s\" is a direct map, parsing", + child->n_map); + } + parse_map(child, child->n_map, NULL); + } +} + +static void +node_expand_direct_maps(struct node *n) +{ + + node_expand_maps(n, false); +} + +void +node_expand_indirect_maps(struct node *n) +{ + + node_expand_maps(n, true); +} + +static char * +node_path_x(const struct node *n, char *x) +{ + char *path; + size_t len; + + if (n->n_parent == NULL) + return (x); + + /* + * Return "/-" for direct maps only if we were asked for path + * to the "/-" node itself, not to any of its subnodes. + */ + if (n->n_parent->n_parent == NULL && + strcmp(n->n_key, "/-") == 0 && + x[0] != '\0') { + return (x); + } + + path = separated_concat(n->n_key, x, '/'); + free(x); + + /* + * Strip trailing slash. + */ + len = strlen(path); + assert(len > 0); + if (path[len - 1] == '/') + path[len - 1] = '\0'; + + return (node_path_x(n->n_parent, path)); +} + +/* + * Return full path for node, consisting of concatenated + * paths of node itself and all its parents, up to the root. + */ +char * +node_path(const struct node *n) +{ + + return (node_path_x(n, checked_strdup(""))); +} + +static char * +node_options_x(const struct node *n, char *x) +{ + char *options; + + options = separated_concat(x, n->n_options, ','); + if (n->n_parent == NULL) + return (options); + + return (node_options_x(n->n_parent, options)); +} + +/* + * Return options for node, consisting of concatenated + * options from the node itself and all its parents, + * up to the root. + */ +char * +node_options(const struct node *n) +{ + + return (node_options_x(n, checked_strdup(""))); +} + +static void +node_print_indent(const struct node *n, int indent) +{ + const struct node *child, *first_child; + char *path, *options; + + path = node_path(n); + options = node_options(n); + + /* + * Do not show both parent and child node if they have the same + * mountpoint; only show the child node. This means the typical, + * "key location", map entries are shown in a single line; + * the "key mountpoint1 location2 mountpoint2 location2" entries + * take multiple lines. + */ + first_child = TAILQ_FIRST(&n->n_children); + if (first_child == NULL || TAILQ_NEXT(first_child, n_next) != NULL || + strcmp(path, node_path(first_child)) != 0) { + assert(n->n_location == NULL || n->n_map == NULL); + printf("%*.s%-*s %s%-*s %-*s # %s map %s at %s:%d\n", + indent, "", + 25 - indent, + path, + options[0] != '\0' ? "-" : " ", + 20, + options[0] != '\0' ? options : "", + 20, + n->n_location != NULL ? n->n_location : n->n_map != NULL ? n->n_map : "", + node_is_direct_map(n) ? "direct" : "indirect", + indent == 0 ? "referenced" : "defined", + n->n_config_file, n->n_config_line); + } + + free(path); + free(options); + + TAILQ_FOREACH(child, &n->n_children, n_next) + node_print_indent(child, indent + 2); +} + +void +node_print(const struct node *n) +{ + const struct node *child; + + TAILQ_FOREACH(child, &n->n_children, n_next) + node_print_indent(child, 0); +} + +struct node * +node_find(struct node *node, const char *path) +{ + struct node *child, *found; + char *tmp; + + //log_debugx("looking up %s in %s", path, node->n_key); + + tmp = node_path(node); + if (strncmp(tmp, path, strlen(tmp)) != 0) { + free(tmp); + return (NULL); + } + free(tmp); + + TAILQ_FOREACH(child, &node->n_children, n_next) { + found = node_find(child, path); + if (found != NULL) + return (found); + } + + return (node); +} + +/* + * Canonical form of a map entry looks like this: + * + * key [-options] [ [/mountpoint] [-options2] location ... ] + * + * Entries for executable maps are slightly different, as they + * lack the 'key' field and are always single-line; the key field + * for those maps is taken from 'executable_key' argument. + * + * We parse it in such a way that a map always has two levels - first + * for key, and the second, for the mountpoint. + */ +static void +parse_map_yyin(struct node *parent, const char *map, const char *executable_key) +{ + char *key = NULL, *options = NULL, *mountpoint = NULL, + *options2 = NULL, *location = NULL; + int ret; + struct node *node; + + lineno = 1; + + if (executable_key != NULL) + key = checked_strdup(executable_key); + + for (;;) { + ret = yylex(); + if (ret == 0 || ret == NEWLINE) { + if (key != NULL || options != NULL) { + log_errx(1, "truncated entry at %s, line %d", + map, lineno); + } + if (ret == 0 || executable_key != NULL) { + /* + * End of file. + */ + break; + } else { + key = options = NULL; + continue; + } + } + if (key == NULL) { + key = checked_strdup(yytext); + if (key[0] == '+') { + node_new(parent, key, NULL, NULL, map, lineno); + key = options = NULL; + continue; + } + continue; + } else if (yytext[0] == '-') { + if (options != NULL) { + log_errx(1, "duplicated options at %s, line %d", + map, lineno); + } + /* + * +1 to skip leading "-". + */ + options = checked_strdup(yytext + 1); + continue; + } + + /* + * We can't properly handle a situation where the map key + * is "/". Ignore such entries. + * + * XXX: According to Piete Brooks, Linux automounter uses + * "/" as a wildcard character in LDAP maps. Perhaps + * we should work around this braindamage by substituting + * "*" for "/"? + */ + if (strcmp(key, "/") == 0) { + log_warnx("nonsensical map key \"/\" at %s, line %d; " + "ignoring map entry ", map, lineno); + + /* + * Skip the rest of the entry. + */ + do { + ret = yylex(); + } while (ret != 0 && ret != NEWLINE); + + key = options = NULL; + continue; + } + + //log_debugx("adding map node, %s", key); + node = node_new(parent, key, options, NULL, map, lineno); + key = options = NULL; + + for (;;) { + if (yytext[0] == '/') { + if (mountpoint != NULL) { + log_errx(1, "duplicated mountpoint " + "in %s, line %d", map, lineno); + } + if (options2 != NULL || location != NULL) { + log_errx(1, "mountpoint out of order " + "in %s, line %d", map, lineno); + } + mountpoint = checked_strdup(yytext); + goto again; + } + + if (yytext[0] == '-') { + if (options2 != NULL) { + log_errx(1, "duplicated options " + "in %s, line %d", map, lineno); + } + if (location != NULL) { + log_errx(1, "options out of order " + "in %s, line %d", map, lineno); + } + options2 = checked_strdup(yytext + 1); + goto again; + } + + if (location != NULL) { + log_errx(1, "too many arguments " + "in %s, line %d", map, lineno); + } + location = checked_strdup(yytext); + + if (mountpoint == NULL) + mountpoint = checked_strdup("/"); + if (options2 == NULL) + options2 = checked_strdup(""); + +#if 0 + log_debugx("adding map node, %s %s %s", + mountpoint, options2, location); +#endif + node_new(node, mountpoint, options2, location, + map, lineno); + mountpoint = options2 = location = NULL; +again: + ret = yylex(); + if (ret == 0 || ret == NEWLINE) { + if (mountpoint != NULL || options2 != NULL || + location != NULL) { + log_errx(1, "truncated entry " + "in %s, line %d", map, lineno); + } + break; + } + } + } +} + +static bool +file_is_executable(const char *path) +{ + struct stat sb; + int error; + + error = stat(path, &sb); + if (error != 0) + log_err(1, "cannot stat %s", path); + if ((sb.st_mode & S_IXUSR) || (sb.st_mode & S_IXGRP) || + (sb.st_mode & S_IXOTH)) + return (true); + return (false); +} + +/* + * Parse a special map, eg. "-hosts". + */ +static void +parse_special_map(struct node *parent, const char *map, const char *key) +{ + char *path; + int error, ret; + + assert(map[0] == '-'); + + if (key == NULL) { + log_debugx("skipping map %s due to forced -nobrowse", map); + return; + } + + /* + * +1 to skip leading "-" in map name. + */ + ret = asprintf(&path, "%s/special_%s", AUTO_SPECIAL_PREFIX, map + 1); + if (ret < 0) + log_err(1, "asprintf"); + + yyin = auto_popen(path, key, NULL); + assert(yyin != NULL); + + parse_map_yyin(parent, map, key); + + error = auto_pclose(yyin); + yyin = NULL; + if (error != 0) + log_errx(1, "failed to handle special map \"%s\"", map); + + node_expand_includes(parent, false); + node_expand_direct_maps(parent); + + free(path); +} + +/* + * Retrieve and parse map from directory services, eg. LDAP. + * Note that it's different from executable maps, in that + * the include script outputs the whole map to standard output + * (as opposed to executable maps that only output a single + * entry, without the key), and it takes the map name as an + * argument, instead of key. + */ +static void +parse_included_map(struct node *parent, const char *map) +{ + int error; + + assert(map[0] != '-'); + assert(map[0] != '/'); + + error = access(AUTO_INCLUDE_PATH, F_OK); + if (error != 0) { + log_errx(1, "directory services not configured;" + " %s does not exist", AUTO_INCLUDE_PATH); + } + + yyin = auto_popen(AUTO_INCLUDE_PATH, map, NULL); + assert(yyin != NULL); + + parse_map_yyin(parent, map, NULL); + + error = auto_pclose(yyin); + yyin = NULL; + if (error != 0) + log_errx(1, "failed to handle remote map \"%s\"", map); + + node_expand_includes(parent, false); + node_expand_direct_maps(parent); +} + +void +parse_map(struct node *parent, const char *map, const char *key) +{ + char *path = NULL; + int error, ret; + bool executable; + + assert(map != NULL); + assert(map[0] != '\0'); + + log_debugx("parsing map \"%s\"", map); + + if (map[0] == '-') + return (parse_special_map(parent, map, key)); + + if (map[0] == '/') { + path = checked_strdup(map); + } else { + ret = asprintf(&path, "%s/%s", AUTO_MAP_PREFIX, map); + if (ret < 0) + log_err(1, "asprintf"); + log_debugx("map \"%s\" maps to \"%s\"", map, path); + + /* + * See if the file exists. If not, try to obtain the map + * from directory services. + */ + error = access(path, F_OK); + if (error != 0) { + log_debugx("map file \"%s\" does not exist; falling " + "back to directory services", path); + return (parse_included_map(parent, map)); + } + } + + executable = file_is_executable(path); + + if (executable) { + log_debugx("map \"%s\" is executable", map); + + if (key != NULL) { + yyin = auto_popen(path, key, NULL); + } else { + yyin = auto_popen(path, NULL); + } + assert(yyin != NULL); + } else { + yyin = fopen(path, "r"); + if (yyin == NULL) + log_err(1, "unable to open \"%s\"", path); + } + + free(path); + path = NULL; + + parse_map_yyin(parent, map, executable ? key : NULL); + + if (executable) { + error = auto_pclose(yyin); + yyin = NULL; + if (error != 0) { + log_errx(1, "failed to handle executable map \"%s\"", + map); + } + } else { + fclose(yyin); + } + yyin = NULL; + + log_debugx("done parsing map \"%s\"", map); + + node_expand_includes(parent, false); + node_expand_direct_maps(parent); +} + +static void +parse_master_yyin(struct node *root, const char *master) +{ + char *mountpoint = NULL, *map = NULL, *options = NULL; + int ret; + + /* + * XXX: 1 gives incorrect values; wtf? + */ + lineno = 0; + + for (;;) { + ret = yylex(); + if (ret == 0 || ret == NEWLINE) { + if (mountpoint != NULL) { + //log_debugx("adding map for %s", mountpoint); + node_new_map(root, mountpoint, options, map, + master, lineno); + } + if (ret == 0) { + break; + } else { + mountpoint = map = options = NULL; + continue; + } + } + if (mountpoint == NULL) { + mountpoint = checked_strdup(yytext); + } else if (map == NULL) { + map = checked_strdup(yytext); + } else if (options == NULL) { + /* + * +1 to skip leading "-". + */ + options = checked_strdup(yytext + 1); + } else { + log_errx(1, "too many arguments at %s, line %d", + master, lineno); + } + } +} + +void +parse_master(struct node *root, const char *master) +{ + + log_debugx("parsing auto_master file at \"%s\"", master); + + yyin = fopen(master, "r"); + if (yyin == NULL) + err(1, "unable to open %s", master); + + parse_master_yyin(root, master); + + fclose(yyin); + yyin = NULL; + + log_debugx("done parsing \"%s\"", master); + + node_expand_includes(root, true); + node_expand_direct_maps(root); +} + +/* + * Two things daemon(3) does, that we actually also want to do + * when running in foreground, is closing the stdin and chdiring + * to "/". This is what we do here. + */ +void +lesser_daemon(void) +{ + int error, fd; + + error = chdir("/"); + if (error != 0) + log_warn("chdir"); + + fd = open(_PATH_DEVNULL, O_RDWR, 0); + if (fd < 0) { + log_warn("cannot open %s", _PATH_DEVNULL); + return; + } + + error = dup2(fd, STDIN_FILENO); + if (error != 0) + log_warn("dup2"); + + error = close(fd); + if (error != 0) { + /* Bloody hell. */ + log_warn("close"); + } +} + +int +main(int argc, char **argv) +{ + char *cmdname; + + if (argv[0] == NULL) + log_errx(1, "NULL command name"); + + cmdname = basename(argv[0]); + + if (strcmp(cmdname, "automount") == 0) + return (main_automount(argc, argv)); + else if (strcmp(cmdname, "automountd") == 0) + return (main_automountd(argc, argv)); + else if (strcmp(cmdname, "autounmountd") == 0) + return (main_autounmountd(argc, argv)); + else + log_errx(1, "binary name should be either \"automount\", " + "\"automountd\", or \"autounmountd\""); +} Index: usr.sbin/automountd/common.h =================================================================== --- usr.sbin/automountd/common.h (revision 0) +++ usr.sbin/automountd/common.h (working copy) @@ -0,0 +1,110 @@ +/*- + * 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 AUTOMOUNTD_H +#define AUTOMOUNTD_H + +#include +#include + +#define AUTO_MASTER_PATH "/etc/auto_master" +#define AUTO_MAP_PREFIX "/etc" +#define AUTO_SPECIAL_PREFIX "/etc/autofs" +#define AUTO_INCLUDE_PATH AUTO_SPECIAL_PREFIX "/include" + +struct node { + TAILQ_ENTRY(node) n_next; + TAILQ_HEAD(nodehead, node) n_children; + struct node *n_parent; + char *n_key; + char *n_options; + char *n_location; + char *n_map; + const char *n_config_file; + int n_config_line; +}; + +struct defined_value { + TAILQ_ENTRY(defined_value) d_next; + char *d_name; + char *d_value; +}; + +void log_init(int level); +void log_set_peer_name(const char *name); +void log_set_peer_addr(const char *addr); +void log_err(int, const char *, ...) + __dead2 __printf0like(2, 3); +void log_errx(int, const char *, ...) + __dead2 __printf0like(2, 3); +void log_warn(const char *, ...) __printf0like(1, 2); +void log_warnx(const char *, ...) __printflike(1, 2); +void log_debugx(const char *, ...) __printf0like(1, 2); + +char *checked_strdup(const char *); +char *separated_concat(const char *s1, const char *s2, char separator); +void create_directory(const char *path); + +struct node *node_new_root(void); +struct node *node_new(struct node *parent, char *key, char *options, char *location, const char *config_file, int config_line); +struct node *node_new_map(struct node *parent, char *key, char *options, char *map, const char *config_file, int config_line); +struct node *node_find(struct node *root, const char *mountpoint); +bool node_is_direct_map(const struct node *n); +char *node_path(const struct node *n); +char *node_options(const struct node *n); +void node_expand_ampersand(struct node *root, const char *key); +void node_expand_wildcard(struct node *root, const char *key); +int node_expand_defined(struct node *root); +void node_expand_indirect_maps(struct node *n); +void node_print(const struct node *n); +void parse_master(struct node *root, const char *path); +void parse_map(struct node *parent, const char *map, const char *args); +char *defined_expand(const char *string); +void defined_init(void); +void defined_parse_and_add(char *def); +void lesser_daemon(void); + +int main_automount(int argc, char **argv); +int main_automountd(int argc, char **argv); +int main_autounmountd(int argc, char **argv); + +FILE *auto_popen(const char *argv0, ...); +int auto_pclose(FILE *iop); + +/* + * lex(1) stuff. + */ +extern int lineno; + +#define STR 1 +#define NEWLINE 2 + +#endif /* !AUTOMOUNTD_H */ Index: usr.sbin/automountd/defined.c =================================================================== --- usr.sbin/automountd/defined.c (revision 0) +++ usr.sbin/automountd/defined.c (working copy) @@ -0,0 +1,270 @@ +/*- + * 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$ + */ + +/* + * All the "defined" stuff is for handling variables, + * such as ${OSNAME}, in maps. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" + +static TAILQ_HEAD(, defined_value) defined_values; + +static const char * +defined_find(const char *name) +{ + struct defined_value *d; + + TAILQ_FOREACH(d, &defined_values, d_next) { + if (strcmp(d->d_name, name) == 0) + return (d->d_value); + } + + return (NULL); +} + +char * +defined_expand(const char *string) +{ + const char *value; + char c, *expanded, *name; + int i, ret, before_len = 0, name_off = 0, name_len = 0, after_off = 0; + bool backslashed = false, bracketed = false; + + expanded = checked_strdup(string); + + for (i = 0; string[i] != '\0'; i++) { + c = string[i]; + if (c == '\\' && backslashed == false) { + backslashed = true; + continue; + } + if (backslashed) { + backslashed = false; + continue; + } + backslashed = false; + if (c != '$') + continue; + + /* + * The 'before_len' variable contains the number + * of characters before the '$'. + */ + before_len = i; + assert(i + 1 < (int)strlen(string)); + if (string[i + 1] == '{') + bracketed = true; + + if (string[i + 1] == '\0') { + log_warnx("truncated variable"); + return (NULL); + } + + /* + * Skip '$'. + */ + i++; + + if (bracketed) { + if (string[i + 1] == '\0') { + log_warnx("truncated variable"); + return (NULL); + } + + /* + * Skip '{'. + */ + i++; + } + + /* + * The 'name_off' variable contains the number + * of characters before the variable name, + * including the "$" or "${". + */ + name_off = i; + + for (; string[i] != '\0'; i++) { + c = string[i]; + /* + * XXX: Decide on the set of characters that can be + * used in a variable name. + */ + if (isalnum(c) || c == '_') + continue; + + /* + * End of variable name. + */ + if (bracketed) { + if (c != '}') + continue; + + /* + * The 'after_off' variable contains the number + * of characters before the rest of the string, + * i.e. after the variable name. + */ + after_off = i + 1; + assert(i > 1); + assert(i - 1 > name_off); + name_len = i - name_off; + break; + } + + after_off = i; + assert(i > 1); + assert(i > name_off); + name_len = i - name_off; + break; + } + + name = strndup(string + name_off, name_len); + if (name == NULL) + log_err(1, "strndup"); + value = defined_find(name); + if (value == NULL) { + log_warnx("undefined variable ${%s}", name); + return (NULL); + } + + /* + * Concatenate it back. + */ + ret = asprintf(&expanded, "%.*s%s%s", + before_len, string, value, string + after_off); + if (ret < 0) + log_err(1, "asprintf"); + + //log_debugx("\"%s\" expanded to \"%s\"", string, expanded); + free(name); + + /* + * Figure out where to start searching for next variable. + */ + string = expanded; + i = before_len + strlen(value); + backslashed = bracketed = false; + before_len = name_off = name_len = after_off = 0; + assert(i <= (int)strlen(string)); + } + + if (before_len != 0 || name_off != 0 || name_len != 0 || after_off != 0) { + log_warnx("truncated variable"); + return (NULL); + } + + return (expanded); +} + +static void +defined_add(const char *name, const char *value) +{ + struct defined_value *d; + const char *found; + + found = defined_find(name); + if (found != NULL) + log_errx(1, "variable %s already defined", name); + + log_debugx("defining variable %s=%s", name, value); + + d = calloc(sizeof(*d), 1); + if (d == NULL) + log_err(1, "calloc"); + d->d_name = checked_strdup(name); + d->d_value = checked_strdup(value); + + TAILQ_INSERT_TAIL(&defined_values, d, d_next); +} + +void +defined_parse_and_add(char *def) +{ + char *name, *value; + + value = def; + name = strsep(&value, "="); + + if (value == NULL || value[0] == '\0') + log_errx(1, "missing variable value"); + if (name == NULL || name[0] == '\0') + log_errx(1, "missing variable name"); + + defined_add(name, value); +} + +void +defined_init(void) +{ + struct utsname name; + int error; + + TAILQ_INIT(&defined_values); + + error = uname(&name); + if (error != 0) + log_err(1, "uname"); + + defined_add("ARCH", name.machine); + defined_add("CPU", name.machine); + defined_add("HOST", name.nodename); + defined_add("OSNAME", name.sysname); + defined_add("OSREL", name.release); + defined_add("OSVERS", name.version); +} Index: usr.sbin/automountd/log.c =================================================================== --- usr.sbin/automountd/log.c (revision 0) +++ usr.sbin/automountd/log.c (working copy) @@ -0,0 +1,196 @@ +/*- + * Copyright (c) 2012 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 +#include +#include +#include +#include +#include +#include + +#include "common.h" + +static int log_level = 0; +static char *peer_name = NULL; +static char *peer_addr = NULL; + +#define MSGBUF_LEN 1024 + +void +log_init(int level) +{ + + log_level = level; + openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON); +} + +void +log_set_peer_name(const char *name) +{ + + /* + * XXX: Turn it into assertion? + */ + if (peer_name != NULL) + log_errx(1, "%s called twice", __func__); + if (peer_addr == NULL) + log_errx(1, "%s called before log_set_peer_addr", __func__); + + peer_name = checked_strdup(name); +} + +void +log_set_peer_addr(const char *addr) +{ + + /* + * XXX: Turn it into assertion? + */ + if (peer_addr != NULL) + log_errx(1, "%s called twice", __func__); + + peer_addr = checked_strdup(addr); +} + +static void +log_common(int priority, int log_errno, const char *fmt, va_list ap) +{ + static char msgbuf[MSGBUF_LEN]; + static char msgbuf_strvised[MSGBUF_LEN * 4 + 1]; + int ret; + + ret = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + if (ret < 0) { + fprintf(stderr, "%s: snprintf failed", getprogname()); + syslog(LOG_CRIT, "snprintf failed"); + exit(1); + } + + ret = strnvis(msgbuf_strvised, sizeof(msgbuf_strvised), msgbuf, VIS_NL); + if (ret < 0) { + fprintf(stderr, "%s: strnvis failed", getprogname()); + syslog(LOG_CRIT, "strnvis failed"); + exit(1); + } + + if (log_errno == -1) { + if (peer_name != NULL) { + fprintf(stderr, "%s: %s (%s): %s\n", getprogname(), + peer_addr, peer_name, msgbuf_strvised); + syslog(priority, "%s (%s): %s", + peer_addr, peer_name, msgbuf_strvised); + } else if (peer_addr != NULL) { + fprintf(stderr, "%s: %s: %s\n", getprogname(), + peer_addr, msgbuf_strvised); + syslog(priority, "%s: %s", + peer_addr, msgbuf_strvised); + } else { + fprintf(stderr, "%s: %s\n", getprogname(), msgbuf_strvised); + syslog(priority, "%s", msgbuf_strvised); + } + + } else { + if (peer_name != NULL) { + fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(), + peer_addr, peer_name, msgbuf_strvised, strerror(errno)); + syslog(priority, "%s (%s): %s: %s", + peer_addr, peer_name, msgbuf_strvised, strerror(errno)); + } else if (peer_addr != NULL) { + fprintf(stderr, "%s: %s: %s: %s\n", getprogname(), + peer_addr, msgbuf_strvised, strerror(errno)); + syslog(priority, "%s: %s: %s", + peer_addr, msgbuf_strvised, strerror(errno)); + } else { + fprintf(stderr, "%s: %s: %s\n", getprogname(), + msgbuf_strvised, strerror(errno)); + syslog(priority, "%s: %s", + msgbuf_strvised, strerror(errno)); + } + } +} + +void +log_err(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_CRIT, errno, fmt, ap); + va_end(ap); + + exit(eval); +} + +void +log_errx(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_CRIT, -1, fmt, ap); + va_end(ap); + + exit(eval); +} + +void +log_warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_WARNING, errno, fmt, ap); + va_end(ap); +} + +void +log_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_WARNING, -1, fmt, ap); + va_end(ap); +} + +void +log_debugx(const char *fmt, ...) +{ + va_list ap; + + if (log_level == 0) + return; + + va_start(ap, fmt); + log_common(LOG_DEBUG, -1, fmt, ap); + va_end(ap); +} Index: usr.sbin/automountd/popen.c =================================================================== --- usr.sbin/automountd/popen.c (revision 0) +++ usr.sbin/automountd/popen.c (working copy) @@ -0,0 +1,189 @@ +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Portions of this software were 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. + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +extern char **environ; + +struct pid { + SLIST_ENTRY(pid) next; + FILE *outfp; + pid_t pid; + char *command; +}; +static SLIST_HEAD(, pid) pidlist = SLIST_HEAD_INITIALIZER(pidlist); + +#define ARGV_LEN 42 + +/* + * Replacement for popen(3), without stdin (which we don't use), but with + * stderr, proper logging, and improved command line arguments passing. + * Error handling is built in - if it returns, then it succeeded. + */ +FILE * +auto_popen(const char *argv0, ...) +{ + va_list ap; + struct pid *cur, *p; + pid_t pid; + int error, i, nullfd, outfds[2]; + char *arg, *argv[ARGV_LEN], *command; + + nullfd = open(_PATH_DEVNULL, O_RDWR, 0); + if (nullfd < 0) + log_err(1, "cannot open %s", _PATH_DEVNULL); + + error = pipe(outfds); + if (error != 0) + log_err(1, "pipe"); + + cur = malloc(sizeof(struct pid)); + if (cur == NULL) + log_err(1, "malloc"); + + argv[0] = checked_strdup(argv0); + command = argv[0]; + + va_start(ap, argv0); + for (i = 1;; i++) { + if (i >= ARGV_LEN) + log_errx(1, "too many arguments to auto_popen"); + arg = va_arg(ap, char *); + argv[i] = arg; + if (arg == NULL) + break; + + command = separated_concat(command, arg, ' '); + } + va_end(ap); + + cur->command = checked_strdup(command); + + switch (pid = fork()) { + case -1: /* Error. */ + log_err(1, "fork"); + /* NOTREACHED */ + case 0: /* Child. */ + dup2(nullfd, STDIN_FILENO); + dup2(outfds[1], STDOUT_FILENO); + + close(nullfd); + close(outfds[0]); + close(outfds[1]); + + SLIST_FOREACH(p, &pidlist, next) + close(fileno(p->outfp)); + execvp(argv[0], argv); + log_err(1, "failed to execute %s", argv[0]); + /* NOTREACHED */ + } + + log_debugx("executing \"%s\" as pid %d", command, pid); + + /* Parent; assume fdopen can't fail. */ + cur->outfp = fdopen(outfds[0], "r"); + close(nullfd); + close(outfds[1]); + + /* Link into list of file descriptors. */ + cur->pid = pid; + SLIST_INSERT_HEAD(&pidlist, cur, next); + + return (cur->outfp); +} + +int +auto_pclose(FILE *iop) +{ + struct pid *cur, *last = NULL; + int status; + pid_t pid; + + /* + * Find the appropriate file pointer and remove it from the list. + */ + SLIST_FOREACH(cur, &pidlist, next) { + if (cur->outfp == iop) + break; + last = cur; + } + if (cur == NULL) { + return (-1); + } + if (last == NULL) + SLIST_REMOVE_HEAD(&pidlist, next); + else + SLIST_REMOVE_AFTER(last, next); + + fclose(cur->outfp); + + do { + pid = wait4(cur->pid, &status, 0, NULL); + } while (pid == -1 && errno == EINTR); + + if (WIFSIGNALED(status)) { + log_warnx("\"%s\", pid %d, terminated with signal %d", + cur->command, pid, WTERMSIG(status)); + return (status); + } + + if (WEXITSTATUS(status) != 0) { + log_warnx("\"%s\", pid %d, terminated with exit status %d", + cur->command, pid, WEXITSTATUS(status)); + return (status); + } + + log_debugx("\"%s\", pid %d, terminated gracefully", cur->command, pid); + + free(cur->command); + free(cur); + + return (pid == -1 ? -1 : status); +} Index: usr.sbin/automountd/token.l =================================================================== --- usr.sbin/automountd/token.l (revision 0) +++ usr.sbin/automountd/token.l (working copy) @@ -0,0 +1,57 @@ +%{ +/*- + * 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 +#include +#include + +#include "common.h" + +int lineno; + +#define YY_DECL int yylex(void) +extern int yylex(void); + +%} + +%option noinput +%option nounput +%option noyywrap + +%% +[a-zA-Z0-9\.\+-_/\:\[\]$&{}]+ { return STR; } +#.*\n { lineno++; return NEWLINE; }; +\\\n { lineno++; }; +\n { lineno++; return NEWLINE; } +[ \t]+ /* ignore whitespace */; +. { return STR; } +%%