diff -aNur a/distrib/sets/lists/base/mi b/distrib/sets/lists/base/mi
--- a/distrib/sets/lists/base/mi	Sun Mar 25 12:06:46 2018
+++ b/distrib/sets/lists/base/mi	Sun Mar 25 12:09:38 2018
@@ -48,6 +48,15 @@
 ./etc/amd
 ./etc/amd/master.sample
 ./etc/authpf
+./etc/autofs
+./etc/autofs/include_ldap
+./etc/autofs/include_nis
+./etc/autofs/include_nis_nullfs
+./etc/autofs/special_hosts
+./etc/autofs/special_media
+./etc/autofs/special_noauto
+./etc/autofs/special_null
+./etc/auto_master
 ./etc/daily
 ./etc/disktab
 ./etc/examples
@@ -138,6 +147,8 @@
 ./etc/rc.d
 ./etc/rc.d/amd
 ./etc/rc.d/apmd
+./etc/rc.d/automountd
+./etc/rc.d/autounmountd
 ./etc/rc.d/bgpd
 ./etc/rc.d/bootparamd
 ./etc/rc.d/cron
@@ -2354,6 +2365,9 @@
 ./usr/sbin/arp
 ./usr/sbin/authpf
 ./usr/sbin/authpf-noip
+./usr/sbin/automount
+./usr/sbin/automountd
+./usr/sbin/autounmountd
 ./usr/sbin/bgpctl
 ./usr/sbin/bgpd
 ./usr/sbin/chat
@@ -2375,6 +2389,7 @@
 ./usr/sbin/edquota
 ./usr/sbin/eigrpctl
 ./usr/sbin/eigrpd
+./usr/sbin/fstyp
 ./usr/sbin/ftp-proxy
 ./usr/sbin/fw_update
 ./usr/sbin/group
diff -aNur a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
--- a/distrib/sets/lists/comp/mi	Sun Mar 25 12:06:46 2018
+++ b/distrib/sets/lists/comp/mi	Sun Mar 25 12:09:38 2018
@@ -898,6 +898,8 @@
 ./usr/include/miscfs/fuse
 ./usr/include/miscfs/fuse/fusefs.h
 ./usr/include/miscfs/fuse/fusefs_node.h
+./usr/include/miscfs/autofs/autofs.h
+./usr/include/miscfs/autofs/autofs_ioctl.h
 ./usr/include/msdosfs
 ./usr/include/msdosfs/bootsect.h
 ./usr/include/msdosfs/bpb.h
diff -aNur a/distrib/sets/lists/etc/mi b/distrib/sets/lists/etc/mi
--- a/distrib/sets/lists/etc/mi	Sun Mar 25 12:06:46 2018
+++ b/distrib/sets/lists/etc/mi	Sun Mar 25 12:09:38 2018
@@ -1,6 +1,14 @@
 ./.cshrc
 ./.profile
 ./etc/acme-client.conf
+./etc/auto_master
+./etc/autofs/include_ldap
+./etc/autofs/include_nis
+./etc/autofs/include_nis_nullfs
+./etc/autofs/special_hosts
+./etc/autofs/special_media
+./etc/autofs/special_noauto
+./etc/autofs/special_null
 ./etc/changelist
 ./etc/dumpdates
 ./etc/fbtab
diff -aNur a/distrib/sets/lists/man/mi b/distrib/sets/lists/man/mi
--- a/distrib/sets/lists/man/mi	Sun Mar 25 12:06:46 2018
+++ b/distrib/sets/lists/man/mi	Sun Mar 25 12:09:38 2018
@@ -1916,6 +1916,8 @@
 ./usr/share/man/man5/acme-client.conf.5
 ./usr/share/man/man5/aliases.5
 ./usr/share/man/man5/ar.5
+./usr/share/man/man5/autofs.5
+./usr/share/man/man5/auto_master.5
 ./usr/share/man/man5/bgpd.conf.5
 ./usr/share/man/man5/bootparams.5
 ./usr/share/man/man5/bsd.port.arch.mk.5
@@ -2094,6 +2096,9 @@
 ./usr/share/man/man8/atactl.8
 ./usr/share/man/man8/authpf.8
 ./usr/share/man/man8/autoinstall.8
+./usr/share/man/man8/automount.8
+./usr/share/man/man8/automountd.8
+./usr/share/man/man8/autounmountd.8
 ./usr/share/man/man8/badsect.8
 ./usr/share/man/man8/bgpctl.8
 ./usr/share/man/man8/bgpd.8
@@ -2135,6 +2140,7 @@
 ./usr/share/man/man8/fsck_msdos.8
 ./usr/share/man/man8/fsdb.8
 ./usr/share/man/man8/fsirand.8
+./usr/share/man/man8/fstyp.8
 ./usr/share/man/man8/ftp-proxy.8
 ./usr/share/man/man8/ftpd.8
 ./usr/share/man/man8/genassym.sh.8
diff -aNur a/etc/MAKEDEV.common b/etc/MAKEDEV.common
--- a/etc/MAKEDEV.common	Sun Mar 25 12:06:46 2018
+++ b/etc/MAKEDEV.common	Sun Mar 25 12:09:38 2018
@@ -164,6 +164,7 @@
 target(all, hotplug)dnl
 target(all, pppx)dnl
 target(all, fuse)dnl
+target(all, autofs)dnl
 target(all, vmm)dnl
 target(all, pvbus, 0, 1)dnl
 target(all, bpf)dnl
@@ -199,6 +200,8 @@
 _mkdev(cd, cd*, {-dodisk2 cd $U major_cd_b major_cd_c $U 0{--}ifstep(cd)-})dnl
 __devitem(fuse, fuse, Userland Filesystem, fuse 4)dnl
 _mcdev(fuse, fuse, fuse, {-major_fuse_c-}, 600)dnl
+__devitem(autofs, autofs, Automounter Filesystem, autofs 5)dnl
+_mkdev(autofs, autofs, {-M autofs c major_autofs_c $U 600-})dnl
 __devitem(ch, {-ch*-}, SCSI media changers)dnl
 _mcdev(ch, ch*, ch, {-major_ch_c-}, 660, operator)dnl
 __devitem(uk, uk*, Unknown SCSI devices)dnl
diff -aNur a/etc/Makefile b/etc/Makefile
--- a/etc/Makefile	Sun Mar 25 12:06:46 2018
+++ b/etc/Makefile	Sun Mar 25 12:09:38 2018
@@ -57,7 +57,8 @@
 	ripd.conf sasyncd.conf snmpd.conf vm.conf ypldap.conf
 
 # -r-xr-xr-x
-RCDAEMONS=amd apmd bgpd bootparamd cron dhcpd dhcrelay dhcrelay6 dvmrpd \
+RCDAEMONS=amd apmd automountd autounmountd bgpd bootparamd cron \
+	dhcpd dhcrelay dhcrelay6 dvmrpd \
 	eigrpd ftpd ftpproxy ftpproxy6 hostapd hotplugd httpd identd ifstated \
 	iked inetd isakmpd iscsid ldapd ldattach ldomd ldpd lockd \
 	lpd mopd mountd mrouted nfsd npppd nsd ntpd ospf6d ospfd \
@@ -106,6 +107,7 @@
 	${INSTALL} -c -o root -g wheel -m 644 unbound.conf ${DESTDIR}/var/unbound/etc
 	${INSTALL} -c -o ${BINOWN} -g ${BINGRP} -m 555 \
 	    etc.${MACHINE}/MAKEDEV ${DESTDIR}/dev
+	${INSTALL} -c -o root -g wheel -m 644 auto_master ${DESTDIR}/etc
 	cd root; \
 		${INSTALL} -c -o root -g wheel -m 644 dot.cshrc \
 		    ${DESTDIR}/root/.cshrc; \
@@ -142,6 +144,21 @@
 	cd amd; \
 		${INSTALL} -c -o root -g wheel -m 644 master.sample \
 		    ${DESTDIR}/etc/amd
+	cd autofs; \
+		${INSTALL} -c -o root -g wheel -m 755 include_ldap \
+		    ${DESTDIR}/etc/autofs/; \
+		${INSTALL} -c -o root -g wheel -m 755 include_nis \
+		    ${DESTDIR}/etc/autofs/; \
+		${INSTALL} -c -o root -g wheel -m 755 include_nis_nullfs \
+		    ${DESTDIR}/etc/autofs/; \
+		${INSTALL} -c -o root -g wheel -m 755 special_hosts \
+		    ${DESTDIR}/etc/autofs/; \
+		${INSTALL} -c -o root -g wheel -m 755 special_media \
+		    ${DESTDIR}/etc/autofs/; \
+		${INSTALL} -c -o root -g wheel -m 755 special_noauto \
+		    ${DESTDIR}/etc/autofs/; \
+		${INSTALL} -c -o root -g wheel -m 755 special_null \
+		    ${DESTDIR}/etc/autofs/
 	cd ppp; \
 		${INSTALL} -c -o root -g wheel -m 600 chap-secrets \
 		    ${DESTDIR}/etc/ppp; \
diff -aNur a/etc/auto_master b/etc/auto_master
--- a/etc/auto_master	Thu Jan  1 09:00:00 1970
+++ b/etc/auto_master	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,9 @@
+# $NetBSD: auto_master,v 1.1 2018/01/09 03:31:14 christos Exp $
+#
+# 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,noatime
+#/-		-noauto
diff -aNur a/etc/autofs/include_ldap b/etc/autofs/include_ldap
--- a/etc/autofs/include_ldap	Thu Jan  1 09:00:00 1970
+++ b/etc/autofs/include_ldap	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# $NetBSD: include_ldap,v 1.1 2018/01/09 03:31:14 christos Exp $
+#
+
+# 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"
+
+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 -aNur a/etc/autofs/include_nis b/etc/autofs/include_nis
--- a/etc/autofs/include_nis	Thu Jan  1 09:00:00 1970
+++ b/etc/autofs/include_nis	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# $NetBSD: include_nis,v 1.1 2018/01/09 03:31:14 christos Exp $
+#
+
+if [ -n "$2" ]; then
+	ypmatch $2 $1
+	exit 0
+fi
+
+if [ -n "$1" ]; then
+	ypcat -k $1
+	exit 0
+fi
diff -aNur a/etc/autofs/include_nis_nullfs b/etc/autofs/include_nis_nullfs
--- a/etc/autofs/include_nis_nullfs	Thu Jan  1 09:00:00 1970
+++ b/etc/autofs/include_nis_nullfs	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,180 @@
+#!/usr/bin/awk -f
+#-
+# Copyright (c) 2017 G. Paul Ziemba
+# 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.
+#
+# $NetBSD: include_nis_nullfs,v 1.1 2018/01/09 03:31:14 christos Exp $
+#
+
+#
+# /etc/autofs/include_nis_nullfs
+#
+# automountd Directory Services script for NIS
+#
+# SYNOPSIS
+#       include_nis_nullfs <mapname>
+#
+#       include_nis_nullfs <mapname> <key>
+#
+# DESCRIPTION
+#
+#       This script provides a Directory Services map for automountd
+#       based on NIS. Please see auto_master(5) for general information.
+#
+#       The first form, with one argument, emits the entire named NIS map.
+#       The second form, with two arguments, emits the map entry for the
+#       key given in the second argument.
+#
+#       This script attempts to determine the names and IP addresses
+#       of the local host. Map entries matching the local host are
+#       rewritten to specify nullfs mounts (instead of the default
+#       NFS) to reduce access overhead in the kernel.
+#
+#	If a map entry contains multiple location fields, it is not changed.
+#
+
+
+# Populate list of names and IP addrs thet mean "this host"
+# into myhostnames array
+BEGIN {
+    #
+    # Set self hostnames
+    #
+
+    "hostname -s" | getline;
+    myhostnames[$0] = 1;
+
+    "hostname -f" | getline;
+    myhostnames[$0] = 1;
+
+    myhostnames["localhost"] = 1
+
+    "hostname -f" | getline;
+    localdomain=$0
+    myhostnames["localhost."localdomain] = 1
+
+    while ("ifconfig" | getline) {
+	if ($1 == "inet") {
+	    myhostnames[$2] = 1;
+	}
+    }
+
+    # debug
+#            print "--- hostname list start ----"
+#            for (i in myhostnames) {
+#                print i
+#            }
+#            print "--- hostname list end ----"
+
+    if (ARGC == 2) {
+	# mapname only
+	while ("ypcat -k " ARGV[1] | getline) {
+	    proc_mapline(1)
+	}
+    }
+    if (ARGC == 3) {
+	# mapname and keyname
+	while ("ypmatch " ARGV[2] " " ARGV[1] | getline) {
+	    proc_mapline(0)
+	}
+    }
+    exit 0
+}
+
+function is_self(hostname)
+{
+    if (myhostnames[hostname]) {
+	return 1
+    }
+    return 0
+}
+
+#
+# Lines are of the form [key] [-opts] location1 [... locationN]
+#
+# indicate index of key field with first positional parameter
+# 1 means keyfield is the first field
+# 0 means keyfield is not present
+#
+function proc_mapline(keyfield)
+{
+    optionsfield = 0
+    locationfield = 0
+    locationcount = 0
+
+    for (i=keyfield+1; i <= NF; ++i) {
+	if (!optionsfield) {
+	    if ($i ~ /^-/) {
+		# the first options field found on the line
+		optionsfield = i;
+		continue
+	    }
+	}
+	# Assumption: location contains colon (":")
+	if (optionsfield && ($i ~ /:/) && ($i !~ /^-/)) {
+	    ++locationcount
+	    if (!locationfield) {
+		# the first location field found on the line
+		locationfield = i
+	    }
+	}
+    }
+
+    #
+    # If location not found, do not modify.
+    #
+    # If there is more than one location, do not modify. Rationale:
+    # Options are applied to all locations. We ca not have "nullfs"
+    # for only some locations and "nfs" for others for a given
+    # map key (i.e., a line). The usual reason for multiple
+    # locations is for redundancy using replicated volumes on
+    # multiple hosts, so multiple hosts imply fstype=nfs (the
+    # FreeBSD default for automounter maps).
+    #
+    # Hypothetically there could be a map entry with multiple
+    # locations all with host parts matching "me". In that case,
+    # it would be safe to rewrite the locations and specify
+    # nullfs, but the code does not handle this case.
+    #
+    if (locationcount == 1) {
+	#
+	# We have a line with exactly one location field
+	#
+	# Assumption: location has no more than one colon (":")
+	#
+	n=split($locationfield,location,":")
+	if (is_self(location[1])) {
+	    $locationfield = ":" location[2]
+	    if (optionsfield) {
+		# append to existing options
+		$optionsfield = $optionsfield ",fstype=nullfs"
+	    } else {
+		# sneak in ahead of location
+		$locationfield = "-fstype=nullfs " $locationfield
+	    }
+	}
+    }
+
+    print
+}
diff -aNur a/etc/autofs/special_hosts b/etc/autofs/special_hosts
--- a/etc/autofs/special_hosts	Thu Jan  1 09:00:00 1970
+++ b/etc/autofs/special_hosts	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# $NetBSD: special_hosts,v 1.1 2018/01/09 03:31:14 christos Exp $
+#
+
+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 -aNur a/etc/autofs/special_media b/etc/autofs/special_media
--- a/etc/autofs/special_media	Thu Jan  1 09:00:00 1970
+++ b/etc/autofs/special_media	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,130 @@
+#!/bin/sh
+#
+# $NetBSD: special_media,v 1.1 2018/01/09 03:31:14 christos Exp $
+#
+
+# 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
+		_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"
+
+	case "${_fstype}" in
+	"exfat")
+		if [ -f "/usr/local/sbin/mount.exfat" ]; then
+			echo "-mountprog=/usr/local/sbin/mount.exfat,fstype=${_fstype},nosuid	:/dev/${_p}"
+		else
+			/usr/bin/logger -p info -t "special_media[$$]" \
+			    "Cannot mount ${_fstype} formatted device /dev/${_p}: Install sysutils/fusefs-exfat first"
+			exit 1
+		fi
+		;;
+	"ntfs")
+		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
+		;;
+	"ext2fs" | "msdosfs")
+		echo "-fstype=${_fstype},nosuid,async	:/dev/${_p}"
+		;;
+	*)
+		echo "-fstype=${_fstype},nosuid	:/dev/${_p}"
+		;;
+	esac
+}
+
+# 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
+		_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: -media map is unusable at the moment.
+# This script needs to be able to get the list of devices/partitions. FreeBSD can do this via GEOM sysctl.
+#HW_DISKNAMES=$(sysctl hw.disknames)
+#if [ $? -ne 0 ]; then
+#	exit 1
+#fi
+# Cut "hw.disknames =" and ignore the next " " before names start.
+#DEVICES=$(echo ${HW_DISKNAMES} | awk '{$1=$2=""; print substr($0,3)}' | awk '{gsub(" ", "\n"); print}' | sort)
+
+# ${DEVICES} should contain one device/partition for each line.
+DEVICES=""
+
+if [ $# -eq 0 ]; then
+	print_available
+	exit 0
+fi
+
+print_one "$1"
+exit 0
+
diff -aNur a/etc/autofs/special_noauto b/etc/autofs/special_noauto
--- a/etc/autofs/special_noauto	Thu Jan  1 09:00:00 1970
+++ b/etc/autofs/special_noauto	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# $NetBSD: special_noauto,v 1.1 2018/01/09 03:31:14 christos Exp $
+#
+
+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 -aNur a/etc/autofs/special_null b/etc/autofs/special_null
--- a/etc/autofs/special_null	Thu Jan  1 09:00:00 1970
+++ b/etc/autofs/special_null	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,4 @@
+#!/usr/bin/true
+#
+# $NetBSD: special_null,v 1.1 2018/01/09 03:31:14 christos Exp $
+#
diff -aNur a/etc/etc.amd64/MAKEDEV b/etc/etc.amd64/MAKEDEV
--- a/etc/etc.amd64/MAKEDEV	Sun Mar 25 12:06:46 2018
+++ b/etc/etc.amd64/MAKEDEV	Sun Mar 25 12:09:38 2018
@@ -74,6 +74,7 @@
 #	drm*	Direct Rendering Manager
 #	fd	fd/* nodes
 #	fuse	Userland Filesystem
+#	autofs	Automounter Filesystem
 #	gpio*	General Purpose Input/Output
 #	gpr*	GPR400 smartcard reader
 #	hotplug	devices hot plugging
@@ -340,6 +341,10 @@
 	M gpio$U c 88 $U 600
 	;;
 
+autofs)
+	M autofs c 98 $U 600
+	;;
+
 fuse)
 	M fuse$U c 92 $U 600
 	;;
@@ -568,8 +573,8 @@
 	R sd5 sd6 sd7 sd8 sd9 cd0 cd1 rd0 tap0 tap1 tap2 tap3 tun0
 	R tun1 tun2 tun3 bio pty0 fd1 fd1B fd1C fd1D fd1E fd1F fd1G
 	R fd1H fd0 fd0B fd0C fd0D fd0E fd0F fd0G fd0H diskmap vscsi0
-	R ch0 audio0 audio1 audio2 bpf pvbus0 pvbus1 vmm fuse pppx
-	R hotplug ptm gpr0 local wscons pci0 pci1 pci2 pci3 uall
+	R ch0 audio0 audio1 audio2 bpf pvbus0 pvbus1 vmm autofs fuse
+	R pppx hotplug ptm gpr0 local wscons pci0 pci1 pci2 pci3 uall
 	R rmidi0 rmidi1 rmidi2 rmidi3 rmidi4 rmidi5 rmidi6 rmidi7
 	R tuner0 radio0 speaker video0 video1 uk0 random lpa0 lpa1
 	R lpa2 lpt0 lpt1 lpt2 tty00 tty01 tty02 tty03 tty04 tty05
diff -aNur a/etc/etc.amd64/MAKEDEV.md b/etc/etc.amd64/MAKEDEV.md
--- a/etc/etc.amd64/MAKEDEV.md	Sun Mar 25 12:06:46 2018
+++ b/etc/etc.amd64/MAKEDEV.md	Sun Mar 25 12:09:38 2018
@@ -71,6 +71,7 @@
 _DEV(drm, 87)
 _DEV(fdesc, 22)
 _DEV(fuse, 92)
+_DEV(autofs, 98)
 _DEV(gpio, 88)
 _DEV(gpr, 80)
 _DEV(hotplug, 82)
diff -aNur a/etc/mtree/4.4BSD.dist b/etc/mtree/4.4BSD.dist
--- a/etc/mtree/4.4BSD.dist	Sun Mar 25 12:06:46 2018
+++ b/etc/mtree/4.4BSD.dist	Sun Mar 25 12:09:38 2018
@@ -16,6 +16,8 @@
     ..
     authpf			uname=root mode=0755
     ..
+    autofs
+    ..
     examples
     ..
     firmware
diff -aNur a/etc/rc b/etc/rc
--- a/etc/rc	Sun Mar 25 12:06:46 2018
+++ b/etc/rc	Sun Mar 25 12:09:38 2018
@@ -583,7 +583,7 @@
 	fi
 fi
 
-start_daemon hostapd lpd smtpd slowcgi httpd ftpd
+start_daemon automountd autounmountd hostapd lpd smtpd slowcgi httpd ftpd
 start_daemon ftpproxy ftpproxy6 tftpd tftpproxy identd inetd rarpd bootparamd
 start_daemon rbootd mopd vmd spamd spamlogd sndiod
 echo '.'
diff -aNur a/etc/rc.conf b/etc/rc.conf
--- a/etc/rc.conf	Sun Mar 25 12:06:46 2018
+++ b/etc/rc.conf	Sun Mar 25 12:09:38 2018
@@ -76,6 +76,8 @@
 			# for ps/2 or usb mice: "", serial: "-p /dev/cua00"
 xenodm_flags=NO		# on some architectures, you must also
 			# disable console getty in /etc/ttys
+automountd_flags=NO
+autounmountd_flags=NO
 
 # services related to RPC, NFS, and YP
 amd_flags=NO		# also see amd_master below
diff -aNur a/etc/rc.d/automountd b/etc/rc.d/automountd
--- a/etc/rc.d/automountd	Thu Jan  1 09:00:00 1970
+++ b/etc/rc.d/automountd	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,11 @@
+#!/bin/ksh
+
+daemon="/usr/sbin/automountd"
+
+. /etc/rc.d/rc.subr
+
+rc_post() {
+	rm -f /var/run/automountd.pid
+}
+
+rc_cmd $1
diff -aNur a/etc/rc.d/autounmountd b/etc/rc.d/autounmountd
--- a/etc/rc.d/autounmountd	Thu Jan  1 09:00:00 1970
+++ b/etc/rc.d/autounmountd	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,11 @@
+#!/bin/ksh
+
+daemon="/usr/sbin/autounmountd"
+
+. /etc/rc.d/rc.subr
+
+rc_post() {
+	rm -f /var/run/autounmountd.pid
+}
+
+rc_cmd $1
diff -aNur a/lib/libc/sys/kqueue.2 b/lib/libc/sys/kqueue.2
--- a/lib/libc/sys/kqueue.2	Sun Mar 25 12:07:49 2018
+++ b/lib/libc/sys/kqueue.2	Sun Mar 25 12:09:38 2018
@@ -465,6 +465,9 @@
 On return,
 .Va fflags
 contains the events which triggered the filter.
+.It Dv EVFILT_FS
+Establishes a file system monitor.
+Currently it only monitors file system mount and unmount actions.
 .El
 .Sh RETURN VALUES
 .Fn kqueue
diff -aNur a/lib/libc/sys/sysctl.2 b/lib/libc/sys/sysctl.2
--- a/lib/libc/sys/sysctl.2	Sun Mar 25 12:07:49 2018
+++ b/lib/libc/sys/sysctl.2	Sun Mar 25 12:09:38 2018
@@ -27,7 +27,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd $Mdocdate: March 5 2018 $
+.Dd $Mdocdate: March 24 2018 $
 .Dt SYSCTL 2
 .Os
 .Sh NAME
@@ -2066,6 +2066,30 @@
 The number of pages used for fusebuf memory.
 .It Dv FUSEFS_WAITFBUFS Pq Va vfs.fuse.fusefs_fbufs_wait
 The number of fusebufs waiting for a response.
+.El
+.It AUTOFS
+.Bl -column "AUTOFS_RETRY_ATTEMPTS" "Type" "Changeable" -offset indent
+.It Sy "Third level name" Ta Sy "Type" Ta Sy "Changeable"
+.It Dv AUTOFS_DEBUG_TUNABLE Ta "int" Ta "yes"
+.It Dv AUTOFS_MOUNT_ON_STAT Ta "int" Ta "yes"
+.It Dv AUTOFS_TIMEOUT Ta "int" Ta "yes"
+.It Dv AUTOFS_CACHE Ta "int" Ta "yes"
+.It Dv AUTOFS_RETRY_ATTEMPTS Ta "int" Ta "yes"
+.It Dv AUTOFS_RETRY_DELAY Ta "int" Ta "yes"
+.El
+.Bl -tag -width Ds
+.It Dv AUTOFS_DEBUG_TUNABLE Pq Va vfs.autofs.autofs_debug_tunable
+Enable debug messages.
+.It Dv AUTOFS_MOUNT_ON_STAT Pq Va vfs.autofs.autofs_mount_on_stat
+Trigger mount on stat(2) on mountpoint.
+.It Dv AUTOFS_TIMEOUT Pq Va vfs.autofs.autofs_timeout
+The number of seconds to wait for automountd(8).
+.It Dv AUTOFS_CACHE Pq Va vfs.autofs.autofs_cache
+The number of seconds to wait before reinvoking.
+.It Dv AUTOFS_RETRY_ATTEMPTS Pq Va vfs.autofs.autofs_retry_attempts
+The number of attempts before failing mount.
+.It Dv AUTOFS_RETRY_DELAY Pq Va vfs.autofs.autofs_retry_delay
+The number of seconds before retrying.
 .El
 .El
 .El
diff -aNur a/sbin/mount/mntopts.h b/sbin/mount/mntopts.h
--- a/sbin/mount/mntopts.h	Sun Mar 25 12:07:58 2018
+++ b/sbin/mount/mntopts.h	Sun Mar 25 12:09:38 2018
@@ -67,6 +67,7 @@
 #define MOPT_GROUPQUOTA	{ "groupquota",	0, MFLAG_SET | MFLAG_STRVAL \
 					    | MFLAG_OPT }
 #define MOPT_SOFTDEP	{ "softdep",	MNT_SOFTDEP, MFLAG_SET }
+#define MOPT_AUTOMOUNTED	{ "automounted", MNT_AUTOMOUNTED, MFLAG_SET }
 
 /* Control flags. */
 #define MOPT_FORCE	{ "force",	MNT_FORCE, MFLAG_SET }
@@ -97,7 +98,8 @@
 	MOPT_NODEV,							\
 	MOPT_NOEXEC,							\
 	MOPT_NOSUID,							\
-	MOPT_RDONLY
+	MOPT_RDONLY,							\
+	MOPT_AUTOMOUNTED
 
 int getmntopts(const char *, const struct mntopt *, int *);
 int getmntopt(char **, union mntval *, const struct mntopt *, int *);
diff -aNur a/sbin/mount/mount.c b/sbin/mount/mount.c
--- a/sbin/mount/mount.c	Sun Mar 25 12:07:58 2018
+++ b/sbin/mount/mount.c	Sun Mar 25 12:09:38 2018
@@ -95,6 +95,7 @@
 	{ MNT_ROOTFS,		1,	"root file system",	"" },
 	{ MNT_SYNCHRONOUS,	0,	"synchronous",		"sync" },
 	{ MNT_SOFTDEP,		0,	"softdep",		"softdep" },
+	{ MNT_AUTOMOUNTED,	0,	"automounted",		"automounted" },
 	{ 0,			0,	"",			"" }
 };
 
diff -aNur a/sbin/sysctl/sysctl.c b/sbin/sysctl/sysctl.c
--- a/sbin/sysctl/sysctl.c	Sun Mar 25 12:07:58 2018
+++ b/sbin/sysctl/sysctl.c	Sun Mar 25 12:09:38 2018
@@ -87,6 +87,7 @@
 #include <ufs/ffs/ffs_extern.h>
 
 #include <miscfs/fuse/fusefs.h>
+#include <miscfs/autofs/autofs.h>
 
 #include <nfs/nfsproto.h>
 #include <nfs/nfs.h>
@@ -1174,6 +1175,7 @@
 struct ctlname ffsname[] = FFS_NAMES;
 struct ctlname nfsname[] = FS_NFS_NAMES;
 struct ctlname fusefsname[] = FUSEFS_NAMES;
+struct ctlname autofsname[] = AUTOFS_NAMES;
 struct list *vfsvars;
 int *vfs_typenums;
 
@@ -1235,6 +1237,10 @@
 		if (!strcmp(vfc.vfc_name, MOUNT_FUSEFS)) {
 			vfsvars[cnt].list = fusefsname;
 			vfsvars[cnt].size = FUSEFS_MAXID;
+		}
+		if (!strcmp(vfc.vfc_name, MOUNT_AUTOFS)) {
+			vfsvars[cnt].list = autofsname;
+			vfsvars[cnt].size = AUTOFS_MAXID;
 		}
 		vfs_typenums[cnt] = vfc.vfc_typenum;
 		strlcat(&names[loc], vfc.vfc_name, sizeof names - loc);
diff -aNur a/share/man/man5/Makefile b/share/man/man5/Makefile
--- a/share/man/man5/Makefile	Sun Mar 25 12:07:58 2018
+++ b/share/man/man5/Makefile	Sun Mar 25 12:09:38 2018
@@ -1,7 +1,7 @@
 #	$OpenBSD: Makefile,v 1.55 2017/02/07 11:37:23 tb Exp $
 #	$NetBSD: Makefile,v 1.14 1995/05/11 23:13:15 cgd Exp $
 
-MAN=	acct.5 ar.5 bsd.port.mk.5 bsd.port.arch.mk.5 bsd.regress.mk.5 \
+MAN=	acct.5 ar.5 autofs.5 bsd.port.mk.5 bsd.port.arch.mk.5 bsd.regress.mk.5 \
 	changelist.5 core.5 \
 	defaultdomain.5 dir.5 disktab.5 elf.5 ethers.5 fbtab.5 files.conf.5 \
 	fs.5 fstab.5 genassym.cf.5 group.5 hostname.if.5 hosts.5 installurl.5 \
diff -aNur a/share/man/man5/autofs.5 b/share/man/man5/autofs.5
--- a/share/man/man5/autofs.5	Thu Jan  1 09:00:00 1970
+++ b/share/man/man5/autofs.5	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,128 @@
+.\" Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+.\" Copyright (c) 2017 The NetBSD Foundation, Inc.
+.\" Copyright (c) 2016 The DragonFly Project
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Tomohiro Kusumi.
+.\"
+.\" This software was developed by Edward Tomasz Napierala under sponsorship
+.\" from the FreeBSD Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 16, 2018
+.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 .
+The
+.Nm
+driver first appeared in
+.Nx 9.0 .
+The
+.Nm
+driver first appeared in
+.Ox 6.x .
+.Sh AUTHORS
+.An -nosplit
+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 ,
+.Nx
+and
+.Ox
+by
+.An Tomohiro Kusumi Aq Mt tkusumi@netbsd.org .
diff -aNur a/share/man/man8/man8.amd64/MAKEDEV.8 b/share/man/man8/man8.amd64/MAKEDEV.8
--- a/share/man/man8/man8.amd64/MAKEDEV.8	Sun Mar 25 12:07:58 2018
+++ b/share/man/man8/man8.amd64/MAKEDEV.8	Sun Mar 25 12:09:38 2018
@@ -1,11 +1,11 @@
-.\" $OpenBSD: MAKEDEV.8,v 1.82 2017/06/06 08:12:01 tb Exp $
+.\" $OpenBSD$
 .\"
 .\" THIS FILE AUTOMATICALLY GENERATED.  DO NOT EDIT.
 .\" generated from:
 .\"
 .\"	OpenBSD: etc.amd64/MAKEDEV.md,v 1.69 2016/09/11 19:59:53 deraadt Exp 
-.\"	OpenBSD: MAKEDEV.common,v 1.94 2016/09/11 19:59:51 deraadt Exp 
-.\"	OpenBSD: MAKEDEV.man,v 1.8 2016/03/12 17:59:27 espie Exp 
+.\"	OpenBSD: MAKEDEV.common,v 1.98 2017/11/14 20:21:29 naddy Exp 
+.\"	OpenBSD: MAKEDEV.man,v 1.9 2017/06/06 08:11:23 tb Exp 
 .\"	OpenBSD: MAKEDEV.mansub,v 1.2 2004/02/20 19:13:01 miod Exp 
 .\"
 .\" Copyright (c) 2004, Miodrag Vallat
@@ -218,6 +218,9 @@
 .It Ar fuse
 Userland Filesystem, see
 .Xr fuse 4 .
+.It Ar autofs
+Automounter Filesystem, see
+.Xr autofs 5 .
 .It Ar gpio*
 General Purpose Input/Output, see
 .Xr gpio 4 .
diff -aNur a/sys/arch/amd64/amd64/conf.c b/sys/arch/amd64/amd64/conf.c
--- a/sys/arch/amd64/amd64/conf.c	Sun Mar 25 12:07:59 2018
+++ b/sys/arch/amd64/amd64/conf.c	Sun Mar 25 12:09:38 2018
@@ -184,6 +184,7 @@
 #include "pvbus.h"
 #include "ipmi.h"
 #include "switch.h"
+#include "autofs.h"
 
 struct cdevsw	cdevsw[] =
 {
@@ -297,6 +298,7 @@
 	cdev_pvbus_init(NPVBUS,pvbus),	/* 95: pvbus(4) control interface */
 	cdev_ipmi_init(NIPMI,ipmi),	/* 96: ipmi */
 	cdev_switch_init(NSWITCH,switch), /* 97: switch(4) control interface */
+	cdev_autofs_init(NAUTOFS,autofs), /* 98: autofs */
 };
 int	nchrdev = nitems(cdevsw);
 
diff -aNur a/sys/conf/GENERIC b/sys/conf/GENERIC
--- a/sys/conf/GENERIC	Sun Mar 25 12:08:00 2018
+++ b/sys/conf/GENERIC	Sun Mar 25 12:09:38 2018
@@ -43,6 +43,7 @@
 option		FIFO		# FIFOs; RECOMMENDED
 #option		TMPFS		# efficient memory file system
 option		FUSE		# FUSE
+option		AUTOFS		# Automounter Filesystem
 
 option		SOCKET_SPLICE	# Socket Splicing for TCP and UDP
 option		TCP_ECN		# Explicit Congestion Notification for TCP
@@ -109,5 +110,6 @@
 pseudo-device	bio	1	# ioctl multiplexing device
 
 pseudo-device	fuse		# fuse device
+pseudo-device	autofs		# autofs device
 
 option		BOOT_CONFIG	# add support for boot -c
diff -aNur a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files	Sun Mar 25 12:08:00 2018
+++ b/sys/conf/files	Sun Mar 25 12:09:38 2018
@@ -571,6 +571,11 @@
 file	miscfs/fuse/fuse_vnops.c	fuse
 file	miscfs/fuse/fusebuf.c		fuse
 
+pseudo-device autofs
+file	miscfs/autofs/autofs.c		autofs	needs-flag
+file	miscfs/autofs/autofs_vfsops.c	autofs
+file	miscfs/autofs/autofs_vnops.c	autofs
+
 pseudo-device pf: ifnet
 file	net/pf.c			pf	needs-flag
 file	net/pf_norm.c			pf
diff -aNur a/sys/kern/Makefile b/sys/kern/Makefile
--- a/sys/kern/Makefile	Sun Mar 25 12:08:03 2018
+++ b/sys/kern/Makefile	Sun Mar 25 12:09:38 2018
@@ -36,7 +36,7 @@
 	kern \
 	lib/libkern lib/libkern/arch/${MACHINE} \
 	lib/libkern/arch/${MACHINE_ARCH} \
-	miscfs miscfs/deadfs miscfs/fifofs \
+	miscfs miscfs/deadfs miscfs/fifofs miscfs/autofs \
 	msdosfs \
 	net netinet netmpls \
 	nfs \
diff -aNur a/sys/kern/kern_event.c b/sys/kern/kern_event.c
--- a/sys/kern/kern_event.c	Sun Mar 25 12:08:03 2018
+++ b/sys/kern/kern_event.c	Sun Mar 25 12:09:38 2018
@@ -98,6 +98,9 @@
 void	filt_timerdetach(struct knote *kn);
 int	filt_timer(struct knote *kn, long hint);
 void	filt_seltruedetach(struct knote *kn);
+int	filt_fsattach(struct knote *kn);
+void	filt_fsdetach(struct knote *kn);
+int	filt_fs(struct knote *kn, long hint);
 
 struct filterops kqread_filtops =
 	{ 1, NULL, filt_kqdetach, filt_kqueue };
@@ -107,6 +110,8 @@
 	{ 1, filt_fileattach, NULL, NULL };
 struct filterops timer_filtops =
         { 0, filt_timerattach, filt_timerdetach, filt_timer };
+struct filterops fs_filtops =
+	{ 0, filt_fsattach, filt_fsdetach, filt_fs, };
 
 struct	pool knote_pool;
 struct	pool kqueue_pool;
@@ -138,6 +143,7 @@
 	&sig_filtops,			/* EVFILT_SIGNAL */
 	&timer_filtops,			/* EVFILT_TIMER */
 	&file_filtops,			/* EVFILT_DEVICE */
+	&fs_filtops,			/* EVFILT_FS */
 };
 
 void KQREF(struct kqueue *);
@@ -430,6 +436,37 @@
 
 	/* Nothing more to do */
 	return (0);
+}
+
+/*
+ * Filter event method for EVFILT_FS.
+ */
+struct klist fs_klist = SLIST_HEAD_INITIALIZER(&fs_klist);
+
+int
+filt_fsattach(struct knote *kn)
+{
+	kn->kn_flags |= EV_CLEAR;
+	SLIST_INSERT_HEAD(&fs_klist, kn, kn_selnext);
+
+	return 0;
+}
+
+void
+filt_fsdetach(struct knote *kn)
+{
+	SLIST_REMOVE(&fs_klist, kn, knote, kn_selnext);
+}
+
+int
+filt_fs(struct knote *kn, long hint)
+{
+	int rv;
+
+	kn->kn_fflags |= hint;
+	rv = (kn->kn_fflags != 0);
+
+	return rv;
 }
 
 int
diff -aNur a/sys/kern/vfs_init.c b/sys/kern/vfs_init.c
--- a/sys/kern/vfs_init.c	Sun Mar 25 12:08:03 2018
+++ b/sys/kern/vfs_init.c	Sun Mar 25 12:09:38 2018
@@ -89,6 +89,10 @@
 extern  const struct vfsops tmpfs_vfsops;
 #endif
 
+#ifdef AUTOFS
+extern  const struct vfsops autofs_vfsops;
+#endif
+
 /* Set up the filesystem operations for vnodes. */
 static struct vfsconf vfsconflist[] = {
 #ifdef FFS
@@ -139,6 +143,11 @@
 #ifdef TMPFS
 	{ &tmpfs_vfsops, MOUNT_TMPFS, 19, 0, MNT_LOCAL, NULL,
 	    sizeof(struct tmpfs_args) },
+#endif
+
+#ifdef AUTOFS
+	{ &autofs_vfsops, MOUNT_AUTOFS, 20, 0, MNT_LOCAL, NULL,
+	    sizeof(struct autofs_args) },
 #endif
 };
 
diff -aNur a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c
--- a/sys/kern/vfs_syscalls.c	Sun Mar 25 12:08:03 2018
+++ b/sys/kern/vfs_syscalls.c	Sun Mar 25 12:09:38 2018
@@ -252,10 +252,10 @@
 		mp->mnt_flag |= MNT_WANTRDWR;
 	mp->mnt_flag &=~ (MNT_NOSUID | MNT_NOEXEC | MNT_WXALLOWED | MNT_NODEV |
 	    MNT_SYNCHRONOUS | MNT_ASYNC | MNT_SOFTDEP | MNT_NOATIME |
-	    MNT_NOPERM | MNT_FORCE);
+	    MNT_NOPERM | MNT_FORCE | MNT_AUTOMOUNTED);
 	mp->mnt_flag |= flags & (MNT_NOSUID | MNT_NOEXEC | MNT_WXALLOWED |
 	    MNT_NODEV | MNT_SYNCHRONOUS | MNT_ASYNC | MNT_SOFTDEP |
-	    MNT_NOATIME | MNT_NOPERM | MNT_FORCE);
+	    MNT_NOATIME | MNT_NOPERM | MNT_FORCE | MNT_AUTOMOUNTED);
 	/*
 	 * Mount the filesystem.
 	 */
@@ -311,6 +311,8 @@
 		vfs_unbusy(vp->v_mount);
 		vput(vp);
 	}
+	if (!error)
+		KNOTE(&fs_klist, VQ_MOUNT);
 fail:
 	if (args)
 		free(args, M_TEMP, vfsp->vfc_datasize);
@@ -469,6 +471,8 @@
 		if (error)
 			goto err;
 	}
+	if (!error)
+		KNOTE(&fs_klist, VQ_UNMOUNT);
 	return (0);
 
 err:
diff -aNur a/sys/miscfs/autofs/autofs.c b/sys/miscfs/autofs/autofs.c
--- a/sys/miscfs/autofs/autofs.c	Thu Jan  1 09:00:00 1970
+++ b/sys/miscfs/autofs/autofs.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,550 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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.
+ * 3. 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 "autofs.h"
+
+#include <sys/atomic.h>
+#include <sys/queue.h>
+#include <sys/signalvar.h>
+
+struct pool	autofs_request_pool;
+struct pool	autofs_node_pool;
+struct autofs_softc	*autofs_softc = NULL;
+struct taskq	*autofs_tmo_tq = NULL;
+
+int	autofs_debug = 1;
+int	autofs_mount_on_stat = 0;
+int	autofs_timeout = 30;
+int	autofs_cache = 600;
+int	autofs_retry_attempts = 3;
+int	autofs_retry_delay = 1;
+int	autofs_interruptible = 1;
+
+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_entry, autofs_node_cmp);
+
+bool
+autofs_ignore_thread(void)
+{
+
+	if (autofs_softc->sc_dev_opened == false)
+		return false;
+
+	rw_enter_read(&autofs_softc->sc_lock);
+	if (autofs_softc->sc_dev_sid == curproc->p_p->ps_pgrp->pg_id) {
+		rw_exit_read(&autofs_softc->sc_lock);
+		/*
+		 * This thread is the one that got triggered by the
+		 * filesystem access, so don't let this thread trigger.
+		 */
+		return true;
+	}
+	rw_exit_read(&autofs_softc->sc_lock);
+
+	return false;
+}
+
+static char *
+autofs_path(struct autofs_node *anp)
+{
+	struct autofs_mount *amp = anp->an_mount;
+	size_t len;
+	char *path, *tmp;
+
+	path = kstrdup("");
+	for (; anp->an_parent; anp = anp->an_parent) {
+		len = strlen(anp->an_name) + strlen(path) + 2;
+		tmp = malloc(len, M_TEMP, M_WAITOK | M_ZERO);
+		snprintf(tmp, len, "%s/%s", anp->an_name, path);
+		free(path, M_TEMP, strlen(path) + 1);
+		path = tmp;
+	}
+
+	len = strlen(amp->am_on) + strlen(path) + 2;
+	tmp = malloc(len, M_TEMP, M_WAITOK | M_ZERO);
+	snprintf(tmp, len, "%s/%s", amp->am_on, path);
+	free(path, M_TEMP, strlen(path) + 1);
+	path = tmp;
+
+	return path;
+}
+
+static void
+autofs_request_timeout_task(void *context)
+{
+	struct autofs_request *ar = (void *)context;
+
+	rw_enter_write(&autofs_softc->sc_lock);
+	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;
+	wakeup(autofs_softc->ident);
+	rw_exit_write(&autofs_softc->sc_lock);
+}
+
+static void
+autofs_request_timeout(void *context)
+{
+	struct autofs_request *ar = context;
+
+	task_set(&ar->ar_tk, autofs_request_timeout_task, ar);
+	task_add(autofs_tmo_tq, &ar->ar_tk);
+}
+
+bool
+autofs_cached(struct autofs_node *anp, const char *component, int componentlen)
+{
+	struct autofs_mount *amp = anp->an_mount;
+
+	rw_assert_unlocked(&amp->am_lock);
+
+	/*
+	 * 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 && componentlen && anp->an_wildcards) {
+		int error;
+		KASSERT(amp->am_root == anp);
+		rw_enter_read(&amp->am_lock);
+		error = autofs_node_find(anp, component, componentlen, NULL);
+		rw_exit_read(&amp->am_lock);
+		if (error)
+			return false;
+	}
+
+	return anp->an_cached;
+}
+
+void
+autofs_flush(struct autofs_mount *amp)
+{
+	struct autofs_node *anp = amp->am_root;
+	struct autofs_node *child;
+
+	rw_enter_write(&amp->am_lock);
+	RB_FOREACH(child, autofs_node_tree, &anp->an_children)
+		autofs_node_uncache(child);
+	autofs_node_uncache(amp->am_root);
+	rw_exit_write(&amp->am_lock);
+
+	AUTOFS_DEBUG("%s flushed", amp->am_on);
+}
+
+/*
+ * The set/restore sigmask functions are used to (temporarily) overwrite
+ * the thread sigmask during triggering.
+ * XXX Not implemented (implemented in FreeBSD, DragonFly and NetBSD).
+ */
+static void
+autofs_set_sigmask(sigset_t *oldset)
+{
+#if 0
+	/*
+	 * List of signals that can interrupt an autofs trigger.
+	 */
+	static int autofs_sig_set[] = {
+		SIGINT,
+		SIGTERM,
+		SIGHUP,
+		SIGKILL,
+		SIGQUIT,
+	};
+#endif
+}
+
+static void
+autofs_restore_sigmask(sigset_t *set)
+{
+
+}
+
+static int
+autofs_trigger_one(struct autofs_node *anp, const char *component,
+    int componentlen)
+{
+	struct autofs_mount *amp = anp->an_mount;
+	struct autofs_request *ar;
+	char *key, *path;
+	int error = 0, request_error;
+	bool wildcards;
+
+	rw_assert_wrlock(&autofs_softc->sc_lock);
+
+	if (!anp->an_parent) {
+		key = kstrndup(component, componentlen);
+	} else {
+		struct autofs_node *firstanp;
+		for (firstanp = anp; firstanp->an_parent->an_parent;
+		    firstanp = firstanp->an_parent)
+			continue;
+		key = kstrdup(firstanp->an_name);
+	}
+
+	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));
+		KASSERT(!strcmp(ar->ar_prefix, amp->am_prefix));
+		KASSERT(!strcmp(ar->ar_options, amp->am_options));
+		break;
+	}
+
+	if (ar) {
+		atomic_add_int(&ar->ar_refcount, 1);
+	} else {
+		ar = pool_get(&autofs_request_pool, PR_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_set(&ar->ar_timeout, autofs_request_timeout, ar);
+		timeout_add_sec(&ar->ar_timeout, autofs_timeout);
+		ar->ar_refcount = 1;
+		TAILQ_INSERT_TAIL(&autofs_softc->sc_requests, ar, ar_next);
+	}
+
+	wakeup(autofs_softc->ident);
+	while (ar->ar_done == false) {
+		if (autofs_interruptible) {
+			sigset_t oldset;
+			autofs_set_sigmask(&oldset);
+			error = rwsleep(autofs_softc->ident,
+			    &autofs_softc->sc_lock, PCATCH, autofs_softc->ident,
+			    0);
+			autofs_restore_sigmask(&oldset);
+			if (error) {
+				AUTOFS_WARN("cv_wait_sig for %s failed "
+				    "with error %d", ar->ar_path, error);
+				break;
+			}
+		} else {
+			error = rwsleep(autofs_softc->ident,
+			    &autofs_softc->sc_lock, PCATCH, autofs_softc->ident,
+			    0);
+		}
+	}
+
+	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;
+
+	/*
+	 * Check if this is the last reference.
+	 */
+	if (!atomic_add_int_nv(&ar->ar_refcount, -1)) {
+		TAILQ_REMOVE(&autofs_softc->sc_requests, ar, ar_next);
+		rw_exit_write(&autofs_softc->sc_lock);
+		timeout_del(&ar->ar_timeout);
+		pool_put(&autofs_request_pool, ar);
+		rw_enter_write(&autofs_softc->sc_lock);
+	}
+
+	/*
+	 * 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 && !request_error && autofs_cache > 0) {
+		autofs_node_cache(anp);
+		anp->an_wildcards = wildcards;
+		timeout_add_sec(&anp->an_timeout, autofs_cache);
+	}
+
+	free(key, M_TEMP, strlen(key) + 1);
+	free(path, M_TEMP, strlen(path) + 1);
+
+	if (error)
+		return error;
+	return request_error;
+}
+
+int
+autofs_trigger(struct autofs_node *anp, const char *component,
+    int componentlen)
+{
+
+	for (;;) {
+		int error, dummy;
+
+		error = autofs_trigger_one(anp, component, componentlen);
+		if (!error) {
+			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);
+		rw_exit_write(&autofs_softc->sc_lock);
+		tsleep(&dummy, PCATCH, "autofs_retry", autofs_retry_delay * hz);
+		rw_enter_write(&autofs_softc->sc_lock);
+	}
+}
+
+static int
+autofs_ioctl_request(struct autofs_daemon_request *adr)
+{
+	struct autofs_request *ar;
+
+	rw_enter_write(&autofs_softc->sc_lock);
+	for (;;) {
+		int error;
+		TAILQ_FOREACH(ar, &autofs_softc->sc_requests, ar_next) {
+			if (ar->ar_done)
+				continue;
+			if (ar->ar_in_progress)
+				continue;
+			break;
+		}
+
+		if (ar)
+			break;
+
+		error = rwsleep(autofs_softc->ident,
+		    &autofs_softc->sc_lock, PCATCH, autofs_softc->ident, 0);
+		if (error) {
+			rw_exit_write(&autofs_softc->sc_lock);
+			return error;
+		}
+	}
+
+	ar->ar_in_progress = true;
+	autofs_softc->sc_dev_sid = curproc->p_p->ps_pgrp->pg_id;
+	rw_exit_write(&autofs_softc->sc_lock);
+
+	adr->adr_id = ar->ar_id;
+	strlcpy(adr->adr_from, ar->ar_from, sizeof(adr->adr_from));
+	strlcpy(adr->adr_path, ar->ar_path, sizeof(adr->adr_path));
+	strlcpy(adr->adr_prefix, ar->ar_prefix, sizeof(adr->adr_prefix));
+	strlcpy(adr->adr_key, ar->ar_key, sizeof(adr->adr_key));
+	strlcpy(adr->adr_options, ar->ar_options, sizeof(adr->adr_options));
+
+	return 0;
+}
+
+static int
+autofs_ioctl_done(struct autofs_daemon_done *add)
+{
+	struct autofs_request *ar;
+
+	rw_enter_write(&autofs_softc->sc_lock);
+	TAILQ_FOREACH(ar, &autofs_softc->sc_requests, ar_next)
+		if (ar->ar_id == add->add_id)
+			break;
+
+	if (!ar) {
+		rw_exit_write(&autofs_softc->sc_lock);
+		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;
+	wakeup(autofs_softc->ident);
+
+	rw_exit_write(&autofs_softc->sc_lock);
+
+	return 0;
+}
+
+void
+autofsattach(int num)
+{
+
+}
+
+int
+autofsopen(dev_t dev, int flags, int fmt, struct proc * p)
+{
+
+	rw_enter_write(&autofs_softc->sc_lock);
+	/*
+	 * 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) {
+		rw_exit_write(&autofs_softc->sc_lock);
+		return EBUSY;
+	}
+
+	autofs_softc->sc_dev_opened = true;
+	rw_exit_write(&autofs_softc->sc_lock);
+
+	return 0;
+}
+
+int
+autofsclose(dev_t dev, int flags, int fmt, struct proc *p)
+{
+
+	rw_enter_write(&autofs_softc->sc_lock);
+	KASSERT(autofs_softc->sc_dev_opened);
+	autofs_softc->sc_dev_opened = false;
+	rw_exit_write(&autofs_softc->sc_lock);
+
+	return 0;
+}
+
+int
+autofsioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct proc *p)
+{
+
+	KASSERT(autofs_softc->sc_dev_opened);
+
+	switch (cmd) {
+	case AUTOFSREQUEST:
+		return autofs_ioctl_request(
+		    (struct autofs_daemon_request *)data);
+	case AUTOFSDONE:
+		return autofs_ioctl_done(
+		    (struct autofs_daemon_done *)data);
+	default:
+		AUTOFS_DEBUG("invalid cmd %lx", cmd);
+		return EINVAL;
+	}
+	return EINVAL;
+}
+
+/*
+ * Copied from sys/libkern in FreeBSD.
+ */
+char *
+kstrdup(const char *str)
+{
+	size_t len;
+	char *copy;
+
+	len = strlen(str) + 1;
+	copy = malloc(len, M_TEMP, M_WAITOK | M_ZERO);
+	bcopy(str, copy, len);
+
+	return copy;
+}
+
+char *
+kstrndup(const char *str, size_t maxlen)
+{
+	size_t len;
+	char *copy;
+
+	len = strnlen(str, maxlen) + 1;
+	copy = malloc(len, M_TEMP, M_WAITOK | M_ZERO);
+	bcopy(str, copy, len);
+	copy[len - 1] = '\0';
+
+	return copy;
+}
diff -aNur a/sys/miscfs/autofs/autofs.h b/sys/miscfs/autofs/autofs.h
--- a/sys/miscfs/autofs/autofs.h	Thu Jan  1 09:00:00 1970
+++ b/sys/miscfs/autofs/autofs.h	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,213 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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_AUTOFS_H_
+#define _AUTOFS_AUTOFS_H_
+
+/*
+ * sysctl(2) defines
+ * XXX AUTOFS_INTERRUPTIBLE not implemented.
+ */
+#define AUTOFS_DEBUG_TUNABLE	1 /* Enable debug messages */
+#define AUTOFS_MOUNT_ON_STAT	2 /* Trigger mount on stat(2) on mountpoint */
+#define AUTOFS_TIMEOUT		3 /* Number of seconds to wait for automountd(8) */
+#define AUTOFS_CACHE		4 /* Number of seconds to wait before reinvoking */
+#define AUTOFS_RETRY_ATTEMPTS	5 /* Number of attempts before failing mount */
+#define AUTOFS_RETRY_DELAY	6 /* Number of seconds before retrying */
+//#define AUTOFS_INTERRUPTIBLE	7 /* Allow requests to be interrupted by signal */
+#define AUTOFS_MAXID		7 /* Number of valid autofs ids */
+
+#define AUTOFS_NAMES { \
+	{ 0, 0 }, \
+	{ "autofs_debug", CTLTYPE_INT }, \
+	{ "autofs_mount_on_stat", CTLTYPE_INT }, \
+	{ "autofs_timeout", CTLTYPE_INT }, \
+	{ "autofs_cache", CTLTYPE_INT }, \
+	{ "autofs_retry_attempts", CTLTYPE_INT }, \
+	{ "autofs_retry_delay", CTLTYPE_INT }, \
+}
+
+#ifdef _KERNEL
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/pool.h>
+#include <sys/malloc.h>
+#include <sys/tree.h>
+#include <sys/lock.h>
+#include <sys/rwlock.h>
+#include <sys/timeout.h>
+#include <sys/task.h>
+#include <sys/mount.h>
+#include <sys/vnode.h>
+#include <sys/proc.h>
+
+#include "autofs_ioctl.h"
+
+#define AUTOFS_ROOTINO	((ino_t)1)
+#define VFSTOAUTOFS(mp)	((struct autofs_mount *)((mp)->mnt_data))
+#define VTOI(vp)	((struct autofs_node *)((vp)->v_data))
+
+extern struct pool	autofs_request_pool;
+extern struct pool	autofs_node_pool;
+extern struct autofs_softc	*autofs_softc;
+extern struct taskq	*autofs_tmo_tq;
+
+/*
+ * sysctl(2) tunables
+ */
+extern int	autofs_debug;
+extern int	autofs_mount_on_stat;
+extern int	autofs_timeout;
+extern int	autofs_cache;
+extern int	autofs_retry_attempts;
+extern int	autofs_retry_delay;
+extern int	autofs_interruptible;
+
+#define AUTOFS_DEBUG(X, ...)				\
+	do {						\
+		if (autofs_debug > 1)			\
+			printf("%s: " X "\n",		\
+			    __func__, ## __VA_ARGS__);	\
+	} while (0)
+
+#define AUTOFS_WARN(X, ...)				\
+	do {						\
+		if (autofs_debug > 0) {			\
+			printf("WARNING: %s: " X "\n",	\
+			    __func__, ## __VA_ARGS__);	\
+		}					\
+	} while (0)
+
+/*
+ * APRINTF is only for debugging.
+ */
+#define APRINTF(X, ...)					\
+	printf("### %s(%s|%d): " X, __func__,		\
+	    curproc->p_p->ps_comm, curproc->p_tid, ## __VA_ARGS__)
+
+struct autofs_node {
+	RB_ENTRY(autofs_node)		an_entry;
+	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 rwlock			an_node_lock;
+	struct rrwlock			an_vn_lock; /* vop */
+	bool				an_cached;
+	bool				an_wildcards;
+	struct timeout			an_timeout;
+	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 rwlock			am_lock;
+	char				am_from[AUTOFS_MAXPATHLEN];
+	char				am_on[AUTOFS_MAXPATHLEN];
+	char				am_options[AUTOFS_MAXPATHLEN];
+	char				am_prefix[AUTOFS_MAXPATHLEN];
+	ino_t				am_last_ino;
+};
+
+struct autofs_request {
+	struct task			ar_tk; /* must be first */
+	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[AUTOFS_MAXPATHLEN];
+	char				ar_path[AUTOFS_MAXPATHLEN];
+	char				ar_prefix[AUTOFS_MAXPATHLEN];
+	char				ar_key[AUTOFS_MAXPATHLEN];
+	char				ar_options[AUTOFS_MAXPATHLEN];
+	struct timeout			ar_timeout;
+	volatile u_int			ar_refcount;
+};
+
+struct autofs_softc {
+	char				*ident;
+	struct rwlock			sc_lock;
+	TAILQ_HEAD(, autofs_request)	sc_requests;
+	bool				sc_dev_opened;
+	pid_t				sc_dev_sid;
+	int				sc_last_request_id;
+};
+
+int	autofs_trigger(struct autofs_node *anp, const char *component,
+	    int componentlen);
+bool	autofs_cached(struct autofs_node *anp, const char *component,
+	    int componentlen);
+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,
+	    struct vnode **vpp);
+char	*kstrdup(const char *str);
+char	*kstrndup(const char *str, size_t maxlen);
+
+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_entry, autofs_node_cmp);
+
+#endif /* _KERNEL */
+#endif /* !_AUTOFS_AUTOFS_H_ */
diff -aNur a/sys/miscfs/autofs/autofs_ioctl.h b/sys/miscfs/autofs/autofs_ioctl.h
--- a/sys/miscfs/autofs/autofs_ioctl.h	Thu Jan  1 09:00:00 1970
+++ b/sys/miscfs/autofs/autofs_ioctl.h	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,110 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2013 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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_AUTOFS_IOCTL_H_
+#define _AUTOFS_AUTOFS_IOCTL_H_
+
+#include <sys/param.h>
+#include <sys/ioccom.h>
+
+#define AUTOFS_PATH		"/dev/autofs"
+#define AUTOFS_MAXPATHLEN	256
+
+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[AUTOFS_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[AUTOFS_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[AUTOFS_MAXPATHLEN];
+
+	/*
+	 * Map key, also used as command argument for dynamic maps; in example
+	 * above that would be '192.168.1.3'.
+	 */
+	char		adr_key[AUTOFS_MAXPATHLEN];
+
+	/*
+	 * Mount options from auto_master(5).
+	 */
+	char		adr_options[AUTOFS_MAXPATHLEN];
+};
+
+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 AUTOFSDONE	_IOW('I', 0x02, struct autofs_daemon_done)
+
+#endif /* !_AUTOFS_AUTOFS_IOCTL_H */
diff -aNur a/sys/miscfs/autofs/autofs_vfsops.c b/sys/miscfs/autofs/autofs_vfsops.c
--- a/sys/miscfs/autofs/autofs_vfsops.c	Thu Jan  1 09:00:00 1970
+++ b/sys/miscfs/autofs/autofs_vfsops.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,307 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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 "autofs.h"
+
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+
+static int	autofs_statfs(struct mount *, struct statfs *, struct proc *p);
+
+static int
+autofs_init(struct vfsconf *vfsp)
+{
+
+	KASSERT(!autofs_softc);
+
+	pool_init(&autofs_request_pool, sizeof(struct autofs_request), 0,
+	    IPL_NONE, PR_WAITOK, "autofs_request", NULL);
+	pool_init(&autofs_node_pool, sizeof(struct autofs_node), 0,
+	    IPL_NONE, PR_WAITOK, "autofs_node", NULL);
+
+	autofs_softc = malloc(sizeof(*autofs_softc), M_TEMP, M_WAITOK | M_ZERO);
+	KASSERT(autofs_softc);
+
+	TAILQ_INIT(&autofs_softc->sc_requests);
+	autofs_softc->ident = "autofswait";
+	rw_init(&autofs_softc->sc_lock, "autofssclock");
+	autofs_softc->sc_dev_opened = false;
+
+	autofs_tmo_tq = taskq_create("autofstmo", 1, IPL_NONE, 0);
+	KASSERT(autofs_tmo_tq);
+
+	return 0;
+}
+
+static int
+autofs_mount(struct mount *mp, const char *path, void *data,
+    struct nameidata *ndp, struct proc *p)
+{
+	struct autofs_args *args = data;
+	struct autofs_mount *amp = VFSTOAUTOFS(mp);
+	struct statfs *sbp = &mp->mnt_stat;
+	int error;
+
+	if (mp->mnt_flag & MNT_UPDATE) {
+		if (amp == NULL)
+			return EIO;
+		autofs_flush(amp);
+		return 0;
+	}
+
+	if (!args)
+		return EINVAL;
+	if (amp != NULL)
+		return EBUSY;
+
+	/*
+	 * Copy-in ->f_mntfromname string.
+	 */
+	memset(sbp->f_mntfromname, 0, sizeof(sbp->f_mntfromname));
+	error = copyinstr(args->from, sbp->f_mntfromname,
+	    sizeof(sbp->f_mntfromname), NULL);
+	if (error)
+		goto fail;
+	strlcpy(sbp->f_mntonname, path, sizeof(sbp->f_mntonname));
+
+	/*
+	 * Allocate the autofs mount.
+	 */
+	amp = malloc(sizeof(*amp), M_TEMP, M_WAITOK | M_ZERO);
+	mp->mnt_data = 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));
+
+	/*
+	 * Copy-in master_options string.
+	 */
+	error = copyinstr(args->master_options, amp->am_options,
+	    sizeof(amp->am_options), NULL);
+	if (error)
+		goto fail;
+
+	/*
+	 * Copy-in master_prefix string.
+	 */
+	error = copyinstr(args->master_prefix, amp->am_prefix,
+	    sizeof(amp->am_prefix), NULL);
+	if (error)
+		goto fail;
+
+	/*
+	 * Initialize the autofs mount.
+	 */
+	rw_init(&amp->am_lock, "autofsamlock");
+	amp->am_last_ino = AUTOFS_ROOTINO;
+
+	rw_enter_write(&amp->am_lock);
+	error = autofs_node_new(NULL, amp, ".", -1, &amp->am_root);
+	rw_exit_write(&amp->am_lock);
+	KASSERT(!error);
+	KASSERT(amp->am_root->an_ino == AUTOFS_ROOTINO);
+
+	autofs_statfs(mp, sbp, NULL);
+	vfs_getnewfsid(mp);
+
+	return 0;
+fail:
+	mp->mnt_data = NULL;
+	free(amp, M_TEMP, sizeof(*amp));
+
+	return error;
+}
+
+static int
+autofs_unmount(struct mount *mp, int mntflags, struct proc *p)
+{
+	struct autofs_mount *amp = VFSTOAUTOFS(mp);
+	int error, flags;
+
+	flags = 0;
+	if (mntflags & MNT_FORCE)
+		flags |= FORCECLOSE;
+	error = vflush(mp, NULL, 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 (;;) {
+		struct autofs_request *ar;
+		int dummy;
+		bool found;
+
+		found = false;
+		rw_enter_write(&autofs_softc->sc_lock);
+		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) {
+			rw_exit_write(&autofs_softc->sc_lock);
+			break;
+		}
+
+		wakeup(autofs_softc->ident);
+		rw_exit_write(&autofs_softc->sc_lock);
+
+		tsleep(&dummy, PCATCH, "autofs_unmount", hz);
+	}
+
+	rw_enter_write(&amp->am_lock);
+	while (!RB_EMPTY(&amp->am_root->an_children)) {
+		struct autofs_node *anp;
+		/*
+		 * Force delete all nodes when more than one level of
+		 * directories are created via indirect map. Autofs doesn't
+		 * support rmdir(2), thus this is the only way to get out.
+		 */
+		anp = RB_MIN(autofs_node_tree, &amp->am_root->an_children);
+		while (!RB_EMPTY(&anp->an_children))
+			anp = RB_MIN(autofs_node_tree, &anp->an_children);
+		autofs_node_delete(anp);
+	}
+	autofs_node_delete(amp->am_root);
+
+	mp->mnt_data = NULL;
+	rw_exit_write(&amp->am_lock);
+
+	free(amp, M_TEMP, sizeof(*amp));
+
+	return 0;
+}
+
+static int
+autofs_start(struct mount *mp, int flags, struct proc *p)
+{
+
+	return 0;
+}
+
+static int
+autofs_root(struct mount *mp, struct vnode **vpp)
+{
+	struct autofs_mount *amp;
+	struct autofs_node *anp;
+
+	amp = VFSTOAUTOFS(mp);
+	anp = amp->am_root;
+
+	rw_enter_write(&anp->an_node_lock);
+	return autofs_node_vn(anp, mp, vpp);
+}
+
+static int
+autofs_statfs(struct mount *mp, struct statfs *sbp, struct proc *p)
+{
+
+	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;
+	sbp->f_favail = 0;
+
+	copy_statfs_info(sbp, mp);
+
+	return 0;
+}
+
+static int
+autofs_sync(struct mount *mp, int waitfor, int stall, struct ucred *cred,
+    struct proc *p)
+{
+
+	return 0;
+}
+
+static int
+autofs_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp,
+    size_t newlen, struct proc *p)
+{
+
+	/* all sysctl names at this level are terminal */
+	if (namelen != 1)
+		return ENOTDIR;		/* overloaded */
+
+	switch (name[0]) {
+	case AUTOFS_DEBUG_TUNABLE:
+		return sysctl_int(oldp, oldlenp, newp, newlen, &autofs_debug);
+	case AUTOFS_MOUNT_ON_STAT:
+		return sysctl_int(oldp, oldlenp, newp, newlen,
+		    &autofs_mount_on_stat);
+	case AUTOFS_TIMEOUT:
+		return sysctl_int(oldp, oldlenp, newp, newlen, &autofs_timeout);
+	case AUTOFS_CACHE:
+		return sysctl_int(oldp, oldlenp, newp, newlen, &autofs_cache);
+	case AUTOFS_RETRY_ATTEMPTS:
+		return sysctl_int(oldp, oldlenp, newp, newlen,
+		    &autofs_retry_attempts);
+	case AUTOFS_RETRY_DELAY:
+		return sysctl_int(oldp, oldlenp, newp, newlen,
+		    &autofs_retry_delay);
+	default:
+		return EOPNOTSUPP;
+	}
+	return EINVAL;
+}
+
+struct vfsops autofs_vfsops = {
+	autofs_mount,		/* vfs_mount */
+	autofs_start,		/* vfs_start */
+	autofs_unmount,		/* vfs_unmount */
+	autofs_root,		/* vfs_root */
+	(void *)eopnotsupp,	/* vfs_quotactl */
+	autofs_statfs,		/* vfs_statfs */
+	autofs_sync,		/* vfs_sync */
+	(void *)eopnotsupp,	/* vfs_vget */
+	(void *)eopnotsupp,	/* vfs_fhtovp */
+	(void *)eopnotsupp,	/* vfs_vptofh */
+	autofs_init,		/* vfs_init */
+	autofs_sysctl,		/* vfs_sysctl */
+	(void *)eopnotsupp,	/* vfs_checkexp */
+};
diff -aNur a/sys/miscfs/autofs/autofs_vnops.c b/sys/miscfs/autofs/autofs_vnops.c
--- a/sys/miscfs/autofs/autofs_vnops.c	Thu Jan  1 09:00:00 1970
+++ b/sys/miscfs/autofs/autofs_vnops.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,856 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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 "autofs.h"
+
+#include <sys/stat.h>
+#include <sys/dirent.h>
+#include <sys/namei.h>
+#include <sys/stdint.h>
+
+static int	autofs_trigger_vn(struct vnode *vp, const char *path,
+		    int pathlen, struct vnode **newvp, struct proc *p);
+
+static int
+autofs_access(void *v)
+{
+	struct vop_access_args /* {
+		struct vnode *a_vp;
+		int a_mode;
+		struct ucred *a_cred;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct vnode *vp __unused = ap->a_vp;
+
+	KASSERT(VOP_ISLOCKED(vp));
+	/*
+	 * Nothing to do here; the only kind of access control
+	 * needed is in autofs_mkdir().
+	 */
+	return 0;
+}
+
+static int
+autofs_getattr(void *v)
+{
+	struct vop_getattr_args /* {
+		struct vnode *a_vp;
+		struct vattr *a_vap;
+		struct ucred *a_cred;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct vnode *vp = ap->a_vp;
+	struct vattr *vap = ap->a_vap;
+	struct autofs_node *anp = VTOI(vp);
+
+	KASSERT(vp->v_type == VDIR);
+
+	/*
+	 * The reason we must do this is that some tree-walking software,
+	 * namely fts(3), assumes that stat(".") results will not change
+	 * between chdir("subdir") and chdir(".."), and fails with ENOENT
+	 * otherwise.
+	 */
+	if (autofs_mount_on_stat &&
+	    autofs_cached(anp, NULL, 0) == false &&
+	    autofs_ignore_thread() == false) {
+		struct vnode *newvp = NULL;
+		int error = autofs_trigger_vn(vp, "", 0, &newvp, ap->a_p);
+		if (error)
+			return error;
+		/*
+		 * Already mounted here.
+		 */
+		if (newvp) {
+			error = VOP_GETATTR(newvp, vap, ap->a_cred, ap->a_p);
+			vput(newvp);
+			return error;
+		}
+	}
+
+	vattr_null(vap);
+
+	vap->va_type = VDIR;
+	vap->va_mode = 0755;
+	vap->va_nlink = 3;
+	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_rdev = 0;
+	vap->va_bytes = S_BLKSIZE;
+	vap->va_filerev = 0;
+	vap->va_vaflags = 0;
+	vap->va_spare = 0;
+
+	return 0;
+}
+
+/*
+ * Unlock the vnode, request automountd(8) action, and then lock it back.
+ * If anything got mounted on top of the vnode, return the new filesystem's
+ * root vnode in 'newvp', locked.  A caller needs to vput() the 'newvp'.
+ */
+static int
+autofs_trigger_vn(struct vnode *vp, const char *path, int pathlen,
+    struct vnode **newvp, struct proc *p)
+{
+	struct autofs_node *anp;
+	int error, lock_flags;
+
+	anp = vp->v_data;
+
+	/*
+	 * Release the vnode lock, so that other operations, in partcular
+	 * mounting a filesystem on top of it, can proceed.  Increase use
+	 * count, to prevent the vnode from being deallocated and to prevent
+	 * filesystem from being unmounted.
+	 */
+	lock_flags = VOP_ISLOCKED(vp);
+	vref(vp);
+	VOP_UNLOCK(vp, p);
+
+	rw_enter_write(&autofs_softc->sc_lock);
+
+	/*
+	 * Workaround for mounting the same thing multiple times; revisit.
+	 */
+	if (vp->v_mountedhere) {
+		error = 0;
+		goto mounted;
+	}
+
+	error = autofs_trigger(anp, path, pathlen);
+mounted:
+	rw_exit_write(&autofs_softc->sc_lock);
+	vn_lock(vp, lock_flags | LK_RETRY, p);
+	vrele(vp);
+
+	if (error)
+		return error;
+
+	if (!vp->v_mountedhere) {
+		*newvp = NULL;
+		return 0;
+	} else {
+		/*
+		 * If the operation that succeeded was mount, then mark
+		 * the node as non-cached.  Otherwise, if someone unmounts
+		 * the filesystem before the cache times out, we will fail
+		 * to trigger.
+		 */
+		autofs_node_uncache(anp);
+	}
+
+	error = VFS_ROOT(vp->v_mountedhere, newvp);
+	if (error) {
+		AUTOFS_WARN("VFS_ROOT() failed with error %d", error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int
+autofs_lookup(void *v)
+{
+	struct vop_lookup_args /* {
+		struct vnode *a_dvp;
+		struct vnode **a_vpp;
+		struct componentname *a_cnp;
+	} */ *ap = v;
+	struct vnode *dvp = ap->a_dvp;
+	struct vnode **vpp = ap->a_vpp;
+	struct componentname *cnp = ap->a_cnp;
+	struct autofs_mount *amp = VFSTOAUTOFS(dvp->v_mount);
+	struct autofs_node *anp, *child;
+	int error;
+	const int lastcn = (cnp->cn_flags & ISLASTCN) != 0;
+	const int lockparent = (cnp->cn_flags & LOCKPARENT) != 0;
+
+	KASSERT(VOP_ISLOCKED(dvp));
+
+	cnp->cn_flags &= ~PDIRUNLOCK;
+	anp = VTOI(dvp);
+	*vpp = NULL;
+
+	/* Check accessibility of directory. */
+	KASSERT(!VOP_ACCESS(dvp, VEXEC, cnp->cn_cred, curproc));
+
+	/*
+	 * Only creation of directories is allowed.
+	 * Once created, directories can't be renamed or deleted.
+	 */
+	KASSERT(cnp->cn_nameiop != RENAME);
+	KASSERT(cnp->cn_nameiop != DELETE);
+
+	/*
+	 * Avoid doing a linear scan of the directory if the requested
+	 * directory/name couple is already in the cache.
+	 */
+	error = cache_lookup(dvp, vpp, cnp);
+	if (error == ENOENT) {
+		return error;
+	} else if (error != -1) {
+		KASSERT(error >= 0);
+		return 0;
+	}
+
+	if (cnp->cn_flags & ISDOTDOT) {
+		/*
+		 * Lookup of ".." case.
+		 */
+		if (!anp->an_parent) {
+			error = ENOENT;
+			goto out;
+		}
+
+		/*
+		 * Lock the parent an_node_lock before releasing the vnode
+		 * lock, and thus prevents parent from disappearing.
+		 */
+		rw_enter_write(&anp->an_node_lock);
+		VOP_UNLOCK(dvp, curproc);
+		cnp->cn_flags |= PDIRUNLOCK;
+
+		/*
+		 * Get a vnode of the '..' entry and re-acquire the lock.
+		 * The vnode lock can be recursive.
+		 */
+		error = autofs_node_vn(anp, amp->am_mp, vpp);
+		if (error) {
+			if (vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY, curproc) == 0)
+				cnp->cn_flags &= ~PDIRUNLOCK;
+			return error;
+		}
+
+		if (lockparent && lastcn) {
+			error = vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY, curproc);
+			if (error) {
+				vput(*vpp);
+				return error;
+			}
+			cnp->cn_flags &= ~PDIRUNLOCK;
+		}
+		goto out;
+	} else if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') {
+		/*
+		 * Lookup of "." case.
+		 */
+		vref(dvp);
+		*vpp = dvp;
+		error = 0;
+		goto done;
+	}
+
+	if (autofs_cached(anp, cnp->cn_nameptr, cnp->cn_namelen) == false &&
+	    autofs_ignore_thread() == false) {
+		struct vnode *newvp = NULL;
+		error = autofs_trigger_vn(dvp, cnp->cn_nameptr, cnp->cn_namelen,
+		    &newvp, curproc);
+		if (error)
+			return error;
+		/*
+		 * Already mounted here.
+		 */
+		if (newvp) {
+			error = VOP_LOOKUP(newvp, vpp, cnp);
+			vput(newvp);
+			return error;
+		}
+	}
+
+	rw_enter_read(&amp->am_lock);
+	error = autofs_node_find(anp, cnp->cn_nameptr, cnp->cn_namelen, &child);
+	if (error) {
+		if (lastcn && cnp->cn_nameiop == CREATE) {
+			error = VOP_ACCESS(dvp, VWRITE, cnp->cn_cred, curproc);
+			if (error)
+				goto out;
+			rw_exit_read(&amp->am_lock);
+			/*
+			 * We are creating an entry in the file system, so
+			 * save its name for further use.
+			 */
+			cnp->cn_flags |= SAVENAME;
+			if (!lockparent) {
+				VOP_UNLOCK(dvp, curproc);
+				cnp->cn_flags |= PDIRUNLOCK;
+			}
+			return EJUSTRETURN;
+		}
+
+		rw_exit_read(&amp->am_lock);
+		error = ENOENT;
+		goto done;
+	}
+
+	/*
+	 * Dropping the node here is ok, because we never remove nodes.
+	 */
+	rw_exit_read(&amp->am_lock);
+
+	/* Get a vnode for the matching entry. */
+	rw_enter_write(&child->an_node_lock);
+	error = autofs_node_vn(child, amp->am_mp, vpp);
+done:
+	/*
+	 * Cache the result, unless request was for creation (as it does
+	 * not improve the performance).
+	 */
+	if ((cnp->cn_flags & MAKEENTRY) && cnp->cn_nameiop != CREATE)
+		cache_enter(dvp, *vpp, cnp);
+out:
+	/*
+	 * If (1) we succeded, (2) found a distinct vnode to return and (3) were
+	 * either not explicitly told to keep the parent locked or are in the
+	 * middle of a lookup, unlock the parent vnode.
+	 */
+	if ((error == 0 || error == EJUSTRETURN) && /* (1) */
+	    *vpp != dvp &&			    /* (2) */
+	    (!lockparent || !lastcn)) {		    /* (3) */
+		VOP_UNLOCK(dvp, curproc);
+		cnp->cn_flags |= PDIRUNLOCK;
+	} else {
+		KASSERT(VOP_ISLOCKED(dvp));
+	}
+
+	KASSERT((*vpp && VOP_ISLOCKED(*vpp)) || error);
+
+	return error;
+}
+
+static int
+autofs_open(void *v)
+{
+	struct vop_open_args /* {
+		struct vnode *a_vp;
+		int a_mode;
+		struct ucred *a_cred;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct vnode *vp __unused = ap->a_vp;
+
+	KASSERT(VOP_ISLOCKED(vp));
+	return 0;
+}
+
+static int
+autofs_close(void *v)
+{
+	struct vop_close_args /* {
+		struct vnode *a_vp;
+		int a_fflag;
+		struct ucred *a_cred;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct vnode *vp __unused = ap->a_vp;
+
+	KASSERT(VOP_ISLOCKED(vp));
+	return 0;
+}
+
+static int
+autofs_fsync(void *v)
+{
+	struct vop_fsync_args /* {
+		struct vnode *a_vp;
+		struct ucred *a_cred;
+		int a_waitfor;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct vnode *vp __unused = ap->a_vp;
+
+	KASSERT(VOP_ISLOCKED(vp));
+	return 0;
+}
+
+static int
+autofs_mkdir(void *v)
+{
+	struct vop_mkdir_args /* {
+		struct vnode *a_dvp;
+		struct vnode **a_vpp;
+		struct componentname *a_cnp;
+		struct vattr *a_vap;
+	} */ *ap = v;
+	struct vnode *dvp = ap->a_dvp;
+	struct vnode **vpp = ap->a_vpp;
+	struct componentname *cnp = ap->a_cnp;
+	struct autofs_mount *amp = VFSTOAUTOFS(dvp->v_mount);
+	struct autofs_node *anp = VTOI(dvp);
+	struct autofs_node *child = NULL;
+	int error;
+
+	KASSERT(ap->a_vap->va_type == VDIR);
+
+	/*
+	 * Do not allow mkdir() if the calling thread is not
+	 * automountd(8) descendant.
+	 */
+	if (autofs_ignore_thread() == false) {
+		vput(dvp);
+		return EPERM;
+	}
+
+	rw_enter_write(&amp->am_lock);
+	error = autofs_node_new(anp, amp, cnp->cn_nameptr, cnp->cn_namelen,
+	    &child);
+	if (error) {
+		rw_exit_write(&amp->am_lock);
+		vput(dvp);
+		return error;
+	}
+	rw_exit_write(&amp->am_lock);
+
+	rw_enter_write(&child->an_node_lock);
+	error = autofs_node_vn(child, amp->am_mp, vpp);
+	vput(dvp);
+
+	return error;
+}
+
+static int
+autofs_print(void *v)
+{
+	struct vop_print_args /* {
+		struct vnode *a_vp;
+	} */ *ap = v;
+	struct vnode *vp = ap->a_vp;
+	struct autofs_node *anp = VTOI(vp);
+
+	printf("tag VT_AUTOFS, node %p, ino %jd, name %s, cached %d, "
+	    "retries %d, wildcards %d",
+	    anp, (intmax_t)anp->an_ino, anp->an_name, anp->an_cached,
+	    anp->an_retries, anp->an_wildcards);
+	printf("\n");
+
+	return 0;
+}
+
+static int
+autofs_readdir_one(struct uio *uio, const char *name, ino_t ino,
+    size_t *reclenp)
+{
+	struct dirent dirent;
+
+	dirent.d_fileno = ino;
+	dirent.d_type = DT_DIR;
+	strlcpy(dirent.d_name, name, sizeof(dirent.d_name));
+	dirent.d_namlen = strlen(dirent.d_name);
+	dirent.d_reclen = DIRENT_SIZE(&dirent);
+
+	if (reclenp)
+		*reclenp = dirent.d_reclen;
+
+	if (!uio)
+		return 0;
+
+	if (uio->uio_resid < dirent.d_reclen)
+		return EINVAL;
+
+	return uiomove(&dirent, dirent.d_reclen, uio);
+}
+
+static size_t
+autofs_dirent_reclen(const char *name)
+{
+	size_t reclen;
+
+	autofs_readdir_one(NULL, name, -1, &reclen);
+
+	return reclen;
+}
+
+static int
+autofs_readdir(void *v)
+{
+	struct vop_readdir_args /* {
+		struct vnode *a_vp;
+		struct uio *a_uio;
+		struct ucred *a_cred;
+		int *a_eofflag;
+	} */ *ap = v;
+	struct vnode *vp = ap->a_vp;
+	struct uio *uio = ap->a_uio;
+	size_t initial_resid = ap->a_uio->uio_resid;
+	struct autofs_mount *amp = VFSTOAUTOFS(vp->v_mount);
+	struct autofs_node *anp = VTOI(vp);
+	struct autofs_node *child;
+	size_t reclen, reclens;
+	int error;
+
+	KASSERT(VOP_ISLOCKED(vp));
+
+	if (vp->v_type != VDIR)
+		return ENOTDIR;
+
+	if (autofs_cached(anp, NULL, 0) == false &&
+	    autofs_ignore_thread() == false) {
+		struct vnode *newvp = NULL;
+		error = autofs_trigger_vn(vp, "", 0, &newvp, curproc);
+		if (error)
+			return error;
+		/*
+		 * Already mounted here.
+		 */
+		if (newvp) {
+			error = VOP_READDIR(newvp, ap->a_uio, ap->a_cred,
+			    ap->a_eofflag);
+			vput(newvp);
+			return error;
+		}
+	}
+
+	if (uio->uio_offset < 0)
+		return EINVAL;
+
+	if (ap->a_eofflag)
+		*ap->a_eofflag = 0;
+
+	/*
+	 * 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.
+	 */
+	rw_enter_read(&amp->am_lock);
+	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) {
+			rw_exit_read(&amp->am_lock);
+			return EINVAL;
+		}
+
+		error = autofs_readdir_one(uio, child->an_name, child->an_ino,
+		    &reclen);
+		reclens += reclen;
+		if (error) {
+			rw_exit_read(&amp->am_lock);
+			goto out;
+		}
+	}
+	rw_exit_read(&amp->am_lock);
+
+	if (ap->a_eofflag)
+		*ap->a_eofflag = 1;
+
+	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_inactive(void *v)
+{
+	struct vop_inactive_args /* {
+		struct vnode *a_vp;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct vnode *vp = ap->a_vp;
+
+	KASSERT(VOP_ISLOCKED(vp));
+
+	VOP_UNLOCK(vp, ap->a_p);
+
+	/*
+	 * We do not reclaim autofs_node here; instead we are
+	 * destroying them in autofs_node_delete().
+	 */
+	return 0;
+}
+
+static int
+autofs_reclaim(void *v)
+{
+	struct vop_reclaim_args /* {
+		struct vnode *a_vp;
+		struct proc *a_p;
+	} */ *ap = v;
+	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().
+	 */
+	rw_enter_write(&anp->an_node_lock);
+	anp->an_vnode = NULL;
+	vp->v_data = NULL;
+	rw_exit_write(&anp->an_node_lock);
+
+	return 0;
+}
+
+static int
+autofs_lock(void *v)
+{
+	struct vop_lock_args /* {
+		struct vnode *a_vp;
+		int a_flags;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct autofs_node *anp = VTOI(ap->a_vp);
+
+	return rrw_enter(&anp->an_vn_lock, ap->a_flags & LK_RWFLAGS);
+}
+
+static int
+autofs_unlock(void *v)
+{
+	struct vop_unlock_args /* {
+		struct vnode *a_vp;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct autofs_node *anp = VTOI(ap->a_vp);
+
+	rrw_exit(&anp->an_vn_lock);
+
+	return 0;
+}
+
+static int
+autofs_islocked(void *v)
+{
+	struct vop_islocked_args /* {
+		struct vnode *a_vp;
+	} */ *ap = v;
+	struct autofs_node *anp = VTOI(ap->a_vp);
+
+	return rrw_status(&anp->an_vn_lock);
+}
+
+static struct vops autofs_vops = {
+	.vop_lookup	= autofs_lookup,
+	.vop_open	= autofs_open,
+	.vop_close	= autofs_close,
+	.vop_access	= autofs_access,
+	.vop_getattr	= autofs_getattr,
+	.vop_fsync	= autofs_fsync,
+	.vop_mkdir	= autofs_mkdir,
+	.vop_readdir	= autofs_readdir,
+	.vop_inactive	= autofs_inactive,
+	.vop_reclaim	= autofs_reclaim,
+	.vop_lock	= autofs_lock,
+	.vop_unlock	= autofs_unlock,
+	.vop_print	= autofs_print,
+	.vop_islocked	= autofs_islocked,
+};
+
+static void
+autofs_node_timeout(void *context)
+{
+	struct autofs_node *anp = context;
+
+	autofs_node_uncache(anp);
+}
+
+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;
+
+	rw_assert_wrlock(&amp->am_lock);
+
+	if (parent) {
+		rw_assert_wrlock(&parent->an_mount->am_lock);
+		KASSERT(autofs_node_find(parent, name, namelen, NULL) ==
+		    ENOENT);
+	}
+
+	anp = pool_get(&autofs_node_pool, PR_WAITOK);
+	anp->an_name = kstrndup(name, namelen);
+	anp->an_ino = amp->am_last_ino++;
+	timeout_set(&anp->an_timeout, autofs_node_timeout, anp);
+	rw_init(&anp->an_node_lock, "autofsvnlock");
+	rrw_init_flags(&anp->an_vn_lock, "autofsvoplock", RWL_DUPOK);
+	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)
+		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;
+
+	rw_assert_anylock(&parent->an_mount->am_lock);
+
+	if (namelen >= 0)
+		find.an_name = kstrndup(name, namelen);
+	else
+		find.an_name = kstrdup(name);
+
+	anp = RB_FIND(autofs_node_tree, &parent->an_children, &find);
+	if (anp) {
+		error = 0;
+		if (anpp)
+			*anpp = anp;
+	} else {
+		error = ENOENT;
+	}
+
+	free(find.an_name, M_TEMP, strlen(find.an_name) + 1);
+
+	return error;
+}
+
+void
+autofs_node_delete(struct autofs_node *anp)
+{
+
+	rw_assert_wrlock(&anp->an_mount->am_lock);
+	KASSERT(RB_EMPTY(&anp->an_children));
+
+	timeout_del(&anp->an_timeout);
+
+	if (anp->an_parent)
+		RB_REMOVE(autofs_node_tree, &anp->an_parent->an_children, anp);
+
+	free(anp->an_name, M_TEMP, strlen(anp->an_name) + 1);
+	pool_put(&autofs_node_pool, anp);
+}
+
+/*
+ * A caller must have acquired the node lock.
+ * The node lock is released when returning from this function.
+ */
+int
+autofs_node_vn(struct autofs_node *anp, struct mount *mp, struct vnode **vpp)
+{
+	struct vnode *vp;
+	int error;
+
+again:
+	rw_assert_wrlock(&anp->an_node_lock);
+
+	if ((vp = anp->an_vnode) != NULL) {
+		error = vget(vp, LK_EXCLUSIVE, curproc);
+		rw_exit_write(&anp->an_node_lock);
+		if (error == ENOENT) {
+			rw_enter_write(&anp->an_node_lock);
+			goto again;
+		}
+
+		*vpp = vp;
+		return error;
+	}
+
+	error = getnewvnode(VT_AUTOFS, mp, &autofs_vops, &vp);
+	if (error) {
+		rw_exit_write(&anp->an_node_lock);
+		return error;
+	}
+
+	uvm_vnp_setsize(vp, 0);
+	vp->v_type = VDIR;
+	if (anp->an_parent == NULL)
+		vp->v_flag |= VROOT;
+	vp->v_data = anp;
+	anp->an_vnode = vp;
+	vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, curproc);
+	rw_exit_write(&anp->an_node_lock);
+
+	KASSERT(VOP_ISLOCKED(vp));
+	*vpp = vp;
+
+	return 0;
+}
diff -aNur a/sys/sys/conf.h b/sys/sys/conf.h
--- a/sys/sys/conf.h	Sun Mar 25 12:08:03 2018
+++ b/sys/sys/conf.h	Sun Mar 25 12:09:38 2018
@@ -475,6 +475,13 @@
 	(dev_type_stop((*))) enodev, 0, (dev_type_poll((*))) enodev, \
 	(dev_type_mmap((*))) enodev, 0 }
 
+/* open, close, ioctl */
+#define cdev_autofs_init(c,n) { \
+	dev_init(c,n,open), dev_init(c,n,close), (dev_type_read((*))) enodev, \
+	(dev_type_write((*))) enodev, dev_init(c,n,ioctl), \
+	(dev_type_stop((*))) enodev, 0, selfalse, \
+	(dev_type_mmap((*))) enodev }
+
 #endif
 
 /*
@@ -607,6 +614,7 @@
 cdev_decl(fuse);
 cdev_decl(pvbus);
 cdev_decl(ipmi);
+cdev_decl(autofs);
 
 #endif
 
diff -aNur a/sys/sys/event.h b/sys/sys/event.h
--- a/sys/sys/event.h	Sun Mar 25 12:08:03 2018
+++ b/sys/sys/event.h	Sun Mar 25 12:09:38 2018
@@ -39,8 +39,9 @@
 #define EVFILT_SIGNAL		(-6)	/* attached to struct process */
 #define EVFILT_TIMER		(-7)	/* timers */
 #define EVFILT_DEVICE		(-8)	/* devices */
+#define EVFILT_FS		(-9)	/* filesystem events */
 
-#define EVFILT_SYSCOUNT		8
+#define EVFILT_SYSCOUNT		9
 
 #define EV_SET(kevp_, a, b, c, d, e, f) do {	\
 	struct kevent *kevp = (kevp_);		\
@@ -191,6 +192,8 @@
 extern int	filt_seltrue(struct knote *kn, long hint);
 extern int	seltrue_kqfilter(dev_t, struct knote *);
 extern void	klist_invalidate(struct klist *);
+
+extern struct	klist fs_klist;	/* EVFILT_FS */
 
 #else	/* !_KERNEL */
 
diff -aNur a/sys/sys/mount.h b/sys/sys/mount.h
--- a/sys/sys/mount.h	Sun Mar 25 12:08:03 2018
+++ b/sys/sys/mount.h	Sun Mar 25 12:09:38 2018
@@ -249,6 +249,15 @@
 };
 
 /*
+ * Arguments to mount autofs file systems
+ */
+struct autofs_args {
+	const char	*from;
+	const char	*master_options;
+	const char	*master_prefix;
+};
+
+/*
  * file system statistics
  */
 
@@ -316,6 +325,7 @@
 #define	MOUNT_UDF	"udf"		/* UDF */
 #define	MOUNT_TMPFS	"tmpfs"		/* tmpfs */
 #define	MOUNT_FUSEFS	"fuse"		/* FUSE */
+#define	MOUNT_AUTOFS	"autofs"	/* Automounter Filesystem */
 
 /*
  * Structure per mounted file system.  Each mounted file system has an
@@ -375,7 +385,7 @@
 /*
  * Mask of flags that are visible to statfs()
  */
-#define	MNT_VISFLAGMASK	0x0400ffff
+#define	MNT_VISFLAGMASK	0x1400ffff
 
 #define	MNT_BITS \
     "\20\001RDONLY\002SYNCHRONOUS\003NOEXEC\004NOSUID\005NODEV\006NOPERM" \
@@ -393,6 +403,7 @@
 #define MNT_WANTRDWR	0x02000000	/* want upgrade to read/write */
 #define MNT_SOFTDEP     0x04000000      /* soft dependencies being done */
 #define MNT_DOOMED	0x08000000	/* device behind filesystem is gone */
+#define	MNT_AUTOMOUNTED	0x10000000	/* mounted by automountd(8) */
 
 /*
  * Flags for various system call interfaces.
@@ -472,6 +483,11 @@
 	int64_t highflops;		/* total failed flips to above DMA */
 	int64_t dmaflips;		/* total flips from high to DMA */
 };
+
+/* vfsquery flags for kqueue(2) */
+#define VQ_MOUNT	0x0001	/* new filesystem arrived */
+#define VQ_UNMOUNT	0x0002	/* filesystem has left */
+
 #ifdef _KERNEL
 extern struct bcachestats bcstats;
 extern long buflowpages, bufhighpages, bufbackpages;
diff -aNur a/sys/sys/vnode.h b/sys/sys/vnode.h
--- a/sys/sys/vnode.h	Sun Mar 25 12:08:03 2018
+++ b/sys/sys/vnode.h	Sun Mar 25 12:09:38 2018
@@ -66,13 +66,13 @@
 enum vtagtype	{
 	VT_NON, VT_UFS, VT_NFS, VT_MFS, VT_MSDOSFS,
 	VT_PORTAL, VT_PROCFS, VT_AFS, VT_ISOFS, VT_ADOSFS,
-	VT_EXT2FS, VT_VFS, VT_NTFS, VT_UDF, VT_FUSEFS, VT_TMPFS,
+	VT_EXT2FS, VT_VFS, VT_NTFS, VT_UDF, VT_FUSEFS, VT_TMPFS, VT_AUTOFS
 };
 
 #define	VTAG_NAMES \
     "NON", "UFS", "NFS", "MFS", "MSDOSFS",			\
     "unused", "unused", "unused", "ISOFS", "unused",		\
-    "EXT2FS", "VFS", "NTFS", "UDF", "FUSEFS", "TMPFS"
+    "EXT2FS", "VFS", "NTFS", "UDF", "FUSEFS", "TMPFS", "AUTOFS"
 
 /*
  * Each underlying filesystem allocates its own private area and hangs
diff -aNur a/usr.bin/showmount/showmount.8 b/usr.bin/showmount/showmount.8
--- a/usr.bin/showmount/showmount.8	Sun Mar 25 12:08:04 2018
+++ b/usr.bin/showmount/showmount.8	Sun Mar 25 12:09:38 2018
@@ -33,7 +33,7 @@
 .\"
 .\"     @(#)showmount.8	8.3 (Berkeley) 3/29/95
 .\"
-.Dd $Mdocdate: May 31 2007 $
+.Dd $Mdocdate: March 16 2018 $
 .Dt SHOWMOUNT 8
 .Os
 .Sh NAME
@@ -41,7 +41,7 @@
 .Nd show remote NFS mounts on host
 .Sh SYNOPSIS
 .Nm showmount
-.Op Fl 3ade
+.Op Fl 3adeE
 .Op Ar host
 .Sh DESCRIPTION
 .Nm
@@ -73,6 +73,12 @@
 Show the
 .Ar host Ns 's
 exports list.
+.It Fl E
+Show the
+.Ar host Ns 's
+exports list in a script-friendly format.
+Client addresses and the header are not shown, and special
+characters are escaped.
 .El
 .Sh SEE ALSO
 .Xr mount 8 ,
diff -aNur a/usr.bin/showmount/showmount.c b/usr.bin/showmount/showmount.c
--- a/usr.bin/showmount/showmount.c	Sun Mar 25 12:08:04 2018
+++ b/usr.bin/showmount/showmount.c	Sun Mar 25 12:09:38 2018
@@ -56,6 +56,7 @@
 
 #define	DODUMP		0x1
 #define	DOEXPORTS	0x2
+#define	DOPARSABLEEXPORTS	0x4
 
 struct mountlist {
 	struct mountlist *ml_left;
@@ -108,7 +109,7 @@
 	if (pledge("stdio rpath inet dns", NULL) == -1)
 		err(1, "pledge");
 
-	while ((ch = getopt(argc, argv, "ade3")) != -1)
+	while ((ch = getopt(argc, argv, "adeE3")) != -1)
 		switch (ch) {
 		case 'a':
 			if (type == 0) {
@@ -127,6 +128,9 @@
 		case 'e':
 			rpcs |= DOEXPORTS;
 			break;
+		case 'E':
+			rpcs |= DOPARSABLEEXPORTS;
+			break;
 		case '3':
 			mntvers = 3;
 			break;
@@ -136,6 +140,13 @@
 	argc -= optind;
 	argv += optind;
 
+	if ((rpcs & DOPARSABLEEXPORTS) != 0) {
+		if ((rpcs & DOEXPORTS) != 0)
+			errx(1, "-E cannot be used with -e");
+		if ((rpcs & DODUMP) != 0)
+			errx(1, "-E cannot be used with -a or -d");
+	}
+
 	if (argc > 0)
 		host = *argv;
 	else
@@ -173,7 +184,7 @@
 			exit(1);
 		}
 	}
-	if (rpcs & DOEXPORTS) {
+	if (rpcs & (DOEXPORTS | DOPARSABLEEXPORTS)) {
 		estat = clnt_call(client, RPCMNT_EXPORT, xdr_void, NULL,
 		    xdr_exports, (char *)&exports, timeout);
 		if (estat != RPC_SUCCESS) {
@@ -223,6 +234,18 @@
 		}
 	}
 
+	if (rpcs & DOPARSABLEEXPORTS) {
+		exp = exports;
+		while (exp) {
+			char strvised[1024 * 4 + 1];
+			int nbytes = strnvis(strvised, exp->ex_dirp,
+			    sizeof(strvised), VIS_GLOB | VIS_NL);
+			if (nbytes == -1)
+				err(1, "strsnvis");
+			printf("%s\n", strvised);
+			exp = exp->ex_next;
+		}
+	}
 	exit(0);
 }
 
diff -aNur a/usr.sbin/Makefile b/usr.sbin/Makefile
--- a/usr.sbin/Makefile	Sun Mar 25 12:08:06 2018
+++ b/usr.sbin/Makefile	Sun Mar 25 12:09:38 2018
@@ -3,9 +3,9 @@
 .include <bsd.own.mk>
 
 SUBDIR=	ac accton acme-client acpidump adduser amd apm apmd arp \
-	authpf bgpctl bgpd bind chroot config cron crunchgen dev_mkdb \
+	authpf autofs bgpctl bgpd bind chroot config cron crunchgen dev_mkdb \
 	dhcpd dhcrelay dhcrelay6 dvmrpctl dvmrpd edquota eeprom eigrpd eigrpctl \
-	fdformat ftp-proxy gpioctl hostapd hostctl hotplugd httpd identd \
+	fdformat fstyp ftp-proxy gpioctl hostapd hostctl hotplugd httpd identd \
 	ifstated ikectl inetd installboot iostat iscsictl iscsid kgmon \
 	kvm_mkdb ldapd ldapctl ldomctl ldomd ldpd ldpctl lpr makefs \
 	mailwrapper map-mbone memconfig mksuncd mkuboot mopd mrinfo \
diff -aNur a/usr.sbin/autofs/Makefile b/usr.sbin/autofs/Makefile
--- a/usr.sbin/autofs/Makefile	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/Makefile	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,16 @@
+WARNS=6
+PROG=	automount
+SRCS=	automount.c automountd.c autounmountd.c common.c \
+	defined.c log.c popen.c token.l
+
+CFLAGS+=	-I${.CURDIR} -I${.CURDIR}/../../sys
+
+MAN=	automount.8 automountd.8 autounmountd.8 auto_master.5
+
+DPADD=	${LIBUTIL}
+LDADD=	-lutil
+
+LINKS=	${BINDIR}/automount ${BINDIR}/automountd
+LINKS+=	${BINDIR}/automount ${BINDIR}/autounmountd
+
+.include <bsd.prog.mk>
diff -aNur a/usr.sbin/autofs/auto_master.5 b/usr.sbin/autofs/auto_master.5
--- a/usr.sbin/autofs/auto_master.5	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/auto_master.5	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,401 @@
+.\" Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+.\" Copyright (c) 2017 The NetBSD Foundation, Inc.
+.\" Copyright (c) 2016 The DragonFly Project
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Tomohiro Kusumi.
+.\"
+.\" This software was developed by Edward Tomasz Napierala under sponsorship
+.\" from the FreeBSD Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 16, 2018
+.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 file system 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 a newline character:
+.Bd -literal -offset indent
+.Ar mountpoint Ar map_name Op Fl Ar options
+.Ed
+.Pp
+.Ar mountpoint
+is either a fully specified path, or
+.Pa /- .
+When
+.Ar mountpoint
+is a full path,
+.Ar map_name
+must reference an indirect map.
+Otherwise,
+.Ar map_name
+must reference a direct map.
+See
+.Sx "MAP SYNTAX"
+below.
+.Pp
+.Ar map_name
+specifies map to use.
+If
+.Ar map_name
+begins with
+.Li - ,
+it specifies a special map.
+See
+.Sx "MAP SYNTAX"
+below.
+If
+.Ar map_name
+is not a fully specified path
+.Pq it does not start with Li / ,
+.Xr automountd 8
+will search for that name in
+.Pa /etc .
+Otherwise it will use the path as given.
+If the file indicated by
+.Ar 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
+.Op Fl Ar options
+is an optional field that starts with
+.Fl
+and can contain generic file system mount options.
+.Pp
+The following example specifies that the
+.Pa /etc/auto_example
+indirect map will be mounted on
+.Pa /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
+.Ar key Oo Fl Ar options Oc Oo Ar mountpoint Oo Fl Ar options Oc Oc Ar location Op ...
+.Ed
+.Pp
+In most cases, it can be simplified to:
+.Bd -literal -offset indent
+.Ar key Oo Fl Ar options Oc Ar location
+.Ed
+.Pp
+.Ar 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
+.Fl .
+When mounting the file system, options supplied to
+.Nm
+and options specified in the map entry are concatenated together.
+The special option
+.Ic fstype
+is used to specify file system 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
+.Ic fstype
+is
+.Ql nfs .
+The special option
+.Ic nobrowse
+is used to disable creation of top-level directories for special
+and executable maps.
+.Pp
+The optional
+.Ar mountpoint
+field is used to specify multiple mount points for a single key.
+.Pp
+The
+.Ar location
+field specifies the file system 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
+*	192.168.1.1:/share/&
+.Ed
+.Pp
+The
+.Ar location
+field may contain references to variables, like:
+.Bd -literal -offset indent
+sys	192.168.1.1:/sys/${OSNAME}
+.Ed
+.Pp
+Defined variables are:
+.Pp
+.Bl -tag -width "Dv OSNAME" -compact
+.It Dv ARCH
+Expands to the output of
+.Li "uname -p" .
+.It Dv CPU
+Same as
+.Dv ARCH .
+.It Dv HOST
+Expands to the output of
+.Li "uname -n" .
+.It Dv OSNAME
+Expands to the output of
+.Li "uname -s" .
+.It Dv OSREL
+Expands to the output of
+.Li "uname -r" .
+.It Dv 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
+.Pa / ,
+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
+.Ic intr
+and
+.Ic nfsv4
+mount options, described in
+.Xr mount_nfs 8 :
+.Bd -literal -offset indent
+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
+share -fstype=smbfs,-N ://@server/share
+.Ed
+.Pp
+Automatically mount the CD drive on access:
+.Bd -literal -offset indent
+cd -fstype=cd9660 :/dev/cd0
+.Ed
+.Sh SPECIAL MAPS
+Special maps have names beginning with
+.Li - .
+Supported special maps are:
+.Pp
+.Bl -tag -width ".Ic -noauto" -compact
+.It Ic -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
+.Pa /net/ Ns 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 Ic -media
+Query devices that are not yet mounted, but contain valid file systems.
+Generally used to access files on removable media.
+.It Ic -noauto
+Mount file systems configured in
+.Xr fstab 5
+as "noauto".
+This needs to be set up as a direct map.
+.It Ic -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
+/- auto_example
+.Ed
+.Pp
+and this in the
+.Pa /etc/auto_example
+map file:
+.Bd -literal -offset indent
+/example/x -intr,nfsv4 192.168.1.1:/share/example/x
+/example/share -fstype=smbfs,-N ://@server/share
+/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
++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
+.Nm
+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
+.An -nosplit
+The
+.Nm
+configuration file functionality was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the
+.Fx
+Foundation.
+.Pp
+The
+.Nm
+configuration file functionality was ported to
+.Dx ,
+.Nx
+and
+.Ox
+by
+.An Tomohiro Kusumi Aq Mt tkusumi@netbsd.org .
+.Sh BUGS
+The
+.Li -media
+special map is currently unsupported on
+.Nx and
+.Ox .
diff -aNur a/usr.sbin/autofs/automount.8 b/usr.sbin/autofs/automount.8
--- a/usr.sbin/autofs/automount.8	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/automount.8	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,139 @@
+.\" Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+.\" Copyright (c) 2017 The NetBSD Foundation, Inc.
+.\" Copyright (c) 2016 The DragonFly Project
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Tomohiro Kusumi.
+.\"
+.\" This software was developed by Edward Tomasz Napierala under sponsorship
+.\" from the FreeBSD Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 16, 2018
+.Dt AUTOMOUNT 8
+.Os
+.Sh NAME
+.Nm automount
+.Nd update autofs mounts
+.Sh SYNOPSIS
+.Nm
+.Op Fl cfLuv
+.Op Fl D Ar name=value
+.Op Fl o Ar options
+.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
+file systems to match.
+These options are available:
+.Bl -tag -width ".Fl v"
+.It Fl c
+Flush caches, discarding possibly stale information obtained from maps
+and directory services.
+.It Fl D
+Define a variable.
+It is only useful with
+.Fl L .
+.It Fl f
+Force unmount, to be used with
+.Fl u .
+.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 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 file systems mounted by
+.Xr automountd 8 .
+.Xr autofs 5
+mounts are not unmounted.
+To unmount all
+.Xr autofs 5
+mounts, use
+.Cm "umount -At autofs".
+.It Fl v
+Increase verbosity.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Unmount all file systems mounted by
+.Xr automountd 8 :
+.Pp
+.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 .
+The
+.Nm
+command appeared in
+.Nx 9.0 .
+The
+.Nm
+command appeared in
+.Ox 6.x .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the
+.Fx
+Foundation.
+.Pp
+The
+.Nm
+was ported to
+.Dx ,
+.Nx
+and
+.Ox
+by
+.An Tomohiro Kusumi Aq Mt tkusumi@netbsd.org .
diff -aNur a/usr.sbin/autofs/automount.c b/usr.sbin/autofs/automount.c
--- a/usr.sbin/autofs/automount.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/automount.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,351 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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_args args;
+	int error;
+	char *cwd;
+
+	/*
+	 * There is no guarantee we are at /, so chdir to /.
+	 */
+	cwd = getcwd(NULL, 0);
+	if (chdir("/") != 0)
+		log_warn("failed to chdir to /");
+	create_directory(fspath);
+	if (chdir(cwd) != 0)
+		log_warn("failed to restore cwd");
+	free(cwd);
+
+	log_debugx("mounting %s on %s, prefix \"%s\", options \"%s\"",
+	    from, fspath, prefix, options);
+
+	memset(&args, 0, sizeof(args));
+	args.from = from;
+	args.master_options = options;
+	args.master_prefix = prefix;
+
+	error = mount("autofs", fspath, 0, &args);
+	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;
+	static const char rootdir[] = "/";
+
+	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,
+			    rootdir, mntbuf, nitems);
+		}
+	}
+}
+
+static void
+flush_autofs(const char *fspath)
+{
+	struct autofs_args args;
+	int error;
+
+	log_debugx("flushing %s", fspath);
+
+	/*
+	 * OpenBSD fails here if data argument is NULL.
+	 */
+	memset(&args, 0, sizeof(args));
+	error = mount("autofs", fspath, MNT_UPDATE, &args);
+	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);
+	}
+}
+
+__dead static void
+usage_automount(void)
+{
+
+	fprintf(stderr, "Usage: %s [-D name=value][-o opts][-Lcfuv]\n",
+	    getprogname());
+	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 -aNur a/usr.sbin/autofs/automountd.8 b/usr.sbin/autofs/automountd.8
--- a/usr.sbin/autofs/automountd.8	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/automountd.8	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,132 @@
+.\" Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+.\" Copyright (c) 2017 The NetBSD Foundation, Inc.
+.\" Copyright (c) 2016 The DragonFly Project
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Tomohiro Kusumi.
+.\"
+.\" This software was developed by Edward Tomasz Napierala under sponsorship
+.\" from the FreeBSD Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 16, 2018
+.Dt AUTOMOUNTD 8
+.Os
+.Sh NAME
+.Nm automountd
+.Nd daemon handling autofs mount requests
+.Sh SYNOPSIS
+.Nm
+.Op Fl div
+.Op Fl D Ar name=value
+.Op Fl m Ar maxproc
+.Op Fl o Ar options
+.Sh DESCRIPTION
+The
+.Nm
+daemon is responsible for handling
+.Xr autofs 5
+mount requests, parsing maps,
+and mounting file systems 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 file systems 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 d
+Debug mode: increase verbosity and do not daemonize.
+.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 o Ar options
+Specify mount options.
+Options specified here will be overridden by options entered in maps or
+.Xr auto_master 5 .
+.It Fl v
+Increase verbosity.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr auto_master 5 ,
+.Xr autofs 5 ,
+.Xr automount 8 ,
+.Xr autounmountd 8
+.Sh HISTORY
+The
+.Nm
+daemon appeared in
+.Fx 10.1 .
+The
+.Nm
+daemon appeared in
+.Dx 4.5 .
+The
+.Nm
+command appeared in
+.Nx 9.0 .
+The
+.Nm
+command appeared in
+.Ox 6.x .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the
+.Fx
+Foundation.
+.Pp
+The
+.Nm
+was ported to
+.Dx ,
+.Nx
+and
+.Ox
+by
+.An Tomohiro Kusumi Aq Mt tkusumi@netbsd.org .
diff -aNur a/usr.sbin/autofs/automountd.c b/usr.sbin/autofs/automountd.c
--- a/usr.sbin/autofs/automountd.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/automountd.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,571 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+#include <miscfs/autofs/autofs_ioctl.h>
+
+#include "common.h"
+
+static int nchildren = 0;
+static int autofs_fd;
+static int request_id;
+static bool do_quick_exit = false;
+static char nfs_def_retry[] = "1";
+
+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;
+	size_t dstsize = strlen(*optionsp) + 1;
+
+	tofree = *optionsp;
+
+	newoptions = calloc(1, dstsize);
+	if (newoptions == NULL)
+		log_err(1, "calloc");
+
+	size_t olen = strlen(option);
+	while ((pair = strsep(optionsp, ",")) != NULL) {
+		/*
+		 * FreeBSD may use strncasecmp(3) here.
+		 */
+		if (strncmp(pair, option, olen) == 0) {
+			picked = checked_strdup(pair + olen);
+		} else {
+			if (first)
+				first = false;
+			else
+				strlcat(newoptions, ",", dstsize);
+			strlcat(newoptions, pair, dstsize);
+		}
+	}
+
+	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)
+{
+
+	if (!do_quick_exit)
+		done(EIO, true);
+}
+
+/*
+ * Emulate quick_exit(3) which doesn't exist in OpenBSD.
+ */
+__dead static void
+quick_exit(int status)
+{
+
+	assert(status == EXIT_SUCCESS);
+	assert(!do_quick_exit);
+	do_quick_exit = true;
+
+	log_debugx("quick exit pid %d, status %d", getpid(), status);
+
+	exit(status);
+}
+
+__dead 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(EXIT_SUCCESS);
+		}
+
+		/*
+		 * 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(EXIT_SUCCESS);
+	}
+
+	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");
+	}
+
+	retrycnt = NULL;
+	if (strcmp(fstype, "nfs") == 0) {
+		/*
+		 * The mount_nfs(8) command defaults to retry DEF_RETRY(10000).
+		 * 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");
+			retrycnt = nfs_def_retry;
+		}
+	}
+
+	/*
+	 * NetBSD and OpenBSD don't have -o retrycnt= option which is available
+	 * on FreeBSD and DragonFly, so use -R if the target type is NFS
+	 * (or add -o retrycnt=... to mount_nfs(8)).
+	 */
+	if (retrycnt) {
+		assert(!strcmp(fstype, "nfs"));
+		f = auto_popen("mount_nfs", "-o", options, "-R", retrycnt,
+		    node->n_location, adr->adr_path, NULL);
+	} else {
+		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(EXIT_SUCCESS);
+}
+
+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;
+}
+
+__dead static void
+usage_automountd(void)
+{
+
+	fprintf(stderr, "Usage: %s [-D name=value][-m maxproc]"
+	    "[-o opts][-Tidv]\n", getprogname());
+	exit(EXIT_FAILURE);
+}
+
+int
+main_automountd(int argc, char **argv)
+{
+	pid_t pid;
+	char *options = NULL;
+	struct autofs_daemon_request request;
+	int ch, debug = 0, error, maxproc = 30;
+	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);
+
+	autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
+	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");
+			exit(EXIT_FAILURE);
+		}
+	} else {
+		lesser_daemon();
+	}
+
+	/*
+	 * Call pidfile(3) after daemon(3).
+	 */
+	if (pidfile(NULL) == -1) {
+		if (errno == EEXIST)
+			log_errx(1, "daemon already running");
+		else if (errno == ENAMETOOLONG)
+			log_errx(1, "pidfile name too long");
+		log_err(1, "cannot create pidfile");
+	}
+
+	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;
+		}
+
+		handle_request(&request, options, incomplete_hierarchy);
+	}
+
+	return EXIT_SUCCESS;
+}
diff -aNur a/usr.sbin/autofs/autounmountd.8 b/usr.sbin/autofs/autounmountd.8
--- a/usr.sbin/autofs/autounmountd.8	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/autounmountd.8	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,118 @@
+.\" Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+.\" Copyright (c) 2017 The NetBSD Foundation, Inc.
+.\" Copyright (c) 2016 The DragonFly Project
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Tomohiro Kusumi.
+.\"
+.\" This software was developed by Edward Tomasz Napierala under sponsorship
+.\" from the FreeBSD Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 16, 2018
+.Dt AUTOUNMOUNTD 8
+.Os
+.Sh NAME
+.Nm autounmountd
+.Nd daemon unmounting automounted file systems
+.Sh SYNOPSIS
+.Nm
+.Op Fl r Ar time
+.Op Fl t Ar time
+.Op Fl dv
+.Sh DESCRIPTION
+The
+.Nm
+daemon is responsible for unmounting file systems mounted by
+.Xr automountd 8 .
+On startup,
+.Nm
+retrieves a list of file systems that have the
+.Li automounted
+mount option set.
+The list is updated every time a file system is mounted or unmounted.
+After a specified time passes,
+.Nm
+attempts to unmount a file system, 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 file system
+after a previous attempt failed, possibly due to file system being busy.
+The default value is 600, or ten minutes.
+.It Fl t
+Number of seconds to wait before trying to unmount a file system.
+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 .
+The
+.Nm
+command appeared in
+.Nx 9.0 .
+The
+.Nm
+command appeared in
+.Ox 6.x .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the
+.Fx
+Foundation.
+.Pp
+The
+.Nm
+was ported to
+.Dx ,
+.Nx
+and
+.Ox
+by
+.An Tomohiro Kusumi Aq Mt tkusumi@netbsd.org .
diff -aNur a/usr.sbin/autofs/autounmountd.c b/usr.sbin/autofs/autounmountd.c
--- a/usr.sbin/autofs/autounmountd.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/autounmountd.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,348 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "common.h"
+
+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(1, sizeof(*af));
+	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;
+}
+
+__dead static void
+usage_autounmountd(void)
+{
+
+	fprintf(stderr, "Usage: %s [-r time][-t time][-dv]\n",
+	    getprogname());
+	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 = (int)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) {
+		if (errno == EINTR)
+			return;
+		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;
+	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);
+
+	if (dont_daemonize == false) {
+		if (daemon(0, 0) == -1) {
+			log_warn("cannot daemonize");
+			exit(1);
+		}
+	}
+
+	/*
+	 * Call pidfile(3) after daemon(3).
+	 */
+	if (pidfile(NULL) == -1) {
+		if (errno == EEXIST)
+			log_errx(1, "daemon already running");
+		else if (errno == ENAMETOOLONG)
+			log_errx(1, "pidfile name too long");
+		log_err(1, "cannot create pidfile");
+	}
+
+	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 =
+			    (double)difftime((time_t)expiration_time,
+			    (time_t)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 -aNur a/usr.sbin/autofs/common.c b/usr.sbin/autofs/common.c
--- a/usr.sbin/autofs/common.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/common.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,1190 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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: head/usr.sbin/autofs/common.c 303527 2016-07-30 01:10:05Z bapt $
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+
+extern FILE *yyin;
+extern char *yytext;
+extern int yylex(void);
+
+static void	parse_master_yyin(struct node *root, const char *master);
+static void	parse_map_yyin(struct node *parent, const char *map,
+		    const char *executable_key);
+
+char *
+checked_strdup(const char *s)
+{
+	char *c;
+
+	assert(s != NULL);
+
+	c = strdup(s);
+	if (c == NULL)
+		log_err(1, "strdup");
+	return c;
+}
+
+/*
+ * Concatenate two strings, inserting separator between them, unless not needed.
+ */
+char *
+concat(const char *s1, char separator, const char *s2)
+{
+	char *result;
+	char s1last, s2first;
+	int ret;
+
+	if (s1 == NULL)
+		s1 = "";
+	if (s2 == NULL)
+		s2 = "";
+
+	if (s1[0] == '\0')
+		s1last = '\0';
+	else
+		s1last = s1[strlen(s1) - 1];
+
+	s2first = s2[0];
+
+	if (s1last == separator && s2first == separator) {
+		/*
+		 * If s1 ends with the separator and s2 begins with
+		 * it - skip the latter; otherwise concatenating "/"
+		 * and "/foo" would end up returning "//foo".
+		 */
+		ret = asprintf(&result, "%s%s", s1, s2 + 1);
+	} else if (s1last == separator || s2first == separator ||
+	    s1[0] == '\0' || s2[0] == '\0') {
+		ret = asprintf(&result, "%s%s", s1, s2);
+	} else {
+		ret = asprintf(&result, "%s%c%s", s1, separator, s2);
+	}
+	if (ret < 0)
+		log_err(1, "asprintf");
+
+	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(&copy, "/");
+		if (component == NULL)
+			break;
+		tmp = concat(partial, '/', component);
+		free(partial);
+		partial = tmp;
+		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");
+
+	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 ret;
+	size_t i, 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;
+
+		ret = asprintf(&expanded, "%.*s%s%s",
+		    (int)before_len, string, key, string + before_len + 1);
+		if (ret < 0)
+			log_err(1, "asprintf");
+
+		/*
+		 * Figure out where to start searching for next variable.
+		 */
+		string = expanded;
+		i = before_len + strlen(key);
+		backslashed = false;
+	}
+
+	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, cummulative_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 && cummulative_error == 0)
+			cummulative_error = error;
+	}
+
+	return cummulative_error;
+}
+
+static bool
+node_is_direct_key(const struct node *n)
+{
+
+	return n->n_parent != NULL && n->n_parent->n_parent == NULL &&
+	    strcmp(n->n_key, "/-") == 0;
+}
+
+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;
+
+	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;
+		}
+
+		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("");
+
+			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, (size_t)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);
+	return (sb.st_mode & S_IXUSR) || (sb.st_mode & S_IXGRP) ||
+	    (sb.st_mode & S_IXOTH);
+}
+
+/*
+ * 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) {
+				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 -aNur a/usr.sbin/autofs/common.h b/usr.sbin/autofs/common.h
--- a/usr.sbin/autofs/common.h	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/common.h	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,119 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef AUTOMOUNTD_H
+#define	AUTOMOUNTD_H
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#define	AUTO_MASTER_PATH	"/etc/auto_master"
+#define	AUTO_MAP_PREFIX		"/etc"
+#define	AUTO_SPECIAL_PREFIX	"/etc/autofs"
+#define	AUTO_INCLUDE_PATH	AUTO_SPECIAL_PREFIX "/include"
+
+struct node {
+	TAILQ_ENTRY(node)	n_next;
+	TAILQ_HEAD(nodehead, node)	n_children;
+	struct node		*n_parent;
+	char			*n_key;
+	char			*n_options;
+	char			*n_location;
+	char			*n_map;
+	const char		*n_config_file;
+	int			n_config_line;
+};
+
+struct defined_value {
+	TAILQ_ENTRY(defined_value)	d_next;
+	char				*d_name;
+	char				*d_value;
+};
+
+void	log_init(int);
+void	log_set_peer_name(const char *);
+void	log_set_peer_addr(const char *);
+void	log_err(int, const char *, ...);
+void	log_errx(int, const char *, ...);
+void	log_warn(const char *, ...);
+void	log_warnx(const char *, ...);
+void	log_debugx(const char *, ...);
+
+char	*checked_strdup(const char *);
+char	*concat(const char *, char, const char *);
+void	create_directory(const char *);
+
+struct node *node_new_root(void);
+struct node *node_new(struct node *, char *, char *, char *, const char *,
+    int);
+struct node *node_new_map(struct node *, char *, char *, char *,
+    const char *, int);
+struct node *node_find(struct node *, const char *mountpoint);
+bool	node_is_direct_map(const struct node *);
+bool	node_has_wildcards(const struct node *);
+char	*node_path(const struct node *);
+char	*node_options(const struct node *);
+void	node_expand_ampersand(struct node *, const char *);
+void	node_expand_wildcard(struct node *, const char *);
+int	node_expand_defined(struct node *);
+void	node_expand_indirect_maps(struct node *);
+void	node_print(const struct node *, const char *);
+void	parse_master(struct node *, const char *);
+void	parse_map(struct node *, const char *, const char *, bool *);
+char	*defined_expand(const char *);
+void	defined_init(void);
+void	defined_parse_and_add(char *);
+void	lesser_daemon(void);
+
+int	main_automount(int, char **);
+int	main_automountd(int, char **);
+int	main_autounmountd(int, char **);
+
+FILE	*auto_popen(const char *, ...);
+int	auto_pclose(FILE *);
+
+/*
+ * lex(1) stuff.
+ */
+extern int lineno;
+
+#define	STR	1
+#define	NEWLINE	2
+
+#endif /* !AUTOMOUNTD_H */
diff -aNur a/usr.sbin/autofs/defined.c b/usr.sbin/autofs/defined.c
--- a/usr.sbin/autofs/defined.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/defined.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,259 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+/*
+ * All the "defined" stuff is for handling variables,
+ * such as ${OSNAME}, in maps.
+ */
+
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+
+static TAILQ_HEAD(, defined_value)	defined_values;
+
+static const char *
+defined_find(const char *name)
+{
+	struct defined_value *d;
+
+	TAILQ_FOREACH(d, &defined_values, d_next) {
+		if (strcmp(d->d_name, name) == 0)
+			return d->d_value;
+	}
+
+	return NULL;
+}
+
+char *
+defined_expand(const char *string)
+{
+	const char *value;
+	char c, *expanded, *name;
+	int ret;
+	size_t i, 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 < 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((unsigned char)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",
+		    (int)before_len, string, value, string + after_off);
+		if (ret < 0)
+			log_err(1, "asprintf");
+
+		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 <= 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(1, sizeof(*d));
+	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 -aNur a/usr.sbin/autofs/log.c b/usr.sbin/autofs/log.c
--- a/usr.sbin/autofs/log.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/log.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,199 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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 <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <vis.h>
+
+#include "common.h"
+
+static int log_level = 0;
+static char *peer_name = NULL;
+static char *peer_addr = NULL;
+
+#define	MSGBUF_LEN	1024
+
+void
+log_init(int level)
+{
+
+	log_level = level;
+	openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON);
+}
+
+void
+log_set_peer_name(const char *name)
+{
+
+	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)
+{
+
+	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(EXIT_FAILURE);
+	}
+
+	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(EXIT_FAILURE);
+	}
+
+	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);
+		}
+	}
+}
+
+__dead 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);
+}
+
+__dead 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 -aNur a/usr.sbin/autofs/popen.c b/usr.sbin/autofs/popen.c
--- a/usr.sbin/autofs/popen.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/popen.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 1988, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+
+extern char **environ;
+
+struct pid {
+	SLIST_ENTRY(pid) next;
+	FILE *outfp;
+	pid_t pid;
+	char *command;
+};
+static SLIST_HEAD(, pid) pidlist = SLIST_HEAD_INITIALIZER(pidlist);
+
+#define	ARGV_LEN	42
+
+/*
+ * Replacement for popen(3), without stdin (which we do not use), but with
+ * stderr, proper logging, and improved command line arguments passing.
+ * Error handling is built in - if it returns, then it succeeded.
+ */
+FILE *
+auto_popen(const char *argv0, ...)
+{
+	va_list ap;
+	struct pid *cur, *p;
+	pid_t pid;
+	int error, i, nullfd, outfds[2];
+	char *arg, *argv[ARGV_LEN], *command;
+
+	nullfd = open(_PATH_DEVNULL, O_RDWR, 0);
+	if (nullfd < 0)
+		log_err(1, "cannot open %s", _PATH_DEVNULL);
+
+	error = pipe(outfds);
+	if (error != 0)
+		log_err(1, "pipe");
+
+	cur = malloc(sizeof(struct pid));
+	if (cur == NULL)
+		log_err(1, "malloc");
+
+	argv[0] = checked_strdup(argv0);
+	command = argv[0];
+
+	va_start(ap, argv0);
+	for (i = 1;; i++) {
+		if (i >= ARGV_LEN)
+			log_errx(1, "too many arguments to auto_popen");
+		arg = va_arg(ap, char *);
+		argv[i] = arg;
+		if (arg == NULL)
+			break;
+
+		command = concat(command, ' ', arg);
+	}
+	va_end(ap);
+
+	cur->command = checked_strdup(command);
+
+	switch (pid = fork()) {
+	case -1:			/* Error. */
+		log_err(1, "fork");
+		/* NOTREACHED */
+	case 0:				/* Child. */
+		dup2(nullfd, STDIN_FILENO);
+		dup2(outfds[1], STDOUT_FILENO);
+
+		close(nullfd);
+		close(outfds[0]);
+		close(outfds[1]);
+
+		SLIST_FOREACH(p, &pidlist, next)
+			close(fileno(p->outfp));
+		execvp(argv[0], argv);
+		log_err(1, "failed to execute %s", argv[0]);
+		/* NOTREACHED */
+	}
+
+	log_debugx("executing \"%s\" as pid %d", command, pid);
+
+	/* Parent; assume fdopen cannot fail. */
+	cur->outfp = fdopen(outfds[0], "r");
+	close(nullfd);
+	close(outfds[1]);
+
+	/* Link into list of file descriptors. */
+	cur->pid = pid;
+	SLIST_INSERT_HEAD(&pidlist, cur, next);
+
+	return cur->outfp;
+}
+
+int
+auto_pclose(FILE *iop)
+{
+	struct pid *cur, *last = NULL;
+	int status;
+	pid_t pid;
+
+	/*
+	 * Find the appropriate file pointer and remove it from the list.
+	 */
+	SLIST_FOREACH(cur, &pidlist, next) {
+		if (cur->outfp == iop)
+			break;
+		last = cur;
+	}
+	if (cur == NULL) {
+		return -1;
+	}
+	if (last == NULL)
+		SLIST_REMOVE_HEAD(&pidlist, next);
+	else
+		SLIST_REMOVE_AFTER(last, next);
+
+	fclose(cur->outfp);
+
+	do {
+		pid = wait4(cur->pid, &status, 0, NULL);
+	} while (pid == -1 && errno == EINTR);
+
+	if (WIFSIGNALED(status)) {
+		log_warnx("\"%s\", pid %d, terminated with signal %d",
+		    cur->command, pid, WTERMSIG(status));
+		return status;
+	}
+
+	if (WEXITSTATUS(status) != 0) {
+		log_warnx("\"%s\", pid %d, terminated with exit status %d",
+		    cur->command, pid, WEXITSTATUS(status));
+		return status;
+	}
+
+	log_debugx("\"%s\", pid %d, terminated gracefully", cur->command, pid);
+
+	free(cur->command);
+	free(cur);
+
+	return pid == -1 ? -1 : status;
+}
diff -aNur a/usr.sbin/autofs/token.l b/usr.sbin/autofs/token.l
--- a/usr.sbin/autofs/token.l	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/autofs/token.l	Sun Mar 25 12:09:38 2018
@@ -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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "common.h"
+
+int lineno;
+
+#define	YY_DECL int yylex(void)
+extern int	yylex(void);
+
+%}
+
+%option noinput
+%option nounput
+%option noyywrap
+
+%%
+\"[^"]+\"		{ yytext++; yytext[strlen(yytext) - 1] = '\0'; return STR; };
+[a-zA-Z0-9\.\+-_/\:\[\]$&%{}]+ { return STR; }
+#.*\n			{ lineno++; return NEWLINE; };
+\\\n			{ lineno++; };
+\n			{ lineno++; return NEWLINE; }
+[ \t]+			/* ignore whitespace */;
+.			{ return STR; }
+%%
diff -aNur a/usr.sbin/fstyp/Makefile b/usr.sbin/fstyp/Makefile
--- a/usr.sbin/fstyp/Makefile	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/Makefile	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,10 @@
+.include <bsd.own.mk>
+
+PROG=	fstyp
+SRCS=	cd9660.c exfat.c ext2fs.c fstyp.c msdosfs.c ntfs.c ufs.c
+
+MAN=	fstyp.8
+
+WARNS?=	6
+
+.include <bsd.prog.mk>
diff -aNur a/usr.sbin/fstyp/cd9660.c b/usr.sbin/fstyp/cd9660.c
--- a/usr.sbin/fstyp/cd9660.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/cd9660.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,66 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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.
+ */
+
+#include <sys/param.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fstyp.h"
+
+#define	ISO9660_MAGIC	"\x01" "CD001" "\x01\x00"
+#define	ISO9660_OFFSET	0x8000
+#define	VOLUME_LEN	32
+
+int
+fstyp_cd9660(FILE *fp, char *label, size_t size)
+{
+	char *sector, *volume;
+
+	sector = read_buf(fp, ISO9660_OFFSET, 512);
+	if (sector == NULL)
+		return 1;
+	if (bcmp(sector, ISO9660_MAGIC, sizeof(ISO9660_MAGIC) - 1) != 0) {
+		free(sector);
+		return 1;
+	}
+	volume = sector + 0x28;
+	bzero(label, size);
+	strlcpy(label, volume, MIN(size, VOLUME_LEN));
+	free(sector);
+	rtrim(label, size);
+	return 0;
+}
diff -aNur a/usr.sbin/fstyp/exfat.c b/usr.sbin/fstyp/exfat.c
--- a/usr.sbin/fstyp/exfat.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/exfat.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 Conrad Meyer <cem@FreeBSD.org>
+ * 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.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fstyp.h"
+
+struct exfat_vbr {
+	char		ev_jmp[3];
+	char		ev_fsname[8];
+	char		ev_zeros[53];
+	uint64_t	ev_part_offset;
+	uint64_t	ev_vol_length;
+	uint32_t	ev_fat_offset;
+	uint32_t	ev_fat_length;
+	uint32_t	ev_cluster_offset;
+	uint32_t	ev_cluster_count;
+	uint32_t	ev_rootdir_cluster;
+	uint32_t	ev_vol_serial;
+	uint16_t	ev_fs_revision;
+	uint16_t	ev_vol_flags;
+	uint8_t		ev_log_bytes_per_sect;
+	uint8_t		ev_log_sect_per_clust;
+	uint8_t		ev_num_fats;
+	uint8_t		ev_drive_sel;
+	uint8_t		ev_percent_used;
+} __packed;
+
+int
+fstyp_exfat(FILE *fp, char *label, size_t size)
+{
+	struct exfat_vbr *ev;
+
+	ev = read_buf(fp, 0, 512);
+	if (ev == NULL || strncmp(ev->ev_fsname, "EXFAT   ", 8) != 0)
+		goto fail;
+
+	/*
+	 * Reading the volume label requires walking the root directory to look
+	 * for a special label file.  Left as an exercise for the reader.
+	 */
+	free(ev);
+	return 0;
+
+fail:
+	free(ev);
+	return 1;
+}
diff -aNur a/usr.sbin/fstyp/ext2fs.c b/usr.sbin/fstyp/ext2fs.c
--- a/usr.sbin/fstyp/ext2fs.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/ext2fs.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,88 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2005 Stanislav Sedov
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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 <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fstyp.h"
+
+#define EXT2FS_SB_OFFSET	1024
+#define EXT2_SUPER_MAGIC	0xef53
+#define EXT2_DYNAMIC_REV	1
+
+typedef struct e2sb {
+	uint8_t		fake1[56];
+	uint16_t	s_magic;
+	uint8_t		fake2[18];
+	uint32_t	s_rev_level;
+	uint8_t		fake3[40];
+	char		s_volume_name[16];
+} e2sb_t;
+
+int
+fstyp_ext2fs(FILE *fp, char *label, size_t size)
+{
+	e2sb_t *fs;
+	char *s_volume_name;
+
+	fs = read_buf(fp, EXT2FS_SB_OFFSET, 512);
+	if (fs == NULL)
+		return 1;
+
+	/* Check for magic and versio n*/
+	if (fs->s_magic == EXT2_SUPER_MAGIC &&
+	    fs->s_rev_level == EXT2_DYNAMIC_REV) {
+		//G_LABEL_DEBUG(1, "ext2fs file system detected on %s.",
+		//    pp->name);
+	} else {
+		free(fs);
+		return 1;
+	}
+
+	s_volume_name = fs->s_volume_name;
+	/* Terminate label */
+	s_volume_name[sizeof(fs->s_volume_name) - 1] = '\0';
+
+	if (s_volume_name[0] == '/')
+		s_volume_name += 1;
+
+	strlcpy(label, s_volume_name, size);
+	free(fs);
+
+	return 0;
+}
diff -aNur a/usr.sbin/fstyp/fstyp.8 b/usr.sbin/fstyp/fstyp.8
--- a/usr.sbin/fstyp/fstyp.8	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/fstyp.8	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,152 @@
+.\" Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+.\" Copyright (c) 2017 The NetBSD Foundation, Inc.
+.\" Copyright (c) 2016 The DragonFly Project
+.\" Copyright (c) 2014 The FreeBSD Foundation
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Tomohiro Kusumi.
+.\"
+.\" This software was developed by Edward Tomasz Napierala under sponsorship
+.\" from the FreeBSD Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 16, 2018
+.Dt FSTYP 8
+.Os
+.Sh NAME
+.Nm fstyp
+.Nd determine file system type
+.Sh SYNOPSIS
+.Nm
+.Op Fl lsu
+.Ar special
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to determine the file system type on a given device.
+It can recognize ISO-9660, exFAT, Ext2, FAT, NTFS, and UFS file systems.
+When the
+.Fl u
+flag is specified,
+.Nm
+also recognizes certain additional metadata formats that cannot be
+handled using
+.Xr mount 8 ,
+if any.
+.Pp
+The file system name is printed to the standard output
+as, respectively:
+.Bl -item -offset indent -compact
+.It
+cd9660
+.It
+exfat
+.It
+ext2fs
+.It
+msdosfs
+.It
+ntfs
+.It
+ufs
+.El
+.Pp
+Because
+.Nm
+is built specifically to detect file system types, it differs from
+.Xr file 1
+in several ways.
+The output is machine-parsable, file system labels are supported,
+and it does not try to recognize any file format other than file systems.
+.Pp
+These options are available:
+.Bl -tag -width ".Fl l"
+.It Fl l
+In addition to file system type, print file system label if available.
+.It Fl s
+Ignore file type.
+By default,
+.Nm
+only works on regular files and disk-like device nodes.
+Trying to read other file types might have unexpected consequences or hang
+indefinitely.
+.It Fl u
+Include file systems and devices that cannot be mounted directly by
+.Xr mount 8 .
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs or the file system
+type is not recognized.
+.Sh SEE ALSO
+.Xr file 1 ,
+.Xr autofs 5 ,
+.Xr mount 8 ,
+.Xr zpool 8
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Fx 10.2 .
+The
+.Nm
+command appeared in
+.Dx 4.5 .
+The
+.Nm
+command appeared in
+.Nx 9.0 .
+The
+.Nm
+command appeared in
+.Ox 6.x .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the
+.Fx
+Foundation.
+ZFS and GELI support was added by
+.An Allan Jude Aq Mt allanjude@FreeBSD.org .
+.Pp
+The
+.Nm
+utility was ported to
+.Dx ,
+.Nx
+and
+.Ox
+by
+.An Tomohiro Kusumi Aq Mt tkusumi@netbsd.org .
+.Sh BUGS
+hammer is currently unsupported on
+.Nx .
+zfs, geli and hammer are currently unsupported on
+.Ox .
diff -aNur a/usr.sbin/fstyp/fstyp.c b/usr.sbin/fstyp/fstyp.c
--- a/usr.sbin/fstyp/fstyp.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/fstyp.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,254 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/disklabel.h>
+#include <sys/dkio.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "fstyp.h"
+
+#define	LABEL_LEN	256
+
+typedef int (*fstyp_function)(FILE *, char *, size_t);
+typedef int (*fsvtyp_function)(const char *, char *, size_t);
+
+static struct {
+	const char	*name;
+	fstyp_function	function;
+	bool		unmountable;
+} fstypes[] = {
+	{ "cd9660", &fstyp_cd9660, false },
+	{ "exfat", &fstyp_exfat, false },
+	{ "ext2fs", &fstyp_ext2fs, false },
+	{ "msdosfs", &fstyp_msdosfs, false },
+	{ "ntfs", &fstyp_ntfs, false },
+	{ "ufs", &fstyp_ufs, false },
+	{ NULL, NULL, NULL }
+};
+
+static struct {
+	const char	*name;
+	fsvtyp_function	function;
+	bool		unmountable;
+} fsvtypes[] = {
+	/* Unused by OpenBSD at the moment. */
+	{ NULL, NULL, NULL }
+};
+
+void *
+read_buf(FILE *fp, off_t off, size_t len)
+{
+	int error;
+	size_t nread;
+	void *buf;
+
+	error = fseeko(fp, off, SEEK_SET);
+	if (error != 0) {
+		warn("cannot seek to %jd", (uintmax_t)off);
+		return NULL;
+	}
+
+	buf = malloc(len);
+	if (buf == NULL) {
+		warn("cannot malloc %zd bytes of memory", len);
+		return NULL;
+	}
+
+	nread = fread(buf, len, 1, fp);
+	if (nread != 1) {
+		free(buf);
+		if (feof(fp) == 0)
+			warn("fread");
+		return NULL;
+	}
+
+	return buf;
+}
+
+char *
+checked_strdup(const char *s)
+{
+	char *c;
+
+	c = strdup(s);
+	if (c == NULL)
+		err(EXIT_FAILURE, "strdup");
+	return c;
+}
+
+void
+rtrim(char *label, size_t size)
+{
+	for (size_t i = size; i > 0; i--) {
+		size_t j = i - 1;
+		if (label[j] == '\0')
+			continue;
+		else if (label[j] == ' ')
+			label[j] = '\0';
+		else
+			break;
+	}
+}
+
+__dead static void
+usage(void)
+{
+
+	fprintf(stderr, "Usage: %s [-l] [-s] [-u] special\n", getprogname());
+	exit(EXIT_FAILURE);
+}
+
+static void
+type_check(const char *path, FILE *fp)
+{
+	int error, fd;
+	struct stat sb;
+	struct disklabel dl;
+
+	fd = fileno(fp);
+
+	error = fstat(fd, &sb);
+	if (error != 0)
+		err(EXIT_FAILURE, "%s: fstat", path);
+
+	if (S_ISREG(sb.st_mode))
+		return;
+
+	error = ioctl(fd, DIOCGDINFO, &dl);
+	if (error != 0)
+		errx(EXIT_FAILURE, "%s: not a disk", path);
+}
+
+int
+main(int argc, char **argv)
+{
+	int ch, error, i, nbytes;
+	bool ignore_type = false, show_label = false, show_unmountable = false;
+	char label[LABEL_LEN + 1], strvised[LABEL_LEN * 4 + 1];
+	char *path;
+	const char *name = NULL;
+	FILE *fp;
+	fstyp_function fstyp_f;
+	fsvtyp_function fsvtyp_f;
+
+	while ((ch = getopt(argc, argv, "lsu")) != -1) {
+		switch (ch) {
+		case 'l':
+			show_label = true;
+			break;
+		case 's':
+			ignore_type = true;
+			break;
+		case 'u':
+			show_unmountable = true;
+			break;
+		default:
+			usage();
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+	if (argc != 1)
+		usage();
+
+	path = argv[0];
+
+	fp = fopen(path, "r");
+	if (fp == NULL)
+		goto fsvtyp; /* DragonFly */
+
+	if (ignore_type == false)
+		type_check(path, fp);
+
+	memset(label, '\0', sizeof(label));
+
+	for (i = 0;; i++) {
+		if (!show_unmountable && fstypes[i].unmountable)
+			continue;
+		fstyp_f = fstypes[i].function;
+		if (fstyp_f == NULL)
+			break;
+
+		error = fstyp_f(fp, label, sizeof(label));
+		if (error == 0) {
+			name = fstypes[i].name;
+			goto done;
+		}
+	}
+fsvtyp:
+	for (i = 0;; i++) {
+		if (!show_unmountable && fsvtypes[i].unmountable)
+			continue;
+		fsvtyp_f = fsvtypes[i].function;
+		if (fsvtyp_f == NULL)
+			break;
+
+		error = fsvtyp_f(path, label, sizeof(label));
+		if (error == 0) {
+			name = fsvtypes[i].name;
+			goto done;
+		}
+	}
+
+	err(EXIT_FAILURE, "%s: filesystem not recognized", path);
+	/*NOTREACHED*/
+done:
+	if (show_label && label[0] != '\0') {
+		nbytes = strnvis(strvised, label, sizeof(strvised),
+		    VIS_GLOB | VIS_NL);
+		if (nbytes == -1)
+			err(EXIT_FAILURE, "strnvis");
+
+		printf("%s %s\n", name, strvised);
+	} else {
+		printf("%s\n", name);
+	}
+
+	return EXIT_SUCCESS;
+}
diff -aNur a/usr.sbin/fstyp/fstyp.h b/usr.sbin/fstyp/fstyp.h
--- a/usr.sbin/fstyp/fstyp.h	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/fstyp.h	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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 FSTYP_H
+#define	FSTYP_H
+
+void	*read_buf(FILE *, off_t, size_t);
+char	*checked_strdup(const char *);
+void	rtrim(char *, size_t);
+
+int	fstyp_cd9660(FILE *, char *, size_t);
+int	fstyp_exfat(FILE *, char *, size_t);
+int	fstyp_ext2fs(FILE *, char *, size_t);
+int	fstyp_msdosfs(FILE *, char *, size_t);
+int	fstyp_ntfs(FILE *, char *, size_t);
+int	fstyp_ufs(FILE *, char *, size_t);
+
+#endif /* !FSTYP_H */
diff -aNur a/usr.sbin/fstyp/msdosfs.c b/usr.sbin/fstyp/msdosfs.c
--- a/usr.sbin/fstyp/msdosfs.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/msdosfs.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,178 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2004 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * Copyright (c) 2006 Tobias Reifenberger
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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.
+ */
+
+#include <sys/param.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fstyp.h"
+#include "msdosfs.h"
+
+#define LABEL_NO_NAME		"NO NAME    "
+
+int
+fstyp_msdosfs(FILE *fp, char *label, size_t size)
+{
+	FAT_BSBPB *pfat_bsbpb;
+	FAT32_BSBPB *pfat32_bsbpb;
+	FAT_DES *pfat_entry;
+	uint8_t *sector0, *sector;
+
+	sector0 = NULL;
+	sector = NULL;
+
+	/* Load 1st sector with boot sector and boot parameter block. */
+	sector0 = read_buf(fp, 0, 512);
+	if (sector0 == NULL)
+		return 1;
+
+	/* Check for the FAT boot sector signature. */
+	if (sector0[510] != 0x55 || sector0[511] != 0xaa) {
+		goto error;
+	}
+
+	/*
+	 * Test if this is really a FAT volume and determine the FAT type.
+	 */
+
+	pfat_bsbpb = (FAT_BSBPB *)sector0;
+	pfat32_bsbpb = (FAT32_BSBPB *)sector0;
+
+	if (UINT16BYTES(pfat_bsbpb->BPB_FATSz16) != 0) {
+		/*
+		 * If the BPB_FATSz16 field is not zero and the string "FAT" is
+		 * at the right place, this should be a FAT12 or FAT16 volume.
+		 */
+		if (strncmp((char*)pfat_bsbpb->BS_FilSysType, "FAT", 3) != 0) {
+			goto error;
+		}
+
+		/* A volume with no name should have "NO NAME    " as label. */
+		if (strncmp((char*)pfat_bsbpb->BS_VolLab, LABEL_NO_NAME,
+		    sizeof(pfat_bsbpb->BS_VolLab)) == 0) {
+			goto endofchecks;
+		}
+		strlcpy(label, (char*)pfat_bsbpb->BS_VolLab,
+		    MIN(size, sizeof(pfat_bsbpb->BS_VolLab) + 1));
+	} else if (UINT32BYTES(pfat32_bsbpb->BPB_FATSz32) != 0) {
+		uint32_t fat_FirstDataSector, fat_BytesPerSector, offset;
+
+		/*
+		 * If the BPB_FATSz32 field is not zero and the string "FAT" is
+		 * at the right place, this should be a FAT32 volume.
+		 */
+		if (strncmp((char*)pfat32_bsbpb->BS_FilSysType, "FAT", 3) != 0)
+		{
+			goto error;
+		}
+
+		/*
+		 * If the volume label is not "NO NAME    " we're done.
+		 */
+		if (strncmp((char*)pfat32_bsbpb->BS_VolLab, LABEL_NO_NAME,
+		    sizeof(pfat32_bsbpb->BS_VolLab)) != 0) {
+			strlcpy(label, (char*)pfat32_bsbpb->BS_VolLab,
+			    MIN(size, sizeof(pfat32_bsbpb->BS_VolLab) + 1));
+			goto endofchecks;
+		}
+
+		/*
+		 * If the volume label "NO NAME    " is in the boot sector, the
+		 * label of FAT32 volumes may be stored as a special entry in
+		 * the root directory.
+		 */
+		fat_FirstDataSector =
+		    UINT16BYTES(pfat32_bsbpb->BPB_RsvdSecCnt) +
+		    (pfat32_bsbpb->BPB_NumFATs *
+		     UINT32BYTES(pfat32_bsbpb->BPB_FATSz32));
+		fat_BytesPerSector = UINT16BYTES(pfat32_bsbpb->BPB_BytsPerSec);
+
+		for (offset = fat_BytesPerSector * fat_FirstDataSector;;
+		    offset += fat_BytesPerSector) {
+			sector = read_buf(fp, offset, fat_BytesPerSector);
+			if (sector == NULL)
+				goto error;
+
+			pfat_entry = (FAT_DES *)sector;
+			do {
+				/* No more entries available. */
+				if (pfat_entry->DIR_Name[0] == 0) {
+					goto endofchecks;
+				}
+
+				/* Skip empty or long name entries. */
+				if (pfat_entry->DIR_Name[0] == 0xe5 ||
+				    (pfat_entry->DIR_Attr &
+				     FAT_DES_ATTR_LONG_NAME) ==
+				    FAT_DES_ATTR_LONG_NAME) {
+					continue;
+				}
+
+				/*
+				 * The name of the entry is the volume label if
+				 * ATTR_VOLUME_ID is set.
+				 */
+				if (pfat_entry->DIR_Attr &
+				    FAT_DES_ATTR_VOLUME_ID) {
+					strlcpy(label, (char*)pfat_entry->DIR_Name,
+					    MIN(size,
+					    sizeof(pfat_entry->DIR_Name) + 1));
+					goto endofchecks;
+				}
+			} while((uint8_t *)(++pfat_entry) <
+			    (uint8_t *)(sector + fat_BytesPerSector));
+			free(sector);
+		}
+	} else {
+		goto error;
+	}
+
+endofchecks:
+	rtrim(label, size);
+
+	free(sector0);
+	free(sector);
+
+	return (0);
+
+error:
+	free(sector0);
+	free(sector);
+
+	return (1);
+}
diff -aNur a/usr.sbin/fstyp/msdosfs.h b/usr.sbin/fstyp/msdosfs.h
--- a/usr.sbin/fstyp/msdosfs.h	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/msdosfs.h	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,146 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2006 Tobias Reifenberger
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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$
+ */
+
+#include <sys/types.h>
+
+/*
+ * Conversion macros for little endian encoded unsigned integers
+ * in byte streams to the local unsigned integer format.
+ */
+#define UINT16BYTES(p) ((uint32_t)((p)[0] + (256*(p)[1])))
+#define UINT32BYTES(p) ((uint32_t)((p)[0] + (256*(p)[1]) +		\
+	    (65536*(p)[2]) + (16777216*(p)[3])))
+
+/*
+ * All following structures are according to:
+ *
+ * Microsoft Extensible Firmware Initiative FAT32 File System Specification
+ * FAT: General Overview of On-Disk Format
+ * Version 1.03, December 6, 2000
+ * Microsoft Corporation
+ */
+
+/*
+ * FAT boot sector and boot parameter block for
+ * FAT12 and FAT16 volumes
+ */
+typedef struct fat_bsbpb {
+	/* common fields */
+	uint8_t BS_jmpBoot[3];
+	uint8_t BS_OEMName[8];
+	uint8_t BPB_BytsPerSec[2];
+	uint8_t BPB_SecPerClus;
+	uint8_t BPB_RsvdSecCnt[2];
+	uint8_t BPB_NumFATs;
+	uint8_t BPB_RootEntCnt[2];
+	uint8_t BPB_TotSec16[2];
+	uint8_t BPB_Media;
+	uint8_t BPB_FATSz16[2];
+	uint8_t BPB_SecPerTrack[2];
+	uint8_t BPB_NumHeads[2];
+	uint8_t BPB_HiddSec[4];
+	uint8_t BPB_TotSec32[4];
+	/* FAT12/FAT16 only fields */
+	uint8_t BS_DrvNum;
+	uint8_t BS_Reserved1;
+	uint8_t BS_BootSig;
+	uint8_t BS_VolID[4];
+	uint8_t BS_VolLab[11];
+	uint8_t BS_FilSysType[8];
+} FAT_BSBPB; /* 62 bytes */
+
+/*
+ * FAT boot sector and boot parameter block for
+ * FAT32 volumes
+ */
+typedef struct fat32_bsbpb {
+	/* common fields */
+	uint8_t BS_jmpBoot[3];
+	uint8_t BS_OEMName[8];
+	uint8_t BPB_BytsPerSec[2];
+	uint8_t BPB_SecPerClus;
+	uint8_t BPB_RsvdSecCnt[2];
+	uint8_t BPB_NumFATs;
+	uint8_t BPB_RootEntCnt[2];
+	uint8_t BPB_TotSec16[2];
+	uint8_t BPB_Media;
+	uint8_t BPB_FATSz16[2];
+	uint8_t BPB_SecPerTrack[2];
+	uint8_t BPB_NumHeads[2];
+	uint8_t BPB_HiddSec[4];
+	uint8_t BPB_TotSec32[4];
+	/* FAT32 only fields */
+	uint8_t BPB_FATSz32[4];
+	uint8_t BPB_ExtFlags[2];
+	uint8_t BPB_FSVer[2];
+	uint8_t BPB_RootClus[4];
+	uint8_t BPB_FSInfo[2];
+	uint8_t BPB_BkBootSec[2];
+	uint8_t BPB_Reserved[12];
+	uint8_t BS_DrvNum;
+	uint8_t BS_Reserved1;
+	uint8_t BS_BootSig;
+	uint8_t BS_VolID[4];
+	uint8_t BS_VolLab[11];
+	uint8_t BS_FilSysType[8];
+} FAT32_BSBPB; /* 90 bytes */
+
+/*
+ * FAT directory entry structure
+ */
+#define	FAT_DES_ATTR_READ_ONLY	0x01
+#define	FAT_DES_ATTR_HIDDEN	0x02
+#define	FAT_DES_ATTR_SYSTEM	0x04
+#define	FAT_DES_ATTR_VOLUME_ID	0x08
+#define	FAT_DES_ATTR_DIRECTORY	0x10
+#define	FAT_DES_ATTR_ARCHIVE	0x20
+#define	FAT_DES_ATTR_LONG_NAME	(FAT_DES_ATTR_READ_ONLY |		\
+				 FAT_DES_ATTR_HIDDEN |			\
+				 FAT_DES_ATTR_SYSTEM |			\
+				 FAT_DES_ATTR_VOLUME_ID)
+
+typedef struct fat_des {
+	uint8_t DIR_Name[11];
+	uint8_t DIR_Attr;
+	uint8_t DIR_NTRes;
+	uint8_t DIR_CrtTimeTenth;
+	uint8_t DIR_CrtTime[2];
+	uint8_t DIR_CrtDate[2];
+	uint8_t DIR_LstAccDate[2];
+	uint8_t DIR_FstClusHI[2];
+	uint8_t DIR_WrtTime[2];
+	uint8_t DIR_WrtDate[2];
+	uint8_t DIR_FstClusLO[2];
+	uint8_t DIR_FileSize[4];
+} FAT_DES;
diff -aNur a/usr.sbin/fstyp/ntfs.c b/usr.sbin/fstyp/ntfs.c
--- a/usr.sbin/fstyp/ntfs.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/ntfs.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,167 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2005 Takanori Watanabe
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fstyp.h"
+
+#define	NTFS_A_VOLUMENAME	0x60
+#define	NTFS_FILEMAGIC		((uint32_t)(0x454C4946))
+#define	NTFS_VOLUMEINO		3
+
+struct ntfs_attr {
+	uint32_t	a_type;
+	uint32_t	reclen;
+	uint8_t		a_flag;
+	uint8_t		a_namelen;
+	uint8_t		a_nameoff;
+	uint8_t		reserved1;
+	uint8_t		a_compression;
+	uint8_t		reserved2;
+	uint16_t	a_index;
+	uint16_t	a_datalen;
+	uint16_t	reserved3;
+	uint16_t	a_dataoff;
+	uint16_t	a_indexed;
+} __packed;
+
+struct ntfs_filerec {
+	uint32_t	fr_hdrmagic;
+	uint16_t	fr_hdrfoff;
+	uint16_t	fr_hdrfnum;
+	uint8_t		reserved[8];
+	uint16_t	fr_seqnum;
+	uint16_t	fr_nlink;
+	uint16_t	fr_attroff;
+	uint16_t	fr_flags;
+	uint32_t	fr_size;
+	uint32_t	fr_allocated;
+	uint64_t	fr_mainrec;
+	uint16_t	fr_attrnum;
+} __packed;
+
+struct ntfs_bootfile {
+	uint8_t		reserved1[3];
+	uint8_t		bf_sysid[8];
+	uint16_t	bf_bps;
+	uint8_t		bf_spc;
+	uint8_t		reserved2[7];
+	uint8_t		bf_media;
+	uint8_t		reserved3[2];
+	uint16_t	bf_spt;
+	uint16_t	bf_heads;
+	uint8_t		reserver4[12];
+	uint64_t	bf_spv;
+	uint64_t	bf_mftcn;
+	uint64_t	bf_mftmirrcn;
+	int8_t		bf_mftrecsz;
+	uint32_t	bf_ibsz;
+	uint32_t	bf_volsn;
+} __packed;
+
+int
+fstyp_ntfs(FILE *fp, char *label, size_t size)
+{
+	struct ntfs_bootfile *bf;
+	struct ntfs_filerec *fr;
+	struct ntfs_attr *atr;
+	off_t voloff;
+	char *filerecp, *ap;
+	int8_t mftrecsz;
+	char vnchar;
+	size_t recsize, j;
+
+	filerecp = NULL;
+
+	bf = read_buf(fp, 0, 512);
+	if (bf == NULL || strncmp((char*)bf->bf_sysid, "NTFS    ", 8) != 0)
+		goto fail;
+
+	mftrecsz = bf->bf_mftrecsz;
+	recsize = mftrecsz > 0 ? (size_t)(mftrecsz * bf->bf_bps * bf->bf_spc)
+	    : (size_t)(1 << -mftrecsz);
+
+	voloff = (off_t)(bf->bf_mftcn * bf->bf_spc * bf->bf_bps +
+	    recsize * NTFS_VOLUMEINO);
+
+	filerecp = read_buf(fp, voloff, recsize);
+	if (filerecp == NULL)
+		goto fail;
+	fr = (struct ntfs_filerec *)filerecp;
+
+	if (fr->fr_hdrmagic != NTFS_FILEMAGIC)
+		goto fail;
+
+	for (ap = filerecp + fr->fr_attroff;
+	    atr = (struct ntfs_attr *)ap, (int)atr->a_type != -1;
+	    ap += atr->reclen) {
+		if (atr->a_type == NTFS_A_VOLUMENAME) {
+			if(atr->a_datalen >= size *2){
+				goto fail;
+			}
+			/*
+			 * UNICODE to ASCII.
+			 * Should we need to use iconv(9)?
+			 */
+			for (j = 0; j < atr->a_datalen; j++) {
+				vnchar = *(ap + atr->a_dataoff + j);
+				if (j & 1) {
+					if (vnchar) {
+						goto fail;
+					}
+				} else {
+					label[j / 2] = vnchar;
+				}
+			}
+			label[j / 2] = 0;
+			break;
+		}
+	}
+
+	free(bf);
+	free(filerecp);
+
+	return 0;
+
+fail:
+	free(bf);
+	free(filerecp);
+
+	return 1;
+}
diff -aNur a/usr.sbin/fstyp/ufs.c b/usr.sbin/fstyp/ufs.c
--- a/usr.sbin/fstyp/ufs.c	Thu Jan  1 09:00:00 1970
+++ b/usr.sbin/fstyp/ufs.c	Sun Mar 25 12:09:38 2018
@@ -0,0 +1,112 @@
+/*-
+ * Copyright (c) 2018 Tomohiro Kusumi <tkusumi@netbsd.org>
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2002, 2003 Gordon Tetlow
+ * Copyright (c) 2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
+ *
+ * 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.
+ */
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <ufs/ffs/fs.h>
+
+#include "fstyp.h"
+
+static const int superblocks[] = SBLOCKSEARCH;
+
+int
+fstyp_ufs(FILE *fp, char *label, size_t labelsize)
+{
+	int sb, superblock;
+	struct fs *fs;
+
+	/*
+	 * Walk through the standard places that superblocks hide and look
+	 * for UFS magic. If we find magic, then check that the size in the
+	 * superblock corresponds to the size of the underlying provider.
+	 * Finally, look for a volume label and create an appropriate
+	 * provider based on that.
+	 */
+	for (sb = 0; (superblock = superblocks[sb]) != -1; sb++) {
+		fs = (struct fs *)read_buf(fp, superblock, SBLOCKSIZE);
+		if (fs == NULL)
+			continue;
+		/*
+		 * Check for magic. We also need to check if file system size
+		 * is equal to providers size, because sysinstall(8) used to
+		 * bogusly put first partition at offset 0 instead of 16, and
+		 * glabel/ufs would find file system on slice instead of
+		 * partition.
+		 */
+#ifdef notyet
+		if (fs->fs_magic == FS_UFS1_MAGIC && fs->fs_fsize > 0 &&
+		    ((pp->mediasize / fs->fs_fsize == fs->fs_old_size) ||
+		    (pp->mediasize / fs->fs_fsize == fs->fs_providersize))) {
+		    	/* Valid UFS1. */
+		} else if (fs->fs_magic == FS_UFS2_MAGIC && fs->fs_fsize > 0 &&
+		    ((pp->mediasize / fs->fs_fsize == fs->fs_size) ||
+		    (pp->mediasize / fs->fs_fsize == fs->fs_providersize))) {
+		    	/* Valid UFS2. */
+		} else {
+			g_free(fs);
+			continue;
+		}
+#else
+		if (fs->fs_magic == FS_UFS1_MAGIC && fs->fs_fsize > 0) {
+		    	/* Valid UFS1. */
+		} else if (fs->fs_magic == FS_UFS2_MAGIC && fs->fs_fsize > 0) {
+		    	/* Valid UFS2. */
+		} else {
+			free(fs);
+			continue;
+		}
+#endif
+
+		if (fs->fs_sblockloc != superblock || fs->fs_ncg < 1 ||
+		    fs->fs_bsize < MINBSIZE ||
+		    (size_t)fs->fs_bsize < sizeof(struct fs)) {
+			free(fs);
+			continue;
+		}
+
+		strlcpy(label, (char*)fs->fs_volname, labelsize);
+
+		free(fs);
+		return 0;
+	}
+
+	return 1;
+}
