summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortrasz <trasz@FreeBSD.org>2014-08-31 21:18:23 +0000
committertrasz <trasz@FreeBSD.org>2014-08-31 21:18:23 +0000
commite8d76f86d25e64bdb4a9ab0b15c509c07491724a (patch)
tree3eebe0ef20c0ab7543612cca9aa64d87a222a4ed
parent0ca40bff6c5041cdf647ca146893c61a26796c76 (diff)
downloadFreeBSD-src-e8d76f86d25e64bdb4a9ab0b15c509c07491724a.zip
FreeBSD-src-e8d76f86d25e64bdb4a9ab0b15c509c07491724a.tar.gz
MFC r270096:
Bring in the new automounter, similar to what's provided in most other UNIX systems, eg. MacOS X and Solaris. It uses Sun-compatible map format, has proper kernel support, and LDAP integration. There are still a few outstanding problems; they will be fixed shortly. Reviewed by: allanjude@, emaste@, kib@, wblock@ (earlier versions) Phabric: D523 Relnotes: yes Sponsored by: The FreeBSD Foundation
-rw-r--r--etc/Makefile4
-rw-r--r--etc/auto_master5
-rw-r--r--etc/autofs/Makefile9
-rw-r--r--etc/autofs/include_ldap38
-rw-r--r--etc/autofs/special_hosts17
-rw-r--r--etc/autofs/special_null4
-rw-r--r--etc/defaults/rc.conf1
-rw-r--r--etc/mtree/BSD.root.dist2
-rw-r--r--etc/rc.d/Makefile3
-rw-r--r--etc/rc.d/automount31
-rw-r--r--etc/rc.d/automountd19
-rw-r--r--etc/rc.d/autounmountd18
-rw-r--r--sbin/mount/mntopts.h6
-rw-r--r--sbin/mount/mount.c1
-rw-r--r--share/man/man5/Makefile1
-rw-r--r--share/man/man5/autofs.599
-rw-r--r--sys/conf/NOTES1
-rw-r--r--sys/conf/files4
-rw-r--r--sys/conf/options1
-rw-r--r--sys/fs/autofs/autofs.c637
-rw-r--r--sys/fs/autofs/autofs.h141
-rw-r--r--sys/fs/autofs/autofs_ioctl.h89
-rw-r--r--sys/fs/autofs/autofs_vfsops.c209
-rw-r--r--sys/fs/autofs/autofs_vnops.c646
-rw-r--r--sys/kern/vfs_mount.c4
-rw-r--r--sys/libkern/strndup.c51
-rw-r--r--sys/modules/Makefile1
-rw-r--r--sys/modules/autofs/Makefile11
-rw-r--r--sys/sys/libkern.h1
-rw-r--r--sys/sys/mount.h6
-rw-r--r--usr.sbin/Makefile1
-rw-r--r--usr.sbin/autofs/Makefile33
-rw-r--r--usr.sbin/autofs/auto_master.5272
-rw-r--r--usr.sbin/autofs/automount.8107
-rw-r--r--usr.sbin/autofs/automount.c345
-rw-r--r--usr.sbin/autofs/automountd.8103
-rw-r--r--usr.sbin/autofs/automountd.c498
-rw-r--r--usr.sbin/autofs/autounmountd.888
-rw-r--r--usr.sbin/autofs/autounmountd.c351
-rw-r--r--usr.sbin/autofs/common.c1129
-rw-r--r--usr.sbin/autofs/common.h112
-rw-r--r--usr.sbin/autofs/defined.c270
-rw-r--r--usr.sbin/autofs/log.c196
-rw-r--r--usr.sbin/autofs/popen.c191
-rw-r--r--usr.sbin/autofs/token.l57
45 files changed, 5808 insertions, 5 deletions
diff --git a/etc/Makefile b/etc/Makefile
index 0cb7b52..976981a 100644
--- a/etc/Makefile
+++ b/etc/Makefile
@@ -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
diff --git a/etc/auto_master b/etc/auto_master
new file mode 100644
index 0000000..16d55e2
--- /dev/null
+++ b/etc/auto_master
@@ -0,0 +1,5 @@
+# $FreeBSD$
+#
+# Automounter master map, see auto_master(5) for details.
+#
+/net -hosts -nosuid
diff --git a/etc/autofs/Makefile b/etc/autofs/Makefile
new file mode 100644
index 0000000..c9eda50
--- /dev/null
+++ b/etc/autofs/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+FILES= include_ldap special_hosts special_null
+
+NO_OBJ=
+FILESDIR= /etc/autofs
+FILESMODE= 755
+
+.include <bsd.prog.mk>
diff --git a/etc/autofs/include_ldap b/etc/autofs/include_ldap
new file mode 100644
index 0000000..58970c0
--- /dev/null
+++ b/etc/autofs/include_ldap
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+# 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<NF; i++) {
+ printf "%s%s", $(i), OFS
+ }
+ printf "%s%s", $NF, ORS
+}
+
+# Double colon after attribute name means the value is in Base64.
+$1 == "'$VALUE_ATTRIBUTE'::" && key {
+ printf "%s%s", key, OFS
+ key = ""
+ for (i=2; i<NF; i++) {
+ printf "%s%s", $(i), OFS
+ }
+ printf "%s", $NF | "b64decode -rp"
+ close("b64decode -rp")
+ printf "%s", ORS
+}
+'
diff --git a/etc/autofs/special_hosts b/etc/autofs/special_hosts
new file mode 100644
index 0000000..f81449d
--- /dev/null
+++ b/etc/autofs/special_hosts
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+if [ $# -eq 0 ]; then
+ out=`getent hosts`
+ [ $? -eq 0 ] || exit 1
+ echo "$out" | awk '{ print $2 }' | sort -u
+ exit 0
+fi
+
+out=`showmount -e "$1"`
+[ $? -eq 0 ] || exit 1
+echo "$out" | awk -v host="$1" \
+ 'NR > 1 { printf "%s\t%s:%s ", $1, host, $1 } END { printf "\n" }'
+
diff --git a/etc/autofs/special_null b/etc/autofs/special_null
new file mode 100644
index 0000000..41c1000
--- /dev/null
+++ b/etc/autofs/special_null
@@ -0,0 +1,4 @@
+#!/usr/bin/true
+#
+# $FreeBSD$
+#
diff --git a/etc/defaults/rc.conf b/etc/defaults/rc.conf
index ac73973..97da01e 100644
--- a/etc/defaults/rc.conf
+++ b/etc/defaults/rc.conf
@@ -311,6 +311,7 @@ amd_enable="NO" # Run amd service with $amd_flags (or NO).
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).
diff --git a/etc/mtree/BSD.root.dist b/etc/mtree/BSD.root.dist
index c02e4f7..e3ca074 100644
--- a/etc/mtree/BSD.root.dist
+++ b/etc/mtree/BSD.root.dist
@@ -24,6 +24,8 @@
etc
X11
..
+ autofs
+ ..
bluetooth
..
defaults
diff --git a/etc/rc.d/Makefile b/etc/rc.d/Makefile
index 375da6d..e522fed 100644
--- a/etc/rc.d/Makefile
+++ b/etc/rc.d/Makefile
@@ -20,6 +20,9 @@ FILES= DAEMON \
atm3 \
auditd \
auditdistd \
+ automount \
+ automountd \
+ autounmountd \
bgfsck \
${_bluetooth} \
bootparams \
diff --git a/etc/rc.d/automount b/etc/rc.d/automount
new file mode 100644
index 0000000..63bda42
--- /dev/null
+++ b/etc/rc.d/automount
@@ -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"
diff --git a/etc/rc.d/automountd b/etc/rc.d/automountd
new file mode 100644
index 0000000..6d74665
--- /dev/null
+++ b/etc/rc.d/automountd
@@ -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"
diff --git a/etc/rc.d/autounmountd b/etc/rc.d/autounmountd
new file mode 100644
index 0000000..c57f90d
--- /dev/null
+++ b/etc/rc.d/autounmountd
@@ -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"
diff --git a/sbin/mount/mntopts.h b/sbin/mount/mntopts.h
index 86a350f..d273dde 100644
--- a/sbin/mount/mntopts.h
+++ b/sbin/mount/mntopts.h
@@ -33,7 +33,7 @@
struct mntopt {
const char *m_option; /* option name */
int m_inverse; /* if a negative option, e.g. "atime" */
- int m_flag; /* bit to set, e.g. MNT_RDONLY */
+ long long m_flag; /* bit to set, e.g. MNT_RDONLY */
int m_altloc; /* 1 => set bit in altflags */
};
@@ -55,6 +55,7 @@ struct mntopt {
#define MOPT_MULTILABEL { "multilabel", 0, MNT_MULTILABEL, 0 }
#define MOPT_ACLS { "acls", 0, MNT_ACLS, 0 }
#define MOPT_NFS4ACLS { "nfsv4acls", 0, MNT_NFS4ACLS, 0 }
+#define MOPT_AUTOMOUNTED { "automounted",0, MNT_AUTOMOUNTED, 0 }
/* Control flags. */
#define MOPT_FORCE { "force", 0, MNT_FORCE, 0 }
@@ -89,7 +90,8 @@ struct mntopt {
MOPT_NOCLUSTERW, \
MOPT_MULTILABEL, \
MOPT_ACLS, \
- MOPT_NFS4ACLS
+ MOPT_NFS4ACLS, \
+ MOPT_AUTOMOUNTED
void getmntopts(const char *, const struct mntopt *, int *, int *);
void rmslashes(char *, char *);
diff --git a/sbin/mount/mount.c b/sbin/mount/mount.c
index 91c7d7c..5ea45df 100644
--- a/sbin/mount/mount.c
+++ b/sbin/mount/mount.c
@@ -114,6 +114,7 @@ static struct opt {
{ MNT_ACLS, "acls" },
{ MNT_NFS4ACLS, "nfsv4acls" },
{ MNT_GJOURNAL, "gjournal" },
+ { MNT_AUTOMOUNTED, "automounted" },
{ 0, NULL }
};
diff --git a/share/man/man5/Makefile b/share/man/man5/Makefile
index e0bd222..5a3a4d7 100644
--- a/share/man/man5/Makefile
+++ b/share/man/man5/Makefile
@@ -7,6 +7,7 @@
MAN= acct.5 \
ar.5 \
a.out.5 \
+ autofs.5 \
bluetooth.device.conf.5 \
bluetooth.hosts.5 \
bluetooth.protocols.5 \
diff --git a/share/man/man5/autofs.5 b/share/man/man5/autofs.5
new file mode 100644
index 0000000..a7a49b3
--- /dev/null
+++ b/share/man/man5/autofs.5
@@ -0,0 +1,99 @@
+.\" 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 the
+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 the automounter infrastructure.
+Its job is to pass mount requests to the
+.Xr automountd 8
+daemon, and pause the processes trying to access the automounted filesystem
+until the mount is completed.
+It is mounted by the
+.Xr automount 8 .
+.Sh OPTIONS
+These 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 specified in
+.Xr auto_master 5 :
+.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 Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/sys/conf/NOTES b/sys/conf/NOTES
index 9002000..5fdc582 100644
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -1017,6 +1017,7 @@ options FFS #Fast filesystem
options NFSCLIENT #Network File System client
# The rest are optional:
+options AUTOFS #Automounter filesystem
options CD9660 #ISO 9660 filesystem
options FDESCFS #File descriptor filesystem
options FUSE #FUSE support module
diff --git a/sys/conf/files b/sys/conf/files
index 6eed819..f884e9b 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -2589,6 +2589,9 @@ dev/xen/xenpci/xenpci.c optional xenpci
dev/xen/timer/timer.c optional xen | xenhvm
dev/xl/if_xl.c optional xl pci
dev/xl/xlphy.c optional xl pci
+fs/autofs/autofs.c optional autofs
+fs/autofs/autofs_vfsops.c optional autofs
+fs/autofs/autofs_vnops.c optional autofs
fs/deadfs/dead_vnops.c standard
fs/devfs/devfs_devs.c standard
fs/devfs/devfs_dir.c standard
@@ -3125,6 +3128,7 @@ libkern/strcmp.c standard
libkern/strcpy.c standard
libkern/strcspn.c standard
libkern/strdup.c standard
+libkern/strndup.c standard
libkern/strlcat.c standard
libkern/strlcpy.c standard
libkern/strlen.c standard
diff --git a/sys/conf/options b/sys/conf/options
index 33ce752..a613612 100644
--- a/sys/conf/options
+++ b/sys/conf/options
@@ -218,6 +218,7 @@ INCLUDE_CONFIG_FILE opt_config.h
# time, since the corresponding lkms cannot work if there are any static
# dependencies. Unusability is enforced by hiding the defines for the
# options in a never-included header.
+AUTOFS opt_dontuse.h
CD9660 opt_dontuse.h
EXT2FS opt_dontuse.h
FDESCFS opt_dontuse.h
diff --git a/sys/fs/autofs/autofs.c b/sys/fs/autofs/autofs.c
new file mode 100644
index 0000000..9715eea
--- /dev/null
+++ b/sys/fs/autofs/autofs.c
@@ -0,0 +1,637 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+/*-
+ * Copyright (c) 1989, 1991, 1993, 1995
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Macklem at The University of Guelph.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/buf.h>
+#include <sys/conf.h>
+#include <sys/dirent.h>
+#include <sys/ioccom.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/refcount.h>
+#include <sys/sx.h>
+#include <sys/sysctl.h>
+#include <sys/syscallsubr.h>
+#include <sys/vnode.h>
+#include <machine/atomic.h>
+#include <vm/uma.h>
+
+#include "autofs.h"
+#include "autofs_ioctl.h"
+
+MALLOC_DEFINE(M_AUTOFS, "autofs", "Automounter filesystem");
+
+uma_zone_t autofs_request_zone;
+uma_zone_t autofs_node_zone;
+
+static int autofs_open(struct cdev *dev, int flags, int fmt,
+ struct thread *td);
+static int autofs_close(struct cdev *dev, int flag, int fmt,
+ struct thread *td);
+static int autofs_ioctl(struct cdev *dev, u_long cmd, caddr_t arg,
+ int mode, struct thread *td);
+
+static struct cdevsw autofs_cdevsw = {
+ .d_version = D_VERSION,
+ .d_open = autofs_open,
+ .d_close = autofs_close,
+ .d_ioctl = autofs_ioctl,
+ .d_name = "autofs",
+};
+
+/*
+ * List of signals that can interrupt an autofs trigger. Might be a good
+ * idea to keep it synchronised with list in sys/fs/nfs/nfs_commonkrpc.c.
+ */
+int autofs_sig_set[] = {
+ SIGINT,
+ SIGTERM,
+ SIGHUP,
+ SIGKILL,
+ SIGQUIT
+};
+
+struct autofs_softc *sc;
+
+SYSCTL_NODE(_vfs, OID_AUTO, autofs, CTLFLAG_RD, 0, "Automounter filesystem");
+int autofs_debug = 1;
+TUNABLE_INT("vfs.autofs.debug", &autofs_debug);
+SYSCTL_INT(_vfs_autofs, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &autofs_debug, 1, "Enable debug messages");
+int autofs_mount_on_stat = 0;
+TUNABLE_INT("vfs.autofs.mount_on_stat", &autofs_mount_on_stat);
+SYSCTL_INT(_vfs_autofs, OID_AUTO, mount_on_stat, CTLFLAG_RWTUN,
+ &autofs_mount_on_stat, 0, "Trigger mount on stat(2) on mountpoint");
+int autofs_timeout = 30;
+TUNABLE_INT("vfs.autofs.timeout", &autofs_timeout);
+SYSCTL_INT(_vfs_autofs, OID_AUTO, timeout, CTLFLAG_RWTUN,
+ &autofs_timeout, 30, "Number of seconds to wait for automountd(8)");
+int autofs_cache = 600;
+TUNABLE_INT("vfs.autofs.cache", &autofs_cache);
+SYSCTL_INT(_vfs_autofs, OID_AUTO, cache, CTLFLAG_RWTUN,
+ &autofs_cache, 600, "Number of seconds to wait before reinvoking "
+ "automountd(8) for any given file or directory");
+int autofs_retry_attempts = 3;
+TUNABLE_INT("vfs.autofs.retry_attempts", &autofs_retry_attempts);
+SYSCTL_INT(_vfs_autofs, OID_AUTO, retry_attempts, CTLFLAG_RWTUN,
+ &autofs_retry_attempts, 3, "Number of attempts before failing mount");
+int autofs_retry_delay = 1;
+TUNABLE_INT("vfs.autofs.retry_delay", &autofs_retry_delay);
+SYSCTL_INT(_vfs_autofs, OID_AUTO, retry_delay, CTLFLAG_RWTUN,
+ &autofs_retry_delay, 1, "Number of seconds before retrying");
+int autofs_interruptible = 1;
+TUNABLE_INT("vfs.autofs.interruptible", &autofs_interruptible);
+SYSCTL_INT(_vfs_autofs, OID_AUTO, interruptible, CTLFLAG_RWTUN,
+ &autofs_interruptible, 1, "Allow requests to be interrupted by signal");
+
+int
+autofs_init(struct vfsconf *vfsp)
+{
+ int error;
+
+ sc = malloc(sizeof(*sc), M_AUTOFS, M_WAITOK | M_ZERO);
+
+ autofs_request_zone = uma_zcreate("autofs_request",
+ sizeof(struct autofs_request), NULL, NULL, NULL, NULL,
+ UMA_ALIGN_PTR, 0);
+ autofs_node_zone = uma_zcreate("autofs_node",
+ sizeof(struct autofs_node), NULL, NULL, NULL, NULL,
+ UMA_ALIGN_PTR, 0);
+
+ TAILQ_INIT(&sc->sc_requests);
+ cv_init(&sc->sc_cv, "autofscv");
+ sx_init(&sc->sc_lock, "autofslk");
+
+ error = make_dev_p(MAKEDEV_CHECKNAME, &sc->sc_cdev, &autofs_cdevsw,
+ NULL, UID_ROOT, GID_WHEEL, 0600, "autofs");
+ if (error != 0) {
+ AUTOFS_WARN("failed to create device node, error %d", error);
+ free(sc, M_AUTOFS);
+ return (error);
+ }
+ sc->sc_cdev->si_drv1 = sc;
+
+ return (0);
+}
+
+int
+autofs_uninit(struct vfsconf *vfsp)
+{
+
+ sx_xlock(&sc->sc_lock);
+ if (sc->sc_dev_opened) {
+ sx_xunlock(&sc->sc_lock);
+ return (EBUSY);
+ }
+ if (sc->sc_cdev != NULL)
+ destroy_dev(sc->sc_cdev);
+
+ uma_zdestroy(autofs_request_zone);
+ uma_zdestroy(autofs_node_zone);
+
+ sx_xunlock(&sc->sc_lock);
+ /*
+ * XXX: Race with open?
+ */
+ free(sc, M_AUTOFS);
+
+ return (0);
+}
+
+bool
+autofs_ignore_thread(const struct thread *td)
+{
+ struct proc *p;
+
+ p = td->td_proc;
+
+ if (sc->sc_dev_opened == false)
+ return (false);
+
+ PROC_LOCK(p);
+ if (p->p_session->s_sid == sc->sc_dev_sid) {
+ PROC_UNLOCK(p);
+ return (true);
+ }
+ PROC_UNLOCK(p);
+
+ return (false);
+}
+
+static char *
+autofs_path(struct autofs_node *anp)
+{
+ struct autofs_mount *amp;
+ char *path, *tmp;
+
+ amp = anp->an_mount;
+
+ path = strdup("", M_AUTOFS);
+ for (; anp->an_parent != NULL; anp = anp->an_parent) {
+ tmp = malloc(strlen(anp->an_name) + strlen(path) + 2,
+ M_AUTOFS, M_WAITOK);
+ strcpy(tmp, anp->an_name);
+ strcat(tmp, "/");
+ strcat(tmp, path);
+ free(path, M_AUTOFS);
+ path = tmp;
+ }
+
+ tmp = malloc(strlen(amp->am_mountpoint) + strlen(path) + 2,
+ M_AUTOFS, M_WAITOK);
+ strcpy(tmp, amp->am_mountpoint);
+ strcat(tmp, "/");
+ strcat(tmp, path);
+ free(path, M_AUTOFS);
+ path = tmp;
+
+ return (path);
+}
+
+static void
+autofs_callout(void *context)
+{
+ struct autofs_request *ar;
+ struct autofs_softc *sc;
+
+ ar = context;
+ sc = ar->ar_mount->am_softc;
+
+ sx_xlock(&sc->sc_lock);
+ AUTOFS_WARN("request %d for %s timed out after %d seconds",
+ ar->ar_id, ar->ar_path, autofs_timeout);
+ /*
+ * XXX: EIO perhaps?
+ */
+ ar->ar_error = ETIMEDOUT;
+ ar->ar_done = true;
+ ar->ar_in_progress = false;
+ cv_broadcast(&sc->sc_cv);
+ sx_xunlock(&sc->sc_lock);
+}
+
+bool
+autofs_cached(struct autofs_node *anp, const char *component, int componentlen)
+{
+ int error;
+ struct autofs_mount *amp;
+
+ amp = anp->an_mount;
+
+ AUTOFS_ASSERT_UNLOCKED(amp);
+
+ /*
+ * For top-level nodes we need to request automountd(8)
+ * assistance even if the node is marked as cached,
+ * but the requested subdirectory does not exist. This
+ * is necessary for wildcard indirect map keys to work.
+ */
+ if (anp->an_parent == NULL && componentlen != 0) {
+ AUTOFS_LOCK(amp);
+ error = autofs_node_find(anp, component, componentlen, NULL);
+ AUTOFS_UNLOCK(amp);
+ if (error != 0)
+ return (false);
+ }
+
+ return (anp->an_cached);
+}
+
+static void
+autofs_cache_callout(void *context)
+{
+ struct autofs_node *anp;
+
+ anp = context;
+ anp->an_cached = false;
+}
+
+/*
+ * The set/restore sigmask functions are used to (temporarily) overwrite
+ * the thread td_sigmask during triggering.
+ */
+static void
+autofs_set_sigmask(sigset_t *oldset)
+{
+ sigset_t newset;
+ int i;
+
+ SIGFILLSET(newset);
+ /* Remove the autofs set of signals from newset */
+ PROC_LOCK(curproc);
+ mtx_lock(&curproc->p_sigacts->ps_mtx);
+ for (i = 0 ; i < sizeof(autofs_sig_set)/sizeof(int) ; i++) {
+ /*
+ * But make sure we leave the ones already masked
+ * by the process, i.e. remove the signal from the
+ * temporary signalmask only if it wasn't already
+ * in p_sigmask.
+ */
+ if (!SIGISMEMBER(curthread->td_sigmask, autofs_sig_set[i]) &&
+ !SIGISMEMBER(curproc->p_sigacts->ps_sigignore,
+ autofs_sig_set[i])) {
+ SIGDELSET(newset, autofs_sig_set[i]);
+ }
+ }
+ mtx_unlock(&curproc->p_sigacts->ps_mtx);
+ kern_sigprocmask(curthread, SIG_SETMASK, &newset, oldset,
+ SIGPROCMASK_PROC_LOCKED);
+ PROC_UNLOCK(curproc);
+}
+
+static void
+autofs_restore_sigmask(sigset_t *set)
+{
+
+ kern_sigprocmask(curthread, SIG_SETMASK, set, NULL, 0);
+}
+
+static int
+autofs_trigger_one(struct autofs_node *anp,
+ const char *component, int componentlen)
+{
+ sigset_t oldset;
+ struct autofs_mount *amp;
+ struct autofs_softc *sc;
+ struct autofs_node *firstanp;
+ struct autofs_request *ar;
+ char *key, *path;
+ int error = 0, request_error, last;
+
+ amp = VFSTOAUTOFS(anp->an_vnode->v_mount);
+ sc = amp->am_softc;
+
+ sx_assert(&sc->sc_lock, SA_XLOCKED);
+
+ if (anp->an_parent == NULL) {
+ key = strndup(component, componentlen, M_AUTOFS);
+ } else {
+ for (firstanp = anp; firstanp->an_parent->an_parent != NULL;
+ firstanp = firstanp->an_parent)
+ continue;
+ key = strdup(firstanp->an_name, M_AUTOFS);
+ }
+
+ path = autofs_path(anp);
+
+ TAILQ_FOREACH(ar, &sc->sc_requests, ar_next) {
+ if (strcmp(ar->ar_path, path) != 0)
+ continue;
+ if (strcmp(ar->ar_key, key) != 0)
+ continue;
+
+ KASSERT(strcmp(ar->ar_from, amp->am_from) == 0,
+ ("from changed; %s != %s", ar->ar_from, amp->am_from));
+ KASSERT(strcmp(ar->ar_prefix, amp->am_prefix) == 0,
+ ("prefix changed; %s != %s",
+ ar->ar_prefix, amp->am_prefix));
+ KASSERT(strcmp(ar->ar_options, amp->am_options) == 0,
+ ("options changed; %s != %s",
+ ar->ar_options, amp->am_options));
+
+ break;
+ }
+
+ if (ar != NULL) {
+ refcount_acquire(&ar->ar_refcount);
+ } else {
+ ar = uma_zalloc(autofs_request_zone, M_WAITOK | M_ZERO);
+ ar->ar_mount = amp;
+
+ ar->ar_id = atomic_fetchadd_int(&sc->sc_last_request_id, 1);
+ strlcpy(ar->ar_from, amp->am_from, sizeof(ar->ar_from));
+ strlcpy(ar->ar_path, path, sizeof(ar->ar_path));
+ strlcpy(ar->ar_prefix, amp->am_prefix, sizeof(ar->ar_prefix));
+ strlcpy(ar->ar_key, key, sizeof(ar->ar_key));
+ strlcpy(ar->ar_options,
+ amp->am_options, sizeof(ar->ar_options));
+
+ callout_init(&ar->ar_callout, 1);
+ callout_reset(&ar->ar_callout,
+ autofs_timeout * hz, autofs_callout, ar);
+ refcount_init(&ar->ar_refcount, 1);
+ TAILQ_INSERT_TAIL(&sc->sc_requests, ar, ar_next);
+ }
+
+ cv_broadcast(&sc->sc_cv);
+ while (ar->ar_done == false) {
+ if (autofs_interruptible != 0) {
+ autofs_set_sigmask(&oldset);
+ error = cv_wait_sig(&sc->sc_cv, &sc->sc_lock);
+ autofs_restore_sigmask(&oldset);
+ if (error != 0) {
+ /*
+ * XXX: For some reson this returns -1
+ * instead of EINTR, wtf?!
+ */
+ error = EINTR;
+ AUTOFS_WARN("cv_wait_sig for %s failed "
+ "with error %d", ar->ar_path, error);
+ break;
+ }
+ } else {
+ cv_wait(&sc->sc_cv, &sc->sc_lock);
+ }
+ }
+
+ request_error = ar->ar_error;
+ if (request_error != 0) {
+ AUTOFS_WARN("request for %s completed with error %d",
+ ar->ar_path, request_error);
+ }
+
+ last = refcount_release(&ar->ar_refcount);
+ if (last) {
+ TAILQ_REMOVE(&sc->sc_requests, ar, ar_next);
+ /*
+ * XXX: Is it safe?
+ */
+ sx_xunlock(&sc->sc_lock);
+ callout_drain(&ar->ar_callout);
+ sx_xlock(&sc->sc_lock);
+ uma_zfree(autofs_request_zone, ar);
+ }
+
+ /*
+ * Note that we do not do negative caching on purpose. This
+ * way the user can retry access at any time, e.g. after fixing
+ * the failure reason, without waiting for cache timer to expire.
+ */
+ if (error == 0 && request_error == 0 && autofs_cache > 0) {
+ anp->an_cached = true;
+ callout_reset(&anp->an_callout, autofs_cache * hz,
+ autofs_cache_callout, anp);
+ }
+
+ free(key, M_AUTOFS);
+ free(path, M_AUTOFS);
+
+ if (error != 0)
+ return (error);
+ return (request_error);
+}
+
+/*
+ * Send request to automountd(8) and wait for completion.
+ */
+int
+autofs_trigger(struct autofs_node *anp,
+ const char *component, int componentlen)
+{
+ int error;
+
+ for (;;) {
+ error = autofs_trigger_one(anp, component, componentlen);
+ if (error == 0) {
+ anp->an_retries = 0;
+ return (0);
+ }
+ if (error == EINTR) {
+ AUTOFS_DEBUG("trigger interrupted by signal, "
+ "not retrying");
+ anp->an_retries = 0;
+ return (error);
+ }
+ anp->an_retries++;
+ if (anp->an_retries >= autofs_retry_attempts) {
+ AUTOFS_DEBUG("trigger failed %d times; returning "
+ "error %d", anp->an_retries, error);
+ anp->an_retries = 0;
+ return (error);
+
+ }
+ AUTOFS_DEBUG("trigger failed with error %d; will retry in "
+ "%d seconds, %d attempts left", error, autofs_retry_delay,
+ autofs_retry_attempts - anp->an_retries);
+ sx_xunlock(&sc->sc_lock);
+ pause("autofs_retry", autofs_retry_delay * hz);
+ sx_xlock(&sc->sc_lock);
+ }
+}
+
+static int
+autofs_ioctl_request(struct autofs_softc *sc, struct autofs_daemon_request *adr)
+{
+ struct autofs_request *ar;
+ int error;
+
+ sx_xlock(&sc->sc_lock);
+ for (;;) {
+ TAILQ_FOREACH(ar, &sc->sc_requests, ar_next) {
+ if (ar->ar_done)
+ continue;
+ if (ar->ar_in_progress)
+ continue;
+
+ break;
+ }
+
+ if (ar != NULL)
+ break;
+
+ error = cv_wait_sig(&sc->sc_cv, &sc->sc_lock);
+ if (error != 0) {
+ /*
+ * XXX: For some reson this returns -1 instead
+ * of EINTR, wtf?!
+ */
+ error = EINTR;
+ sx_xunlock(&sc->sc_lock);
+ AUTOFS_DEBUG("failed with error %d", error);
+ return (error);
+ }
+ }
+
+ ar->ar_in_progress = true;
+ sx_xunlock(&sc->sc_lock);
+
+ adr->adr_id = ar->ar_id;
+ strlcpy(adr->adr_from, ar->ar_from, sizeof(adr->adr_from));
+ strlcpy(adr->adr_path, ar->ar_path, sizeof(adr->adr_path));
+ strlcpy(adr->adr_prefix, ar->ar_prefix, sizeof(adr->adr_prefix));
+ strlcpy(adr->adr_key, ar->ar_key, sizeof(adr->adr_key));
+ strlcpy(adr->adr_options, ar->ar_options, sizeof(adr->adr_options));
+
+ PROC_LOCK(curproc);
+ sc->sc_dev_sid = curproc->p_session->s_sid;
+ PROC_UNLOCK(curproc);
+
+ return (0);
+}
+
+static int
+autofs_ioctl_done(struct autofs_softc *sc, struct autofs_daemon_done *add)
+{
+ struct autofs_request *ar;
+
+ sx_xlock(&sc->sc_lock);
+ TAILQ_FOREACH(ar, &sc->sc_requests, ar_next) {
+ if (ar->ar_id == add->add_id)
+ break;
+ }
+
+ if (ar == NULL) {
+ sx_xunlock(&sc->sc_lock);
+ AUTOFS_DEBUG("id %d not found", add->add_id);
+ return (ESRCH);
+ }
+
+ ar->ar_error = add->add_error;
+ ar->ar_done = true;
+ ar->ar_in_progress = false;
+ cv_broadcast(&sc->sc_cv);
+
+ sx_xunlock(&sc->sc_lock);
+
+ return (0);
+}
+
+static int
+autofs_open(struct cdev *dev, int flags, int fmt, struct thread *td)
+{
+
+ sx_xlock(&sc->sc_lock);
+ if (sc->sc_dev_opened) {
+ sx_xunlock(&sc->sc_lock);
+ return (EBUSY);
+ }
+
+ sc->sc_dev_opened = true;
+ sx_xunlock(&sc->sc_lock);
+
+ return (0);
+}
+
+static int
+autofs_close(struct cdev *dev, int flag, int fmt, struct thread *td)
+{
+
+ sx_xlock(&sc->sc_lock);
+ KASSERT(sc->sc_dev_opened, ("not opened?"));
+ sc->sc_dev_opened = false;
+ sx_xunlock(&sc->sc_lock);
+
+ return (0);
+}
+
+static int
+autofs_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int mode,
+ struct thread *td)
+{
+
+ KASSERT(sc->sc_dev_opened, ("not opened?"));
+
+ switch (cmd) {
+ case AUTOFSREQUEST:
+ return (autofs_ioctl_request(sc,
+ (struct autofs_daemon_request *)arg));
+ case AUTOFSDONE:
+ return (autofs_ioctl_done(sc,
+ (struct autofs_daemon_done *)arg));
+ default:
+ AUTOFS_DEBUG("invalid cmd %lx", cmd);
+ return (EINVAL);
+ }
+}
diff --git a/sys/fs/autofs/autofs.h b/sys/fs/autofs/autofs.h
new file mode 100644
index 0000000..ec1ca66
--- /dev/null
+++ b/sys/fs/autofs/autofs.h
@@ -0,0 +1,141 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef AUTOFS_H
+#define AUTOFS_H
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#define VFSTOAUTOFS(mp) ((struct autofs_mount *)((mp)->mnt_data))
+
+MALLOC_DECLARE(M_AUTOFS);
+
+extern uma_zone_t autofs_request_zone;
+extern uma_zone_t autofs_node_zone;
+
+extern int autofs_debug;
+extern int autofs_mount_on_stat;
+
+#define AUTOFS_DEBUG(X, ...) \
+ if (autofs_debug > 1) { \
+ printf("%s: " X "\n", __func__, ## __VA_ARGS__);\
+ } while (0)
+
+#define AUTOFS_WARN(X, ...) \
+ if (autofs_debug > 0) { \
+ printf("WARNING: %s: " X "\n", \
+ __func__, ## __VA_ARGS__); \
+ } while (0)
+
+#define AUTOFS_LOCK(X) sx_xlock(&X->am_lock)
+#define AUTOFS_UNLOCK(X) sx_xunlock(&X->am_lock)
+#define AUTOFS_ASSERT_LOCKED(X) sx_assert(&X->am_lock, SA_XLOCKED)
+#define AUTOFS_ASSERT_UNLOCKED(X) sx_assert(&X->am_lock, SA_UNLOCKED)
+
+struct autofs_node {
+ TAILQ_ENTRY(autofs_node) an_next;
+ char *an_name;
+ int an_fileno;
+ struct autofs_node *an_parent;
+ TAILQ_HEAD(, autofs_node) an_children;
+ struct autofs_mount *an_mount;
+ struct vnode *an_vnode;
+ struct sx an_vnode_lock;
+ bool an_cached;
+ struct callout an_callout;
+ int an_retries;
+ struct timespec an_ctime;
+};
+
+struct autofs_mount {
+ TAILQ_ENTRY(autofs_mount) am_next;
+ struct autofs_softc *am_softc;
+ struct autofs_node *am_root;
+ struct mount *am_mp;
+ struct sx am_lock;
+ char am_from[MAXPATHLEN];
+ char am_mountpoint[MAXPATHLEN];
+ char am_options[MAXPATHLEN];
+ char am_prefix[MAXPATHLEN];
+ int am_last_fileno;
+};
+
+struct autofs_request {
+ TAILQ_ENTRY(autofs_request) ar_next;
+ struct autofs_mount *ar_mount;
+ int ar_id;
+ bool ar_done;
+ int ar_error;
+ bool ar_in_progress;
+ char ar_from[MAXPATHLEN];
+ char ar_path[MAXPATHLEN];
+ char ar_prefix[MAXPATHLEN];
+ char ar_key[MAXPATHLEN];
+ char ar_options[MAXPATHLEN];
+ struct callout ar_callout;
+ volatile u_int ar_refcount;
+};
+
+struct autofs_softc {
+ device_t sc_dev;
+ struct cdev *sc_cdev;
+ struct cv sc_cv;
+ struct sx sc_lock;
+ TAILQ_HEAD(, autofs_request) sc_requests;
+ bool sc_dev_opened;
+ pid_t sc_dev_sid;
+ int sc_last_request_id;
+};
+
+/*
+ * Limits and constants
+ */
+#define AUTOFS_NAMELEN 24
+#define AUTOFS_FSNAMELEN 16 /* equal to MFSNAMELEN */
+#define AUTOFS_DELEN (8 + AUTOFS_NAMELEN)
+
+int autofs_init(struct vfsconf *vfsp);
+int autofs_uninit(struct vfsconf *vfsp);
+int autofs_trigger(struct autofs_node *anp, const char *component,
+ int componentlen);
+bool autofs_cached(struct autofs_node *anp, const char *component,
+ int componentlen);
+bool autofs_ignore_thread(const struct thread *td);
+int autofs_node_new(struct autofs_node *parent, struct autofs_mount *amp,
+ const char *name, int namelen, struct autofs_node **anpp);
+int autofs_node_find(struct autofs_node *parent,
+ const char *name, int namelen, struct autofs_node **anpp);
+void autofs_node_delete(struct autofs_node *anp);
+int autofs_node_vn(struct autofs_node *anp, struct mount *mp,
+ struct vnode **vpp);
+
+#endif /* !AUTOFS_H */
diff --git a/sys/fs/autofs/autofs_ioctl.h b/sys/fs/autofs/autofs_ioctl.h
new file mode 100644
index 0000000..8d03ef7
--- /dev/null
+++ b/sys/fs/autofs/autofs_ioctl.h
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 2013 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef AUTOFS_IOCTL_H
+#define AUTOFS_IOCTL_H
+
+#define AUTOFS_PATH "/dev/autofs"
+
+struct autofs_daemon_request {
+ /*
+ * Request identifier.
+ */
+ int adr_id;
+
+ /*
+ * The "from" field, containing map name. For example,
+ * when accessing '/net/192.168.1.3/tank/vm/', that would
+ * be '-hosts'.
+ */
+ char adr_from[MAXPATHLEN];
+
+ /*
+ * Full path to the node being looked up; for requests that result
+ * in actual mount it is the full mount path.
+ */
+ char adr_path[MAXPATHLEN];
+
+ /*
+ * Prefix, which is basically the mountpoint from auto_master(5).
+ * In example above that would be "/net"; for direct maps it is "/".
+ */
+ char adr_prefix[MAXPATHLEN];
+
+ /*
+ * Map key, also used as command argument for dynamic maps; in example
+ * above that would be '192.168.1.3'.
+ */
+ char adr_key[MAXPATHLEN];
+
+ /*
+ * Mount options from auto_master(5).
+ */
+ char adr_options[MAXPATHLEN];
+};
+
+struct autofs_daemon_done {
+ /*
+ * Identifier, copied from adr_id.
+ */
+ int add_id;
+
+ /*
+ * Error number, possibly returned to userland.
+ */
+ int add_error;
+};
+
+#define AUTOFSREQUEST _IOR('I', 0x01, struct autofs_daemon_request)
+#define AUTOFSDONE _IOW('I', 0x02, struct autofs_daemon_done)
+
+#endif /* !AUTOFS_IOCTL_H */
diff --git a/sys/fs/autofs/autofs_vfsops.c b/sys/fs/autofs/autofs_vfsops.c
new file mode 100644
index 0000000..0c1f36b
--- /dev/null
+++ b/sys/fs/autofs/autofs_vfsops.c
@@ -0,0 +1,209 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/condvar.h>
+#include <sys/ioccom.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/sx.h>
+#include <sys/vnode.h>
+
+#include "autofs.h"
+
+static const char *autofs_opts[] = {
+ "from", "master_options", "master_prefix", NULL
+};
+
+extern struct autofs_softc *sc;
+
+static int
+autofs_mount(struct mount *mp)
+{
+ struct autofs_mount *amp;
+ char *from, *fspath, *options, *prefix;
+ int error;
+
+ if (vfs_filteropt(mp->mnt_optnew, autofs_opts))
+ return (EINVAL);
+
+ if (mp->mnt_flag & MNT_UPDATE)
+ return (0);
+
+ if (vfs_getopt(mp->mnt_optnew, "from", (void **)&from, NULL))
+ return (EINVAL);
+
+ if (vfs_getopt(mp->mnt_optnew, "fspath", (void **)&fspath, NULL))
+ return (EINVAL);
+
+ if (vfs_getopt(mp->mnt_optnew, "master_options", (void **)&options, NULL))
+ return (EINVAL);
+
+ if (vfs_getopt(mp->mnt_optnew, "master_prefix", (void **)&prefix, NULL))
+ return (EINVAL);
+
+ amp = malloc(sizeof(*amp), M_AUTOFS, M_WAITOK | M_ZERO);
+ mp->mnt_data = amp;
+ amp->am_mp = mp;
+ amp->am_softc = sc;
+ strlcpy(amp->am_from, from, sizeof(amp->am_from));
+ strlcpy(amp->am_mountpoint, fspath, sizeof(amp->am_mountpoint));
+ strlcpy(amp->am_options, options, sizeof(amp->am_options));
+ strlcpy(amp->am_prefix, prefix, sizeof(amp->am_prefix));
+ sx_init(&amp->am_lock, "autofslk");
+ amp->am_last_fileno = 1;
+
+ vfs_getnewfsid(mp);
+
+ AUTOFS_LOCK(amp);
+ error = autofs_node_new(NULL, amp, ".", -1, &amp->am_root);
+ if (error != 0) {
+ AUTOFS_UNLOCK(amp);
+ free(amp, M_AUTOFS);
+ return (error);
+ }
+ AUTOFS_UNLOCK(amp);
+
+ vfs_mountedfrom(mp, from);
+
+ return (0);
+}
+
+static int
+autofs_unmount(struct mount *mp, int mntflags)
+{
+ struct autofs_mount *amp;
+ struct autofs_node *anp;
+ struct autofs_request *ar;
+ int error, flags;
+ bool found;
+
+ amp = VFSTOAUTOFS(mp);
+
+ flags = 0;
+ if (mntflags & MNT_FORCE)
+ flags |= FORCECLOSE;
+ error = vflush(mp, 0, flags, curthread);
+ if (error != 0) {
+ AUTOFS_WARN("vflush failed with error %d", error);
+ return (error);
+ }
+
+ /*
+ * All vnodes are gone, and new one will not appear - so,
+ * no new triggerings. We can iterate over outstanding
+ * autofs_requests and terminate them.
+ */
+ for (;;) {
+ found = false;
+ sx_xlock(&sc->sc_lock);
+ TAILQ_FOREACH(ar, &sc->sc_requests, ar_next) {
+ if (ar->ar_mount != amp)
+ continue;
+ ar->ar_error = ENXIO;
+ ar->ar_done = true;
+ ar->ar_in_progress = false;
+ found = true;
+ }
+ sx_xunlock(&sc->sc_lock);
+ if (found == false)
+ break;
+
+ cv_broadcast(&sc->sc_cv);
+ pause("autofs_umount", 1);
+ }
+
+ AUTOFS_LOCK(amp);
+
+ /*
+ * Not terribly efficient, but at least not recursive.
+ */
+ while (!TAILQ_EMPTY(&amp->am_root->an_children)) {
+ anp = TAILQ_FIRST(&amp->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(&amp->am_lock);
+
+ free(amp, M_AUTOFS);
+
+ return (0);
+}
+
+static int
+autofs_root(struct mount *mp, int flags, struct vnode **vpp)
+{
+ struct autofs_mount *amp;
+ int error;
+
+ amp = VFSTOAUTOFS(mp);
+
+ error = autofs_node_vn(amp->am_root, mp, vpp);
+
+ return (error);
+}
+
+static int
+autofs_statfs(struct mount *mp, struct statfs *sbp)
+{
+
+ sbp->f_bsize = 512;
+ sbp->f_iosize = 0;
+ sbp->f_blocks = 0;
+ sbp->f_bfree = 0;
+ sbp->f_bavail = 0;
+ sbp->f_files = 0;
+ sbp->f_ffree = 0;
+
+ return (0);
+}
+
+static struct vfsops autofs_vfsops = {
+ .vfs_fhtovp = NULL, /* XXX */
+ .vfs_mount = autofs_mount,
+ .vfs_unmount = autofs_unmount,
+ .vfs_root = autofs_root,
+ .vfs_statfs = autofs_statfs,
+ .vfs_init = autofs_init,
+ .vfs_uninit = autofs_uninit,
+};
+
+VFS_SET(autofs_vfsops, autofs, VFCF_SYNTHETIC | VFCF_NETWORK);
+MODULE_VERSION(autofs, 1);
diff --git a/sys/fs/autofs/autofs_vnops.c b/sys/fs/autofs/autofs_vnops.c
new file mode 100644
index 0000000..7623145
--- /dev/null
+++ b/sys/fs/autofs/autofs_vnops.c
@@ -0,0 +1,646 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/condvar.h>
+#include <sys/dirent.h>
+#include <sys/fcntl.h>
+#include <sys/lock.h>
+#include <sys/mount.h>
+#include <sys/mutex.h>
+#include <sys/namei.h>
+#include <sys/signalvar.h>
+#include <sys/systm.h>
+#include <sys/vnode.h>
+#include <machine/atomic.h>
+#include <vm/uma.h>
+
+#include "autofs.h"
+
+static int autofs_trigger_vn(struct vnode *vp, const char *path,
+ int pathlen, struct vnode **newvp);
+
+static int
+autofs_access(struct vop_access_args *ap)
+{
+
+ /*
+ * Nothing to do here; the only kind of access control
+ * needed is in autofs_mkdir().
+ */
+
+ return (0);
+}
+
+static int
+autofs_getattr(struct vop_getattr_args *ap)
+{
+ struct vnode *vp, *newvp;
+ struct autofs_node *anp;
+ struct mount *mp;
+ struct vattr *vap;
+ int error;
+
+ vp = ap->a_vp;
+ anp = vp->v_data;
+ mp = vp->v_mount;
+ vap = ap->a_vap;
+
+ KASSERT(ap->a_vp->v_type == VDIR, ("!VDIR"));
+
+ /*
+ * The reason we must do this is that some tree-walking software,
+ * namely fts(3), assumes that stat(".") results will not change
+ * between chdir("subdir") and chdir(".."), and fails with ENOENT
+ * otherwise.
+ */
+ if (autofs_mount_on_stat && autofs_cached(anp, NULL, 0) == false &&
+ autofs_ignore_thread(curthread) == false) {
+ error = autofs_trigger_vn(vp, "", 0, &newvp);
+ if (error != 0)
+ return (error);
+
+ if (newvp != NULL) {
+ error = VOP_GETATTR(newvp, ap->a_vap,
+ ap->a_cred);
+ vput(newvp);
+ return (error);
+ }
+ }
+
+ vap->va_type = VDIR;
+ vap->va_mode = 0755;
+ vap->va_nlink = 3; /* XXX */
+ vap->va_uid = 0;
+ vap->va_gid = 0;
+ vap->va_rdev = NODEV;
+ vap->va_fsid = mp->mnt_stat.f_fsid.val[0];
+ vap->va_fileid = anp->an_fileno;
+ vap->va_size = 512; /* XXX */
+ vap->va_blocksize = 512;
+ vap->va_mtime = anp->an_ctime;
+ vap->va_atime = anp->an_ctime;
+ vap->va_ctime = anp->an_ctime;
+ vap->va_birthtime = anp->an_ctime;
+ vap->va_gen = 0;
+ vap->va_flags = 0;
+ vap->va_rdev = 0;
+ vap->va_bytes = 512; /* XXX */
+ vap->va_filerev = 0;
+ vap->va_spare = 0;
+
+ return (0);
+}
+
+/*
+ * Unlock the vnode, request automountd(8) action, and then lock it back.
+ * If anything got mounted on top of the vnode, return the new filesystem's
+ * root vnode in 'newvp', locked.
+ */
+static int
+autofs_trigger_vn(struct vnode *vp, const char *path, int pathlen,
+ struct vnode **newvp)
+{
+ struct autofs_node *anp;
+ struct autofs_mount *amp;
+ struct autofs_softc *sc;
+ int error, lock_flags;
+
+ anp = vp->v_data;
+ amp = VFSTOAUTOFS(vp->v_mount);
+ sc = amp->am_softc;
+
+ /*
+ * Release the vnode lock, so that other operations, in partcular
+ * mounting a filesystem on top of it, can proceed. Increase use
+ * count, to prevent the vnode from being deallocated and to prevent
+ * filesystem from being unmounted.
+ */
+ lock_flags = VOP_ISLOCKED(vp);
+ vref(vp);
+ VOP_UNLOCK(vp, 0);
+
+ sx_xlock(&sc->sc_lock);
+
+ /*
+ * XXX: Workaround for mounting the same thing multiple times; revisit.
+ */
+ if (vp->v_mountedhere != NULL) {
+ error = 0;
+ goto mounted;
+ }
+
+ error = autofs_trigger(anp, path, pathlen);
+mounted:
+ sx_xunlock(&sc->sc_lock);
+ vn_lock(vp, lock_flags | LK_RETRY);
+ vunref(vp);
+ if ((vp->v_iflag & VI_DOOMED) != 0) {
+ AUTOFS_DEBUG("VI_DOOMED");
+ return (ENOENT);
+ }
+
+ if (error != 0)
+ return (error);
+
+ if (vp->v_mountedhere == NULL) {
+ *newvp = NULL;
+ return (0);
+ } else {
+ /*
+ * If the operation that succeeded was mount, then mark
+ * the node as non-cached. Otherwise, if someone unmounts
+ * the filesystem before the cache times out, we will fail
+ * to trigger.
+ */
+ anp->an_cached = false;
+ }
+
+ error = VFS_ROOT(vp->v_mountedhere, lock_flags, newvp);
+ if (error != 0) {
+ AUTOFS_WARN("VFS_ROOT() failed with error %d", error);
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+autofs_lookup(struct vop_lookup_args *ap)
+{
+ struct vnode *dvp, *newvp, **vpp;
+ struct mount *mp;
+ struct autofs_mount *amp;
+ struct autofs_node *anp, *child;
+ struct componentname *cnp;
+ int error, lock_flags;
+
+ dvp = ap->a_dvp;
+ vpp = ap->a_vpp;
+ mp = dvp->v_mount;
+ amp = VFSTOAUTOFS(mp);
+ anp = dvp->v_data;
+ cnp = ap->a_cnp;
+
+ if (cnp->cn_flags & ISDOTDOT) {
+ KASSERT(anp->an_parent != NULL, ("NULL parent"));
+ /*
+ * Note that in this case, dvp is the child vnode, and we are
+ * looking up the parent vnode - exactly reverse from normal
+ * operation. To preserve lock order, we unlock the child
+ * (dvp), obtain the lock on parent (*vpp) in autofs_node_vn(),
+ * then relock the child. We use vhold()/vdrop() to prevent
+ * dvp from being freed in the meantime.
+ */
+ lock_flags = VOP_ISLOCKED(dvp);
+ vhold(dvp);
+ VOP_UNLOCK(dvp, 0);
+ error = autofs_node_vn(anp->an_parent, mp, vpp);
+ if (error != 0) {
+ AUTOFS_WARN("autofs_node_vn() failed with error %d",
+ error);
+ }
+ vn_lock(dvp, lock_flags | LK_RETRY);
+ vdrop(dvp);
+
+ return (error);
+ }
+
+ if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') {
+ vref(dvp);
+ *vpp = dvp;
+
+ return (0);
+ }
+
+ if (autofs_cached(anp, cnp->cn_nameptr, cnp->cn_namelen) == false &&
+ autofs_ignore_thread(cnp->cn_thread) == false) {
+ error = autofs_trigger_vn(dvp,
+ cnp->cn_nameptr, cnp->cn_namelen, &newvp);
+ if (error != 0)
+ return (error);
+
+ if (newvp != NULL) {
+ error = VOP_LOOKUP(newvp, ap->a_vpp, ap->a_cnp);
+
+ /*
+ * Instead of figuring out whether our vnode should
+ * be locked or not given the error and cnp flags,
+ * just "copy" the lock status from vnode returned
+ * by mounted filesystem's VOP_LOOKUP(). Get rid
+ * of that new vnode afterwards.
+ */
+ lock_flags = VOP_ISLOCKED(newvp);
+ if (lock_flags == 0) {
+ VOP_UNLOCK(dvp, 0);
+ vrele(newvp);
+ } else {
+ vput(newvp);
+ }
+ return (error);
+ }
+ }
+
+ if (cnp->cn_nameiop == RENAME)
+ return (EOPNOTSUPP);
+
+ AUTOFS_LOCK(amp);
+ error = autofs_node_find(anp, cnp->cn_nameptr, cnp->cn_namelen, &child);
+ if (error != 0) {
+ if ((cnp->cn_flags & ISLASTCN) && cnp->cn_nameiop == CREATE) {
+ AUTOFS_UNLOCK(amp);
+ return (EJUSTRETURN);
+ }
+
+ AUTOFS_UNLOCK(amp);
+ return (ENOENT);
+ }
+
+ /*
+ * XXX: Dropping the node here is ok, because we never remove nodes.
+ */
+ AUTOFS_UNLOCK(amp);
+
+ error = autofs_node_vn(child, mp, vpp);
+ if (error != 0) {
+ if ((cnp->cn_flags & ISLASTCN) && cnp->cn_nameiop == CREATE)
+ return (EJUSTRETURN);
+
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+autofs_mkdir(struct vop_mkdir_args *ap)
+{
+ struct vnode *vp;
+ struct autofs_node *anp;
+ struct autofs_mount *amp;
+ struct autofs_node *child;
+ int error;
+
+ vp = ap->a_dvp;
+ anp = vp->v_data;
+ amp = VFSTOAUTOFS(vp->v_mount);
+
+ /*
+ * Do not allow mkdir() if the calling thread is not
+ * automountd(8) descendant.
+ */
+ if (autofs_ignore_thread(curthread) == false)
+ return (EPERM);
+
+ AUTOFS_LOCK(amp);
+ error = autofs_node_new(anp, amp, ap->a_cnp->cn_nameptr,
+ ap->a_cnp->cn_namelen, &child);
+ if (error != 0) {
+ AUTOFS_UNLOCK(amp);
+ return (error);
+ }
+ AUTOFS_UNLOCK(amp);
+
+ error = autofs_node_vn(child, vp->v_mount, ap->a_vpp);
+
+ return (error);
+}
+
+static int
+autofs_readdir_one(struct uio *uio, const char *name, int fileno)
+{
+ struct dirent dirent;
+ int error, i;
+
+ memset(&dirent, 0, sizeof(dirent));
+ dirent.d_type = DT_DIR;
+ dirent.d_reclen = AUTOFS_DELEN;
+ dirent.d_fileno = fileno;
+ /* PFS_DELEN was picked to fit PFS_NAMLEN */
+ for (i = 0; i < AUTOFS_NAMELEN - 1 && name[i] != '\0'; ++i)
+ dirent.d_name[i] = name[i];
+ dirent.d_name[i] = 0;
+ dirent.d_namlen = i;
+
+ error = uiomove(&dirent, AUTOFS_DELEN, uio);
+ return (error);
+}
+
+static int
+autofs_readdir(struct vop_readdir_args *ap)
+{
+ struct vnode *vp, *newvp;
+ struct autofs_mount *amp;
+ struct autofs_node *anp, *child;
+ struct uio *uio;
+ off_t offset;
+ int error, i, resid;
+
+ vp = ap->a_vp;
+ amp = VFSTOAUTOFS(vp->v_mount);
+ anp = vp->v_data;
+ uio = ap->a_uio;
+
+ KASSERT(vp->v_type == VDIR, ("!VDIR"));
+
+ if (autofs_cached(anp, NULL, 0) == false &&
+ autofs_ignore_thread(curthread) == false) {
+ error = autofs_trigger_vn(vp, "", 0, &newvp);
+ if (error != 0)
+ return (error);
+
+ if (newvp != NULL) {
+ error = VOP_READDIR(newvp, ap->a_uio, ap->a_cred,
+ ap->a_eofflag, ap->a_ncookies, ap->a_cookies);
+ vput(newvp);
+ return (error);
+ }
+ }
+
+ /* only allow reading entire entries */
+ offset = uio->uio_offset;
+ resid = uio->uio_resid;
+ if (offset < 0 || offset % AUTOFS_DELEN != 0 ||
+ (resid && resid < AUTOFS_DELEN))
+ return (EINVAL);
+ if (resid == 0)
+ return (0);
+
+ if (ap->a_eofflag != NULL)
+ *ap->a_eofflag = TRUE;
+
+ if (offset == 0 && resid >= AUTOFS_DELEN) {
+ error = autofs_readdir_one(uio, ".", anp->an_fileno);
+ if (error != 0)
+ return (error);
+ offset += AUTOFS_DELEN;
+ resid -= AUTOFS_DELEN;
+ }
+
+ if (offset == AUTOFS_DELEN && resid >= AUTOFS_DELEN) {
+ if (anp->an_parent == NULL) {
+ /*
+ * XXX: Right?
+ */
+ error = autofs_readdir_one(uio, "..", anp->an_fileno);
+ } else {
+ error = autofs_readdir_one(uio, "..",
+ anp->an_parent->an_fileno);
+ }
+ if (error != 0)
+ return (error);
+ offset += AUTOFS_DELEN;
+ resid -= AUTOFS_DELEN;
+ }
+
+ i = 2; /* Account for "." and "..". */
+ AUTOFS_LOCK(amp);
+ TAILQ_FOREACH(child, &anp->an_children, an_next) {
+ if (resid < AUTOFS_DELEN) {
+ if (ap->a_eofflag != NULL)
+ *ap->a_eofflag = 0;
+ break;
+ }
+
+ /*
+ * Skip entries returned by previous call to getdents().
+ */
+ i++;
+ if (i * AUTOFS_DELEN <= offset)
+ continue;
+
+ error = autofs_readdir_one(uio, child->an_name,
+ child->an_fileno);
+ if (error != 0) {
+ AUTOFS_UNLOCK(amp);
+ return (error);
+ }
+ offset += AUTOFS_DELEN;
+ resid -= AUTOFS_DELEN;
+ }
+
+ AUTOFS_UNLOCK(amp);
+ return (0);
+}
+
+static int
+autofs_reclaim(struct vop_reclaim_args *ap)
+{
+ struct vnode *vp = ap->a_vp;
+ struct autofs_node *anp = vp->v_data;
+
+ vp = ap->a_vp;
+ anp = vp->v_data;
+
+ /*
+ * We do not free autofs_node here; instead we are
+ * destroying them in autofs_node_delete().
+ */
+ sx_xlock(&anp->an_vnode_lock);
+ anp->an_vnode = NULL;
+ vp->v_data = NULL;
+ sx_xunlock(&anp->an_vnode_lock);
+
+ return (0);
+}
+
+struct vop_vector autofs_vnodeops = {
+ .vop_default = &default_vnodeops,
+
+ .vop_access = autofs_access,
+ .vop_lookup = autofs_lookup,
+ .vop_create = VOP_EOPNOTSUPP,
+ .vop_getattr = autofs_getattr,
+ .vop_link = VOP_EOPNOTSUPP,
+ .vop_mkdir = autofs_mkdir,
+ .vop_mknod = VOP_EOPNOTSUPP,
+ .vop_read = VOP_EOPNOTSUPP,
+ .vop_readdir = autofs_readdir,
+ .vop_remove = VOP_EOPNOTSUPP,
+ .vop_rename = VOP_EOPNOTSUPP,
+ .vop_rmdir = VOP_EOPNOTSUPP,
+ .vop_setattr = VOP_EOPNOTSUPP,
+ .vop_symlink = VOP_EOPNOTSUPP,
+ .vop_write = VOP_EOPNOTSUPP,
+ .vop_reclaim = autofs_reclaim,
+};
+
+int
+autofs_node_new(struct autofs_node *parent, struct autofs_mount *amp,
+ const char *name, int namelen, struct autofs_node **anpp)
+{
+ struct autofs_node *anp;
+
+ if (parent != NULL)
+ AUTOFS_ASSERT_LOCKED(parent->an_mount);
+
+ anp = uma_zalloc(autofs_node_zone, M_WAITOK | M_ZERO);
+ if (namelen >= 0)
+ anp->an_name = strndup(name, namelen, M_AUTOFS);
+ else
+ anp->an_name = strdup(name, M_AUTOFS);
+ anp->an_fileno = atomic_fetchadd_int(&amp->am_last_fileno, 1);
+ callout_init(&anp->an_callout, 1);
+ /*
+ * The reason for SX_NOWITNESS here is that witness(4)
+ * cannot tell vnodes apart, so the following perfectly
+ * valid lock order...
+ *
+ * vnode lock A -> autofsvlk B -> vnode lock B
+ *
+ * ... gets reported as a LOR.
+ */
+ sx_init_flags(&anp->an_vnode_lock, "autofsvlk", SX_NOWITNESS);
+ getnanotime(&anp->an_ctime);
+ anp->an_parent = parent;
+ anp->an_mount = amp;
+ if (parent != NULL)
+ TAILQ_INSERT_TAIL(&parent->an_children, anp, an_next);
+ TAILQ_INIT(&anp->an_children);
+
+ *anpp = anp;
+ return (0);
+}
+
+int
+autofs_node_find(struct autofs_node *parent, const char *name,
+ int namelen, struct autofs_node **anpp)
+{
+ struct autofs_node *anp;
+
+ AUTOFS_ASSERT_LOCKED(parent->an_mount);
+
+ TAILQ_FOREACH(anp, &parent->an_children, an_next) {
+ if (namelen >= 0) {
+ if (strncmp(anp->an_name, name, namelen) != 0)
+ continue;
+ } else {
+ if (strcmp(anp->an_name, name) != 0)
+ continue;
+ }
+
+ if (anpp != NULL)
+ *anpp = anp;
+ return (0);
+ }
+
+ return (ENOENT);
+}
+
+void
+autofs_node_delete(struct autofs_node *anp)
+{
+ struct autofs_node *parent;
+
+ AUTOFS_ASSERT_LOCKED(anp->an_mount);
+ KASSERT(TAILQ_EMPTY(&anp->an_children), ("have children"));
+
+ callout_drain(&anp->an_callout);
+
+ parent = anp->an_parent;
+ if (parent != NULL)
+ TAILQ_REMOVE(&parent->an_children, anp, an_next);
+ sx_destroy(&anp->an_vnode_lock);
+ free(anp->an_name, M_AUTOFS);
+ uma_zfree(autofs_node_zone, anp);
+}
+
+int
+autofs_node_vn(struct autofs_node *anp, struct mount *mp, struct vnode **vpp)
+{
+ struct vnode *vp;
+ int error;
+
+ AUTOFS_ASSERT_UNLOCKED(anp->an_mount);
+
+ sx_xlock(&anp->an_vnode_lock);
+
+ vp = anp->an_vnode;
+ if (vp != NULL) {
+ error = vget(vp, LK_EXCLUSIVE | LK_RETRY, curthread);
+ if (error != 0) {
+ AUTOFS_WARN("vget failed with error %d", error);
+ sx_xunlock(&anp->an_vnode_lock);
+ return (error);
+ }
+ if (vp->v_iflag & VI_DOOMED) {
+ /*
+ * We got forcibly unmounted.
+ */
+ AUTOFS_DEBUG("doomed vnode");
+ sx_xunlock(&anp->an_vnode_lock);
+ vput(vp);
+
+ return (ENOENT);
+ }
+
+ *vpp = vp;
+ sx_xunlock(&anp->an_vnode_lock);
+ return (0);
+ }
+
+ error = getnewvnode("autofs", mp, &autofs_vnodeops, &vp);
+ if (error != 0) {
+ sx_xunlock(&anp->an_vnode_lock);
+ return (error);
+ }
+
+ error = vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
+ if (error != 0) {
+ sx_xunlock(&anp->an_vnode_lock);
+ vdrop(vp);
+ return (error);
+ }
+
+ vp->v_type = VDIR;
+ if (anp->an_parent == NULL)
+ vp->v_vflag |= VV_ROOT;
+ vp->v_data = anp;
+
+ error = insmntque(vp, mp);
+ if (error != 0) {
+ AUTOFS_WARN("insmntque() failed with error %d", error);
+ sx_xunlock(&anp->an_vnode_lock);
+ return (error);
+ }
+
+ KASSERT(anp->an_vnode == NULL, ("lost race"));
+ anp->an_vnode = vp;
+
+ sx_xunlock(&anp->an_vnode_lock);
+
+ *vpp = vp;
+ return (0);
+}
diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c
index 674e526..c407699 100644
--- a/sys/kern/vfs_mount.c
+++ b/sys/kern/vfs_mount.c
@@ -649,6 +649,10 @@ vfs_donmount(struct thread *td, uint64_t fsflags, struct uio *fsoptions)
fsflags |= MNT_SYNCHRONOUS;
else if (strcmp(opt->name, "union") == 0)
fsflags |= MNT_UNION;
+ else if (strcmp(opt->name, "automounted") == 0) {
+ fsflags |= MNT_AUTOMOUNTED;
+ vfs_freeopt(optlist, opt);
+ }
}
/*
diff --git a/sys/libkern/strndup.c b/sys/libkern/strndup.c
new file mode 100644
index 0000000..43983d5
--- /dev/null
+++ b/sys/libkern/strndup.c
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (c) 2003 Networks Associates Technology, Inc.
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Network
+ * Associates Laboratories, the Security Research Division of Network
+ * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
+ * ("CBOSS"), as part of the DARPA CHATS research program.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/libkern.h>
+#include <sys/malloc.h>
+
+char *
+strndup(const char *string, size_t maxlen, struct malloc_type *type)
+{
+ size_t len;
+ char *copy;
+
+ len = strnlen(string, maxlen) + 1;
+ copy = malloc(len, type, M_WAITOK);
+ bcopy(string, copy, len);
+ copy[len - 1] = '\0';
+ return (copy);
+}
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
index 1e26093..429901f 100644
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -44,6 +44,7 @@ SUBDIR= \
ata \
ath \
ath_pci \
+ autofs \
${_auxio} \
${_bce} \
bfe \
diff --git a/sys/modules/autofs/Makefile b/sys/modules/autofs/Makefile
new file mode 100644
index 0000000..5aaa892
--- /dev/null
+++ b/sys/modules/autofs/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../fs/autofs
+
+KMOD= autofs
+SRCS= vnode_if.h \
+ autofs.c \
+ autofs_vnops.c \
+ autofs_vfsops.c
+
+.include <bsd.kmod.mk>
diff --git a/sys/sys/libkern.h b/sys/sys/libkern.h
index 3665b3a..5f850fb 100644
--- a/sys/sys/libkern.h
+++ b/sys/sys/libkern.h
@@ -117,6 +117,7 @@ int strcmp(const char *, const char *);
char *strcpy(char * __restrict, const char * __restrict);
size_t strcspn(const char * __restrict, const char * __restrict) __pure;
char *strdup(const char *__restrict, struct malloc_type *);
+char *strndup(const char *__restrict, size_t, struct malloc_type *);
size_t strlcat(char *, const char *, size_t);
size_t strlcpy(char *, const char *, size_t);
size_t strlen(const char *);
diff --git a/sys/sys/mount.h b/sys/sys/mount.h
index e0c2696..0c9a1da 100644
--- a/sys/sys/mount.h
+++ b/sys/sys/mount.h
@@ -260,6 +260,7 @@ void __mnt_vnode_markerfree_active(struct vnode **mvp, struct mount *);
#define MNT_NOCLUSTERR 0x0000000040000000ULL /* disable cluster read */
#define MNT_NOCLUSTERW 0x0000000080000000ULL /* disable cluster write */
#define MNT_SUJ 0x0000000100000000ULL /* using journaled soft updates */
+#define MNT_AUTOMOUNTED 0x0000000200000000ULL /* mounted by automountd(8) */
/*
* NFS export related mount flags.
@@ -296,7 +297,7 @@ void __mnt_vnode_markerfree_active(struct vnode **mvp, struct mount *);
MNT_NOCLUSTERW | MNT_SUIDDIR | MNT_SOFTDEP | \
MNT_IGNORE | MNT_EXPUBLIC | MNT_NOSYMFOLLOW | \
MNT_GJOURNAL | MNT_MULTILABEL | MNT_ACLS | \
- MNT_NFS4ACLS)
+ MNT_NFS4ACLS | MNT_AUTOMOUNTED)
/* Mask of flags that can be updated. */
#define MNT_UPDATEMASK (MNT_NOSUID | MNT_NOEXEC | \
@@ -304,7 +305,8 @@ void __mnt_vnode_markerfree_active(struct vnode **mvp, struct mount *);
MNT_NOATIME | \
MNT_NOSYMFOLLOW | MNT_IGNORE | \
MNT_NOCLUSTERR | MNT_NOCLUSTERW | MNT_SUIDDIR | \
- MNT_ACLS | MNT_USER | MNT_NFS4ACLS)
+ MNT_ACLS | MNT_USER | MNT_NFS4ACLS | \
+ MNT_AUTOMOUNTED)
/*
* External filesystem command modifier flags.
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index b13cf49..5bbf5b9 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -5,6 +5,7 @@
SUBDIR= adduser \
arp \
+ autofs \
binmiscctl \
bootparamd \
bsdconfig \
diff --git a/usr.sbin/autofs/Makefile b/usr.sbin/autofs/Makefile
new file mode 100644
index 0000000..fab6865
--- /dev/null
+++ b/usr.sbin/autofs/Makefile
@@ -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 <bsd.prog.mk>
diff --git a/usr.sbin/autofs/auto_master.5 b/usr.sbin/autofs/auto_master.5
new file mode 100644
index 0000000..f923f4f
--- /dev/null
+++ b/usr.sbin/autofs/auto_master.5
@@ -0,0 +1,272 @@
+.\" 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 31, 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
+and terminated by newline character:
+.Bd -literal -offset indent
+.Pa mountpoint Pa map_name Op Ar -options
+.Ed
+.Pp
+.Pa mountpoint
+is either a fully specified path, or
+.Li /- .
+When
+.Pa mountpoint
+is a full path,
+.Pa map_name
+must reference an indirect map.
+Otherwise,
+.Pa map_name
+must reference a direct map.
+See
+.Sx "MAP SYNTAX" below.
+.Pp
+.Pa map_name
+specifies map to use.
+If
+.Pa map_name
+begins with
+.Li - ,
+it specifies a special map.
+See
+.Sx "MAP SYNTAX"
+below.
+If
+.Pa map_name
+is not a fully specified path
+.Pq it does not start with Li / ,
+.Xr automountd 8
+will search for that name in
+.Li /etc .
+Otherwise it will use the path as given.
+If the file indicated by
+.Pa map_name
+is executable,
+.Xr automountd 8
+will assume it is an executable map.
+See
+.Sx "MAP SYNTAX"
+below.
+Otherwise, the file is opened and the contents parsed.
+.Pp
+.Pa -options
+is an optional field that starts with
+.Li -
+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
+and terminated by newline character:
+.Bd -literal -offset indent
+.Pa key Oo Ar -options Oc Oo Ar mountpoint Oo -options Oc Oc Ar location Op ...
+.Ed
+.Pp
+In most cases, it can be simplified to:
+.Bd -literal -offset indent
+.Pa key Oo Ar -options Oc Ar location
+.Ed
+.Pp
+.Pa key
+is the 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
+.Ar options
+field, if present, must begin with
+.Li - .
+When mounting the filesystem, options supplied to
+.Nm
+and options specified in the map entry are concatenated together.
+The special option
+.Li fstype
+is used to specify filesystem type.
+It is not passed to the mount program as an option.
+Instead, it is passed as argument to
+.Cm "mount -t".
+.Pp
+The optional
+.Pa mountpoint
+field is used to specify multiple mount points
+for a single key.
+.Pp
+The
+.Ar location
+field specifies the filesystem to be mounted.
+To pass location that begins with
+.Li / ,
+prefix it with colon.
+For example,
+.Li :/dev/cd0 .
+.Pp
+This example, when used with the
+.Nm
+example above, specifies that the NFS share
+.Li 192.168.1.1:/share/example/x
+will be mounted on
+.Pa /example/x/
+when any process attempts to access that mountpoint, with
+.Li intr
+and
+.Li nfsv4
+mount options:
+.Bd -literal -offset indent
+.Li x -intr,nfsv4 192.168.1.1:/share/example/x
+.Ed
+.Pp
+Automatically mount the CD drive on access:
+.Bd -literal -offset indent
+.Li cd -intr,fstype=cd9660 :/dev/cd0
+.Ed
+.Sh SPECIAL MAPS
+Special maps have names beginning with
+.Li - .
+Supported special maps are:
+.Pp
+.Bl -tag -width "-hosts" -compact
+.It Li -hosts
+This map queries the remote NFS server and maps exported volumes.
+It is traditionally mounted on
+.Pa /net .
+It enables access to files on a remote NFS server by accessing
+.Pa /net/nfs-server-ip/share-name/
+directory, without the need for any further configuration.
+.It Li -null
+This map prevents the
+.Xr automountd 8
+from mounting anything on the mountpoint.
+.El
+.Sh EXECUTABLE MAPS
+If the map file specified in
+.Nm
+has execute bit set, the
+.Xr automountd 8
+will execute it and parse the standard output instead of parsing
+the file contents.
+.Sh INDIRECT VERSUS DIRECT MAPS
+Indirect maps are referred to in
+.Nm
+by entries with a fully qualified path as a mount point, and must contain only
+relative paths as keys.
+Direct maps are referred to in
+.Nm
+by entries with
+.Li /-
+as the mountpoint, and must contain only fully qualified paths as keys.
+For indirect maps, the final mount point is determined by concatenating the
+.Nm
+mountpoint with the map entry key and optional map entry mountpoint.
+For direct maps, the final mount point is determined by concatenating
+the map entry key with the optional map entry mountpoint.
+.Pp
+The example above could be rewritten using direct map, by placing this in
+.Nm :
+.Bd -literal -offset indent
+.Li /- auto_example
+.Ed
+.Pp
+and this in
+.Li /etc/auto_example
+map file:
+.Bd -literal -offset indent
+.Li /example/x -intr,nfsv4 192.168.1.1:/share/example/x
+.Li /example/cd -intr,fstype=cd9660 :/dev/cd0
+.Ed
+.Sh DIRECTORY SERVICES
+Both
+.Nm
+and maps may contain entries consisting of a plus sign and map name:
+.Bd -literal -offset indent
+.Li +auto_master
+.Ed
+.Pp
+Those entries cause
+.Xr automountd 8
+daemon to retrieve the named map from directory services (like LDAP)
+and include it where the entry was.
+.Pp
+If the file containing the map referenced in
+.Nm
+is not found, the map will be retrieved from directory services instead.
+.Pp
+To retrieve entries from directory services,
+.Xr automountd 8
+daemon runs
+.Pa /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 standard output.
+An example script to use LDAP is included in
+.Pa /etc/autofs/include_ldap .
+It can be symlinked to
+.Pa /etc/autofs/include .
+.Sh FILES
+.Bl -tag -width ".Pa /etc/auto_master" -compact
+.It Pa /etc/auto_master
+The default location of the
+.Pa 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 Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/autofs/automount.8 b/usr.sbin/autofs/automount.8
new file mode 100644
index 0000000..2988c68
--- /dev/null
+++ b/usr.sbin/autofs/automount.8
@@ -0,0 +1,107 @@
+.\" 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 that it references, and mounts
+or unmounts
+.Xr autofs 4
+filesystems to match.
+These options are available:
+.Bl -tag -width ".Fl v"
+.It Fl D
+Define a variable.
+It is only useful with
+.Fl L .
+.It Fl L
+Do not mount or unmount anything.
+Instead parse
+.Xr auto_master 5
+and any direct maps, then 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
+.Fl u .
+.It Fl o
+Specify mount options to be used along with the ones specified in the maps.
+It is only useful with
+.Fl L .
+.It Fl u
+Try to unmount filesystems mounted by
+.Xr automountd 8 .
+.Xr autofs 5
+mounts are not unmounted.
+To unmount all
+.Xr autofs
+mounts, use
+.Cm "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.1 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/autofs/automount.c b/usr.sbin/autofs/automount.c
new file mode 100644
index 0000000..ef784a6
--- /dev/null
+++ b/usr.sbin/autofs/automount.c
@@ -0,0 +1,345 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/utsname.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+#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) does not need to parse it.
+ */
+ build_iovec(&iov, &iovlen, "master_options",
+ __DECONST(void *, options), (size_t)-1);
+ build_iovec(&iov, &iovlen, "master_prefix",
+ __DECONST(void *, prefix), (size_t)-1);
+
+ error = nmount(iov, iovlen, 0);
+ if (error != 0) {
+ if (*errmsg != '\0') {
+ log_err(1, "cannot mount %s on %s: %s",
+ from, fspath, errmsg);
+ } else {
+ log_err(1, "cannot mount %s on %s", from, fspath);
+ }
+ }
+}
+
+static void
+mount_if_not_already(const struct node *n, const char *map,
+ const struct statfs *mntbuf, int nitems)
+{
+ const struct statfs *sb;
+ char *mountpoint;
+ char *from;
+ int ret;
+
+ ret = asprintf(&from, "map %s", map);
+ if (ret < 0)
+ log_err(1, "asprintf");
+
+ mountpoint = node_path(n);
+ sb = find_statfs(mntbuf, nitems, mountpoint);
+ if (sb != NULL) {
+ if (strcmp(sb->f_fstypename, "autofs") != 0) {
+ log_debugx("unknown filesystem mounted "
+ "on %s; mounting", mountpoint);
+ /*
+ * XXX: Compare options and 'from',
+ * and update the mount if necessary.
+ */
+ } else {
+ log_debugx("autofs already mounted "
+ "on %s", mountpoint);
+ free(from);
+ free(mountpoint);
+ return;
+ }
+ } else {
+ log_debugx("nothing mounted on %s; mounting",
+ mountpoint);
+ }
+
+ mount_autofs(from, mountpoint, 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);
+}
diff --git a/usr.sbin/autofs/automountd.8 b/usr.sbin/autofs/automountd.8
new file mode 100644
index 0000000..31fc8f2
--- /dev/null
+++ b/usr.sbin/autofs/automountd.8
@@ -0,0 +1,103 @@
+.\" 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 Ar maxproc
+.Op Fl o Ar options
+.Op Fl d
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+daemon is responsible for handling
+.Xr autofs 5
+mount requests, parsing maps,
+and mounting filesystems they specify.
+On startup,
+.Nm
+forks into background and waits for kernel requests.
+When a request is received,
+.Nm
+forks a child process.
+The child process parses the appropriate map and mounts filesystems accordingly.
+Then it signals the kernel to release blocked processes that were waiting
+for the 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
+.Fl i ,
+.Nm
+creates all the subdirectories it can.
+Users may not realize that the wildcard map entry makes it possible to access
+directories that have not yet been created.
+.It Fl m Ar maxproc
+Limit the number of forked
+.Nm
+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 Ar options
+Specify mount options.
+Options specified here ill be overridden by options entered in maps or
+.Xr auto_master 5 .
+.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.1 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/autofs/automountd.c b/usr.sbin/autofs/automountd.c
new file mode 100644
index 0000000..0c743a3
--- /dev/null
+++ b/usr.sbin/autofs/automountd.c
@@ -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 <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/utsname.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+#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 do not want that behaviour, because it leaves mount_nfs(8)
+ * instances and automountd(8) children hanging forever.
+ * Disable retries unless the option was passed explicitly.
+ */
+ retrycnt = pick_option("retrycnt=", &options);
+ if (retrycnt == NULL) {
+ log_debugx("retrycnt not specified in options; "
+ "defaulting to 1");
+ 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);
+}
+
diff --git a/usr.sbin/autofs/autounmountd.8 b/usr.sbin/autofs/autounmountd.8
new file mode 100644
index 0000000..1b5d9a8
--- /dev/null
+++ b/usr.sbin/autofs/autounmountd.8
@@ -0,0 +1,88 @@
+.\" 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
+.Xr automountd 8 .
+On startup,
+.Nm
+retrieves a list of filesystems that have the
+.Li automounted
+mount option set.
+The list is updated every time a filesystem is mounted or unmounted.
+After a specified time passes,
+.Nm
+attempts to unmount a filesystem, retrying after some time if necessary.
+.Pp
+These 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 trying to unmount an expired filesystem
+after a previous attempt failed, possibly due to filesystem being busy.
+The default value is 600, or ten minutes.
+.It Fl t
+Number of seconds to wait before trying to unmount a filesystem.
+The default value is 600, or ten 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.1 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
diff --git a/usr.sbin/autofs/autounmountd.c b/usr.sbin/autofs/autounmountd.c
new file mode 100644
index 0000000..4ec24f7
--- /dev/null
+++ b/usr.sbin/autofs/autounmountd.c
@@ -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 <sys/types.h>
+#include <sys/event.h>
+#include <sys/mount.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libutil.h>
+
+#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);
+}
diff --git a/usr.sbin/autofs/common.c b/usr.sbin/autofs/common.c
new file mode 100644
index 0000000..0847c3b
--- /dev/null
+++ b/usr.sbin/autofs/common.c
@@ -0,0 +1,1129 @@
+/*-
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/utsname.h>
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+#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 do not care about freeing
+ * memory - which is okay here, because we are 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(&copy, "/");
+ 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 cannot properly handle a situation where the map key
+ * is "/". Ignore such entries.
+ *
+ * XXX: According to Piete Brooks, Linux automounter uses
+ * "/" as a wildcard character in LDAP maps. Perhaps
+ * we should work around this braindamage by substituting
+ * "*" for "/"?
+ */
+ if (strcmp(key, "/") == 0) {
+ log_warnx("nonsensical map key \"/\" at %s, line %d; "
+ "ignoring map entry ", map, lineno);
+
+ /*
+ * Skip the rest of the entry.
+ */
+ do {
+ ret = yylex();
+ } while (ret != 0 && ret != NEWLINE);
+
+ key = options = NULL;
+ continue;
+ }
+
+ //log_debugx("adding map node, %s", key);
+ node = node_new(parent, key, options, NULL, map, lineno);
+ key = options = NULL;
+
+ for (;;) {
+ if (yytext[0] == '/') {
+ if (mountpoint != NULL) {
+ log_errx(1, "duplicated mountpoint "
+ "in %s, line %d", map, lineno);
+ }
+ if (options2 != NULL || location != NULL) {
+ log_errx(1, "mountpoint out of order "
+ "in %s, line %d", map, lineno);
+ }
+ mountpoint = checked_strdup(yytext);
+ goto again;
+ }
+
+ if (yytext[0] == '-') {
+ if (options2 != NULL) {
+ log_errx(1, "duplicated options "
+ "in %s, line %d", map, lineno);
+ }
+ if (location != NULL) {
+ log_errx(1, "options out of order "
+ "in %s, line %d", map, lineno);
+ }
+ options2 = checked_strdup(yytext + 1);
+ goto again;
+ }
+
+ if (location != NULL) {
+ log_errx(1, "too many arguments "
+ "in %s, line %d", map, lineno);
+ }
+
+ /*
+ * If location field starts with colon, e.g. ":/dev/cd0",
+ * then strip it.
+ */
+ if (yytext[0] == ':') {
+ location = checked_strdup(yytext + 1);
+ if (location[0] == '\0') {
+ log_errx(1, "empty location in %s, "
+ "line %d", map, lineno);
+ }
+ } else {
+ location = checked_strdup(yytext);
+ }
+
+ if (mountpoint == NULL)
+ mountpoint = checked_strdup("/");
+ if (options2 == NULL)
+ options2 = checked_strdup("");
+
+#if 0
+ log_debugx("adding map node, %s %s %s",
+ mountpoint, options2, location);
+#endif
+ node_new(node, mountpoint, options2, location,
+ map, lineno);
+ mountpoint = options2 = location = NULL;
+again:
+ ret = yylex();
+ if (ret == 0 || ret == NEWLINE) {
+ if (mountpoint != NULL || options2 != NULL ||
+ location != NULL) {
+ log_errx(1, "truncated entry "
+ "in %s, line %d", map, lineno);
+ }
+ break;
+ }
+ }
+ }
+}
+
+static bool
+file_is_executable(const char *path)
+{
+ struct stat sb;
+ int error;
+
+ error = stat(path, &sb);
+ if (error != 0)
+ log_err(1, "cannot stat %s", path);
+ if ((sb.st_mode & S_IXUSR) || (sb.st_mode & S_IXGRP) ||
+ (sb.st_mode & S_IXOTH))
+ return (true);
+ return (false);
+}
+
+/*
+ * Parse a special map, e.g. "-hosts".
+ */
+static void
+parse_special_map(struct node *parent, const char *map, const char *key)
+{
+ char *path;
+ int error, ret;
+
+ assert(map[0] == '-');
+
+ 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, e.g. LDAP.
+ * Note that it is different from executable maps, in that
+ * the include script outputs the whole map to standard output
+ * (as opposed to executable maps that only output a single
+ * entry, without the key), and it takes the map name as an
+ * argument, instead of key.
+ */
+static void
+parse_included_map(struct node *parent, const char *map)
+{
+ int error;
+
+ assert(map[0] != '-');
+ assert(map[0] != '/');
+
+ error = access(AUTO_INCLUDE_PATH, F_OK);
+ if (error != 0) {
+ log_errx(1, "directory services not configured;"
+ " %s does not exist", AUTO_INCLUDE_PATH);
+ }
+
+ yyin = auto_popen(AUTO_INCLUDE_PATH, map, NULL);
+ assert(yyin != NULL);
+
+ parse_map_yyin(parent, map, NULL);
+
+ error = auto_pclose(yyin);
+ yyin = NULL;
+ if (error != 0)
+ log_errx(1, "failed to handle remote map \"%s\"", map);
+
+ node_expand_includes(parent, false);
+ node_expand_direct_maps(parent);
+}
+
+void
+parse_map(struct node *parent, const char *map, const char *key)
+{
+ 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\"");
+}
diff --git a/usr.sbin/autofs/common.h b/usr.sbin/autofs/common.h
new file mode 100644
index 0000000..bc0b6f6
--- /dev/null
+++ b/usr.sbin/autofs/common.h
@@ -0,0 +1,112 @@
+/*-
+ * 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 <sys/queue.h>
+#include <stdbool.h>
+
+#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 */
diff --git a/usr.sbin/autofs/defined.c b/usr.sbin/autofs/defined.c
new file mode 100644
index 0000000..7f1fb4b
--- /dev/null
+++ b/usr.sbin/autofs/defined.c
@@ -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 <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/utsname.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libutil.h>
+
+#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);
+}
diff --git a/usr.sbin/autofs/log.c b/usr.sbin/autofs/log.c
new file mode 100644
index 0000000..a30ebaa
--- /dev/null
+++ b/usr.sbin/autofs/log.c
@@ -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 <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <vis.h>
+
+#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);
+}
diff --git a/usr.sbin/autofs/popen.c b/usr.sbin/autofs/popen.c
new file mode 100644
index 0000000..420fe20
--- /dev/null
+++ b/usr.sbin/autofs/popen.c
@@ -0,0 +1,191 @@
+/*
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <paths.h>
+
+#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 do not 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 cannot 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);
+}
diff --git a/usr.sbin/autofs/token.l b/usr.sbin/autofs/token.l
new file mode 100644
index 0000000..8224042
--- /dev/null
+++ b/usr.sbin/autofs/token.l
@@ -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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#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; }
+%%
OpenPOWER on IntegriCloud