From: Tomohiro Kusumi Date: Wed, 18 May 2016 08:01:32 +0000 (+0900) Subject: autofs: Port autofs from FreeBSD X-Git-Tag: v4.6.0rc~260 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/e2950f411cf734644d0586c7ed409af918988d76 autofs: Port autofs from FreeBSD Brought in basically from FreeBSD@GitHub cac9beab7d53f0c37ce2a2a1b893be59028928f4 with lots of changes. Note that this commit isn't necessarily 1:1 with above commit. Kernel code is basically a rewrite based on the FreeBSD code. Userspace is basically 1:1 with FreeBSD except that lots of small changes (including related commits listed below) were necessary. This is due to autofs being dependent on FreeBSD specific interface, command options and such. For userspace, note that non-functional stuff (e.g. whitespace warnings via git am) are intentionally left to be 1:1 with FreeBSD. Userspace is basically portable, so don't try to obfuscate the real changes made for DragonFly by fixing these for now till things are considered stable unless it's a bug from FreeBSD. Summary of newly added or modified files. - sys/vfs/autofs - autofs filesystem - usr.sbin/autofs - autofs userspace command and daemons - etc/ - configuration files and manpages - others - changes in misc subsystems (not independent of autofs) Related DragonFly commits. - usr.sbin/autofs: Workaround namecache bug after unmount - sys/kern: Don't implement .vfs_sync unless sync is supported - user.sbin/fstyp: Port fstyp from FreeBSD - sys/kern: Retry nlookup if nresolve returned ESTALE - sys/sys: Fix IOCPARM_MAX - sys/sys: Extend IOCPARM_MAX - usr.bin/showmount: Add -E option - sbin/mount_nfs: Add -o retrycnt= option - sys/kern: Add kqueue EVFILT_FS - sys/kern: Add kstrndup() Related DragonFly PRs. - https://bugs.dragonflybsd.org/issues/2900 - https://bugs.dragonflybsd.org/issues/2901 - https://bugs.dragonflybsd.org/issues/2905 - https://bugs.dragonflybsd.org/issues/2907 - https://bugs.dragonflybsd.org/issues/2908 - https://bugs.dragonflybsd.org/issues/2909 - https://bugs.dragonflybsd.org/issues/2912 - https://bugs.dragonflybsd.org/issues/2913 - https://bugs.dragonflybsd.org/issues/2914 Other related resource. - http://lists.dragonflybsd.org/pipermail/users/2016-May/thread.html#249556 - http://lists.dragonflybsd.org/pipermail/users/2016-June/thread.html#249680 - https://www.dragonflydigest.com/2016/05/06/18066.html --- diff --git a/etc/Makefile b/etc/Makefile index f0b4ce7da2..4ab03c0f41 100644 --- a/etc/Makefile +++ b/etc/Makefile @@ -21,7 +21,7 @@ BINUPDATE+=${.CURDIR}/../usr.bin/mail/misc/mail.rc \ # Initial distribution files are installed read-write (644) # -BIN1= amd.map auth.conf \ +BIN1= amd.map auth.conf auto_master \ crontab csh.cshrc csh.login csh.logout \ devtab dhclient.conf dm.conf dntpd.conf \ ftpusers group \ @@ -160,6 +160,8 @@ upgrade_etc: upgrade_check preupgrade remove-obsolete-files chmod 1777 ${DESTDIR}/var/run/sem cd ${UPGRADE_SRCDIR}/rc.d; ${MAKE} install cd ${UPGRADE_SRCDIR}/devd; ${MAKE} install + mkdir -p ${DESTDIR}/etc/autofs + cd ${UPGRADE_SRCDIR}/autofs; ${MAKE} install # "../share/termcap/make etc-termcap" expanded inline here: ${LN} -fs /usr/share/misc/termcap ${DESTDIR}/etc/termcap # "../usr.sbin/rmt/make etc-rmt" expanded inline here: @@ -266,6 +268,7 @@ distribution: cd ${.CURDIR}/periodic; ${MAKE} install cd ${.CURDIR}/rc.d; ${MAKE} install cd ${.CURDIR}/devd; ${MAKE} install + cd ${.CURDIR}/autofs; ${MAKE} install cd ${.CURDIR}/../share/termcap; ${MAKE} etc-termcap cd ${.CURDIR}/../usr.sbin/rmt; ${MAKE} etc-rmt cd ${.CURDIR}; \ diff --git a/etc/auto_master b/etc/auto_master new file mode 100644 index 0000000000..255b7f861b --- /dev/null +++ b/etc/auto_master @@ -0,0 +1,9 @@ +# $FreeBSD$ +# +# Automounter master map, see auto_master(5) for details. +# +/net -hosts -nobrowse,nosuid,intr +# When using the -media special map, make sure to edit devd.conf(5) +# to move the call to "automount -c" out of the comments section. +#/media -media -nosuid +#/- -noauto diff --git a/etc/autofs/Makefile b/etc/autofs/Makefile new file mode 100644 index 0000000000..09d019da6d --- /dev/null +++ b/etc/autofs/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +FILES= include_ldap special_hosts special_media special_noauto special_null + +NO_OBJ= +FILESDIR= /etc/autofs +FILESMODE= 644 +FILESMODE_special_media = 755 +FILESMODE_special_noauto = 755 + +.include diff --git a/etc/autofs/include_ldap b/etc/autofs/include_ldap new file mode 100644 index 0000000000..4cf70bfeef --- /dev/null +++ b/etc/autofs/include_ldap @@ -0,0 +1,55 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# Modify this to suit your needs. The "$1" is the map name, eg. "auto_master". +# To debug, simply run this script with map name as the only parameter. It's +# supposed to output map contents ("key location" pairs) to standard output. +SEARCHBASE="ou=$1,dc=example,dc=com" +ENTRY_ATTRIBUTE="cn" +VALUE_ATTRIBUTE="automountInformation" + +/usr/local/bin/ldapsearch -LLL -x -o ldif-wrap=no -b "$SEARCHBASE" "$ENTRY_ATTRIBUTE" "$VALUE_ATTRIBUTE" | awk ' +$1 == "'$ENTRY_ATTRIBUTE':" { + key = $2 +} + +$1 == "'$VALUE_ATTRIBUTE':" { + for (i = 2; i <= NF; i++) { + value[i] = $(i) + } + nvalues = NF + b64 = 0 +} + +# Double colon after attribute name means the value is in Base64. +$1 == "'$VALUE_ATTRIBUTE'::" { + for (i = 2; i <= NF; i++) { + value[i] = $(i) + } + nvalues = NF + b64 = 1 +} + +# Empty line - end of record. +NF == 0 && key != "" && nvalues > 0 { + printf "%s%s", key, OFS + for (i = 2; i < nvalues; i++) { + printf "%s%s", value[i], OFS + } + if (b64 == 1) { + printf "%s", value[nvalues] | "b64decode -rp" + close("b64decode -rp") + printf "%s", ORS + } else { + printf "%s%s", value[nvalues], ORS + } +} + +NF == 0 { + key = "" + nvalues = 0 + delete value +} +' diff --git a/etc/autofs/special_hosts b/etc/autofs/special_hosts new file mode 100644 index 0000000000..c498546814 --- /dev/null +++ b/etc/autofs/special_hosts @@ -0,0 +1,17 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +if [ $# -eq 0 ]; then + out=`getent hosts` + [ $? -eq 0 ] || exit 1 + echo "$out" | awk '{ print $2 }' | sort -u + exit 0 +fi + +out=`showmount -E "$1"` +[ $? -eq 0 ] || exit 1 +echo "$out" | awk -v host="$1" \ + '{ printf "\"%s\"\t\"%s:%s\" ", $0, host, $0 } END { printf "\n" }' + diff --git a/etc/autofs/special_media b/etc/autofs/special_media new file mode 100755 index 0000000000..cfbf16edc1 --- /dev/null +++ b/etc/autofs/special_media @@ -0,0 +1,120 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# Print newline-separated list of devices available for mounting. +# If there is a filesystem label - use it, otherwise use device name. +print_available() { + local _fstype _fstype_and_label _label _p + + for _p in ${DEVICES}; do + # XXX: Ingore /dev/md* + if [ "`echo ${_p} | sed -n '/^md/p'`" != "" ]; then + continue + fi + + _fstype_and_label="$(fstyp -l "/dev/${_p}" 2> /dev/null)" + if [ $? -ne 0 ]; then + # Ignore devices for which we were unable + # to determine filesystem type. + continue + fi + + _fstype="${_fstype_and_label%% *}" + if [ "${_fstype}" != "${_fstype_and_label}" ]; then + _label="${_fstype_and_label#* }" + # Replace plus signs and slashes with minuses; + # leading plus signs have special meaning in maps, + # and multi-component keys are just not supported. + _label="$(echo ${_label} | sed 's,[+/],-,g')" + echo "${_label}" + continue + fi + + echo "${_p}" + done +} + +# Print a single map entry. +print_map_entry() { + local _fstype _p + + _fstype="$1" + _p="$2" + + if [ "${_fstype}" = "ntfs" ]; then + if [ -f "/usr/local/bin/ntfs-3g" ]; then + echo "-mountprog=/usr/local/bin/ntfs-3g,fstype=${_fstype},nosuid :/dev/${_p}" + else + /usr/bin/logger -p info -t "special_media[$$]" \ + "Cannot mount ${_fstype} formatted device /dev/${_p}: Install sysutils/fusefs-ntfs first" + exit 1 + fi + else + echo "-fstype=${_fstype},nosuid :/dev/${_p}" + fi +} + +# Determine map entry contents for the given key and print out the entry. +print_one() { + local _fstype _fstype_and_label _label _key _p + + _key="$1" + + _fstype="$(fstyp "/dev/${_key}" 2> /dev/null)" + if [ $? -eq 0 ]; then + print_map_entry "${_fstype}" "${_key}" + return + fi + + for _p in ${DEVICES}; do + # XXX: Ingore /dev/md* + if [ "`echo ${_p} | sed -n '/^md/p'`" != "" ]; then + continue + fi + + _fstype_and_label="$(fstyp -l "/dev/${_p}" 2> /dev/null)" + if [ $? -ne 0 ]; then + # Ignore devices for which we were unable + # to determine filesystem type. + continue + fi + + _fstype="${_fstype_and_label%% *}" + if [ "${_fstype}" = "${_fstype_and_label}" ]; then + # No label, try another device. + continue + fi + + _label="${_fstype_and_label#* }" + # Replace plus signs and slashes with minuses; + # leading plus signs have special meaning in maps, + # and multi-component keys are just not supported. + _label="$(echo ${_label} | sed 's,[+/],-,g')" + if [ "${_label}" != "${_key}" ]; then + # Labels don't match, try another device. + continue + fi + + print_map_entry "${_fstype}" "${_p}" + done + + # No matching device - don't print anything, autofs will handle it. +} + +# XXX: Find a better way to get a list of media devices +KERN_DISKS=`sysctl kern.disks` +if [ $? -ne 0 ]; then + exit 1 +fi +DEVICES=`echo ${KERN_DISKS} | awk '{$1=""; print substr($0,2)}' | awk '{gsub(" ", "\n"); print}' | sort` + +if [ $# -eq 0 ]; then + print_available + exit 0 +fi + +print_one "$1" +exit 0 + diff --git a/etc/autofs/special_noauto b/etc/autofs/special_noauto new file mode 100755 index 0000000000..219ec7ea69 --- /dev/null +++ b/etc/autofs/special_noauto @@ -0,0 +1,29 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +print_available() { + sed 's/#.*//' /etc/fstab | awk '$4 ~ /noauto/ { print $2 }' +} + +print_one() { + local _mntpoint + + _mntpoint="${1%/}" + + sed 's/#.*//' /etc/fstab | awk ' + $2 == "'"${_mntpoint}"'" && $4 ~ /noauto/ { + if ($1 ~ /:/) { dev=$1 } else { dev=":"$1 } + print "-fstype=" $3 "," $4, dev + }' +} + +if [ $# -eq 0 ]; then + print_available + exit 0 +fi + +print_one "$1" +exit 0 + diff --git a/etc/autofs/special_null b/etc/autofs/special_null new file mode 100644 index 0000000000..41c10001e6 --- /dev/null +++ b/etc/autofs/special_null @@ -0,0 +1,4 @@ +#!/usr/bin/true +# +# $FreeBSD$ +# diff --git a/etc/defaults/rc.conf b/etc/defaults/rc.conf index 11e886915f..259cd8fd31 100644 --- a/etc/defaults/rc.conf +++ b/etc/defaults/rc.conf @@ -153,6 +153,7 @@ ftpd_flags="" # Flags for ftpd, -D added implicitly. amd_enable="NO" # Run amd service with $amd_flags (or NO). amd_flags="-a /.amd_mnt -l syslog /host /etc/amd.map /net /etc/amd.map" amd_map_program="NO" # Can be set to "ypcat -k amd.master" +autofs_enable="NO" # Run automountd(8) nfs_client_enable="NO" # This host is an NFS client (or NO). nfs_access_cache="5" # Client attribute cache timeout in seconds #nfs_neg_cache="3" # Client attribute negative hit cache timeout diff --git a/etc/mtree/BSD.include.dist b/etc/mtree/BSD.include.dist index 7a52468541..6ecfc3fb5d 100644 --- a/etc/mtree/BSD.include.dist +++ b/etc/mtree/BSD.include.dist @@ -434,6 +434,8 @@ .. smbfs .. + autofs + .. isofs cd9660 .. diff --git a/etc/rc.d/Makefile b/etc/rc.d/Makefile index 68834b5728..8428cc4947 100644 --- a/etc/rc.d/Makefile +++ b/etc/rc.d/Makefile @@ -6,6 +6,7 @@ # note: bgfsk and lomac left out (from 5.0) # FILES= DAEMON LOGIN NETWORKING SERVERS abi accounting addswap adjkerntz amd \ + automount automountd autounmountd \ bootconf bootparams btconfig bthcid ccd cleanvar cryptdisks \ cleartmp cron cryptdisks devd devfs dhclient diskless dmesg dumpon \ fixbootfile fsck ftpd hostname hotplugd \ diff --git a/etc/rc.d/automount b/etc/rc.d/automount new file mode 100644 index 0000000000..7f43b45311 --- /dev/null +++ b/etc/rc.d/automount @@ -0,0 +1,31 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: automount +# REQUIRE: nfsclient automountd +# KEYWORD: nojail shutdown + +. /etc/rc.subr + +name="automount" +rcvar="autofs_enable" +start_cmd="automount_start" +stop_cmd="automount_stop" +required_modules="autofs" + +automount_start() +{ + + /usr/sbin/automount ${automount_flags} +} + +automount_stop() +{ + + /sbin/umount -At autofs +} + +load_rc_config $name +run_rc_command "$1" diff --git a/etc/rc.d/automountd b/etc/rc.d/automountd new file mode 100644 index 0000000000..01a2e0bb88 --- /dev/null +++ b/etc/rc.d/automountd @@ -0,0 +1,19 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: automountd +# REQUIRE: DAEMON +# KEYWORD: nojail + +. /etc/rc.subr + +name="automountd" +rcvar="autofs_enable" +pidfile="/var/run/${name}.pid" +command="/usr/sbin/${name}" +required_modules="autofs" + +load_rc_config $name +run_rc_command "$1" diff --git a/etc/rc.d/autounmountd b/etc/rc.d/autounmountd new file mode 100644 index 0000000000..49a27ba01b --- /dev/null +++ b/etc/rc.d/autounmountd @@ -0,0 +1,18 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: autounmountd +# REQUIRE: DAEMON +# KEYWORD: nojail + +. /etc/rc.subr + +name="autounmountd" +rcvar="autofs_enable" +pidfile="/var/run/${name}.pid" +command="/usr/sbin/${name}" + +load_rc_config $name +run_rc_command "$1" diff --git a/include/Makefile b/include/Makefile index 5b09aeab75..0a1aba8e50 100644 --- a/include/Makefile +++ b/include/Makefile @@ -90,7 +90,8 @@ LSUBDIRS= \ netproto/key netproto/smb \ vfs/isofs/cd9660 vfs/puffs \ vfs/msdosfs vfs/nfs vfs/ntfs \ - vfs/smbfs vfs/udf vfs/ufs vfs/hammer vfs/hammer2 + vfs/smbfs vfs/udf vfs/ufs vfs/hammer vfs/hammer2 \ + vfs/autofs # For SHARED=symlinks, bus/cam is a symlink, so cam/scsi is taken care of LSYMSUBDIRS= ${LSUBDIRS:Nbus/cam/scsi:Nnet/*:Nnetgraph/*:Nnetgraph7/*} diff --git a/include/mntopts.h b/include/mntopts.h index 0d43210419..76cda2fab7 100644 --- a/include/mntopts.h +++ b/include/mntopts.h @@ -57,6 +57,7 @@ struct mntopt { #define MOPT_NOCLUSTERR { "clusterr", 1, MNT_NOCLUSTERR, 0 } #define MOPT_NOCLUSTERW { "clusterw", 1, MNT_NOCLUSTERW, 0 } #define MOPT_SUIDDIR { "suiddir", 0, MNT_SUIDDIR, 0 } +#define MOPT_AUTOMOUNTED { "automounted",0, MNT_AUTOMOUNTED, 0 } #define MOPT_IGNORE { "ignore", 0, MNT_IGNORE, 0 } /* Control flags. */ @@ -90,6 +91,7 @@ struct mntopt { MOPT_RDONLY, \ MOPT_NOCLUSTERR, \ MOPT_NOCLUSTERW, \ + MOPT_AUTOMOUNTED, \ MOPT_IGNORE extern int getmnt_silent; diff --git a/sbin/mount/mount.8 b/sbin/mount/mount.8 index bf6e63c865..0a4ec0bc74 100644 --- a/sbin/mount/mount.8 +++ b/sbin/mount/mount.8 @@ -28,7 +28,7 @@ .\" @(#)mount.8 8.8 (Berkeley) 6/16/94 .\" $FreeBSD: src/sbin/mount/mount.8,v 1.31.2.12 2003/02/23 21:17:42 trhodes Exp $ .\" -.Dd October 7, 2011 +.Dd May 15, 2016 .Dt MOUNT 8 .Os .Sh NAME @@ -121,6 +121,11 @@ This is a flag to set, and should not be used unless you are prepared to recreate the file system should your system crash. +.It Cm automounted +This flag indicates that the file system was mounted by +.Xr automountd 8 . +Automounted file systems are automatically unmounted by +.Xr autounmountd 8 . .It Cm current When used with the .Fl u @@ -415,7 +420,10 @@ have permission to load the module. .Xr mount_tmpfs 8 , .Xr mount_udf 8 , .Xr sysctl 8 , -.Xr umount 8 +.Xr umount 8 , +.Xr automount 8 , +.Xr automountd 8 , +.Xr autounmountd 8 , .Sh HISTORY A .Nm diff --git a/sbin/mountd/mountd.c b/sbin/mountd/mountd.c index 2ca2b91ea7..6ddf9987bd 100644 --- a/sbin/mountd/mountd.c +++ b/sbin/mountd/mountd.c @@ -1141,6 +1141,9 @@ get_exportlist_one(void) *endcp = '\0'; if (check_dirpath(cp) && statfs(cp, &fsb) >= 0) { + if ((fsb.f_flags & MNT_AUTOMOUNTED) != 0) + syslog(LOG_ERR, "Warning: exporting of " + "automounted fs %s not supported", cp); if (got_nondir) { syslog(LOG_ERR, "dirs must be first"); getexp_err(ep, tgrp); diff --git a/share/man/man5/Makefile b/share/man/man5/Makefile index ad8b1595dd..72c5a35fe2 100644 --- a/share/man/man5/Makefile +++ b/share/man/man5/Makefile @@ -54,6 +54,7 @@ MAN= acct.5 \ stab.5 \ sysctl.conf.5 \ tmpfs.5 \ + autofs.5 \ utmp.5 \ utmpx.5 \ uuids.5 \ diff --git a/share/man/man5/autofs.5 b/share/man/man5/autofs.5 new file mode 100644 index 0000000000..60afe696b1 --- /dev/null +++ b/share/man/man5/autofs.5 @@ -0,0 +1,125 @@ +.\" Copyright (c) 2016 The DragonFly Project +.\" Copyright (c) 2014 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Edward Tomasz Napierala under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd April 14, 2016 +.Dt AUTOFS 5 +.Os +.Sh NAME +.Nm autofs +.Nd "automounter filesystem" +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following line in the +kernel configuration file: +.Bd -ragged -offset indent +.Cd "options AUTOFS" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +autofs_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver is the kernel component of the automounter infrastructure. +Its job is to pass mount requests to the +.Xr automountd 8 +daemon, and pause the processes trying to access the automounted filesystem +until the mount is completed. +It is mounted by the +.Xr automount 8 . +.Sh OPTIONS +These options are available when +mounting +.Nm +file systems: +.Bl -tag -width indent +.It Cm master_options +Mount options for all filesystems specified in the map entry. +.It Cm master_prefix +Filesystem mountpoint prefix. +.El +.Sh EXAMPLES +To unmount all mounted +.Nm +filesystems: +.Pp +.Dl "umount -At autofs" +.Pp +To mount +.Nm +filesystems specified in +.Xr auto_master 5 : +.Pp +.Dl "automount" +.Sh SEE ALSO +.Xr auto_master 5 , +.Xr automount 8 , +.Xr automountd 8 , +.Xr autounmountd 8 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 10.1 . +The +.Nm +driver first appeared in +.Dx 4.5 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. +.Pp +The +.Nm +was ported to +.Dx +by +.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com . +.Sh BUGS +The +.Nm +driver on +.Dx +currently does't support +.Pa vfs.autofs.mount_on_stat +sysctl. +This sysctl is disabled by default on both +.Fx +and +.Dx , +but enabling it does nothing on +.Dx . diff --git a/share/man/man5/rc.conf.5 b/share/man/man5/rc.conf.5 index 939d894e56..dfd5ee64da 100644 --- a/share/man/man5/rc.conf.5 +++ b/share/man/man5/rc.conf.5 @@ -2595,6 +2595,47 @@ The last five are optional. They default to an empty string if not set, except for logfile which defaults to .Pa /dev/null if it is not set. +.It Va autofs_enable +.Pq Vt bool +If set to +.Dq Li YES , +start the +.Xr automount 8 +utility and the +.Xr automountd 8 +and +.Xr autounmountd 8 +daemons at boot time. +.It Va automount_flags +.Pq Vt str +If +.Va autofs_enable +is set to +.Dq Li YES , +these are the flags to pass to the +.Xr automount 8 +program. +By default no flags are passed. +.It Va automountd_flags +.Pq Vt str +If +.Va autofs_enable +is set to +.Dq Li YES , +these are the flags to pass to the +.Xr automountd 8 +daemon. +By default no flags are passed. +.It Va autounmountd_flags +.Pq Vt str +If +.Va autofs_enable +is set to +.Dq Li YES , +these are the flags to pass to the +.Xr autounmountd 8 +daemon. +By default no flags are passed. .El .Sh FILES .Bl -tag -width ".Pa /etc/start_if. Ns Aq Ar interface" -compact @@ -2671,7 +2712,12 @@ if it is not set. .Xr yp 8 , .Xr ypbind 8 , .Xr ypserv 8 , -.Xr ypset 8 +.Xr ypset 8 , +.Xr autofs 5 , +.Xr auto_master 5 , +.Xr automount 8 , +.Xr automountd 8 , +.Xr autounmountd 8 .Sh HISTORY The .Nm diff --git a/share/man/man7/hier.7 b/share/man/man7/hier.7 index f0ec935e3a..4bc8eb8987 100644 --- a/share/man/man7/hier.7 +++ b/share/man/man7/hier.7 @@ -28,7 +28,7 @@ .\" @(#)hier.7 8.1 (Berkeley) 6/5/93 .\" $FreeBSD: src/share/man/man7/hier.7,v 1.29.2.17 2003/01/13 21:43:50 ceri Exp $ .\" -.Dd April 28, 2016 +.Dd May 15, 2016 .Dt HIER 7 .Os .Sh NAME @@ -146,6 +146,10 @@ and .It Pa /mnt/ empty directory commonly used by system administrators as a temporary mount point +.It Pa /net/ +automounted NFS shares; +see +.Xr auto_master 5 .It Pa /pfs/ pseudo file system directory (on .Xr hammer 5 diff --git a/sys/conf/files b/sys/conf/files index 44c48de082..6a26a4c963 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1773,6 +1773,9 @@ vfs/tmpfs/tmpfs_fifoops.c optional tmpfs vfs/tmpfs/tmpfs_subr.c optional tmpfs vfs/tmpfs/tmpfs_vfsops.c optional tmpfs vfs/tmpfs/tmpfs_vnops.c optional tmpfs +vfs/autofs/autofs.c optional autofs +vfs/autofs/autofs_vfsops.c optional autofs +vfs/autofs/autofs_vnops.c optional autofs # vm/default_pager.c standard vm/device_pager.c standard diff --git a/sys/conf/options b/sys/conf/options index 5b49a056a1..d9abdccccc 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -134,6 +134,7 @@ PUFFS opt_dontuse.h SMBFS opt_dontuse.h TMPFS opt_dontuse.h UDF opt_dontuse.h +AUTOFS opt_dontuse.h # These static filesystems has one slightly bogus static dependency in # sys/platform/.../i386/autoconf.c. If any of these filesystems are diff --git a/sys/kern/vfs_subr.c b/sys/kern/vfs_subr.c index 15947ef671..5956fe5d38 100644 --- a/sys/kern/vfs_subr.c +++ b/sys/kern/vfs_subr.c @@ -1835,6 +1835,7 @@ vfs_flagstostr(int flags, const struct mountctl_opt *optp, { MNT_NOEXEC, "noexec" }, { MNT_NOSUID, "nosuid" }, { MNT_NODEV, "nodev" }, + { MNT_AUTOMOUNTED, "automounted" }, { MNT_ASYNC, "asynchronous" }, { MNT_SUIDDIR, "suiddir" }, { MNT_SOFTDEP, "soft-updates" }, diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c index 1ef23e5c21..24f963fc18 100644 --- a/sys/kern/vfs_syscalls.c +++ b/sys/kern/vfs_syscalls.c @@ -356,11 +356,13 @@ update: mp->mnt_flag &=~ (MNT_NOSUID | MNT_NOEXEC | MNT_NODEV | MNT_SYNCHRONOUS | MNT_ASYNC | MNT_NOATIME | MNT_NOSYMFOLLOW | MNT_IGNORE | MNT_TRIM | - MNT_NOCLUSTERR | MNT_NOCLUSTERW | MNT_SUIDDIR); + MNT_NOCLUSTERR | MNT_NOCLUSTERW | MNT_SUIDDIR | + MNT_AUTOMOUNTED); mp->mnt_flag |= uap->flags & (MNT_NOSUID | MNT_NOEXEC | MNT_NODEV | MNT_SYNCHRONOUS | MNT_ASYNC | MNT_FORCE | MNT_NOSYMFOLLOW | MNT_IGNORE | MNT_TRIM | - MNT_NOATIME | MNT_NOCLUSTERR | MNT_NOCLUSTERW | MNT_SUIDDIR); + MNT_NOATIME | MNT_NOCLUSTERR | MNT_NOCLUSTERW | MNT_SUIDDIR | + MNT_AUTOMOUNTED); /* * Mount the filesystem. * XXX The final recipients of VFS_MOUNT just overwrite the ndp they diff --git a/sys/sys/mount.h b/sys/sys/mount.h index 5fec43610d..102fe7ec7b 100644 --- a/sys/sys/mount.h +++ b/sys/sys/mount.h @@ -261,6 +261,7 @@ struct mount { #define MNT_NOEXEC 0x00000004 /* can't exec from filesystem */ #define MNT_NOSUID 0x00000008 /* don't honor setuid bits on fs */ #define MNT_NODEV 0x00000010 /* don't interpret special files */ +#define MNT_AUTOMOUNTED 0x00000020 /* mounted by automountd(8) */ #define MNT_ASYNC 0x00000040 /* file system written asynchronously */ #define MNT_SUIDDIR 0x00100000 /* special handling of SUID on dirs */ #define MNT_SOFTDEP 0x00200000 /* soft updates being done */ @@ -304,7 +305,7 @@ struct mount { MNT_ROOTFS | MNT_NOATIME | MNT_NOCLUSTERR| \ MNT_NOCLUSTERW | MNT_SUIDDIR | MNT_SOFTDEP | \ MNT_IGNORE | MNT_NOSYMFOLLOW | MNT_EXPUBLIC| \ - MNT_TRIM) + MNT_TRIM | MNT_AUTOMOUNTED) /* * External filesystem command modifier flags. * Unmount can use the MNT_FORCE flag. diff --git a/sys/sys/vfscache.h b/sys/sys/vfscache.h index bd5db5ab21..42d294c000 100644 --- a/sys/sys/vfscache.h +++ b/sys/sys/vfscache.h @@ -108,7 +108,7 @@ enum vtagtype { VT_PORTAL, VT_NULL, VT_UNUSED10, VT_KERNFS, VT_PROCFS, VT_AFS, VT_ISOFS, VT_UNUSED15, VT_MSDOSFS, VT_TFS, VT_VFS, VT_CODA, VT_NTFS, VT_HPFS, VT_SMBFS, VT_UDF, VT_EXT2FS, VT_SYNTH, - VT_HAMMER, VT_HAMMER2, VT_DEVFS, VT_TMPFS + VT_HAMMER, VT_HAMMER2, VT_DEVFS, VT_TMPFS, VT_AUTOFS }; /* diff --git a/sys/vfs/Makefile b/sys/vfs/Makefile index 458aed583c..882f93395a 100644 --- a/sys/vfs/Makefile +++ b/sys/vfs/Makefile @@ -3,7 +3,7 @@ SUBDIR=fifofs msdosfs portal nfs procfs puffs \ hpfs ntfs smbfs isofs mfs udf \ - nullfs hammer tmpfs + nullfs hammer tmpfs autofs .if defined(WANT_HAMMER2) SUBDIR+= hammer2 diff --git a/sys/vfs/autofs/Makefile b/sys/vfs/autofs/Makefile new file mode 100644 index 0000000000..888f16b937 --- /dev/null +++ b/sys/vfs/autofs/Makefile @@ -0,0 +1,4 @@ +KMOD= autofs +SRCS= autofs.c autofs_vfsops.c autofs_vnops.c + +.include diff --git a/sys/vfs/autofs/autofs.c b/sys/vfs/autofs/autofs.c new file mode 100644 index 0000000000..eb4e04b0b8 --- /dev/null +++ b/sys/vfs/autofs/autofs.c @@ -0,0 +1,707 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * 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. + * + */ +/*- + * Copyright (c) 1989, 1991, 1993, 1995 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Rick Macklem at The University of Guelph. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "autofs.h" +#include "autofs_ioctl.h" + +MALLOC_DEFINE(M_AUTOFS, "autofs", "Automounter filesystem"); + +struct objcache *autofs_request_objcache = NULL; +static struct objcache_malloc_args autofs_request_args = { + sizeof(struct autofs_request), M_AUTOFS, +}; + +struct objcache *autofs_node_objcache = NULL; +static struct objcache_malloc_args autofs_node_args = { + sizeof(struct autofs_node), M_AUTOFS, +}; + +static int autofs_open(struct dev_open_args *ap); +static int autofs_close(struct dev_close_args *ap); +static int autofs_ioctl(struct dev_ioctl_args *ap); + +struct dev_ops autofs_ops = { + { "autofs", 0, 0 }, + .d_open = autofs_open, + .d_close = autofs_close, + .d_ioctl = autofs_ioctl, +}; + +/* + * List of signals that can interrupt an autofs trigger. + */ +int autofs_sig_set[] = { + SIGINT, + SIGTERM, + SIGHUP, + SIGKILL, + SIGQUIT +}; + +struct autofs_softc *autofs_softc = NULL; + +SYSCTL_NODE(_vfs, OID_AUTO, autofs, CTLFLAG_RD, 0, "Automounter filesystem"); +int autofs_debug = 1; +TUNABLE_INT("vfs.autofs.debug", &autofs_debug); +SYSCTL_INT(_vfs_autofs, OID_AUTO, debug, CTLFLAG_RW, + &autofs_debug, 1, "Enable debug messages"); +int autofs_mount_on_stat = 0; /* XXX: Not supported on DragonFly */ +TUNABLE_INT("vfs.autofs.mount_on_stat", &autofs_mount_on_stat); +SYSCTL_INT(_vfs_autofs, OID_AUTO, mount_on_stat, CTLFLAG_RW, + &autofs_mount_on_stat, 0, "Trigger mount on stat(2) on mountpoint " + "(not supported on DragonFly)"); +static int autofs_timeout = 30; +TUNABLE_INT("vfs.autofs.timeout", &autofs_timeout); +SYSCTL_INT(_vfs_autofs, OID_AUTO, timeout, CTLFLAG_RW, + &autofs_timeout, 30, "Number of seconds to wait for automountd(8)"); +static int autofs_cache = 600; +TUNABLE_INT("vfs.autofs.cache", &autofs_cache); +SYSCTL_INT(_vfs_autofs, OID_AUTO, cache, CTLFLAG_RW, + &autofs_cache, 600, "Number of seconds to wait before reinvoking " + "automountd(8) for any given file or directory"); +static int autofs_retry_attempts = 3; +TUNABLE_INT("vfs.autofs.retry_attempts", &autofs_retry_attempts); +SYSCTL_INT(_vfs_autofs, OID_AUTO, retry_attempts, CTLFLAG_RW, + &autofs_retry_attempts, 3, "Number of attempts before failing mount"); +static int autofs_retry_delay = 1; +TUNABLE_INT("vfs.autofs.retry_delay", &autofs_retry_delay); +SYSCTL_INT(_vfs_autofs, OID_AUTO, retry_delay, CTLFLAG_RW, + &autofs_retry_delay, 1, "Number of seconds before retrying"); +static int autofs_interruptible = 1; +TUNABLE_INT("vfs.autofs.interruptible", &autofs_interruptible); +SYSCTL_INT(_vfs_autofs, OID_AUTO, interruptible, CTLFLAG_RW, + &autofs_interruptible, 1, "Allow requests to be interrupted by signal"); + +static __inline pid_t +proc_pgid(const struct proc *p) +{ + return (p->p_pgrp->pg_id); +} + +static int +autofs_node_cmp(const struct autofs_node *a, const struct autofs_node *b) +{ + return (strcmp(a->an_name, b->an_name)); +} + +RB_GENERATE(autofs_node_tree, autofs_node, an_link, autofs_node_cmp); + +static boolean_t +autofs_request_objcache_ctor(void *obj, void *privdata, int ocflags) +{ + struct autofs_request *ar = obj; + + memset(ar, 0, sizeof(*ar)); + return (TRUE); +} + +static boolean_t +autofs_node_objcache_ctor(void *obj, void *privdata, int ocflags) +{ + struct autofs_node *an = obj; + + memset(an, 0, sizeof(*an)); + return (TRUE); +} + +int +autofs_init(struct vfsconf *vfsp) +{ + KASSERT(autofs_softc == NULL, + ("softc %p, should be NULL", autofs_softc)); + + autofs_softc = kmalloc(sizeof(*autofs_softc), M_AUTOFS, + M_WAITOK | M_ZERO); + + autofs_request_objcache = objcache_create("autofs_request", 0, 0, + autofs_request_objcache_ctor, NULL, NULL, + objcache_malloc_alloc, + objcache_malloc_free, + &autofs_request_args); + + autofs_node_objcache = objcache_create("autofs_node", 0, 0, + autofs_node_objcache_ctor, NULL, NULL, + objcache_malloc_alloc, + objcache_malloc_free, + &autofs_node_args); + + TAILQ_INIT(&autofs_softc->sc_requests); + cv_init(&autofs_softc->sc_cv, "autofscv"); + lockinit(&autofs_softc->sc_lock, "autofssclk", 0, 0); + autofs_softc->sc_dev_opened = false; + + autofs_softc->sc_cdev = make_dev(&autofs_ops, 0, UID_ROOT, + GID_OPERATOR, 0640, "autofs"); + if (autofs_softc->sc_cdev == NULL) { + AUTOFS_WARN("failed to create device node"); + objcache_destroy(autofs_request_objcache); + objcache_destroy(autofs_node_objcache); + kfree(autofs_softc, M_AUTOFS); + + return (ENODEV); + } + autofs_softc->sc_cdev->si_drv1 = autofs_softc; + + return (0); +} + +int +autofs_uninit(struct vfsconf *vfsp) +{ + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + if (autofs_softc->sc_dev_opened) { + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + return (EBUSY); + } + + if (autofs_softc->sc_cdev != NULL) + destroy_dev(autofs_softc->sc_cdev); + + objcache_destroy(autofs_request_objcache); + objcache_destroy(autofs_node_objcache); + + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + + kfree(autofs_softc, M_AUTOFS); /* race with open */ + autofs_softc = NULL; + + return (0); +} + +bool +autofs_ignore_thread(void) +{ + struct proc *curp = curproc; + + if (autofs_softc->sc_dev_opened == false) + return (false); + + lwkt_gettoken(&curp->p_token); + if (autofs_softc->sc_dev_sid == proc_pgid(curp)) { + lwkt_reltoken(&curp->p_token); + return (true); + } + lwkt_reltoken(&curp->p_token); + + return (false); +} + +char * +autofs_path(struct autofs_node *anp) +{ + struct autofs_mount *amp = anp->an_mount; + char *path, *tmp; + + path = kstrdup("", M_AUTOFS); + for (; anp->an_parent != NULL; anp = anp->an_parent) { + tmp = kmalloc(strlen(anp->an_name) + strlen(path) + 2, + M_AUTOFS, M_WAITOK); + strcpy(tmp, anp->an_name); + strcat(tmp, "/"); + strcat(tmp, path); + kfree(path, M_AUTOFS); + path = tmp; + } + + tmp = kmalloc(strlen(amp->am_on) + strlen(path) + 2, + M_AUTOFS, M_WAITOK); + strcpy(tmp, amp->am_on); + strcat(tmp, "/"); + strcat(tmp, path); + kfree(path, M_AUTOFS); + path = tmp; + + return (path); +} + +static void +autofs_task(void *context, int pending) +{ + struct autofs_request *ar = context; + + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + AUTOFS_WARN("request %d for %s timed out after %d seconds", + ar->ar_id, ar->ar_path, autofs_timeout); + + ar->ar_error = ETIMEDOUT; + ar->ar_wildcards = true; + ar->ar_done = true; + ar->ar_in_progress = false; + cv_broadcast(&autofs_softc->sc_cv); + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); +} + +bool +autofs_cached(struct autofs_node *anp, const char *component, int componentlen) +{ + struct autofs_mount *amp = anp->an_mount; + int error; + + AUTOFS_ASSERT_UNLOCKED(amp); + + /* + * For root node we need to request automountd(8) assistance even + * if the node is marked as cached, but the requested top-level + * directory does not exist. This is necessary for wildcard indirect + * map keys to work. We don't do this if we know that there are + * no wildcards. + */ + if (anp->an_parent == NULL && componentlen != 0 && anp->an_wildcards) { + KKASSERT(amp->am_root == anp); + lockmgr(&->am_lock, LK_SHARED); + error = autofs_node_find(anp, component, componentlen, NULL); + lockmgr(&->am_lock, LK_RELEASE); + if (error) + return (false); + } + + return (anp->an_cached); +} + +static void +autofs_cache_callout(void *context) +{ + struct autofs_node *anp = context; + + autofs_node_uncache(anp); +} + +void +autofs_flush(struct autofs_mount *amp) +{ + struct autofs_node *anp = amp->am_root; + struct autofs_node *child; + + lockmgr(&->am_lock, LK_EXCLUSIVE); + RB_FOREACH(child, autofs_node_tree, &anp->an_children) { + autofs_node_uncache(child); + } + autofs_node_uncache(amp->am_root); + lockmgr(&->am_lock, LK_RELEASE); + + AUTOFS_DEBUG("%s flushed", amp->am_on); +} + +/* + * The set/restore sigmask functions are used to (temporarily) overwrite + * the thread sigmask during triggering. + */ +static void +autofs_set_sigmask(sigset_t *oldset) +{ + struct lwp *lp = curthread->td_lwp; + sigset_t newset; + int i; + + SIGFILLSET(newset); + /* Remove the autofs set of signals from newset */ + lwkt_gettoken(&lp->lwp_token); + for (i = 0; i < sizeof(autofs_sig_set)/sizeof(int); i++) { + /* + * But make sure we leave the ones already masked + * by the process, i.e. remove the signal from the + * temporary signalmask only if it wasn't already + * in sigmask. + */ + if (!SIGISMEMBER(lp->lwp_sigmask, autofs_sig_set[i]) && + !SIGISMEMBER(lp->lwp_proc->p_sigacts->ps_sigignore, + autofs_sig_set[i])) { + SIGDELSET(newset, autofs_sig_set[i]); + } + } + kern_sigprocmask(SIG_SETMASK, &newset, oldset); + lwkt_reltoken(&lp->lwp_token); +} + +static void +autofs_restore_sigmask(sigset_t *set) +{ + kern_sigprocmask(SIG_SETMASK, set, NULL); +} + +static int +autofs_trigger_one(struct autofs_node *anp, + const char *component, int componentlen) +{ +#define _taskqueue_thread (taskqueue_thread[mycpuid]) + struct autofs_mount *amp = anp->an_mount; + struct autofs_node *firstanp; + struct autofs_request *ar; + sigset_t oldset; + char *key, *path; + int error = 0, request_error, last; + bool wildcards; + + KKASSERT(AUTOFS_LOCK_STATUS(&autofs_softc->sc_lock) == LK_EXCLUSIVE); + + if (anp->an_parent == NULL) { + key = kstrndup(component, componentlen, M_AUTOFS); + } else { + for (firstanp = anp; firstanp->an_parent->an_parent != NULL; + firstanp = firstanp->an_parent) + continue; + key = kstrdup(firstanp->an_name, M_AUTOFS); + } + + path = autofs_path(anp); + + TAILQ_FOREACH(ar, &autofs_softc->sc_requests, ar_next) { + if (strcmp(ar->ar_path, path)) + continue; + if (strcmp(ar->ar_key, key)) + continue; + + KASSERT(strcmp(ar->ar_from, amp->am_from) == 0, + ("from changed; %s != %s", ar->ar_from, amp->am_from)); + KASSERT(strcmp(ar->ar_prefix, amp->am_prefix) == 0, + ("prefix changed; %s != %s", + ar->ar_prefix, amp->am_prefix)); + KASSERT(strcmp(ar->ar_options, amp->am_options) == 0, + ("options changed; %s != %s", + ar->ar_options, amp->am_options)); + break; + } + + if (ar != NULL) { + refcount_acquire(&ar->ar_refcount); + } else { + ar = objcache_get(autofs_request_objcache, M_WAITOK); + ar->ar_mount = amp; + ar->ar_id = autofs_softc->sc_last_request_id++; + ar->ar_done = false; + ar->ar_error = 0; + ar->ar_wildcards = false; + ar->ar_in_progress = false; + strlcpy(ar->ar_from, amp->am_from, sizeof(ar->ar_from)); + strlcpy(ar->ar_path, path, sizeof(ar->ar_path)); + strlcpy(ar->ar_prefix, amp->am_prefix, sizeof(ar->ar_prefix)); + strlcpy(ar->ar_key, key, sizeof(ar->ar_key)); + strlcpy(ar->ar_options, + amp->am_options, sizeof(ar->ar_options)); + TIMEOUT_TASK_INIT(_taskqueue_thread, &ar->ar_task, 0, + autofs_task, ar); + error = taskqueue_enqueue_timeout(_taskqueue_thread, + &ar->ar_task, autofs_timeout * hz); + if (error) + AUTOFS_WARN("taskqueue_enqueue_timeout() failed " + "with error %d", error); + refcount_init(&ar->ar_refcount, 1); + TAILQ_INSERT_TAIL(&autofs_softc->sc_requests, ar, ar_next); + } + + cv_broadcast(&autofs_softc->sc_cv); + while (ar->ar_done == false) { + if (autofs_interruptible) { + autofs_set_sigmask(&oldset); + error = cv_wait_sig(&autofs_softc->sc_cv, + &autofs_softc->sc_lock); + autofs_restore_sigmask(&oldset); + if (error) { + AUTOFS_WARN("cv_wait_sig for %s failed " + "with error %d", ar->ar_path, error); + break; + } + } else { + cv_wait(&autofs_softc->sc_cv, &autofs_softc->sc_lock); + } + } + + request_error = ar->ar_error; + if (request_error) + AUTOFS_WARN("request for %s completed with error %d", + ar->ar_path, request_error); + + wildcards = ar->ar_wildcards; + + last = refcount_release(&ar->ar_refcount); + if (last) { + TAILQ_REMOVE(&autofs_softc->sc_requests, ar, ar_next); + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + taskqueue_cancel_timeout(_taskqueue_thread, &ar->ar_task, NULL); + taskqueue_drain_timeout(_taskqueue_thread, &ar->ar_task); + objcache_put(autofs_request_objcache, ar); + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + } + + /* + * Note that we do not do negative caching on purpose. This + * way the user can retry access at any time, e.g. after fixing + * the failure reason, without waiting for cache timer to expire. + */ + if (error == 0 && request_error == 0 && autofs_cache > 0) { + autofs_node_cache(anp); + anp->an_wildcards = wildcards; + callout_reset(&anp->an_callout, autofs_cache * hz, + autofs_cache_callout, anp); + } + + kfree(key, M_AUTOFS); + kfree(path, M_AUTOFS); + + if (error) + return (error); + return (request_error); +} + +int +autofs_trigger(struct autofs_node *anp, + const char *component, int componentlen) +{ + int error, dummy; + + for (;;) { + error = autofs_trigger_one(anp, component, componentlen); + if (error == 0) { + anp->an_retries = 0; + return (0); + } + if (error == EINTR || error == ERESTART) { + AUTOFS_DEBUG("trigger interrupted by signal, " + "not retrying"); + anp->an_retries = 0; + return (error); + } + anp->an_retries++; + if (anp->an_retries >= autofs_retry_attempts) { + AUTOFS_DEBUG("trigger failed %d times; returning " + "error %d", anp->an_retries, error); + anp->an_retries = 0; + return (error); + + } + AUTOFS_DEBUG("trigger failed with error %d; will retry in " + "%d seconds, %d attempts left", error, autofs_retry_delay, + autofs_retry_attempts - anp->an_retries); + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + tsleep(&dummy, 0, "autofs_retry", autofs_retry_delay * hz); + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + } +} + +static int +autofs_ioctl_request(struct autofs_daemon_request *adr) +{ + struct proc *curp = curproc; + struct autofs_request *ar; + int error; + + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + for (;;) { + TAILQ_FOREACH(ar, &autofs_softc->sc_requests, ar_next) { + if (ar->ar_done) + continue; + if (ar->ar_in_progress) + continue; + break; + } + + if (ar != NULL) + break; + + error = cv_wait_sig(&autofs_softc->sc_cv, + &autofs_softc->sc_lock); + if (error) { + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + return (error); + } + } + + ar->ar_in_progress = true; + + adr->adr_id = ar->ar_id; + strlcpy(adr->adr_from, ar->ar_from, sizeof(adr->adr_from)); + strlcpy(adr->adr_path, ar->ar_path, sizeof(adr->adr_path)); + strlcpy(adr->adr_prefix, ar->ar_prefix, sizeof(adr->adr_prefix)); + strlcpy(adr->adr_key, ar->ar_key, sizeof(adr->adr_key)); + strlcpy(adr->adr_options, ar->ar_options, sizeof(adr->adr_options)); + + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + + lwkt_gettoken(&curp->p_token); + autofs_softc->sc_dev_sid = proc_pgid(curp); + lwkt_reltoken(&curp->p_token); + + return (0); +} + +static int +autofs_ioctl_done_101(struct autofs_daemon_done_101 *add) +{ + struct autofs_request *ar; + + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + TAILQ_FOREACH(ar, &autofs_softc->sc_requests, ar_next) { + if (ar->ar_id == add->add_id) + break; + } + + if (ar == NULL) { + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + AUTOFS_DEBUG("id %d not found", add->add_id); + return (ESRCH); + } + + ar->ar_error = add->add_error; + ar->ar_wildcards = true; + ar->ar_done = true; + ar->ar_in_progress = false; + cv_broadcast(&autofs_softc->sc_cv); + + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + + return (0); +} + +static int +autofs_ioctl_done(struct autofs_daemon_done *add) +{ + struct autofs_request *ar; + + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + TAILQ_FOREACH(ar, &autofs_softc->sc_requests, ar_next) { + if (ar->ar_id == add->add_id) + break; + } + + if (ar == NULL) { + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + AUTOFS_DEBUG("id %d not found", add->add_id); + return (ESRCH); + } + + ar->ar_error = add->add_error; + ar->ar_wildcards = add->add_wildcards; + ar->ar_done = true; + ar->ar_in_progress = false; + cv_broadcast(&autofs_softc->sc_cv); + + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + + return (0); +} + +static int +autofs_open(struct dev_open_args *ap) +{ + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + /* + * We must never block automountd(8) and its descendants, and we use + * session ID to determine that: we store session id of the process + * that opened the device, and then compare it with session ids + * of triggering processes. This means running a second automountd(8) + * instance would break the previous one. The check below prevents + * it from happening. + */ + if (autofs_softc->sc_dev_opened) { + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + return (EBUSY); + } + + autofs_softc->sc_dev_opened = true; + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + + return (0); +} + +static int +autofs_close(struct dev_close_args *ap) +{ + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + KASSERT(autofs_softc->sc_dev_opened, ("not opened?")); + autofs_softc->sc_dev_opened = false; + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + + return (0); +} + +static int +autofs_ioctl(struct dev_ioctl_args *ap) +{ + u_long cmd = ap->a_cmd; + void *arg = ap->a_data; + + KASSERT(autofs_softc->sc_dev_opened, ("not opened?")); + + switch (cmd) { + case AUTOFSREQUEST: + return (autofs_ioctl_request( + (struct autofs_daemon_request *)arg)); + case AUTOFSDONE101: + return (autofs_ioctl_done_101( + (struct autofs_daemon_done_101 *)arg)); + case AUTOFSDONE: + return (autofs_ioctl_done( + (struct autofs_daemon_done *)arg)); + default: + AUTOFS_DEBUG("invalid cmd %lx", cmd); + return (EINVAL); + } +} diff --git a/sys/vfs/autofs/autofs.h b/sys/vfs/autofs/autofs.h new file mode 100644 index 0000000000..8566ef9746 --- /dev/null +++ b/sys/vfs/autofs/autofs.h @@ -0,0 +1,191 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef AUTOFS_H +#define AUTOFS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AUTOFS_ROOTINO ((ino_t)1) +#define VFSTOAUTOFS(mp) ((struct autofs_mount *)((mp)->mnt_data)) +#define VTOI(vp) ((struct autofs_node *)((vp)->v_data)) + +MALLOC_DECLARE(M_AUTOFS); + +extern struct objcache *autofs_request_objcache; +extern struct objcache *autofs_node_objcache; + +extern struct vop_ops autofs_vnode_vops; + +extern int autofs_debug; +extern int autofs_mount_on_stat; + +/* + * APRINTF is only for debugging. + */ +#define APRINTF(A, B, ...) \ + kprintf(A " %s(%s): " B, __func__, curthread->td_comm, ## __VA_ARGS__) + +#define AUTOFS_DEBUG(X, ...) \ + do { \ + if (autofs_debug > 1) \ + kprintf("%s: " X "\n", \ + __func__, ## __VA_ARGS__); \ + } while (0) + +#define AUTOFS_WARN(X, ...) \ + do { \ + if (autofs_debug > 0) { \ + kprintf("WARNING: %s: " X "\n", \ + __func__, ## __VA_ARGS__); \ + } \ + } while (0) + +#define AUTOFS_FATAL(X, ...) \ + kprintf("FATAL: %s: " X "\n", __func__, ## __VA_ARGS__) + +#define AUTOFS_LOCK_STATUS(lock) (lockstatus((lock), curthread)) +#define AUTOFS_ASSERT_LOCKED(X) \ + KKASSERT(AUTOFS_LOCK_STATUS(&(X)->am_lock) & (LK_EXCLUSIVE | LK_SHARED)) +#define AUTOFS_ASSERT_XLOCKED(X) \ + KKASSERT(AUTOFS_LOCK_STATUS(&(X)->am_lock) == LK_EXCLUSIVE) +#define AUTOFS_ASSERT_UNLOCKED(X) \ + KKASSERT(AUTOFS_LOCK_STATUS(&(X)->am_lock) == 0) + +struct autofs_node { + RB_ENTRY(autofs_node) an_link; + char *an_name; + ino_t an_ino; + struct autofs_node *an_parent; + RB_HEAD(autofs_node_tree, + autofs_node) an_children; + struct autofs_mount *an_mount; + struct vnode *an_vnode; + struct lock an_vnode_lock; + bool an_cached; + bool an_wildcards; + struct callout an_callout; + int an_retries; + struct timespec an_ctime; +}; + +struct autofs_mount { + TAILQ_ENTRY(autofs_mount) am_next; + struct autofs_node *am_root; + struct mount *am_mp; + struct lock am_lock; + char am_from[MAXPATHLEN]; + char am_on[MAXPATHLEN]; + char am_options[MAXPATHLEN]; + char am_prefix[MAXPATHLEN]; + ino_t am_last_ino; +#if 0 + struct netexport am_export; +#endif +}; + +struct autofs_request { + TAILQ_ENTRY(autofs_request) ar_next; + struct autofs_mount *ar_mount; + int ar_id; + bool ar_done; + int ar_error; + bool ar_wildcards; + bool ar_in_progress; + char ar_from[MAXPATHLEN]; + char ar_path[MAXPATHLEN]; + char ar_prefix[MAXPATHLEN]; + char ar_key[MAXPATHLEN]; + char ar_options[MAXPATHLEN]; + struct timeout_task ar_task; + volatile u_int ar_refcount; +}; + +struct autofs_softc { + device_t sc_dev; + struct cdev *sc_cdev; + struct cv sc_cv; + struct lock sc_lock; + TAILQ_HEAD(, autofs_request) sc_requests; + bool sc_dev_opened; + pid_t sc_dev_sid; + int sc_last_request_id; +}; + +int autofs_init(struct vfsconf *vfsp); +int autofs_uninit(struct vfsconf *vfsp); +int autofs_trigger(struct autofs_node *anp, const char *component, + int componentlen); +bool autofs_cached(struct autofs_node *anp, const char *component, + int componentlen); +char* autofs_path(struct autofs_node *anp); +void autofs_flush(struct autofs_mount *amp); +bool autofs_ignore_thread(void); +int autofs_node_new(struct autofs_node *parent, struct autofs_mount *amp, + const char *name, int namelen, struct autofs_node **anpp); +int autofs_node_find(struct autofs_node *parent, + const char *name, int namelen, struct autofs_node **anpp); +void autofs_node_delete(struct autofs_node *anp); +int autofs_node_vn(struct autofs_node *anp, struct mount *mp, + int flags, struct vnode **vpp); + +static __inline void +autofs_node_cache(struct autofs_node *anp) +{ + anp->an_cached = true; +} + +static __inline void +autofs_node_uncache(struct autofs_node *anp) +{ + anp->an_cached = false; +} + +RB_PROTOTYPE(autofs_node_tree, autofs_node, an_link, autofs_node_cmp); + +#endif /* !AUTOFS_H */ diff --git a/sys/vfs/autofs/autofs_ioctl.h b/sys/vfs/autofs/autofs_ioctl.h new file mode 100644 index 0000000000..d0167d3c0c --- /dev/null +++ b/sys/vfs/autofs/autofs_ioctl.h @@ -0,0 +1,120 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * Copyright (c) 2013 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef AUTOFS_IOCTL_H +#define AUTOFS_IOCTL_H + +#include +#include + +#define AUTOFS_PATH "/dev/autofs" + +struct autofs_daemon_request { + /* + * Request identifier. + */ + int adr_id; + + /* + * The "from" field, containing map name. For example, + * when accessing '/net/192.168.1.3/tank/vm/', that would + * be '-hosts'. + */ + char adr_from[MAXPATHLEN]; + + /* + * Full path to the node being looked up; for requests that result + * in actual mount it is the full mount path. + */ + char adr_path[MAXPATHLEN]; + + /* + * Prefix, which is basically the mountpoint from auto_master(5). + * In example above that would be "/net"; for direct maps it is "/". + */ + char adr_prefix[MAXPATHLEN]; + + /* + * Map key, also used as command argument for dynamic maps; in example + * above that would be '192.168.1.3'. + */ + char adr_key[MAXPATHLEN]; + + /* + * Mount options from auto_master(5). + */ + char adr_options[MAXPATHLEN]; +}; + +/* + * Compatibility with 10.1-RELEASE automountd(8). + */ +struct autofs_daemon_done_101 { + /* + * Identifier, copied from adr_id. + */ + int add_id; + + /* + * Error number, possibly returned to userland. + */ + int add_error; +}; + +struct autofs_daemon_done { + /* + * Identifier, copied from adr_id. + */ + int add_id; + + /* + * Set to 1 if the map may contain wildcard entries; + * otherwise autofs will do negative caching. + */ + int add_wildcards; + + /* + * Error number, possibly returned to userland. + */ + int add_error; + + /* + * Reserved for future use. + */ + int add_spare[7]; +}; + +#define AUTOFSREQUEST _IOR('I', 0x01, struct autofs_daemon_request) +#define AUTOFSDONE101 _IOW('I', 0x02, struct autofs_daemon_done_101) +#define AUTOFSDONE _IOW('I', 0x03, struct autofs_daemon_done) + +#endif /* !AUTOFS_IOCTL_H */ diff --git a/sys/vfs/autofs/autofs_mount.h b/sys/vfs/autofs/autofs_mount.h new file mode 100644 index 0000000000..56d3ada2d2 --- /dev/null +++ b/sys/vfs/autofs/autofs_mount.h @@ -0,0 +1,40 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * All rights reserved. + * + * 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. + * + */ + +#ifndef AUTOFS_MOUNT_H +#define AUTOFS_MOUNT_H + +#include +#include + +struct autofs_mount_info { + const char *from; + const char *master_options; + const char *master_prefix; +}; + +#endif /* !AUTOFS_MOUNT_H */ diff --git a/sys/vfs/autofs/autofs_vfsops.c b/sys/vfs/autofs/autofs_vfsops.c new file mode 100644 index 0000000000..6931799eea --- /dev/null +++ b/sys/vfs/autofs/autofs_vfsops.c @@ -0,0 +1,238 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * 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 +#include +#include + +#include "autofs.h" +#include "autofs_mount.h" + +static int autofs_statfs(struct mount *mp, struct statfs *sbp, + struct ucred *cred); + +extern struct autofs_softc *autofs_softc; + +static int +autofs_mount(struct mount *mp, char *mntpt, caddr_t data, struct ucred *cred) +{ + struct autofs_mount_info info; + struct autofs_mount *amp; + struct statfs *sbp = &mp->mnt_stat; + char buf[MAXPATHLEN]; + int error; + + if (mp->mnt_flag & MNT_UPDATE) { + autofs_flush(VFSTOAUTOFS(mp)); + return (0); + } + + error = copyin(data, &info, sizeof(info)); + if (error) + return (error); + + memset(sbp->f_mntfromname, 0, sizeof(sbp->f_mntfromname)); + error = copyinstr(info.from, sbp->f_mntfromname, + sizeof(sbp->f_mntfromname), NULL); + if (error) + return (error); + + memset(sbp->f_mntonname, 0, sizeof(sbp->f_mntonname)); + error = copyinstr(mntpt, sbp->f_mntonname, + sizeof(sbp->f_mntonname), NULL); + if (error) + return (error); + + amp = kmalloc(sizeof(*amp), M_AUTOFS, M_WAITOK | M_ZERO); + mp->mnt_data = (qaddr_t)amp; + amp->am_mp = mp; + strlcpy(amp->am_from, sbp->f_mntfromname, sizeof(amp->am_from)); + strlcpy(amp->am_on, sbp->f_mntonname, sizeof(amp->am_on)); + + memset(buf, 0, sizeof(buf)); + error = copyinstr(info.master_options, buf, sizeof(buf), NULL); + if (error) + goto fail; + strlcpy(amp->am_options, buf, sizeof(amp->am_options)); + + memset(buf, 0, sizeof(buf)); + error = copyinstr(info.master_prefix, buf, sizeof(buf), NULL); + if (error) + goto fail; + strlcpy(amp->am_prefix, buf, sizeof(amp->am_prefix)); + + lockinit(&->am_lock, "autofsmnlk", 0, 0); + amp->am_last_ino = AUTOFS_ROOTINO; + + vfs_getnewfsid(mp); + vfs_add_vnodeops(mp, &autofs_vnode_vops, &mp->mnt_vn_norm_ops); + + lockmgr(&->am_lock, LK_EXCLUSIVE); + error = autofs_node_new(NULL, amp, ".", -1, &->am_root); + lockmgr(&->am_lock, LK_RELEASE); + KKASSERT(error == 0); + KKASSERT(amp->am_root->an_ino == AUTOFS_ROOTINO); + + autofs_statfs(mp, sbp, cred); + + return (0); + +fail: + kfree(amp, M_AUTOFS); + return (error); +} + +static int +autofs_unmount(struct mount *mp, int mntflags) +{ + struct autofs_mount *amp = VFSTOAUTOFS(mp); + struct autofs_node *anp; + struct autofs_request *ar; + int error, flags, dummy; + bool found; + + flags = 0; + if (mntflags & MNT_FORCE) + flags |= FORCECLOSE; + error = vflush(mp, 0, flags); + if (error) { + AUTOFS_WARN("vflush failed with error %d", error); + return (error); + } + + /* + * All vnodes are gone, and new one will not appear - so, + * no new triggerings. + */ + for (;;) { + found = false; + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + TAILQ_FOREACH(ar, &autofs_softc->sc_requests, ar_next) { + if (ar->ar_mount != amp) + continue; + ar->ar_error = ENXIO; + ar->ar_done = true; + ar->ar_in_progress = false; + found = true; + } + if (found == false) { + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + break; + } + + cv_broadcast(&autofs_softc->sc_cv); + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + + tsleep(&dummy, 0, "autofs_umount", hz); + } + + lockmgr(&->am_lock, LK_EXCLUSIVE); + while (!RB_EMPTY(&->am_root->an_children)) { + anp = RB_MIN(autofs_node_tree, &->am_root->an_children); + autofs_node_delete(anp); + } + autofs_node_delete(amp->am_root); + mp->mnt_data = NULL; + lockmgr(&->am_lock, LK_RELEASE); + + lockuninit(&->am_lock); + + kfree(amp, M_AUTOFS); + + return (0); +} + +static int +autofs_root(struct mount *mp, struct vnode **vpp) +{ + struct autofs_mount *amp = VFSTOAUTOFS(mp); + int error; + + if (amp->am_root == NULL) { + AUTOFS_FATAL("called without root node %p", mp); + print_backtrace(-1); + *vpp = NULL; + error = EINVAL; + } else { + error = autofs_node_vn(amp->am_root, mp, LK_EXCLUSIVE, vpp); + (*vpp)->v_flag |= VROOT; + KKASSERT((*vpp)->v_type == VDIR); + } + + return (error); +} + +static int +autofs_statfs(struct mount *mp, struct statfs *sbp, struct ucred *cred) +{ + sbp->f_bsize = S_BLKSIZE; + sbp->f_iosize = 0; + sbp->f_blocks = 0; + sbp->f_bfree = 0; + sbp->f_bavail = 0; + sbp->f_files = 0; + sbp->f_ffree = 0; + + return (0); +} + +static int +autofs_statvfs(struct mount *mp, struct statvfs *sbp, struct ucred *cred) +{ + sbp->f_bsize = S_BLKSIZE; + sbp->f_frsize = 0; + sbp->f_blocks = 0; + sbp->f_bfree = 0; + sbp->f_bavail = 0; + sbp->f_files = 0; + sbp->f_ffree = 0; + + return (0); +} + +static struct vfsops autofs_vfsops = { + .vfs_mount = autofs_mount, + .vfs_unmount = autofs_unmount, + .vfs_root = autofs_root, + .vfs_statfs = autofs_statfs, + .vfs_statvfs = autofs_statvfs, + .vfs_init = autofs_init, + .vfs_uninit = autofs_uninit, + .vfs_sync = vfs_stdsync, /* for unmount(2) */ +#if 0 + .vfs_vptofh = NULL, + .vfs_fhtovp = NULL, + .vfs_checkexp = NULL, +#endif +}; + +VFS_SET(autofs_vfsops, autofs, VFCF_SYNTHETIC | VFCF_NETWORK); +MODULE_VERSION(autofs, 1); diff --git a/sys/vfs/autofs/autofs_vnops.c b/sys/vfs/autofs/autofs_vnops.c new file mode 100644 index 0000000000..d4d24fed91 --- /dev/null +++ b/sys/vfs/autofs/autofs_vnops.c @@ -0,0 +1,617 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * 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 +#include +#include +#include +#include +#include +#include + +#include "autofs.h" + +static int autofs_trigger_vn(struct vnode *vp, const char *path, + int pathlen, struct vnode **newvp); + +extern struct autofs_softc *autofs_softc; + +static __inline int +autofs_trigger_vn_dir(struct vnode *vp, struct vnode **newvp) +{ + return (autofs_trigger_vn(vp, "", 0, newvp)); +} + +static __inline size_t +autofs_dirent_reclen(const char *name) +{ + return (_DIRENT_RECLEN(strlen(name))); +} + +static int +test_fs_root(struct vnode *vp) +{ + int error; + + if ((error = vget(vp, LK_SHARED)) != 0) { + AUTOFS_WARN("vget failed with error %d", error); + return (1); + } + + if (((vp->v_flag & VROOT) == 0) || (vp->v_tag == VT_AUTOFS)) { + vput(vp); + return (1); + } + + return (0); +} + +static int +nlookup_fs_root(struct autofs_node *anp, struct vnode **vpp) +{ + struct vnode *vp; + struct nlookupdata nd; + char *path; + int error; + + path = autofs_path(anp); + + error = nlookup_init(&nd, path, UIO_SYSSPACE, NLC_FOLLOW); + if (error == 0) { + error = nlookup(&nd); + if (error == 0) { + vp = nd.nl_nch.ncp->nc_vp; + error = test_fs_root(vp); + if (error == 0) + *vpp = vp; + } + } + nlookup_done(&nd); + kfree(path, M_AUTOFS); + + return (error); +} + +static int +autofs_access(struct vop_access_args *ap) +{ + /* + * Nothing to do here; the only kind of access control + * needed is in autofs_mkdir(). + */ + return (0); +} + +static int +autofs_getattr(struct vop_getattr_args *ap) +{ + struct vnode *vp = ap->a_vp; + struct vattr *vap = ap->a_vap; + struct autofs_node *anp = VTOI(vp); + + KASSERT(vp->v_type == VDIR, ("!VDIR")); + + /* + * The reason we must do this is that some tree-walking software, + * namely fts(3), assumes that stat(".") results will not change + * between chdir("subdir") and chdir(".."), and fails with ENOENT + * otherwise. + * XXX: Not supported on DragonFly. + */ + if (autofs_mount_on_stat) + AUTOFS_WARN("vfs.autofs.mount_on_stat is not supported"); + + vap->va_type = VDIR; + vap->va_mode = 0755; + vap->va_nlink = 3; /* XXX: FreeBSD had it like this */ + vap->va_uid = 0; + vap->va_gid = 0; + vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0]; + vap->va_fileid = anp->an_ino; + vap->va_size = S_BLKSIZE; + vap->va_blocksize = S_BLKSIZE; + vap->va_mtime = anp->an_ctime; + vap->va_atime = anp->an_ctime; + vap->va_ctime = anp->an_ctime; + vap->va_gen = 0; + vap->va_flags = 0; + vap->va_rmajor = 0; + vap->va_rminor = 0; + vap->va_bytes = S_BLKSIZE; + vap->va_filerev = 0; + vap->va_spare = 0; + + return (0); +} + +static int +autofs_trigger_vn(struct vnode *vp, const char *path, int pathlen, + struct vnode **newvp) +{ + struct autofs_node *anp = VTOI(vp); + struct vnode *nvp = NULL; + int error; + + KKASSERT(!vn_islocked(vp)); + + if (test_fs_root(vp) == 0) + goto mounted; + + /* + * Don't remove this. Without having this extra nlookup, + * automountd tries to mount the target filesystem twice + * and the second attempt to mount returns an error. + */ + if (nlookup_fs_root(anp, &nvp) == 0) + goto mounted; + + lockmgr(&autofs_softc->sc_lock, LK_EXCLUSIVE); + error = autofs_trigger(anp, path, pathlen); + lockmgr(&autofs_softc->sc_lock, LK_RELEASE); + + if (error) + return (error); + + if (nlookup_fs_root(anp, &nvp)) + return (0); + + /* + * If the operation that succeeded was mount, then mark + * the node as non-cached. Otherwise, if someone unmounts + * the filesystem before the cache times out, we will fail + * to trigger. + */ + autofs_node_uncache(anp); +mounted: + *newvp = nvp; + KKASSERT(vn_islocked(*newvp)); + + return (0); +} + +static int +autofs_nresolve(struct vop_nresolve_args *ap) +{ + struct vnode *vp = NULL; + struct vnode *dvp = ap->a_dvp; + struct nchandle *nch = ap->a_nch; + struct namecache *ncp = nch->ncp; + struct autofs_mount *amp = VFSTOAUTOFS(dvp->v_mount); + struct autofs_node *anp = VTOI(dvp); + struct autofs_node *child = NULL; + int error; + + if (autofs_cached(anp, ncp->nc_name, ncp->nc_nlen) == false && + autofs_ignore_thread() == false) { + struct vnode *newvp = NULL; + + cache_hold(nch); + cache_unlock(nch); + error = autofs_trigger_vn(dvp, + ncp->nc_name, ncp->nc_nlen, &newvp); + cache_lock(nch); + cache_drop(nch); + + if (error) + return (error); + if (newvp != NULL) { + KKASSERT(newvp->v_tag != VT_AUTOFS); + vput(newvp); + return (ESTALE); + } + return (0); + } + + lockmgr(&->am_lock, LK_SHARED); + error = autofs_node_find(anp, ncp->nc_name, ncp->nc_nlen, &child); + lockmgr(&->am_lock, LK_RELEASE); + + if (error) { + cache_setvp(nch, NULL); + return (0); + } + + error = autofs_node_vn(child, dvp->v_mount, LK_EXCLUSIVE, &vp); + if (error == 0) { + KKASSERT(vn_islocked(vp)); + vn_unlock(vp); + cache_setvp(nch, vp); + vrele(vp); + return (0); + } + + return (error); +} + +static int +autofs_nmkdir(struct vop_nmkdir_args *ap) +{ + struct vnode *vp = NULL; + struct vnode *dvp = ap->a_dvp; + struct nchandle *nch = ap->a_nch; + struct namecache *ncp = nch->ncp; + struct autofs_mount *amp = VFSTOAUTOFS(dvp->v_mount); + struct autofs_node *anp = VTOI(dvp); + struct autofs_node *child = NULL; + int error; + + /* + * Do not allow mkdir() if the calling thread is not + * automountd(8) descendant. + */ + if (autofs_ignore_thread() == false) + return (EPERM); + + lockmgr(&->am_lock, LK_EXCLUSIVE); + error = autofs_node_new(anp, amp, ncp->nc_name, ncp->nc_nlen, &child); + lockmgr(&->am_lock, LK_RELEASE); + KKASSERT(error == 0); + + error = autofs_node_vn(child, dvp->v_mount, LK_EXCLUSIVE, &vp); + if (error == 0) { + KKASSERT(vn_islocked(vp)); + cache_setunresolved(nch); + cache_setvp(nch, vp); + *(ap->a_vpp) = vp; + return (0); + } + + return (error); +} + +static int +autofs_readdir_one(struct uio *uio, const char *name, ino_t ino, + size_t *reclenp) +{ + int error = 0; + + if (reclenp != NULL) + *reclenp = autofs_dirent_reclen(name); + + if (vop_write_dirent(&error, uio, ino, DT_DIR, strlen(name), name)) + return (EINVAL); + + return (error); +} + +static int +autofs_readdir(struct vop_readdir_args *ap) +{ + struct vnode *vp = ap->a_vp; + struct autofs_mount *amp = VFSTOAUTOFS(vp->v_mount); + struct autofs_node *anp = VTOI(vp); + struct autofs_node *child; + struct uio *uio = ap->a_uio; + ssize_t initial_resid = ap->a_uio->uio_resid; + size_t reclen, reclens; + int error; + + KASSERT(vp->v_type == VDIR, ("!VDIR")); + + if (autofs_cached(anp, NULL, 0) == false && + autofs_ignore_thread() == false) { + struct vnode *newvp = NULL; + error = autofs_trigger_vn_dir(vp, &newvp); + if (error) + return (error); + if (newvp != NULL) { + KKASSERT(newvp->v_tag != VT_AUTOFS); + vn_unlock(newvp); + error = VOP_READDIR(newvp, ap->a_uio, ap->a_cred, + ap->a_eofflag, ap->a_ncookies, ap->a_cookies); + vrele(newvp); + return (error); + } + /* FALLTHROUGH */ + } + + if (uio->uio_offset < 0) + return (EINVAL); + + if (ap->a_eofflag != NULL) + *ap->a_eofflag = FALSE; + + /* + * Write out the directory entry for ".". + */ + if (uio->uio_offset == 0) { + error = autofs_readdir_one(uio, ".", anp->an_ino, &reclen); + if (error) + goto out; + } + reclens = autofs_dirent_reclen("."); + + /* + * Write out the directory entry for "..". + */ + if (uio->uio_offset <= reclens) { + if (uio->uio_offset != reclens) + return (EINVAL); + error = autofs_readdir_one(uio, "..", + (anp->an_parent ? anp->an_parent->an_ino : anp->an_ino), + &reclen); + if (error) + goto out; + } + reclens += autofs_dirent_reclen(".."); + + /* + * Write out the directory entries for subdirectories. + */ + lockmgr(&->am_lock, LK_SHARED); + RB_FOREACH(child, autofs_node_tree, &anp->an_children) { + /* + * Check the offset to skip entries returned by previous + * calls to getdents(). + */ + if (uio->uio_offset > reclens) { + reclens += autofs_dirent_reclen(child->an_name); + continue; + } + + /* + * Prevent seeking into the middle of dirent. + */ + if (uio->uio_offset != reclens) { + lockmgr(&->am_lock, LK_RELEASE); + return (EINVAL); + } + + error = autofs_readdir_one(uio, child->an_name, + child->an_ino, &reclen); + reclens += reclen; + if (error) { + lockmgr(&->am_lock, LK_RELEASE); + goto out; + } + } + lockmgr(&->am_lock, LK_RELEASE); + + if (ap->a_eofflag != NULL) + *ap->a_eofflag = TRUE; + + return (0); +out: + /* + * Return error if the initial buffer was too small to do anything. + */ + if (uio->uio_resid == initial_resid) + return (error); + + /* + * Don't return an error if we managed to copy out some entries. + */ + if (uio->uio_resid < reclen) + return (0); + + return (error); +} + +static int +autofs_reclaim(struct vop_reclaim_args *ap) +{ + struct vnode *vp = ap->a_vp; + struct autofs_node *anp = VTOI(vp); + + /* + * We do not free autofs_node here; instead we are + * destroying them in autofs_node_delete(). + */ + lockmgr(&anp->an_vnode_lock, LK_EXCLUSIVE); + anp->an_vnode = NULL; + vp->v_data = NULL; + lockmgr(&anp->an_vnode_lock, LK_RELEASE); + + return (0); +} + +static int +autofs_mountctl(struct vop_mountctl_args *ap) +{ + struct mount *mp; +#if 0 + struct autofs_mount *amp; +#endif + int res; + + mp = ap->a_head.a_ops->head.vv_mount; + lwkt_gettoken(&mp->mnt_token); + + switch (ap->a_op) { +#if 0 + case MOUNTCTL_SET_EXPORT: + amp = (struct autofs_mount*)mp->mnt_data; + if (ap->a_ctllen != sizeof(struct export_args)) + res = (EINVAL); + else + res = vfs_export(mp, &->am_export, + (const struct export_args*)ap->a_ctl); + break; +#endif + default: + res = vop_stdmountctl(ap); + break; + } + + lwkt_reltoken(&mp->mnt_token); + return (res); +} + +static int +autofs_print(struct vop_print_args *ap) +{ + struct autofs_node *anp = VTOI(ap->a_vp); + + kprintf("tag VT_AUTOFS, node %p, ino %jd, name %s, cached %d, retries %d\n", + anp, (intmax_t)anp->an_ino, anp->an_name, anp->an_cached, anp->an_retries); + + return (0); +} + +struct vop_ops autofs_vnode_vops = { + .vop_default = vop_defaultop, + .vop_getpages = vop_stdgetpages, + .vop_putpages = vop_stdputpages, + .vop_access = autofs_access, + .vop_getattr = autofs_getattr, + .vop_nresolve = autofs_nresolve, + .vop_nmkdir = autofs_nmkdir, + .vop_readdir = autofs_readdir, + .vop_reclaim = autofs_reclaim, + .vop_mountctl = autofs_mountctl, + .vop_print = autofs_print, +#if 0 + .vop_nlookupdotdot = NULL, +#endif +}; + +int +autofs_node_new(struct autofs_node *parent, struct autofs_mount *amp, + const char *name, int namelen, struct autofs_node **anpp) +{ + struct autofs_node *anp; + + AUTOFS_ASSERT_XLOCKED(amp); + + if (parent != NULL) { + AUTOFS_ASSERT_XLOCKED(parent->an_mount); + KASSERT(autofs_node_find(parent, name, namelen, NULL) == ENOENT, + ("node \"%s\" already exists", name)); + } + + anp = objcache_get(autofs_node_objcache, M_WAITOK); + if (namelen >= 0) + anp->an_name = kstrndup(name, namelen, M_AUTOFS); + else + anp->an_name = kstrdup(name, M_AUTOFS); + anp->an_ino = amp->am_last_ino++; + callout_init(&anp->an_callout); + lockinit(&anp->an_vnode_lock, "autofsvnlk", 0, 0); + getnanotime(&anp->an_ctime); + anp->an_parent = parent; + anp->an_mount = amp; + anp->an_vnode = NULL; + anp->an_cached = false; + anp->an_wildcards = false; + anp->an_retries = 0; + if (parent != NULL) + RB_INSERT(autofs_node_tree, &parent->an_children, anp); + RB_INIT(&anp->an_children); + + *anpp = anp; + + return (0); +} + +int +autofs_node_find(struct autofs_node *parent, const char *name, + int namelen, struct autofs_node **anpp) +{ + struct autofs_node *anp, find; + int error; + + AUTOFS_ASSERT_LOCKED(parent->an_mount); + + if (namelen >= 0) + find.an_name = kstrndup(name, namelen, M_AUTOFS); + else + find.an_name = kstrdup(name, M_AUTOFS); + + anp = RB_FIND(autofs_node_tree, &parent->an_children, &find); + if (anp != NULL) { + error = 0; + if (anpp != NULL) + *anpp = anp; + } else { + error = ENOENT; + } + + kfree(find.an_name, M_AUTOFS); + + return (error); +} + +void +autofs_node_delete(struct autofs_node *anp) +{ + AUTOFS_ASSERT_XLOCKED(anp->an_mount); + KASSERT(RB_EMPTY(&anp->an_children), ("have children")); + + callout_drain(&anp->an_callout); + + if (anp->an_parent != NULL) + RB_REMOVE(autofs_node_tree, &anp->an_parent->an_children, anp); + + lockuninit(&anp->an_vnode_lock); + kfree(anp->an_name, M_AUTOFS); + objcache_put(autofs_node_objcache, anp); +} + +int +autofs_node_vn(struct autofs_node *anp, struct mount *mp, int flags, + struct vnode **vpp) +{ + struct vnode *vp = NULL; + int error; +retry: + AUTOFS_ASSERT_UNLOCKED(anp->an_mount); + lockmgr(&anp->an_vnode_lock, LK_EXCLUSIVE); + + vp = anp->an_vnode; + if (vp != NULL) { + vhold(vp); + lockmgr(&anp->an_vnode_lock, LK_RELEASE); + + error = vget(vp, flags | LK_RETRY); + if (error) { + AUTOFS_WARN("vget failed with error %d", error); + vdrop(vp); + goto retry; + } + vdrop(vp); + *vpp = vp; + return (0); + } + + lockmgr(&anp->an_vnode_lock, LK_RELEASE); + + error = getnewvnode(VT_AUTOFS, mp, &vp, VLKTIMEOUT, LK_CANRECURSE); + if (error) + return (error); + vp->v_type = VDIR; + vp->v_data = anp; + + KASSERT(anp->an_vnode == NULL, ("lost race")); + anp->an_vnode = vp; + *vpp = vp; + + return (0); +} diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index 6087b45aa3..9dfd89966a 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -12,6 +12,7 @@ SUBDIR= 802_11 \ arp \ asf \ authpf \ + autofs \ bootparamd \ btconfig \ bthcid \ @@ -36,6 +37,7 @@ SUBDIR= 802_11 \ fdcontrol \ fdformat \ fdwrite \ + fstyp \ ftp-proxy \ fwcontrol \ gifconfig \ diff --git a/usr.sbin/autofs/Makefile b/usr.sbin/autofs/Makefile new file mode 100644 index 0000000000..078dc48e5f --- /dev/null +++ b/usr.sbin/autofs/Makefile @@ -0,0 +1,17 @@ +PROG= automount +SRCS= automount.c automountd.c autounmountd.c common.c \ + defined.c log.c popen.c token.l + +CFLAGS+= -I${.CURDIR} +CFLAGS+= -I${.CURDIR}/../../sys + +MAN= automount.8 automountd.8 autounmountd.8 auto_master.5 + +LDADD= -lutil + +WARNS= 6 + +LINKS= ${BINDIR}/automount ${BINDIR}/automountd +LINKS+= ${BINDIR}/automount ${BINDIR}/autounmountd + +.include diff --git a/usr.sbin/autofs/auto_master.5 b/usr.sbin/autofs/auto_master.5 new file mode 100644 index 0000000000..e2004d7d0e --- /dev/null +++ b/usr.sbin/autofs/auto_master.5 @@ -0,0 +1,392 @@ +.\" Copyright (c) 2016 The DragonFly Project +.\" Copyright (c) 2014 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Edward Tomasz Napierala under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd April 14, 2016 +.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. +.Pp +The +.Nm +configuration file functionality was ported to +.Dx +by +.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com . +.Sh BUGS +.Pa /etc/autofs/special_media +on +.Dx +currently can't detect HAMMER filesystem consists of more than one volumes. +.Pp +.Pa /etc/autofs/special_media +on +.Dx +currently ignores md(4) devices by default. diff --git a/usr.sbin/autofs/automount.8 b/usr.sbin/autofs/automount.8 new file mode 100644 index 0000000000..6231be0f9c --- /dev/null +++ b/usr.sbin/autofs/automount.8 @@ -0,0 +1,123 @@ +.\" Copyright (c) 2016 The DragonFly Project +.\" Copyright (c) 2014 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Edward Tomasz Napierala under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd April 14, 2016 +.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 5 +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 . +The +.Nm +command appeared in +.Dx 4.5 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. +.Pp +The +.Nm +was ported to +.Dx +by +.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com . diff --git a/usr.sbin/autofs/automount.c b/usr.sbin/autofs/automount.c new file mode 100644 index 0000000000..5036a2d1f3 --- /dev/null +++ b/usr.sbin/autofs/automount.c @@ -0,0 +1,331 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * 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 +#include +#include +#include +#include +#include +#include + +#include "common.h" + +static int +unmount_by_statfs(const struct statfs *sb, bool force) +{ + int error, flags; + + log_debugx("unmounting %s", sb->f_mntonname); + + flags = 0; + if (force) + flags |= MNT_FORCE; + error = unmount(sb->f_mntonname, flags); + 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 autofs_mount_info info; + int error; + + create_directory(fspath); + + log_debugx("mounting %s on %s, prefix \"%s\", options \"%s\"", + from, fspath, prefix, options); + + memset(&info, 0, sizeof(info)); + info.from = from; + info.master_options = options; + info.master_prefix = prefix; + + error = mount("autofs", fspath, 0, &info); + if (error != 0) + 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) +{ + int error; + + log_debugx("flushing %s", fspath); + + error = mount("autofs", fspath, MNT_UPDATE, NULL); + if (error != 0) + 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 0000000000..be2f41088b --- /dev/null +++ b/usr.sbin/autofs/automountd.8 @@ -0,0 +1,116 @@ +.\" Copyright (c) 2016 The DragonFly Project +.\" Copyright (c) 2014 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Edward Tomasz Napierala under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd April 14, 2016 +.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 , +.Xr hammer 5 +.Sh HISTORY +The +.Nm +daemon appeared in +.Fx 10.1 . +The +.Nm +daemon appeared in +.Dx 4.5 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. +.Pp +The +.Nm +was ported to +.Dx +by +.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com . diff --git a/usr.sbin/autofs/automountd.c b/usr.sbin/autofs/automountd.c new file mode 100644 index 0000000000..c79fe7af70 --- /dev/null +++ b/usr.sbin/autofs/automountd.c @@ -0,0 +1,554 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 0000000000..6c6c85f527 --- /dev/null +++ b/usr.sbin/autofs/autounmountd.8 @@ -0,0 +1,100 @@ +.\" Copyright (c) 2016 The DragonFly Project +.\" Copyright (c) 2014 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Edward Tomasz Napierala under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd April 14, 2016 +.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 . +The +.Nm +daemon appeared in +.Dx 4.5 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. +.Pp +The +.Nm +was ported to +.Dx +by +.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com . diff --git a/usr.sbin/autofs/autounmountd.c b/usr.sbin/autofs/autounmountd.c new file mode 100644 index 0000000000..327b767e63 --- /dev/null +++ b/usr.sbin/autofs/autounmountd.c @@ -0,0 +1,339 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define AUTOUNMOUNTD_PIDFILE "/var/run/autounmountd.pid" + +struct automounted_fs { + TAILQ_ENTRY(automounted_fs) af_next; + time_t af_mount_time; + bool af_mark; + fsid_t af_fsid; + char af_mountpoint[MNAMELEN]; +}; + +static TAILQ_HEAD(, automounted_fs) automounted; + +static struct automounted_fs * +automounted_find(fsid_t fsid) +{ + struct automounted_fs *af; + + TAILQ_FOREACH(af, &automounted, af_next) { + if (af->af_fsid.val[0] == fsid.val[0] && + af->af_fsid.val[1] == fsid.val[1]) + return (af); + } + + return (NULL); +} + +static struct automounted_fs * +automounted_add(fsid_t fsid, const char *mountpoint) +{ + struct automounted_fs *af; + + af = calloc(sizeof(*af), 1); + if (af == NULL) + log_err(1, "calloc"); + af->af_mount_time = time(NULL); + af->af_fsid = fsid; + strlcpy(af->af_mountpoint, mountpoint, sizeof(af->af_mountpoint)); + + TAILQ_INSERT_TAIL(&automounted, af, af_next); + + return (af); +} + +static void +automounted_remove(struct automounted_fs *af) +{ + + TAILQ_REMOVE(&automounted, af, af_next); + free(af); +} + +static void +refresh_automounted(void) +{ + struct automounted_fs *af, *tmpaf; + struct statfs *mntbuf; + int i, nitems; + + nitems = getmntinfo(&mntbuf, MNT_WAIT); + if (nitems <= 0) + log_err(1, "getmntinfo"); + + log_debugx("refreshing list of automounted filesystems"); + + TAILQ_FOREACH(af, &automounted, af_next) + af->af_mark = false; + + for (i = 0; i < nitems; i++) { + if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) { + log_debugx("skipping %s, filesystem type is autofs", + mntbuf[i].f_mntonname); + continue; + } + + if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) { + log_debugx("skipping %s, not automounted", + mntbuf[i].f_mntonname); + continue; + } + + af = automounted_find(mntbuf[i].f_fsid); + if (af == NULL) { + log_debugx("new automounted filesystem found on %s " + "(FSID:%d:%d)", mntbuf[i].f_mntonname, + mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]); + af = automounted_add(mntbuf[i].f_fsid, + mntbuf[i].f_mntonname); + } else { + log_debugx("already known automounted filesystem " + "found on %s (FSID:%d:%d)", mntbuf[i].f_mntonname, + mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]); + } + af->af_mark = true; + } + + TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) { + if (af->af_mark) + continue; + log_debugx("lost filesystem mounted on %s (FSID:%d:%d)", + af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1]); + automounted_remove(af); + } +} + +static int +do_unmount(const fsid_t fsid __unused, const char *mountpoint) +{ + int error; + + error = unmount(mountpoint, 0); + if (error != 0) { + if (errno == EBUSY) { + log_debugx("cannot unmount %s: %s", + mountpoint, strerror(errno)); + } else { + log_warn("cannot unmount %s", mountpoint); + } + } + + 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 = do_unmount(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 0000000000..3ae5c06fe8 --- /dev/null +++ b/usr.sbin/autofs/common.c @@ -0,0 +1,1207 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * 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 +#include +#include +#include +#include +#include +#include +#include +#define _WITH_GETLINE +#include +#include +#include +#include +#include + +#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 0000000000..ea7959fbaa --- /dev/null +++ b/usr.sbin/autofs/common.h @@ -0,0 +1,130 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef AUTOMOUNTD_H +#define AUTOMOUNTD_H + +#include +#include +#include +#include + +#define AUTO_MASTER_PATH "/etc/auto_master" +#define AUTO_MAP_PREFIX "/etc" +#define AUTO_SPECIAL_PREFIX "/etc/autofs" +#define AUTO_INCLUDE_PATH AUTO_SPECIAL_PREFIX "/include" + +/* + * DragonFly has no _SAFE macros (see 085ff963). + */ +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST((head)); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +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 0000000000..2d3987dc7e --- /dev/null +++ b/usr.sbin/autofs/defined.c @@ -0,0 +1,253 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +static TAILQ_HEAD(, defined_value) defined_values; + +static const char * +defined_find(const char *name) +{ + struct defined_value *d; + + TAILQ_FOREACH(d, &defined_values, d_next) { + if (strcmp(d->d_name, name) == 0) + return (d->d_value); + } + + return (NULL); +} + +char * +defined_expand(const char *string) +{ + const char *value; + char c, *expanded, *name; + int i, ret, before_len = 0, name_off = 0, name_len = 0, after_off = 0; + bool backslashed = false, bracketed = false; + + expanded = checked_strdup(string); + + for (i = 0; string[i] != '\0'; i++) { + c = string[i]; + if (c == '\\' && backslashed == false) { + backslashed = true; + continue; + } + if (backslashed) { + backslashed = false; + continue; + } + backslashed = false; + if (c != '$') + continue; + + /* + * The 'before_len' variable contains the number + * of characters before the '$'. + */ + before_len = i; + assert(i + 1 < (int)strlen(string)); + if (string[i + 1] == '{') + bracketed = true; + + if (string[i + 1] == '\0') { + log_warnx("truncated variable"); + return (NULL); + } + + /* + * Skip '$'. + */ + i++; + + if (bracketed) { + if (string[i + 1] == '\0') { + log_warnx("truncated variable"); + return (NULL); + } + + /* + * Skip '{'. + */ + i++; + } + + /* + * The 'name_off' variable contains the number + * of characters before the variable name, + * including the "$" or "${". + */ + name_off = i; + + for (; string[i] != '\0'; i++) { + c = string[i]; + /* + * XXX: Decide on the set of characters that can be + * used in a variable name. + */ + if (isalnum(c) || c == '_') + continue; + + /* + * End of variable name. + */ + if (bracketed) { + if (c != '}') + continue; + + /* + * The 'after_off' variable contains the number + * of characters before the rest of the string, + * i.e. after the variable name. + */ + after_off = i + 1; + assert(i > 1); + assert(i - 1 > name_off); + name_len = i - name_off; + break; + } + + after_off = i; + assert(i > 1); + assert(i > name_off); + name_len = i - name_off; + break; + } + + name = strndup(string + name_off, name_len); + if (name == NULL) + log_err(1, "strndup"); + value = defined_find(name); + if (value == NULL) { + log_warnx("undefined variable ${%s}", name); + return (NULL); + } + + /* + * Concatenate it back. + */ + ret = asprintf(&expanded, "%.*s%s%s", + before_len, string, value, string + after_off); + if (ret < 0) + log_err(1, "asprintf"); + + //log_debugx("\"%s\" expanded to \"%s\"", string, expanded); + free(name); + + /* + * Figure out where to start searching for next variable. + */ + string = expanded; + i = before_len + strlen(value); + backslashed = bracketed = false; + before_len = name_off = name_len = after_off = 0; + assert(i <= (int)strlen(string)); + } + + if (before_len != 0 || name_off != 0 || name_len != 0 || after_off != 0) { + log_warnx("truncated variable"); + return (NULL); + } + + return (expanded); +} + +static void +defined_add(const char *name, const char *value) +{ + struct defined_value *d; + const char *found; + + found = defined_find(name); + if (found != NULL) + log_errx(1, "variable %s already defined", name); + + log_debugx("defining variable %s=%s", name, value); + + d = calloc(sizeof(*d), 1); + if (d == NULL) + log_err(1, "calloc"); + d->d_name = checked_strdup(name); + d->d_value = checked_strdup(value); + + TAILQ_INSERT_TAIL(&defined_values, d, d_next); +} + +void +defined_parse_and_add(char *def) +{ + char *name, *value; + + value = def; + name = strsep(&value, "="); + + if (value == NULL || value[0] == '\0') + log_errx(1, "missing variable value"); + if (name == NULL || name[0] == '\0') + log_errx(1, "missing variable name"); + + defined_add(name, value); +} + +void +defined_init(void) +{ + struct utsname name; + int error; + + TAILQ_INIT(&defined_values); + + error = uname(&name); + if (error != 0) + log_err(1, "uname"); + + defined_add("ARCH", name.machine); + defined_add("CPU", name.machine); + defined_add("HOST", name.nodename); + defined_add("OSNAME", name.sysname); + defined_add("OSREL", name.release); + defined_add("OSVERS", name.version); +} diff --git a/usr.sbin/autofs/log.c b/usr.sbin/autofs/log.c new file mode 100644 index 0000000000..895decc7fa --- /dev/null +++ b/usr.sbin/autofs/log.c @@ -0,0 +1,199 @@ +/*- + * Copyright (c) 2016 The DragonFly Project + * 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 +#include +#include +#include +#include +#include +#include + +#include "common.h" + +static int log_level = 0; +static char *peer_name = NULL; +static char *peer_addr = NULL; + +#define MSGBUF_LEN 1024 + +void +log_init(int level) +{ + + log_level = level; + openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON); +} + +void +log_set_peer_name(const char *name) +{ + + /* + * XXX: Turn it into assertion? + */ + if (peer_name != NULL) + log_errx(1, "%s called twice", __func__); + if (peer_addr == NULL) + log_errx(1, "%s called before log_set_peer_addr", __func__); + + peer_name = checked_strdup(name); +} + +void +log_set_peer_addr(const char *addr) +{ + + /* + * XXX: Turn it into assertion? + */ + if (peer_addr != NULL) + log_errx(1, "%s called twice", __func__); + + peer_addr = checked_strdup(addr); +} + +static void +log_common(int priority, int log_errno, const char *fmt, va_list ap) +{ + static char msgbuf[MSGBUF_LEN]; + static char msgbuf_strvised[MSGBUF_LEN * 4 + 1]; + char *errstr; + 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, msgbuf, sizeof(msgbuf_strvised), 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 { + errstr = strerror(log_errno); + + if (peer_name != NULL) { + fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(), + peer_addr, peer_name, msgbuf_strvised, errstr); + syslog(priority, "%s (%s): %s: %s", + peer_addr, peer_name, msgbuf_strvised, errstr); + } else if (peer_addr != NULL) { + fprintf(stderr, "%s: %s: %s: %s\n", getprogname(), + peer_addr, msgbuf_strvised, errstr); + syslog(priority, "%s: %s: %s", + peer_addr, msgbuf_strvised, errstr); + } else { + fprintf(stderr, "%s: %s: %s\n", getprogname(), + msgbuf_strvised, errstr); + syslog(priority, "%s: %s", + msgbuf_strvised, errstr); + } + } +} + +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 0000000000..85b27632da --- /dev/null +++ b/usr.sbin/autofs/popen.c @@ -0,0 +1,190 @@ +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2016 The DragonFly Project + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Portions of this software were developed by Edward Tomasz Napierala + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +extern char **environ; + +struct pid { + SLIST_ENTRY(pid) next; + FILE *outfp; + pid_t pid; + char *command; +}; +static SLIST_HEAD(, pid) pidlist = SLIST_HEAD_INITIALIZER(pidlist); + +#define ARGV_LEN 42 + +/* + * Replacement for popen(3), without stdin (which we 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 0000000000..5f3b3e165e --- /dev/null +++ b/usr.sbin/autofs/token.l @@ -0,0 +1,59 @@ +%{ +/*- + * Copyright (c) 2016 The DragonFly Project + * Copyright (c) 2014 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include + +#include "common.h" + +int lineno; + +#define YY_DECL int yylex(void) +extern int yylex(void); + +%} + +%option noinput +%option nounput +%option noyywrap + +%% +\"[^"]+\" { 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; } +%%