summaryrefslogtreecommitdiffstats
path: root/usr.sbin
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin')
-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
15 files changed, 3753 insertions, 0 deletions
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