diff options
Diffstat (limited to 'usr.sbin/autofs')
-rw-r--r-- | usr.sbin/autofs/Makefile | 32 | ||||
-rw-r--r-- | usr.sbin/autofs/Makefile.depend | 21 | ||||
-rw-r--r-- | usr.sbin/autofs/auto_master.5 | 374 | ||||
-rw-r--r-- | usr.sbin/autofs/automount.8 | 111 | ||||
-rw-r--r-- | usr.sbin/autofs/automount.c | 396 | ||||
-rw-r--r-- | usr.sbin/autofs/automountd.8 | 103 | ||||
-rw-r--r-- | usr.sbin/autofs/automountd.c | 569 | ||||
-rw-r--r-- | usr.sbin/autofs/autounmountd.8 | 88 | ||||
-rw-r--r-- | usr.sbin/autofs/autounmountd.c | 351 | ||||
-rw-r--r-- | usr.sbin/autofs/common.c | 1225 | ||||
-rw-r--r-- | usr.sbin/autofs/common.h | 114 | ||||
-rw-r--r-- | usr.sbin/autofs/defined.c | 272 | ||||
-rw-r--r-- | usr.sbin/autofs/log.c | 198 | ||||
-rw-r--r-- | usr.sbin/autofs/popen.c | 193 | ||||
-rw-r--r-- | usr.sbin/autofs/token.l | 58 |
15 files changed, 4105 insertions, 0 deletions
diff --git a/usr.sbin/autofs/Makefile b/usr.sbin/autofs/Makefile new file mode 100644 index 0000000..00c79dd --- /dev/null +++ b/usr.sbin/autofs/Makefile @@ -0,0 +1,32 @@ +# $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 + +LIBADD= util + +# 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/Makefile.depend b/usr.sbin/autofs/Makefile.depend new file mode 100644 index 0000000..61f3b2b --- /dev/null +++ b/usr.sbin/autofs/Makefile.depend @@ -0,0 +1,21 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libutil \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +token.o: token.c +token.po: token.c +.endif diff --git a/usr.sbin/autofs/auto_master.5 b/usr.sbin/autofs/auto_master.5 new file mode 100644 index 0000000..89100f7 --- /dev/null +++ b/usr.sbin/autofs/auto_master.5 @@ -0,0 +1,374 @@ +.\" 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 March 13, 2015 +.Dt AUTO_MASTER 5 +.Os +.Sh NAME +.Nm auto_master +.Nd auto_master and map file format +.Sh DESCRIPTION +The automounter configuration consists of the +.Nm +configuration file, which assigns filesystem paths to map names, +and maps, which contain actual mount information. +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. +A wildcard +.Pq Ql * +can be used for the key. +It matches every directory that does not match other keys. +Those directories will not be visible to the user +until accessed. +.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 an argument to +.Cm "mount -t". +The default +.Li fstype +is +.Ql nfs . +The special option +.Li nobrowse +is used to disable creation of top-level directories for special +and executable maps. +.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. +Ampersands +.Pq Ql & +in the +.Ar location +field are replaced with the value of +.Ar key . +This is typically used with wildcards, like: +.Bd -literal -offset indent +.Li * 192.168.1.1:/share/& +.Ed +.Pp +The +.Ar location +field may contain references to variables, like: +.Bd -literal -offset indent +.Li sys 192.168.1.1:/sys/${OSNAME} +.Ed +.Pp +Defined variables are: +.Pp +.Bl -tag -width "-OSNAME" -compact +.It Li ARCH +Expands to the output of +.Li "uname -p" . +.It Li CPU +Same as ARCH. +.It Li HOST +Expands to the output of +.Li "uname -n" . +.It Li OSNAME +Expands to the output of +.Li "uname -s" . +.It Li OSREL +Expands to the output of +.Li "uname -r" . +.It Li OSVERS +Expands to the output of +.Li "uname -v" . +.El +.Pp +Additional variables can be defined with the +.Fl D +option of +.Xr automount 8 +and +.Xr automountd 8 . +.Pp +To pass a location that begins with +.Li / , +prefix it with a colon. +For example, +.Li :/dev/cd0 . +.Pp +This example, when put into +.Pa /etc/auto_example , +and with +.Nm +referring to the map as described 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, described in +.Xr mount_nfs 8 : +.Bd -literal -offset indent +.Li x -intr,nfsv4 192.168.1.1:/share/example/x +.Ed +.Pp +Automatically mount an SMB share on access, as a guest user, +without prompting for a password: +.Bd -literal -offset indent +.Li share -fstype=smbfs,-N ://@server/share +.Ed +.Pp +Automatically mount the CD drive on access: +.Bd -literal -offset indent +.Li cd -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 +Query the remote NFS server and map exported shares. +This map is traditionally mounted on +.Pa /net . +Access to files on a remote NFS server is provided through the +.Pf /net/ Ar nfs-server-ip Ns / Ns Ar share-name Ns/ +directory without any additional configuration. +Directories for individual NFS servers are not present until the first access, +when they are automatically created. +.It Li -media +Query devices that are not yet mounted, but contain valid filesystems. +Generally used to access files on removable media. +.It Li -noauto +Mount filesystems configured in +.Xr fstab 5 +as "noauto". +This needs to be set up as a direct map. +.It Li -null +Prevent +.Xr automountd 8 +from mounting anything on the mountpoint. +.El +.Pp +It is possible to add custom special maps by adding them, as executable +maps named +.Pa special_foo , +to the +.Pa /etc/autofs/ +directory. +.Sh EXECUTABLE MAPS +If the map file specified in +.Nm +has the execute bit set, +.Xr automountd 8 +will execute it and parse the standard output instead of parsing +the file contents. +When called without command line arguments, the executable is +expected to output a list of available map keys separated by +newline characters. +Otherwise, the executable will be called with a key name as +a command line argument. +Output from the executable is expected to be the entry for that key, +not including the key itself. +.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/share -fstype=smbfs,-N ://@server/share +.Li /example/cd -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. +.It Pa /etc/autofs/ +Directory containing shell scripts to implement special maps and directory +services. +.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..c111a99 --- /dev/null +++ b/usr.sbin/autofs/automount.8 @@ -0,0 +1,111 @@ +.\" 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 November 22, 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 c +.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 c +Flush caches, discarding possibly stale information obtained from maps +and directory services. +.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..ce5d861 --- /dev/null +++ b/usr.sbin/autofs/automount.c @@ -0,0 +1,396 @@ +/*- + * 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. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$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 char *options, + const char *prefix, 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, options, prefix); + free(from); + free(mountpoint); +} + +static void +mount_unmount(struct node *root) +{ + struct statfs *mntbuf; + struct node *n, *n2; + 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, n->n_options, + n->n_key, mntbuf, nitems); + continue; + } + + TAILQ_FOREACH(n2, &n->n_children, n_next) { + mount_if_not_already(n2, n->n_map, n->n_options, + "/", mntbuf, nitems); + } + } +} + +static void +flush_autofs(const char *fspath) +{ + struct iovec *iov = NULL; + char errmsg[255]; + int error, iovlen = 0; + + log_debugx("flushing %s", fspath); + 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, "errmsg", + errmsg, sizeof(errmsg)); + + error = nmount(iov, iovlen, MNT_UPDATE); + if (error != 0) { + if (*errmsg != '\0') { + log_err(1, "cannot flush %s: %s", + fspath, errmsg); + } else { + log_err(1, "cannot flush %s", fspath); + } + } +} + +static void +flush_caches(void) +{ + struct statfs *mntbuf; + int i, nitems; + + nitems = getmntinfo(&mntbuf, MNT_WAIT); + if (nitems <= 0) + log_err(1, "getmntinfo"); + + log_debugx("flushing autofs caches"); + + 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; + } + + flush_autofs(mntbuf[i].f_mntonname); + } +} + +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][-Lcfuv]\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, flush = 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:Lfco:uv")) != -1) { + switch (ch) { + case 'D': + defined_parse_and_add(optarg); + break; + case 'L': + show_maps++; + break; + case 'c': + flush = true; + break; + case 'f': + force_unmount = true; + break; + case 'o': + options = 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 (flush) { + flush_caches(); + return (0); + } + + if (do_unmount) { + unmount_automounted(force_unmount); + return (0); + } + + root = node_new_root(); + parse_master(root, AUTO_MASTER_PATH); + + if (show_maps) { + if (show_maps > 1) { + node_expand_indirect_maps(root); + node_expand_ampersand(root, NULL); + } + node_expand_defined(root); + node_print(root, options); + 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..175633b --- /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 March 10, 2015 +.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 will 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..2c9b1a9 --- /dev/null +++ b/usr.sbin/autofs/automountd.c @@ -0,0 +1,569 @@ +/*- + * 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. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$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, bool wildcards) +{ + struct autofs_daemon_done add; + int error; + + memset(&add, 0, sizeof(add)); + add.add_id = request_id; + add.add_wildcards = wildcards; + 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) + log_warn("AUTOFSDONE"); +} + +/* + * 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, true); +} + +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 *key, *options, *fstype, *nobrowse, *retrycnt, *tmp; + int error; + bool wildcards; + + 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) { + /* + * Direct map. autofs(4) doesn't have a way to determine + * correct map key, but since it's a direct map, we can just + * use adr_path instead. + */ + parent = root; + key = checked_strdup(adr->adr_path); + } else { + /* + * Indirect map. + */ + parent = node_new_map(root, checked_strdup(adr->adr_prefix), + NULL, checked_strdup(map), + checked_strdup("[kernel request]"), lineno); + + if (adr->adr_key[0] == '\0') + key = NULL; + else + key = checked_strdup(adr->adr_key); + } + + /* + * "Wildcards" here actually means "make autofs(4) request + * automountd(8) action if the node being looked up does not + * exist, even though the parent is marked as cached". This + * needs to be done for maps with wildcard entries, but also + * for special and executable maps. + */ + parse_map(parent, map, key, &wildcards); + if (!wildcards) + wildcards = node_has_wildcards(parent); + if (wildcards) + log_debugx("map may contain wildcard entries"); + else + log_debugx("map does not contain wildcard entries"); + + if (key != NULL) + node_expand_wildcard(root, 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); + } + + options = node_options(node); + + /* + * Append options from auto_master. + */ + options = concat(options, ',', adr->adr_options); + + /* + * Prepend options passed via automountd(8) command line. + */ + options = concat(cmdline_options, ',', options); + + if (node->n_location == NULL) { + log_debugx("found node defined at %s:%d; not a mountpoint", + node->n_config_file, node->n_config_line); + + nobrowse = pick_option("nobrowse", &options); + if (nobrowse != NULL && key == NULL) { + log_debugx("skipping map %s due to \"nobrowse\" " + "option; exiting", map); + done(0, true); + + /* + * Exit without calling exit_callback(). + */ + quick_exit(0); + } + + /* + * Not a mountpoint; create directories in the autofs mount + * and complete the request. + */ + create_subtree(node, incomplete_hierarchy); + + if (incomplete_hierarchy && key != NULL) { + /* + * We still need to create the single subdirectory + * user is trying to access. + */ + tmp = concat(adr->adr_path, '/', key); + node = node_find(root, tmp); + if (node != NULL) + create_subtree(node, false); + } + + log_debugx("nothing to mount; exiting"); + done(0, wildcards); + + /* + * 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); + + if (key != NULL) + node_expand_ampersand(node, key); + error = node_expand_defined(node); + if (error != 0) { + log_errx(1, "variable expansion failed for %s; " + "failing mount", adr->adr_path); + } + + /* + * Append "automounted". + */ + options = concat(options, ',', "automounted"); + + /* + * Remove "nobrowse", mount(8) doesn't understand it. + */ + pick_option("nobrowse", &options); + + /* + * 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 = concat(options, ',', "retrycnt=1"); + } else { + options = concat(options, ',', + 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"); + + log_debugx("mount done; exiting"); + done(0, wildcards); + + /* + * Exit without calling exit_callback(). + */ + quick_exit(0); +} + +static void +sigchld_handler(int dummy __unused) +{ + + /* + * The only purpose of this handler is to make SIGCHLD + * interrupt the AUTOFSREQUEST ioctl(2), so we can call + * wait_for_children(). + */ +} + +static void +register_sigchld(void) +{ + struct sigaction sa; + int error; + + bzero(&sa, sizeof(sa)); + sa.sa_handler = sigchld_handler; + sigfillset(&sa.sa_mask); + error = sigaction(SIGCHLD, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); + +} + + +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_debugx("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': + options = 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); + + register_sigchld(); + + 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..bf7afa4 --- /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 December 13, 2014 +.Dt AUTOUNMOUNTD 8 +.Os +.Sh NAME +.Nm autounmountd +.Nd daemon unmounting automounted filesystems +.Sh SYNOPSIS +.Nm +.Op Fl d +.Op Fl r Ar time +.Op Fl t Ar 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..b85f3ca --- /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. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$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 = -1.0; + int error; + + 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; + } + } + + 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 nevents; + + if (sleep_time != -1.0) { + assert(sleep_time > 0.0); + timeout.tv_sec = sleep_time; + timeout.tv_nsec = 0; + + log_debugx("waiting for filesystem event for %.0f seconds", sleep_time); + nevents = kevent(kq, NULL, 0, &unused, 1, &timeout); + } else { + log_debugx("waiting for filesystem event"); + nevents = kevent(kq, NULL, 0, &unused, 1, NULL); + } + if (nevents < 0) + log_err(1, "kevent"); + + if (nevents == 0) { + log_debugx("timeout reached"); + assert(sleep_time > 0.0); + } 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 == -1.0) { + sleep_time = mounted_max; + log_debugx("no filesystems to expire"); + } else 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..eae118f --- /dev/null +++ b/usr.sbin/autofs/common.c @@ -0,0 +1,1225 @@ +/*- + * 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. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$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> +#define _WITH_GETLINE +#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); +} + +/* + * Concatenate two strings, inserting separator between them, unless not needed. + */ +char * +concat(const char *s1, char separator, const char *s2) +{ + char *result; + char s1last, s2first; + int ret; + + if (s1 == NULL) + s1 = ""; + if (s2 == NULL) + s2 = ""; + + if (s1[0] == '\0') + s1last = '\0'; + else + s1last = s1[strlen(s1) - 1]; + + s2first = s2[0]; + + if (s1last == separator && s2first == separator) { + /* + * If s1 ends with the separator and s2 begins with + * it - skip the latter; otherwise concatenating "/" + * and "/foo" would end up returning "//foo". + */ + ret = asprintf(&result, "%s%s", s1, s2 + 1); + } else if (s1last == separator || s2first == separator || + s1[0] == '\0' || s2[0] == '\0') { + 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("%s: got %s and %s, returning %s", __func__, s1, s2, result); + + return (result); +} + +void +create_directory(const char *path) +{ + char *component, *copy, *tofree, *partial, *tmp; + int error; + + assert(path[0] == '/'); + + /* + * +1 to skip the leading slash. + */ + copy = tofree = checked_strdup(path + 1); + + partial = checked_strdup(""); + for (;;) { + component = strsep(©, "/"); + if (component == NULL) + break; + tmp = concat(partial, '/', component); + free(partial); + partial = tmp; + //log_debugx("creating \"%s\"", partial); + error = mkdir(partial, 0755); + if (error != 0 && errno != EEXIST) { + log_warn("cannot create %s", partial); + return; + } + } + + 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); + assert(key[0] != '\0'); + 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); + assert(key[0] != '\0'); + 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); +} + +static bool +node_is_direct_key(const struct node *n) +{ + + if (n->n_parent != NULL && n->n_parent->n_parent == NULL && + strcmp(n->n_key, "/-") == 0) { + return (true); + } + + return (false); +} + +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; + } + + return (node_is_direct_key(n)); +} + +bool +node_has_wildcards(const struct node *n) +{ + const struct node *child; + + TAILQ_FOREACH(child, &n->n_children, n_next) { + if (strcmp(child->n_key, "*") == 0) + return (true); + } + + return (false); +} + +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, 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; + + 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 (node_is_direct_key(n) && x[0] != '\0') + return (x); + + assert(n->n_key[0] != '\0'); + path = concat(n->n_key, '/', x); + free(x); + + 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) +{ + char *path; + size_t len; + + path = node_path_x(n, checked_strdup("")); + + /* + * Strip trailing slash, unless the whole path is "/". + */ + len = strlen(path); + if (len > 1 && path[len - 1] == '/') + path[len - 1] = '\0'; + + return (path); +} + +static char * +node_options_x(const struct node *n, char *x) +{ + char *options; + + if (n == NULL) + return (x); + + options = concat(x, ',', n->n_options); + free(x); + + 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, const char *cmdline_options, + int indent) +{ + const struct node *child, *first_child; + char *path, *options, *tmp; + + path = node_path(n); + tmp = node_options(n); + options = concat(cmdline_options, ',', tmp); + free(tmp); + + /* + * 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, cmdline_options, indent + 2); +} + +/* + * Recursively print node with all its children. The cmdline_options + * argument is used for additional options to be prepended to all the + * others - usually those are the options passed by command line. + */ +void +node_print(const struct node *n, const char *cmdline_options) +{ + const struct node *child; + + TAILQ_FOREACH(child, &n->n_children, n_next) + node_print_indent(child, cmdline_options, 0); +} + +static struct node * +node_find_x(struct node *node, const char *path) +{ + struct node *child, *found; + char *tmp; + size_t tmplen; + + //log_debugx("looking up %s in %s", path, node_path(node)); + + if (!node_is_direct_key(node)) { + tmp = node_path(node); + tmplen = strlen(tmp); + if (strncmp(tmp, path, tmplen) != 0) { + free(tmp); + return (NULL); + } + if (path[tmplen] != '/' && path[tmplen] != '\0') { + /* + * If we have two map entries like 'foo' and 'foobar', make + * sure the search for 'foobar' won't match 'foo' instead. + */ + free(tmp); + return (NULL); + } + free(tmp); + } + + TAILQ_FOREACH(child, &node->n_children, n_next) { + found = node_find_x(child, path); + if (found != NULL) + return (found); + } + + if (node->n_parent == NULL || node_is_direct_key(node)) + return (NULL); + + return (node); +} + +struct node * +node_find(struct node *root, const char *path) +{ + struct node *node; + + assert(root->n_parent == NULL); + + node = node_find_x(root, path); + if (node != NULL) + assert(node != root); + + 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) { + /* + * In case of executable map, the key is always + * non-NULL, even if the map is empty. So, make sure + * we don't fail empty maps here. + */ + if ((key != NULL && executable_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; + } + } + } +} + +/* + * Parse output of a special map called without argument. It is a list + * of keys, separated by newlines. They can contain whitespace, so use + * getline(3) instead of lexer used for maps. + */ +static void +parse_map_keys_yyin(struct node *parent, const char *map) +{ + char *line = NULL, *key; + size_t linecap = 0; + ssize_t linelen; + + lineno = 1; + + for (;;) { + linelen = getline(&line, &linecap, yyin); + if (linelen < 0) { + /* + * End of file. + */ + break; + } + if (linelen <= 1) { + /* + * Empty line, consisting of just the newline. + */ + continue; + } + + /* + * "-1" to strip the trailing newline. + */ + key = strndup(line, linelen - 1); + + log_debugx("adding key \"%s\"", key); + node_new(parent, key, NULL, NULL, map, lineno); + lineno++; + } + free(line); +} + +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] == '-'); + + /* + * +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); + + if (key == NULL) { + parse_map_keys_yyin(parent, map); + } else { + 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, + bool *wildcards) +{ + char *path = NULL; + int error, ret; + bool executable; + + assert(map != NULL); + assert(map[0] != '\0'); + + log_debugx("parsing map \"%s\"", map); + + if (wildcards != NULL) + *wildcards = false; + + if (map[0] == '-') { + if (wildcards != NULL) + *wildcards = true; + 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 (wildcards != NULL) + *wildcards = true; + + 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..dc84415 --- /dev/null +++ b/usr.sbin/autofs/common.h @@ -0,0 +1,114 @@ +/*- + * 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 *concat(const char *s1, char separator, const char *s2); +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); +bool node_has_wildcards(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, const char *cmdline_options); +void parse_master(struct node *root, const char *path); +void parse_map(struct node *parent, const char *map, const char *args, + bool *wildcards); +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..eaaea28 --- /dev/null +++ b/usr.sbin/autofs/defined.c @@ -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 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. + * + */ + +/* + * All the "defined" stuff is for handling variables, + * such as ${OSNAME}, in maps. + */ + +#include <sys/cdefs.h> +__FBSDID("$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" + +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..d5682cc --- /dev/null +++ b/usr.sbin/autofs/log.c @@ -0,0 +1,198 @@ +/*- + * 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. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$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..e114880 --- /dev/null +++ b/usr.sbin/autofs/popen.c @@ -0,0 +1,193 @@ +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Portions of this software were developed by Edward Tomasz Napierala + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$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 = 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..6a92b7f --- /dev/null +++ b/usr.sbin/autofs/token.l @@ -0,0 +1,58 @@ +%{ +/*- + * 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 + +%% +\"[^"]+\" { yytext++; yytext[strlen(yytext) - 1] = '\0'; return STR; }; +[a-zA-Z0-9\.\+-_/\:\[\]$&%{}]+ { return STR; } +#.*\n { lineno++; return NEWLINE; }; +\\\n { lineno++; }; +\n { lineno++; return NEWLINE; } +[ \t]+ /* ignore whitespace */; +. { return STR; } +%% |