From 416204bcd6f1e988fefe8f528a949290b8082c78 Mon Sep 17 00:00:00 2001
From: Tomohiro Kusumi <kusumi.tomohiro@gmail.com>
Date: Sun, 7 Jan 2018 13:38:02 +0200
Subject: [PATCH] NetBSD autofs v2

This patch applies to 8.99.11.
---
 distrib/sets/lists/base/mi     |    6 +
 distrib/sets/lists/comp/mi     |    2 +
 distrib/sets/lists/etc/mi      |   10 +
 distrib/sets/lists/man/mi      |   18 +
 distrib/sets/lists/modules/mi  |    2 +
 etc/MAKEDEV.tmpl               |    5 +
 etc/Makefile                   |    4 +-
 etc/auto_master                |    9 +
 etc/autofs/Makefile            |   14 +
 etc/autofs/include_ldap        |   55 ++
 etc/autofs/include_nis         |   14 +
 etc/autofs/include_nis_nullfs  |  180 ++++++
 etc/autofs/special_hosts       |   17 +
 etc/autofs/special_media       |  130 +++++
 etc/autofs/special_noauto      |   29 +
 etc/autofs/special_null        |    4 +
 etc/defaults/rc.conf           |    3 +
 etc/mtree/NetBSD.dist.base     |    2 +
 etc/mtree/special              |    8 +
 etc/rc.d/Makefile              |    2 +-
 etc/rc.d/automount             |   31 +
 etc/rc.d/automountd            |   19 +
 etc/rc.d/autounmountd          |   18 +
 include/mntopts.h              |    4 +-
 lib/libc/sys/kqueue.2          |    5 +-
 sbin/mount/mount.8             |   12 +-
 share/man/man5/Makefile        |    2 +-
 share/man/man5/autofs.5        |  121 ++++
 share/man/man5/rc.conf.5       |   43 +-
 share/man/man7/hier.7          |    6 +-
 sys/arch/amd64/conf/ALL        |    1 +
 sys/arch/amd64/conf/GENERIC    |    1 +
 sys/arch/amd64/conf/MODULAR    |    1 +
 sys/arch/amd64/conf/XEN3_DOM0  |    1 +
 sys/arch/amd64/conf/XEN3_DOMU  |    1 +
 sys/conf/files                 |    1 +
 sys/conf/filesystems.config    |    1 +
 sys/conf/majors                |    1 +
 sys/fs/Makefile                |    2 +-
 sys/fs/autofs/Makefile         |    5 +
 sys/fs/autofs/autofs.c         |  615 ++++++++++++++++++++
 sys/fs/autofs/autofs.h         |  206 +++++++
 sys/fs/autofs/autofs_ioctl.h   |  125 +++++
 sys/fs/autofs/autofs_mount.h   |   45 ++
 sys/fs/autofs/autofs_vfsops.c  |  501 +++++++++++++++++
 sys/fs/autofs/autofs_vnops.c   |  696 +++++++++++++++++++++++
 sys/fs/autofs/files.autofs     |    7 +
 sys/kern/kern_event.c          |   50 ++
 sys/kern/vfs_syscalls.c        |    5 +
 sys/modules/Makefile           |    1 +
 sys/modules/autofs/Makefile    |   11 +
 sys/sys/event.h                |    5 +-
 sys/sys/fstypes.h              |   13 +-
 sys/sys/mount.h                |    5 +
 sys/sys/vnode.h                |    4 +-
 usr.bin/showmount/showmount.8  |   10 +-
 usr.bin/showmount/showmount.c  |   31 +-
 usr.sbin/Makefile              |    4 +-
 usr.sbin/autofs/Makefile       |   15 +
 usr.sbin/autofs/auto_master.5  |  392 +++++++++++++
 usr.sbin/autofs/automount.8    |  133 +++++
 usr.sbin/autofs/automount.c    |  346 ++++++++++++
 usr.sbin/autofs/automountd.8   |  126 +++++
 usr.sbin/autofs/automountd.c   |  598 ++++++++++++++++++++
 usr.sbin/autofs/autounmountd.8 |  110 ++++
 usr.sbin/autofs/autounmountd.c |  350 ++++++++++++
 usr.sbin/autofs/common.c       | 1211 ++++++++++++++++++++++++++++++++++++++++
 usr.sbin/autofs/common.h       |  119 ++++
 usr.sbin/autofs/defined.c      |  257 +++++++++
 usr.sbin/autofs/log.c          |  203 +++++++
 usr.sbin/autofs/popen.c        |  194 +++++++
 usr.sbin/autofs/token.l        |   59 ++
 usr.sbin/fstyp/Makefile        |   26 +
 usr.sbin/fstyp/cd9660.c        |   65 +++
 usr.sbin/fstyp/ext2fs.c        |   87 +++
 usr.sbin/fstyp/fstyp.8         |  148 +++++
 usr.sbin/fstyp/fstyp.c         |  257 +++++++++
 usr.sbin/fstyp/fstyp.h         |   53 ++
 usr.sbin/fstyp/msdosfs.c       |  178 ++++++
 usr.sbin/fstyp/msdosfs.h       |  145 +++++
 usr.sbin/fstyp/ntfs.c          |  165 ++++++
 usr.sbin/fstyp/ufs.c           |  110 ++++
 usr.sbin/fstyp/zfs.c           |   79 +++
 usr.sbin/mountd/mountd.c       |    4 +
 84 files changed, 8528 insertions(+), 26 deletions(-)
 create mode 100644 etc/auto_master
 create mode 100644 etc/autofs/Makefile
 create mode 100644 etc/autofs/include_ldap
 create mode 100644 etc/autofs/include_nis
 create mode 100644 etc/autofs/include_nis_nullfs
 create mode 100644 etc/autofs/special_hosts
 create mode 100755 etc/autofs/special_media
 create mode 100755 etc/autofs/special_noauto
 create mode 100644 etc/autofs/special_null
 create mode 100755 etc/rc.d/automount
 create mode 100755 etc/rc.d/automountd
 create mode 100755 etc/rc.d/autounmountd
 create mode 100644 share/man/man5/autofs.5
 create mode 100644 sys/fs/autofs/Makefile
 create mode 100644 sys/fs/autofs/autofs.c
 create mode 100644 sys/fs/autofs/autofs.h
 create mode 100644 sys/fs/autofs/autofs_ioctl.h
 create mode 100644 sys/fs/autofs/autofs_mount.h
 create mode 100644 sys/fs/autofs/autofs_vfsops.c
 create mode 100644 sys/fs/autofs/autofs_vnops.c
 create mode 100644 sys/fs/autofs/files.autofs
 create mode 100644 sys/modules/autofs/Makefile
 create mode 100644 usr.sbin/autofs/Makefile
 create mode 100644 usr.sbin/autofs/auto_master.5
 create mode 100644 usr.sbin/autofs/automount.8
 create mode 100644 usr.sbin/autofs/automount.c
 create mode 100644 usr.sbin/autofs/automountd.8
 create mode 100644 usr.sbin/autofs/automountd.c
 create mode 100644 usr.sbin/autofs/autounmountd.8
 create mode 100644 usr.sbin/autofs/autounmountd.c
 create mode 100644 usr.sbin/autofs/common.c
 create mode 100644 usr.sbin/autofs/common.h
 create mode 100644 usr.sbin/autofs/defined.c
 create mode 100644 usr.sbin/autofs/log.c
 create mode 100644 usr.sbin/autofs/popen.c
 create mode 100644 usr.sbin/autofs/token.l
 create mode 100644 usr.sbin/fstyp/Makefile
 create mode 100644 usr.sbin/fstyp/cd9660.c
 create mode 100644 usr.sbin/fstyp/ext2fs.c
 create mode 100644 usr.sbin/fstyp/fstyp.8
 create mode 100644 usr.sbin/fstyp/fstyp.c
 create mode 100644 usr.sbin/fstyp/fstyp.h
 create mode 100644 usr.sbin/fstyp/msdosfs.c
 create mode 100644 usr.sbin/fstyp/msdosfs.h
 create mode 100644 usr.sbin/fstyp/ntfs.c
 create mode 100644 usr.sbin/fstyp/ufs.c
 create mode 100644 usr.sbin/fstyp/zfs.c

diff --git a/distrib/sets/lists/base/mi b/distrib/sets/lists/base/mi
index 850ad932250c..0df27386ba52 100644
--- a/distrib/sets/lists/base/mi
+++ b/distrib/sets/lists/base/mi
@@ -91,6 +91,7 @@
 ./etc/X11/xserver				base-x11-root
 ./etc/X11/xsm					base-x11-root
 ./etc/atf					base-atf-bin
+./etc/autofs					base-sys-root
 ./etc/cgd					base-sys-root
 ./etc/defaults					base-sys-root
 ./etc/disklabels				base-obsolete		obsolete
@@ -1104,6 +1105,7 @@
 ./usr/include/event2				base-c-usr
 ./usr/include/filecorefs			base-c-usr
 ./usr/include/fs				base-c-usr
+./usr/include/fs/autofs				base-c-usr
 ./usr/include/fs/efs				base-c-usr
 ./usr/include/fs/hfs				base-c-usr
 ./usr/include/fs/hfsp				base-obsolete		obsolete
@@ -1613,6 +1615,9 @@
 ./usr/sbin/arp					base-netutil-bin
 ./usr/sbin/audit-packages			base-pkgutil-bin
 ./usr/sbin/authpf				base-pf-bin		pf
+./usr/sbin/automount				base-sysutil-root
+./usr/sbin/automountd				base-sysutil-root
+./usr/sbin/autounmountd				base-sysutil-root
 ./usr/sbin/bootpd				base-bootserver-bin
 ./usr/sbin/bootpef				base-bootserver-bin
 ./usr/sbin/bootpgw				base-bootserver-bin
@@ -1674,6 +1679,7 @@
 ./usr/sbin/flush				base-obsolete		obsolete
 ./usr/sbin/fsinfo				base-sysutil-bin
 ./usr/sbin/fssconfig				base-sysutil-bin
+./usr/sbin/fstyp				base-sysutil-bin
 ./usr/sbin/ftp-proxy				base-pf-bin		pf
 ./usr/sbin/fusermount				base-refuse-bin
 ./usr/sbin/fwctl				base-sysutil-bin
diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
index 47cb1cba9037..34299b382e2c 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -827,6 +827,8 @@
 ./usr/include/fnmatch.h				comp-c-include
 ./usr/include/form.h				comp-c-include
 ./usr/include/frame.h				comp-obsolete		obsolete
+./usr/include/fs/autofs/autofs_mount.h		comp-c-include
+./usr/include/fs/autofs/autofs_ioctl.h		comp-c-include
 ./usr/include/fs/efs/efs.h			comp-c-include
 ./usr/include/fs/efs/efs_mount.h		comp-c-include
 ./usr/include/fs/efs/efs_sb.h			comp-c-include
diff --git a/distrib/sets/lists/etc/mi b/distrib/sets/lists/etc/mi
index acdb84f3bea9..6f9645270b23 100644
--- a/distrib/sets/lists/etc/mi
+++ b/distrib/sets/lists/etc/mi
@@ -17,6 +17,13 @@
 ./etc/atf/atf-run.hooks				etc-atf-etc		atf
 ./etc/atf/common.conf				etc-atf-etc		atf
 ./etc/audit-packages.conf			etc-obsolete		obsolete
+./etc/auto_master				etc-sys-etc
+./etc/autofs/include_ldap			etc-sys-etc
+./etc/autofs/include_nis			etc-sys-etc
+./etc/autofs/special_hosts			etc-sys-etc
+./etc/autofs/special_media			etc-sys-etc
+./etc/autofs/special_noauto			etc-sys-etc
+./etc/autofs/special_null			etc-sys-etc
 ./etc/bluetooth					etc-sys-etc
 ./etc/bluetooth/bluetooth.conf			etc-obsolete		obsolete
 ./etc/bluetooth/btattach.conf			etc-sys-etc
@@ -176,6 +183,9 @@
 ./etc/rc.d/altqd				etc-router-rc
 ./etc/rc.d/amd					etc-amd-rc
 ./etc/rc.d/apmd					etc-sys-rc
+./etc/rc.d/automount				etc-sys-rc
+./etc/rc.d/automountd				etc-sys-rc
+./etc/rc.d/autounmountd				etc-sys-rc
 ./etc/rc.d/blacklistd				etc-sys-rc
 ./etc/rc.d/bluetooth				etc-sys-rc
 ./etc/rc.d/bootconf.sh				etc-sys-rc
diff --git a/distrib/sets/lists/man/mi b/distrib/sets/lists/man/mi
index 468f442b4177..2f637c56f58a 100644
--- a/distrib/sets/lists/man/mi
+++ b/distrib/sets/lists/man/mi
@@ -2057,6 +2057,8 @@
 ./usr/share/man/cat5/ar.0			man-sys-catman		.cat
 ./usr/share/man/cat5/atf-formats.0		man-atf-catman		.cat,atf
 ./usr/share/man/cat5/audit-packages.conf.0	man-obsolete		obsolete
+./usr/share/man/cat5/auto_master.0		man-sysutil-catman	.cat
+./usr/share/man/cat5/autofs.0			man-sysutil-catman	.cat
 ./usr/share/man/cat5/blacklistd.conf.0		man-sysutil-catman	.cat
 ./usr/share/man/cat5/bluetooth.conf.0		man-obsolete		obsolete
 ./usr/share/man/cat5/body_checks.0		man-postfix-catman	postfix,.cat
@@ -2368,6 +2370,9 @@
 ./usr/share/man/cat8/atari/makedev.0		man-obsolete		obsolete
 ./usr/share/man/cat8/atrun.0			man-cron-catman		.cat
 ./usr/share/man/cat8/authpf.0			man-pf-catman		pf,.cat
+./usr/share/man/cat8/automount.0		man-sysutil-catman	.cat
+./usr/share/man/cat8/automountd.0		man-sysutil-catman	.cat
+./usr/share/man/cat8/autounmountd.0		man-sysutil-catman	.cat
 ./usr/share/man/cat8/bad144.0			man-sysutil-catman	.cat
 ./usr/share/man/cat8/badsect.0			man-sysutil-catman	.cat
 ./usr/share/man/cat8/bebox/MAKEDEV.0		man-obsolete		obsolete
@@ -2515,6 +2520,7 @@
 ./usr/share/man/cat8/fsinfo.0			man-sysutil-catman	.cat
 ./usr/share/man/cat8/fsirand.0			man-sysutil-catman	.cat
 ./usr/share/man/cat8/fssconfig.0		man-sysutil-catman	.cat
+./usr/share/man/cat8/fstyp.0			man-sysutil-catman	.cat
 ./usr/share/man/cat8/ftp-proxy.0		man-pf-catman		pf,.cat
 ./usr/share/man/cat8/ftpd.0			man-netutil-catman	.cat
 ./usr/share/man/cat8/fusermount.0		man-refuse-catman	.cat
@@ -5103,6 +5109,8 @@
 ./usr/share/man/html5/ar.html			man-sys-htmlman		html
 ./usr/share/man/html5/atf-formats.html		man-atf-htmlman		html,atf
 ./usr/share/man/html5/audit-packages.conf.html	man-obsolete		obsolete
+./usr/share/man/html5/auto_master.html		man-sysutil-htmlman	html
+./usr/share/man/html5/autofs.html		man-sysutil-htmlman	html
 ./usr/share/man/html5/blacklistd.conf.html	man-sysutil-htmlman	html
 ./usr/share/man/html5/body_checks.html		man-postfix-htmlman	postfix,html
 ./usr/share/man/html5/boot.cfg.html		man-sys-htmlman		html
@@ -5365,6 +5373,9 @@
 ./usr/share/man/html8/atari/installboot.html	man-sysutil-htmlman	html
 ./usr/share/man/html8/atrun.html		man-cron-htmlman	html
 ./usr/share/man/html8/authpf.html		man-pf-htmlman		pf,html
+./usr/share/man/html8/automount.html		man-sysutil-htmlman	html
+./usr/share/man/html8/automountd.html		man-sysutil-htmlman	html
+./usr/share/man/html8/autounmountd.html		man-sysutil-htmlman	html
 ./usr/share/man/html8/bad144.html		man-sysutil-htmlman	html
 ./usr/share/man/html8/badsect.html		man-sysutil-htmlman	html
 ./usr/share/man/html8/bioctl.html		man-sysutil-htmlman	html
@@ -5482,6 +5493,7 @@
 ./usr/share/man/html8/fsinfo.html		man-sysutil-htmlman	html
 ./usr/share/man/html8/fsirand.html		man-sysutil-htmlman	html
 ./usr/share/man/html8/fssconfig.html		man-sysutil-htmlman	html
+./usr/share/man/html8/fstyp.html		man-sysutil-htmlman	html
 ./usr/share/man/html8/ftp-proxy.html		man-pf-htmlman		pf,html
 ./usr/share/man/html8/ftpd.html			man-netutil-htmlman	html
 ./usr/share/man/html8/fusermount.html		man-refuse-htmlman	html
@@ -8081,6 +8093,8 @@
 ./usr/share/man/man5/ar.5			man-sys-man		.man
 ./usr/share/man/man5/atf-formats.5		man-atf-man		.man,atf
 ./usr/share/man/man5/audit-packages.conf.5	man-obsolete		obsolete
+./usr/share/man/man5/auto_master.5		man-sysutil-man		.man
+./usr/share/man/man5/autofs.5			man-sysutil-man		.man
 ./usr/share/man/man5/blacklistd.conf.5		man-sysutil-man		.man
 ./usr/share/man/man5/bluetooth.conf.5		man-obsolete		obsolete
 ./usr/share/man/man5/body_checks.5		man-postfix-man		postfix,.man
@@ -8391,6 +8405,9 @@
 ./usr/share/man/man8/atari/makedev.8		man-obsolete		obsolete
 ./usr/share/man/man8/atrun.8			man-cron-man		.man
 ./usr/share/man/man8/authpf.8			man-pf-man		pf,.man
+./usr/share/man/man8/automount.8		man-sysutil-man		.man
+./usr/share/man/man8/automountd.8		man-sysutil-man		.man
+./usr/share/man/man8/autounmountd.8		man-sysutil-man		.man
 ./usr/share/man/man8/bad144.8			man-sysutil-man		.man
 ./usr/share/man/man8/badsect.8			man-sysutil-man		.man
 ./usr/share/man/man8/bebox/MAKEDEV.8		man-obsolete		obsolete
@@ -8538,6 +8555,7 @@
 ./usr/share/man/man8/fsinfo.8			man-sysutil-man		.man
 ./usr/share/man/man8/fsirand.8			man-sysutil-man		.man
 ./usr/share/man/man8/fssconfig.8		man-sysutil-man		.man
+./usr/share/man/man8/fstyp.8			man-sysutil-man		.man
 ./usr/share/man/man8/ftp-proxy.8		man-pf-man		pf,.man
 ./usr/share/man/man8/ftpd.8			man-netutil-man		.man
 ./usr/share/man/man8/fusermount.8		man-refuse-man		.man
diff --git a/distrib/sets/lists/modules/mi b/distrib/sets/lists/modules/mi
index 1c510fa25703..f26e816851c8 100644
--- a/distrib/sets/lists/modules/mi
+++ b/distrib/sets/lists/modules/mi
@@ -28,6 +28,8 @@
 ./@MODULEDIR@/ataraid/ataraid.kmod		base-obsolete		obsolete
 ./@MODULEDIR@/audio				base-kernel-modules	kmod
 ./@MODULEDIR@/audio/audio.kmod			base-kernel-modules	kmod
+./@MODULEDIR@/autofs				base-kernel-modules	kmod
+./@MODULEDIR@/autofs/autofs.kmod		base-kernel-modules	kmod
 ./@MODULEDIR@/blowfish				base-kernel-modules	kmod
 ./@MODULEDIR@/blowfish/blowfish.kmod		base-kernel-modules	kmod
 ./@MODULEDIR@/bpf				base-kernel-modules	kmod
diff --git a/etc/MAKEDEV.tmpl b/etc/MAKEDEV.tmpl
index cb93e5d5d9cf..07a1ed71be58 100644
--- a/etc/MAKEDEV.tmpl
+++ b/etc/MAKEDEV.tmpl
@@ -842,6 +842,7 @@ all)
 	makedev hdmicec0
 	makedev dtrace
 	makedev veriexec
+	makedev autofs
 	makedev local # do this last
 	;;
 
@@ -2242,6 +2243,10 @@ local)
 	fi
 	;;
 
+autofs)
+	mkdev autofs c %autofs_chr% 0
+	;;
+
 *)
 	warn "$i: unknown device"
 	;;
diff --git a/etc/Makefile b/etc/Makefile
index bc44f003df61..e4773367f8d2 100644
--- a/etc/Makefile
+++ b/etc/Makefile
@@ -101,7 +101,7 @@ INSTALLATION_DIRS=	binary binary/sets binary/kernel installation
 BINOWN= root
 BINGRP= wheel
 UTMPGRP= utmp
-BIN1+=	bootptab changelist csh.cshrc csh.login \
+BIN1+=	auto_master bootptab changelist csh.cshrc csh.login \
 	csh.logout daily daily.conf dm.conf envsys.conf floppytab ftpchroot \
 	ftpusers gettytab gpio.conf group hosts hosts.lpd inetd.conf \
 	locate.conf login.conf mailer.conf man.conf monthly monthly.conf \
@@ -345,7 +345,7 @@ install-etc-files: .PHONY .MAKE check_DESTDIR MAKEDEV
     			/dev/null ${DESTDIR}${file}; \
 	else true; fi
 .endfor
-.for subdir in . defaults bluetooth iscsi mtree namedb pam.d powerd rc.d root skel ssh
+.for subdir in . defaults autofs bluetooth iscsi mtree namedb pam.d powerd rc.d root skel ssh
 	${MAKEDIRTARGET} ${subdir} configinstall
 .endfor
 	${MAKEDIRTARGET} ${NETBSDSRCDIR}/external/bsd/dhcpcd/sbin/dhcpcd configinstall
diff --git a/etc/auto_master b/etc/auto_master
new file mode 100644
index 000000000000..55ee99172ea0
--- /dev/null
+++ b/etc/auto_master
@@ -0,0 +1,9 @@
+# $FreeBSD$
+#
+# Automounter master map, see auto_master(5) for details.
+#
+/net		-hosts		-nobrowse,nosuid,intr
+# When using the -media special map, make sure to edit devd.conf(5)
+# to move the call to "automount -c" out of the comments section.
+#/media		-media		-nosuid,noatime
+#/-		-noauto
diff --git a/etc/autofs/Makefile b/etc/autofs/Makefile
new file mode 100644
index 000000000000..105ed3a63fd4
--- /dev/null
+++ b/etc/autofs/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+FILES=		include_ldap \
+		include_nis \
+		include_nis_nullfs \
+		special_hosts \
+		special_media \
+		special_noauto \
+		special_null
+
+FILESDIR=	/etc/autofs
+FILESMODE=	755
+
+.include <bsd.prog.mk>
diff --git a/etc/autofs/include_ldap b/etc/autofs/include_ldap
new file mode 100644
index 000000000000..4cf70bfeef81
--- /dev/null
+++ b/etc/autofs/include_ldap
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+# Modify this to suit your needs.  The "$1" is the map name, eg. "auto_master".
+# To debug, simply run this script with map name as the only parameter.  It's
+# supposed to output map contents ("key location" pairs) to standard output.
+SEARCHBASE="ou=$1,dc=example,dc=com"
+ENTRY_ATTRIBUTE="cn"
+VALUE_ATTRIBUTE="automountInformation"
+
+/usr/local/bin/ldapsearch -LLL -x -o ldif-wrap=no -b "$SEARCHBASE" "$ENTRY_ATTRIBUTE" "$VALUE_ATTRIBUTE" | awk '
+$1 == "'$ENTRY_ATTRIBUTE':" {
+	key = $2
+}
+
+$1 == "'$VALUE_ATTRIBUTE':" {
+	for (i = 2; i <= NF; i++) {
+		value[i] = $(i)
+	}
+	nvalues = NF
+	b64 = 0
+}
+
+# Double colon after attribute name means the value is in Base64.
+$1 == "'$VALUE_ATTRIBUTE'::" {
+	for (i = 2; i <= NF; i++) {
+		value[i] = $(i)
+	}
+	nvalues = NF
+	b64 = 1
+}
+
+# Empty line - end of record.
+NF == 0 && key != "" && nvalues > 0 {
+	printf "%s%s", key, OFS
+	for (i = 2; i < nvalues; i++) {
+		printf "%s%s", value[i], OFS
+	}
+	if (b64 == 1) {
+		printf "%s", value[nvalues] | "b64decode -rp"
+		close("b64decode -rp")
+		printf "%s", ORS
+	} else {
+		printf "%s%s", value[nvalues], ORS
+	}
+}
+
+NF == 0 {
+	key = ""
+	nvalues = 0
+	delete value
+}
+'
diff --git a/etc/autofs/include_nis b/etc/autofs/include_nis
new file mode 100644
index 000000000000..5289984639a1
--- /dev/null
+++ b/etc/autofs/include_nis
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+if [ -n "$2" ]; then
+	ypmatch $2 $1
+	exit 0
+fi
+
+if [ -n "$1" ]; then
+	ypcat -k $1
+	exit 0
+fi
diff --git a/etc/autofs/include_nis_nullfs b/etc/autofs/include_nis_nullfs
new file mode 100644
index 000000000000..0c3b0975d4b5
--- /dev/null
+++ b/etc/autofs/include_nis_nullfs
@@ -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.
+#
+# $FreeBSD$
+#
+
+#
+# /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 --git a/etc/autofs/special_hosts b/etc/autofs/special_hosts
new file mode 100644
index 000000000000..c49854681451
--- /dev/null
+++ b/etc/autofs/special_hosts
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+if [ $# -eq 0 ]; then
+	out=`getent hosts`
+	[ $? -eq 0 ] || exit 1
+	echo "$out" | awk '{ print $2 }' | sort -u
+	exit 0
+fi
+
+out=`showmount -E "$1"`
+[ $? -eq 0 ] || exit 1
+echo "$out" | awk -v host="$1" \
+    '{ printf "\"%s\"\t\"%s:%s\" ", $0, host, $0 } END { printf "\n" }'
+
diff --git a/etc/autofs/special_media b/etc/autofs/special_media
new file mode 100755
index 000000000000..7e5c4f5e6b86
--- /dev/null
+++ b/etc/autofs/special_media
@@ -0,0 +1,130 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+# Print newline-separated list of devices available for mounting.
+# If there is a filesystem label - use it, otherwise use device name.
+print_available() {
+	local _fstype _fstype_and_label _label _p
+
+	for _p in ${DEVICES}; do
+		_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 --git a/etc/autofs/special_noauto b/etc/autofs/special_noauto
new file mode 100755
index 000000000000..09555cd92c15
--- /dev/null
+++ b/etc/autofs/special_noauto
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+print_available() {
+	sed 's/#.*//' /etc/fstab | awk '$4 ~ /noauto/ { print $2 }'
+}
+
+print_one() {
+	local _mntpoint
+
+	_mntpoint="${1%/}"
+
+	sed 's/#.*//' /etc/fstab | awk '
+	$2 == "'"${_mntpoint}"'" && $4 ~ /noauto/ {
+		if ($1 ~ /:/) { dev=$1 } else { dev=":"$1 }
+		print "-fstype=" $3 "," $4, dev
+	}'
+}
+
+if [ $# -eq 0 ]; then
+	print_available
+	exit 0
+fi
+
+print_one "$1"
+exit 0
+
diff --git a/etc/autofs/special_null b/etc/autofs/special_null
new file mode 100644
index 000000000000..41c10001e676
--- /dev/null
+++ b/etc/autofs/special_null
@@ -0,0 +1,4 @@
+#!/usr/bin/true
+#
+# $FreeBSD$
+#
diff --git a/etc/defaults/rc.conf b/etc/defaults/rc.conf
index 580f65cd8f58..89630d91ebbd 100644
--- a/etc/defaults/rc.conf
+++ b/etc/defaults/rc.conf
@@ -330,6 +330,9 @@ sdpd=YES                sdpd_flags=""
 rwhod=NO		rwhod_flags="-u _rwhod"
 devpubd=NO		devpubd_flags=""	# autocreate nodes for new devs
 envsys=NO					# Set /etc/envsys.conf preferences
+autofs=NO		automount_flags=""
+			automountd_flags=""
+			autounmountd_flags=""
 
 # Hardware daemons.
 #
diff --git a/etc/mtree/NetBSD.dist.base b/etc/mtree/NetBSD.dist.base
index 80c27a571915..a62527a44626 100644
--- a/etc/mtree/NetBSD.dist.base
+++ b/etc/mtree/NetBSD.dist.base
@@ -29,6 +29,7 @@
 ./etc/X11/xserver
 ./etc/X11/xsm
 ./etc/atf
+./etc/autofs
 ./etc/bluetooth
 ./etc/cgd			mode=0700
 ./etc/defaults
@@ -139,6 +140,7 @@
 ./usr/include/event2
 ./usr/include/filecorefs
 ./usr/include/fs
+./usr/include/fs/autofs
 ./usr/include/fs/efs
 ./usr/include/fs/hfs
 ./usr/include/fs/nilfs
diff --git a/etc/mtree/special b/etc/mtree/special
index 0c43824ed9e3..845834840f51 100644
--- a/etc/mtree/special
+++ b/etc/mtree/special
@@ -29,6 +29,14 @@
 ./etc/Distfile			type=file mode=0644 optional
 ./etc/amd			type=dir  mode=0755 optional
 ./etc/apm			type=dir  mode=0755 optional
+./etc/auto_master		type=file mode=0644
+./etc/autofs			type=dir  mode=0755
+./etc/autofs/include_ldap	type=file mode=0755
+./etc/autofs/include_nis	type=file mode=0755
+./etc/autofs/special_hosts	type=file mode=0755
+./etc/autofs/special_media	type=file mode=0755
+./etc/autofs/special_noauto	type=file mode=0755
+./etc/autofs/special_null	type=file mode=0755
 ./etc/blacklistd.conf		type=file mode=0644 optional
 ./etc/bluetooth			type=dir  mode=0755
 ./etc/bluetooth/btattach.conf	type=file mode=0644
diff --git a/etc/rc.d/Makefile b/etc/rc.d/Makefile
index 8986e361f335..26b4ed81ec52 100755
--- a/etc/rc.d/Makefile
+++ b/etc/rc.d/Makefile
@@ -15,7 +15,7 @@
 
 CONFIGFILES=\
 		DAEMON DISKS LOGIN NETWORKING SERVERS \
-		accounting altqd amd apmd \
+		accounting altqd amd apmd automount automountd autounmountd \
 		bluetooth bootconf.sh bootparams \
 		ccd cgd cleartmp cron devpubd \
 		dhclient dhcpcd dhcpd dhcrelay dmesg downinterfaces envsys \
diff --git a/etc/rc.d/automount b/etc/rc.d/automount
new file mode 100755
index 000000000000..90078030eda8
--- /dev/null
+++ b/etc/rc.d/automount
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+# PROVIDE: automount
+# REQUIRE: nfsclient automountd
+# KEYWORD: nojail shutdown
+
+. /etc/rc.subr
+
+name="automount"
+rcvar="autofs"
+start_cmd="automount_start"
+stop_cmd="automount_stop"
+required_modules="autofs"
+
+automount_start()
+{
+
+	/usr/sbin/automount ${automount_flags}
+}
+
+automount_stop()
+{
+
+	/sbin/umount -At autofs
+}
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/etc/rc.d/automountd b/etc/rc.d/automountd
new file mode 100755
index 000000000000..0f4f6dc0d748
--- /dev/null
+++ b/etc/rc.d/automountd
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+# PROVIDE: automountd
+# REQUIRE: DAEMON
+# KEYWORD: nojail
+
+. /etc/rc.subr
+
+name="automountd"
+rcvar="autofs"
+pidfile="/var/run/${name}.pid"
+command="/usr/sbin/${name}"
+required_modules="autofs"
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/etc/rc.d/autounmountd b/etc/rc.d/autounmountd
new file mode 100755
index 000000000000..897b2066ee20
--- /dev/null
+++ b/etc/rc.d/autounmountd
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+# PROVIDE: autounmountd
+# REQUIRE: DAEMON
+# KEYWORD: nojail
+
+. /etc/rc.subr
+
+name="autounmountd"
+rcvar="autofs"
+pidfile="/var/run/${name}.pid"
+command="/usr/sbin/${name}"
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/include/mntopts.h b/include/mntopts.h
index 61e5baf677f7..60a2ffe3101e 100644
--- a/include/mntopts.h
+++ b/include/mntopts.h
@@ -62,6 +62,7 @@ struct mntopt {
 #define MOPT_IGNORE		{ "hidden",	0, MNT_IGNORE, 0 }
 #define MOPT_EXTATTR		{ "extattr",	0, MNT_EXTATTR, 0 }
 #define MOPT_DISCARD		{ "discard",	0, MNT_DISCARD, 0 }
+#define MOPT_AUTOMOUNTED	{ "automounted",0, MNT_AUTOMOUNTED, 0 }
 
 /* Control flags. */
 #define MOPT_FORCE		{ "force",	0, MNT_FORCE, 0 }
@@ -96,7 +97,8 @@ struct mntopt {
 	MOPT_UNION,							\
 	MOPT_IGNORE,							\
 	MOPT_SYMPERM,							\
-	MOPT_RUMP
+	MOPT_RUMP,							\
+	MOPT_AUTOMOUNTED
 
 __BEGIN_DECLS
 typedef struct mntoptparse *mntoptparse_t;
diff --git a/lib/libc/sys/kqueue.2 b/lib/libc/sys/kqueue.2
index 71561875f308..9c6011788306 100644
--- a/lib/libc/sys/kqueue.2
+++ b/lib/libc/sys/kqueue.2
@@ -32,7 +32,7 @@
 .\"
 .\" $FreeBSD: src/lib/libc/sys/kqueue.2,v 1.22 2001/06/27 19:55:57 dd Exp $
 .\"
-.Dd June 6, 2017
+.Dd November 25, 2017
 .Dt KQUEUE 2
 .Os
 .Sh NAME
@@ -484,6 +484,9 @@ On return,
 contains the number of times the timeout has expired since the last call to
 .Fn kevent .
 This filter automatically sets the EV_CLEAR flag internally.
+.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 --git a/sbin/mount/mount.8 b/sbin/mount/mount.8
index bebe87c4bb7f..e2e8dad19fa8 100644
--- a/sbin/mount/mount.8
+++ b/sbin/mount/mount.8
@@ -29,7 +29,7 @@
 .\"
 .\"     @(#)mount.8	8.8 (Berkeley) 6/16/94
 .\"
-.Dd November 20, 2017
+.Dd November 25, 2017
 .Dt MOUNT 8
 .Os
 .Sh NAME
@@ -200,6 +200,11 @@ recovery mechanism, or are willing to recreate the file system from scratch.
 Clear
 .Cm async
 mode.
+.It Cm automounted
+This flag indicates that the file system was mounted by
+.Xr automountd 8 .
+Automounted file systems are automatically unmounted by
+.Xr autounmountd 8 .
 .It Cm discard
 Use DISCARD/TRIM commands if disk and driver support it.
 .Pp
@@ -571,7 +576,10 @@ mount -t cd9660 -o nodev,nosuid /dev/cd0a /mnt
 .Xr rump_sysvbfs 8 ,
 .Xr rump_tmpfs 8 ,
 .Xr rump_udf 8 ,
-.Xr umount 8
+.Xr umount 8 ,
+.Xr automount 8 ,
+.Xr automountd 8 ,
+.Xr autounmountd 8
 .Sh HISTORY
 A
 .Nm
diff --git a/share/man/man5/Makefile b/share/man/man5/Makefile
index 0a94682bda7b..049ec4a77ce1 100644
--- a/share/man/man5/Makefile
+++ b/share/man/man5/Makefile
@@ -3,7 +3,7 @@
 
 # missing: dump.5 plot.5
 
-MAN=	a.out.5 acct.5 ar.5 boot.cfg.5 \
+MAN=	a.out.5 acct.5 ar.5 autofs.5 boot.cfg.5 \
 	capfile.5 core.5 daily.5 disktab.5 elf.5 \
 	ethers.5 floppytab.5 forward.5 \
 	fs.5 fstab.5 genassym.cf.5 gpio.conf.5 group.5 hesiod.conf.5 \
diff --git a/share/man/man5/autofs.5 b/share/man/man5/autofs.5
new file mode 100644
index 000000000000..0e54a9e3ea5b
--- /dev/null
+++ b/share/man/man5/autofs.5
@@ -0,0 +1,121 @@
+.\" 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 November 25, 2017
+.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 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
+.Pp
+The
+.Nm
+was ported to
+.Dx
+and
+.Nx
+by
+.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com .
diff --git a/share/man/man5/rc.conf.5 b/share/man/man5/rc.conf.5
index 8dbda125ec6a..7d6debedfacf 100644
--- a/share/man/man5/rc.conf.5
+++ b/share/man/man5/rc.conf.5
@@ -55,7 +55,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd December 17, 2016
+.Dd November 25, 2017
 .Dt RC.CONF 5
 .Os
 .Sh NAME
@@ -1502,6 +1502,47 @@ to support the
 and
 .Xr ruptime 1
 commands.
+.It Sy autofs
+Boolean value.
+If set to
+.Dq Li YES ,
+start the
+.Xr automount 8
+utility and the
+.Xr automountd 8
+and
+.Xr autounmountd 8
+daemons at boot time.
+.It Sy automount_flags
+A string.
+If
+.Sy autofs
+is set to
+.Dq Li YES ,
+these are the flags to pass to the
+.Xr automount 8
+program.
+By default no flags are passed.
+.It Sy automountd_flags
+A string.
+If
+.Sy autofs
+is set to
+.Dq Li YES ,
+these are the flags to pass to the
+.Xr automountd 8
+daemon.
+By default no flags are passed.
+.It Sy autounmountd_flags
+A string.
+If
+.Sy autofs
+is set to
+.Dq Li YES ,
+these are the flags to pass to the
+.Xr autounmountd 8
+daemon.
+By default no flags are passed.
 .El
 .Ss Hardware daemons
 .Bl -tag -width net_interfaces
diff --git a/share/man/man7/hier.7 b/share/man/man7/hier.7
index aadf05f48930..1e3a63c30a25 100644
--- a/share/man/man7/hier.7
+++ b/share/man/man7/hier.7
@@ -29,7 +29,7 @@
 .\"
 .\"	@(#)hier.7	8.5 (Berkeley) 6/1/94
 .\"
-.Dd August 26, 2016
+.Dd November 25, 2017
 .Dt HIER 7
 .Os
 .Sh NAME
@@ -376,6 +376,10 @@ being available.
 .It Sy /mnt/
 Empty directory commonly used by
 system administrators as a temporary mount point.
+.It Sy /net/
+automounted NFS shares;
+see
+.Xr auto_master 5
 .It Sy /netbsd
 Kernel executable image (the operating system loaded into memory
 at boot time).
diff --git a/sys/arch/amd64/conf/ALL b/sys/arch/amd64/conf/ALL
index 28f5e178b212..643edc6225ee 100644
--- a/sys/arch/amd64/conf/ALL
+++ b/sys/arch/amd64/conf/ALL
@@ -178,6 +178,7 @@ file-system	ADOSFS		# AmigaDOS file system
 file-system	NILFS		# experimental - NTT's NiLFS(2)
 file-system	V7FS		# 7th Edition(V7) File System
 file-system	CHFS		# Chip File System
+file-system	AUTOFS		# Automounter Filesystem
 
 # File system options
 options 	QUOTA		# legacy UFS quotas
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index aa750cbfb617..555a52afd0b2 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -165,6 +165,7 @@ file-system	PTYFS		# /dev/ptm support
 #file-system	UDF		# experimental - OSTA UDF CD/DVD file-system
 #file-system	HFS		# experimental - Apple HFS+ (read-only)
 #file-system	NILFS		# experimental - NTT's NiLFS(2)
+#file-system	AUTOFS		# experimental - Automounter Filesystem
 
 # File system options
 # ffs
diff --git a/sys/arch/amd64/conf/MODULAR b/sys/arch/amd64/conf/MODULAR
index 33b45b16deaf..091fa31ee136 100644
--- a/sys/arch/amd64/conf/MODULAR
+++ b/sys/arch/amd64/conf/MODULAR
@@ -59,6 +59,7 @@ options 	MODULAR_DEFAULT_AUTOLOAD
 -no file-system UNION		# union file system
 -no file-system CODA		# Coda File System; also needs vcoda (below)
 -no file-system PTYFS		# /dev/ptm support
+-no file-system AUTOFS		# Automounter Filesystem
 
 -no options 	PPP_BSDCOMP	# BSD-Compress compression support for PPP
 -no options 	PPP_DEFLATE	# Deflate compression support for PPP
diff --git a/sys/arch/amd64/conf/XEN3_DOM0 b/sys/arch/amd64/conf/XEN3_DOM0
index cea34423080b..2b0460c21c65 100644
--- a/sys/arch/amd64/conf/XEN3_DOM0
+++ b/sys/arch/amd64/conf/XEN3_DOM0
@@ -102,6 +102,7 @@ file-system	CODA		# Coda File System; also needs vcoda (below)
 file-system	SMBFS		# experimental - CIFS; also needs nsmb (below)
 file-system	PTYFS		# /dev/pts/N support
 file-system	TMPFS		# Efficient memory file-system
+file-system	AUTOFS		# Automounter Filesystem
 
 # File system options
 options 	QUOTA		# legacy UFS quotas
diff --git a/sys/arch/amd64/conf/XEN3_DOMU b/sys/arch/amd64/conf/XEN3_DOMU
index ee34f86d4051..933da28c9de6 100644
--- a/sys/arch/amd64/conf/XEN3_DOMU
+++ b/sys/arch/amd64/conf/XEN3_DOMU
@@ -94,6 +94,7 @@ file-system	CODA		# Coda File System; also needs vcoda (below)
 file-system	SMBFS		# experimental - CIFS; also needs nsmb (below)
 file-system	PTYFS		# /dev/pts/N support
 file-system	TMPFS		# Efficient memory file-system
+file-system	AUTOFS		# Automounter Filesystem
 
 # File system options
 options 	QUOTA		# legacy UFS quotas
diff --git a/sys/conf/files b/sys/conf/files
index e8f12e27cb7b..2060dc5a328a 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1538,6 +1538,7 @@ include "fs/sysvbfs/files.sysvbfs"
 include "fs/tmpfs/files.tmpfs"
 include "fs/union/files.union"
 include "fs/v7fs/files.v7fs"
+include "fs/autofs/files.autofs"
 include "miscfs/fdesc/files.fdesc"
 include "miscfs/kernfs/files.kernfs"
 include "miscfs/nullfs/files.nullfs"
diff --git a/sys/conf/filesystems.config b/sys/conf/filesystems.config
index 488284be7d78..f3d2397e7caf 100644
--- a/sys/conf/filesystems.config
+++ b/sys/conf/filesystems.config
@@ -25,4 +25,5 @@ file-system	PTYFS		# /dev/ptm support
 #file-system	UDF		# experimental - OSTA UDF CD/DVD file-system
 #file-system	HFS		# experimental - Apple HFS+ (read-only)
 #file-system	NILFS		# experimental - NTT's NiLFS(2)
+file-system	AUTOFS		# Automounter Filesystem
 
diff --git a/sys/conf/majors b/sys/conf/majors
index 68be7cff9be0..a2bbefe981bb 100644
--- a/sys/conf/majors
+++ b/sys/conf/majors
@@ -76,3 +76,4 @@ device-major spkr      char 240		   spkr
 device-major hdmicec   char 340            hdmicec
 device-major nvme      char 341		   nvme
 device-major qemufwcfg char 342		   qemufwcfg
+device-major autofs    char 343            autofs
diff --git a/sys/fs/Makefile b/sys/fs/Makefile
index ec4f4580a5c7..d817d2708603 100644
--- a/sys/fs/Makefile
+++ b/sys/fs/Makefile
@@ -1,6 +1,6 @@
 #	$NetBSD: Makefile,v 1.20 2011/06/27 11:52:24 uch Exp $
 
 SUBDIR=	adosfs cd9660 efs filecorefs hfs msdosfs nilfs ntfs ptyfs	\
-	puffs smbfs sysvbfs tmpfs udf union v7fs
+	puffs smbfs sysvbfs tmpfs udf union v7fs autofs
 
 .include <bsd.kinc.mk>
diff --git a/sys/fs/autofs/Makefile b/sys/fs/autofs/Makefile
new file mode 100644
index 000000000000..a65a9f519bc1
--- /dev/null
+++ b/sys/fs/autofs/Makefile
@@ -0,0 +1,5 @@
+INCSDIR=	/usr/include/fs/autofs
+
+INCS=		autofs_ioctl.h autofs_mount.h
+
+.include <bsd.kinc.mk>
diff --git a/sys/fs/autofs/autofs.c b/sys/fs/autofs/autofs.c
new file mode 100644
index 000000000000..1bf6cc5f56b8
--- /dev/null
+++ b/sys/fs/autofs/autofs.c
@@ -0,0 +1,615 @@
+/*-
+ * 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/queue.h>
+#include <sys/signalvar.h>
+
+MALLOC_DEFINE(M_AUTOFS, "autofs", "Automounter filesystem");
+
+static int	autofs_open(dev_t dev, int flags, int mode, struct lwp *l);
+static int	autofs_close(dev_t dev, int flags, int mode, struct lwp *l);
+static int	autofs_ioctl(dev_t dev, const u_long cmd, void *data, int flag,
+		    struct lwp *l);
+
+struct cdevsw autofs_ops = {
+	.d_open		= autofs_open,
+	.d_close	= autofs_close,
+	.d_ioctl	= autofs_ioctl,
+};
+
+/*
+ * List of signals that can interrupt an autofs trigger.
+ */
+static int autofs_sig_set[] = {
+	SIGINT,
+	SIGTERM,
+	SIGHUP,
+	SIGKILL,
+	SIGQUIT,
+};
+
+struct pool	autofs_request_pool;
+struct pool	autofs_node_pool;
+struct autofs_softc	*autofs_softc = NULL;
+struct workqueue	*autofs_tmo_wq = 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;
+
+	mutex_enter(proc_lock);
+	if (autofs_softc->sc_dev_sid == curproc->p_pgrp->pg_id) {
+		mutex_exit(proc_lock);
+		return true;
+	}
+	mutex_exit(proc_lock);
+
+	return false;
+}
+
+static char *
+autofs_path(struct autofs_node *anp)
+{
+	struct autofs_mount *amp = anp->an_mount;
+	char *path, *tmp;
+
+	path = kstrdup("");
+	for (; anp->an_parent; anp = anp->an_parent) {
+		tmp = malloc(strlen(anp->an_name) + strlen(path) + 2,
+		    M_AUTOFS, M_WAITOK);
+		strcpy(tmp, anp->an_name);
+		strcat(tmp, "/");
+		strcat(tmp, path);
+		free(path, M_AUTOFS);
+		path = tmp;
+	}
+
+	tmp = malloc(strlen(amp->am_on) + strlen(path) + 2,
+	    M_AUTOFS, M_WAITOK);
+	strcpy(tmp, amp->am_on);
+	strcat(tmp, "/");
+	strcat(tmp, path);
+	free(path, M_AUTOFS);
+	path = tmp;
+
+	return path;
+}
+
+void
+autofs_timeout_wq(struct work *wk, void *arg)
+{
+	struct autofs_request *ar = (void *)wk;
+
+	mutex_enter(&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;
+	cv_broadcast(&autofs_softc->sc_cv);
+	mutex_exit(&autofs_softc->sc_lock);
+}
+
+static void
+autofs_timeout_callout(void *context)
+{
+	struct autofs_request *ar = context;
+
+	workqueue_enqueue(autofs_tmo_wq, &ar->ar_wk, NULL);
+}
+
+bool
+autofs_cached(struct autofs_node *anp, const char *component, int componentlen)
+{
+	struct autofs_mount *amp = anp->an_mount;
+
+	KASSERT(!mutex_owned(&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);
+		mutex_enter(&amp->am_lock);
+		error = autofs_node_find(anp, component, componentlen, NULL);
+		mutex_exit(&amp->am_lock);
+		if (error)
+			return false;
+	}
+
+	return anp->an_cached;
+}
+
+static void
+autofs_cache_callout(void *context)
+{
+	struct autofs_node *anp = context;
+
+	autofs_node_uncache(anp);
+}
+
+void
+autofs_flush(struct autofs_mount *amp)
+{
+	struct autofs_node *anp = amp->am_root;
+	struct autofs_node *child;
+
+	mutex_enter(&amp->am_lock);
+	RB_FOREACH(child, autofs_node_tree, &anp->an_children) {
+		autofs_node_uncache(child);
+	}
+	autofs_node_uncache(amp->am_root);
+	mutex_exit(&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.
+ */
+static void
+autofs_set_sigmask(sigset_t *oldset)
+{
+	sigset_t newset;
+	int i;
+
+	sigfillset(&newset);
+	/* Remove the autofs set of signals from newset */
+	mutex_enter(proc_lock);
+	mutex_enter(curproc->p_lock);
+
+	for (i = 0; i < __arraycount(autofs_sig_set); i++) {
+		/*
+		 * But make sure we leave the ones already masked
+		 * by the process, i.e. remove the signal from the
+		 * temporary signalmask only if it wasn't already
+		 * in sigmask.
+		 */
+		if (!sigismasked(curlwp, autofs_sig_set[i]))
+			sigdelset(&newset, autofs_sig_set[i]);
+	}
+	sigprocmask1(curlwp, SIG_SETMASK, &newset, oldset);
+
+	mutex_exit(curproc->p_lock);
+	mutex_exit(proc_lock);
+}
+
+static void
+autofs_restore_sigmask(sigset_t *set)
+{
+
+	mutex_enter(proc_lock);
+	mutex_enter(curproc->p_lock);
+
+	sigprocmask1(curlwp, SIG_SETMASK, set, NULL);
+
+	mutex_exit(curproc->p_lock);
+	mutex_exit(proc_lock);
+}
+
+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;
+
+	KASSERT(mutex_owned(&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));
+
+		callout_init(&ar->ar_callout, 0);
+		callout_reset(&ar->ar_callout, autofs_timeout * hz,
+		    autofs_timeout_callout, ar);
+		ar->ar_refcount = 1;
+		TAILQ_INSERT_TAIL(&autofs_softc->sc_requests, ar, ar_next);
+	}
+
+	cv_broadcast(&autofs_softc->sc_cv);
+	while (ar->ar_done == false) {
+		if (autofs_interruptible) {
+			sigset_t oldset;
+			autofs_set_sigmask(&oldset);
+			error = cv_wait_sig(&autofs_softc->sc_cv,
+			    &autofs_softc->sc_lock);
+			autofs_restore_sigmask(&oldset);
+			if (error) {
+				AUTOFS_WARN("cv_wait_sig for %s failed "
+				    "with error %d", ar->ar_path, error);
+				break;
+			}
+		} else {
+			cv_wait(&autofs_softc->sc_cv, &autofs_softc->sc_lock);
+		}
+	}
+
+	request_error = ar->ar_error;
+	if (request_error)
+		AUTOFS_WARN("request for %s completed with error %d",
+		    ar->ar_path, request_error);
+
+	wildcards = ar->ar_wildcards;
+
+	/*
+	 * 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);
+		mutex_exit(&autofs_softc->sc_lock);
+		callout_halt(&ar->ar_callout, NULL);
+		pool_put(&autofs_request_pool, ar);
+		mutex_enter(&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;
+		callout_reset(&anp->an_callout, autofs_cache * hz,
+		    autofs_cache_callout, anp);
+	}
+
+	free(key, M_AUTOFS);
+	free(path, M_AUTOFS);
+
+	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);
+		mutex_exit(&autofs_softc->sc_lock);
+		tsleep(&dummy, 0, "autofs_retry", autofs_retry_delay * hz);
+		mutex_enter(&autofs_softc->sc_lock);
+	}
+}
+
+static int
+autofs_ioctl_request(struct autofs_daemon_request *adr)
+{
+	struct autofs_request *ar;
+
+	mutex_enter(&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 = cv_wait_sig(&autofs_softc->sc_cv,
+		    &autofs_softc->sc_lock);
+		if (error) {
+			mutex_exit(&autofs_softc->sc_lock);
+			return error;
+		}
+	}
+
+	ar->ar_in_progress = true;
+	mutex_exit(&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));
+
+	mutex_enter(proc_lock);
+	autofs_softc->sc_dev_sid = curproc->p_pgrp->pg_id;
+	mutex_exit(proc_lock);
+
+	return 0;
+}
+
+static int
+autofs_ioctl_done_101(struct autofs_daemon_done_101 *add)
+{
+	struct autofs_request *ar;
+
+	mutex_enter(&autofs_softc->sc_lock);
+	TAILQ_FOREACH(ar, &autofs_softc->sc_requests, ar_next) {
+		if (ar->ar_id == add->add_id)
+			break;
+	}
+
+	if (!ar) {
+		mutex_exit(&autofs_softc->sc_lock);
+		AUTOFS_DEBUG("id %d not found", add->add_id);
+		return ESRCH;
+	}
+
+	ar->ar_error = add->add_error;
+	ar->ar_wildcards = true;
+	ar->ar_done = true;
+	ar->ar_in_progress = false;
+	cv_broadcast(&autofs_softc->sc_cv);
+
+	mutex_exit(&autofs_softc->sc_lock);
+
+	return 0;
+}
+
+static int
+autofs_ioctl_done(struct autofs_daemon_done *add)
+{
+	struct autofs_request *ar;
+
+	mutex_enter(&autofs_softc->sc_lock);
+	TAILQ_FOREACH(ar, &autofs_softc->sc_requests, ar_next) {
+		if (ar->ar_id == add->add_id)
+			break;
+	}
+
+	if (!ar) {
+		mutex_exit(&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;
+	cv_broadcast(&autofs_softc->sc_cv);
+
+	mutex_exit(&autofs_softc->sc_lock);
+
+	return 0;
+}
+
+static int
+autofs_open(dev_t dev, int flags, int mode, struct lwp *l)
+{
+
+	mutex_enter(&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) {
+		mutex_exit(&autofs_softc->sc_lock);
+		return EBUSY;
+	}
+
+	autofs_softc->sc_dev_opened = true;
+	mutex_exit(&autofs_softc->sc_lock);
+
+	return 0;
+}
+
+static int
+autofs_close(dev_t dev, int flags, int mode, struct lwp *l)
+{
+
+	mutex_enter(&autofs_softc->sc_lock);
+	KASSERT(autofs_softc->sc_dev_opened);
+	autofs_softc->sc_dev_opened = false;
+	mutex_exit(&autofs_softc->sc_lock);
+
+	return 0;
+}
+
+static int
+autofs_ioctl(dev_t dev, const u_long cmd, void *data, int flag, struct lwp *l)
+{
+
+	KASSERT(autofs_softc->sc_dev_opened);
+
+	switch (cmd) {
+	case AUTOFSREQUEST:
+		return autofs_ioctl_request(
+		    (struct autofs_daemon_request *)data);
+	case AUTOFSDONE101:
+		return autofs_ioctl_done_101(
+		    (struct autofs_daemon_done_101 *)data);
+	case AUTOFSDONE:
+		return autofs_ioctl_done(
+		    (struct autofs_daemon_done *)data);
+	default:
+		AUTOFS_DEBUG("invalid cmd %lx", cmd);
+		return EINVAL;
+	}
+	return EINVAL;
+}
+
+char *
+__kstrndup(const char *str, size_t maxlen)
+{
+	char *p;
+	size_t len;
+
+	if (!str)
+		return NULL;
+
+	if (maxlen == 0)
+		len = strlen(str);
+	else
+		len = strnlen(str, maxlen);
+	p = malloc(len + 1, M_AUTOFS, M_WAITOK);
+	if (!p)
+		return NULL;
+
+	memcpy(p, str, len);
+	p[len] = '\0';
+
+	return p;
+}
diff --git a/sys/fs/autofs/autofs.h b/sys/fs/autofs/autofs.h
new file mode 100644
index 000000000000..7e02f064c03f
--- /dev/null
+++ b/sys/fs/autofs/autofs.h
@@ -0,0 +1,206 @@
+/*-
+ * 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_H
+#define	AUTOFS_H
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/kmem.h>
+#include <sys/tree.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/mount.h>
+#include <sys/vnode.h>
+#include <sys/timespec.h>
+#include <sys/callout.h>
+#include <sys/workqueue.h>
+#include <sys/conf.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))
+
+MALLOC_DECLARE(M_AUTOFS);
+
+extern int (**autofs_vnodeop_p)(void *);
+extern const struct vnodeopv_desc autofs_vnodeop_opv_desc;
+
+extern struct cdevsw autofs_ops;
+
+extern struct pool autofs_request_pool;
+extern struct pool autofs_node_pool;
+extern struct autofs_softc *autofs_softc;
+extern struct workqueue	*autofs_tmo_wq;
+
+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): " X,			\
+	    __func__, curproc->p_comm, ## __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;
+	kmutex_t			an_vnode_lock;
+	bool				an_cached;
+	bool				an_wildcards;
+	struct callout			an_callout;
+	int				an_retries;
+	struct timespec			an_ctime;
+};
+
+struct autofs_mount {
+	TAILQ_ENTRY(autofs_mount)	am_next;
+	struct autofs_node		*am_root;
+	struct mount			*am_mp;
+	kmutex_t			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 work			ar_wk; /* 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 callout			ar_callout;
+	volatile u_int			ar_refcount;
+};
+
+struct autofs_softc {
+	kcondvar_t			sc_cv;
+	kmutex_t			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);
+void	autofs_timeout_wq(struct work *wk, void *arg);
+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);
+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;
+}
+
+static __inline char *
+kstrdup(const char *str)
+{
+
+	return __kstrndup(str, 0);
+}
+
+static __inline char *
+kstrndup(const char *str, size_t maxlen)
+{
+
+	return __kstrndup(str, maxlen);
+}
+
+RB_PROTOTYPE(autofs_node_tree, autofs_node, an_entry, autofs_node_cmp);
+
+#endif /* !AUTOFS_H */
diff --git a/sys/fs/autofs/autofs_ioctl.h b/sys/fs/autofs/autofs_ioctl.h
new file mode 100644
index 000000000000..d33efc2b1d86
--- /dev/null
+++ b/sys/fs/autofs/autofs_ioctl.h
@@ -0,0 +1,125 @@
+/*-
+ * 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_IOCTL_H
+#define	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];
+};
+
+/*
+ * Compatibility with FreeBSD 10.1-RELEASE automountd(8).
+ */
+struct autofs_daemon_done_101 {
+	/*
+	 * Identifier, copied from adr_id.
+	 */
+	int		add_id;
+
+	/*
+	 * Error number, possibly returned to userland.
+	 */
+	int		add_error;
+};
+
+struct autofs_daemon_done {
+	/*
+	 * Identifier, copied from adr_id.
+	 */
+	int		add_id;
+
+	/*
+	 * Set to 1 if the map may contain wildcard entries;
+	 * otherwise autofs will do negative caching.
+	 */
+	int		add_wildcards;
+
+	/*
+	 * Error number, possibly returned to userland.
+	 */
+	int		add_error;
+
+	/*
+	 * Reserved for future use.
+	 */
+	int		add_spare[7];
+};
+
+#define	AUTOFSREQUEST	_IOR('I', 0x01, struct autofs_daemon_request)
+#define	AUTOFSDONE101	_IOW('I', 0x02, struct autofs_daemon_done_101)
+#define	AUTOFSDONE	_IOW('I', 0x03, struct autofs_daemon_done)
+
+#endif /* !AUTOFS_IOCTL_H */
diff --git a/sys/fs/autofs/autofs_mount.h b/sys/fs/autofs/autofs_mount.h
new file mode 100644
index 000000000000..a7442310eb95
--- /dev/null
+++ b/sys/fs/autofs/autofs_mount.h
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2016 The DragonFly Project
+ * 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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef AUTOFS_MOUNT_H
+#define AUTOFS_MOUNT_H
+
+/*
+ * Arguments to mount autofs filesystem.
+ * This structure should only be used by automount(8).
+ */
+struct autofs_args {
+	const char	*from;
+	const char	*master_options;
+	const char	*master_prefix;
+};
+
+#endif /* !AUTOFS_MOUNT_H */
diff --git a/sys/fs/autofs/autofs_vfsops.c b/sys/fs/autofs/autofs_vfsops.c
new file mode 100644
index 000000000000..9a9195c7c6dd
--- /dev/null
+++ b/sys/fs/autofs/autofs_vfsops.c
@@ -0,0 +1,501 @@
+/*-
+ * 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 "autofs_mount.h"
+
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <miscfs/genfs/genfs.h>
+
+MODULE(MODULE_CLASS_VFS, autofs, NULL);
+
+static int	autofs_statvfs(struct mount *, struct statvfs *);
+
+static void
+autofs_init(void)
+{
+
+	KASSERT(!autofs_softc);
+
+	autofs_softc = kmem_zalloc(sizeof(*autofs_softc), KM_SLEEP);
+
+	pool_init(&autofs_request_pool, sizeof(struct autofs_request), 0, 0, 0,
+	    "autofs_request", &pool_allocator_nointr, IPL_NONE);
+	pool_init(&autofs_node_pool, sizeof(struct autofs_node), 0, 0, 0,
+	    "autofs_node", &pool_allocator_nointr, IPL_NONE);
+
+	TAILQ_INIT(&autofs_softc->sc_requests);
+	cv_init(&autofs_softc->sc_cv, "autofscv");
+	mutex_init(&autofs_softc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
+	autofs_softc->sc_dev_opened = false;
+}
+
+static void
+autofs_done(void)
+{
+
+	mutex_enter(&autofs_softc->sc_lock);
+
+	KASSERT(!autofs_softc->sc_dev_opened);
+	pool_destroy(&autofs_request_pool);
+	pool_destroy(&autofs_node_pool);
+
+	mutex_exit(&autofs_softc->sc_lock);
+
+	/*
+	 * Race with autofs_open().
+	 */
+	kmem_free(autofs_softc, sizeof(*autofs_softc));
+	autofs_softc = NULL;
+}
+
+static int
+autofs_mount(struct mount *mp, const char *path, void *data, size_t *data_len)
+{
+	struct autofs_args *args = data;
+	struct autofs_mount *amp;
+	struct statvfs *sbp = &mp->mnt_stat;
+	int error;
+
+	if (!args)
+		return EINVAL;
+
+	/*
+	 * MNT_GETARGS is unsupported.  Autofs is mounted via automount(8) by
+	 * parsing /etc/auto_master instead of regular mount(8) variants with
+	 * -o getargs support, thus not really needed either.
+	 */
+	if (mp->mnt_flag & MNT_GETARGS)
+		return EOPNOTSUPP;
+
+	if (mp->mnt_flag & MNT_UPDATE) {
+		autofs_flush(VFSTOAUTOFS(mp));
+		return 0;
+	}
+
+	/*
+	 * Allocate the autofs mount.
+	 */
+	amp = kmem_zalloc(sizeof(*amp), KM_SLEEP);
+	mp->mnt_data = amp;
+	amp->am_mp = mp;
+
+	/*
+	 * 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.
+	 */
+	mutex_init(&amp->am_lock, MUTEX_DEFAULT, IPL_NONE);
+	amp->am_last_ino = AUTOFS_ROOTINO;
+
+	mutex_enter(&amp->am_lock);
+	error = autofs_node_new(NULL, amp, ".", -1, &amp->am_root);
+	if (error) {
+		mutex_exit(&amp->am_lock);
+		goto fail;
+	}
+	mutex_exit(&amp->am_lock);
+	KASSERT(amp->am_root->an_ino == AUTOFS_ROOTINO);
+
+	autofs_statvfs(mp, sbp);
+	vfs_getnewfsid(mp);
+
+	error = set_statvfs_info(path, UIO_USERSPACE, args->from, UIO_USERSPACE,
+	    mp->mnt_op->vfs_name, mp, curlwp);
+	if (error)
+		goto fail;
+	strlcpy(amp->am_from, sbp->f_mntfromname, sizeof(amp->am_from));
+	strlcpy(amp->am_on, sbp->f_mntonname, sizeof(amp->am_on));
+
+	return 0;
+
+fail:
+	kmem_free(amp, sizeof(*amp));
+	return error;
+}
+
+static int
+autofs_unmount(struct mount *mp, int mntflags)
+{
+	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;
+		mutex_enter(&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) {
+			mutex_exit(&autofs_softc->sc_lock);
+			break;
+		}
+
+		cv_broadcast(&autofs_softc->sc_cv);
+		mutex_exit(&autofs_softc->sc_lock);
+
+		tsleep(&dummy, 0, "autofs_umount", hz);
+	}
+
+	mutex_enter(&amp->am_lock);
+	while (!RB_EMPTY(&amp->am_root->an_children)) {
+		struct autofs_node *anp;
+		anp = RB_MIN(autofs_node_tree, &amp->am_root->an_children);
+		autofs_node_delete(anp);
+	}
+	autofs_node_delete(amp->am_root);
+	mp->mnt_data = NULL;
+	mutex_exit(&amp->am_lock);
+
+	mutex_destroy(&amp->am_lock);
+
+	kmem_free(amp, sizeof(*amp));
+
+	return 0;
+}
+
+static int
+autofs_start(struct mount *mp, int flags)
+{
+
+	return 0;
+}
+
+static int
+autofs_root(struct mount *mp, struct vnode **vpp)
+{
+	struct autofs_node *anp = VFSTOAUTOFS(mp)->am_root;
+	int error;
+
+	error = vcache_get(mp, &anp, sizeof(anp), vpp);
+	if (error)
+		return error;
+	error = vn_lock(*vpp, LK_EXCLUSIVE);
+	if (error) {
+		vrele(*vpp);
+		*vpp = NULL;
+		return error;
+	}
+
+	return 0;
+}
+
+static int
+autofs_statvfs(struct mount *mp, struct statvfs *sbp)
+{
+
+	sbp->f_bsize = S_BLKSIZE;
+	sbp->f_frsize = S_BLKSIZE;
+	sbp->f_iosize = 0;
+	sbp->f_blocks = 0;
+	sbp->f_bfree = 0;
+	sbp->f_bavail = 0;
+	sbp->f_bresvd = 0;
+	sbp->f_files = 0;
+	sbp->f_ffree = 0;
+	sbp->f_favail = 0;
+	sbp->f_fresvd = 0;
+
+	copy_statvfs_info(sbp, mp);
+
+	return 0;
+}
+
+static int
+autofs_sync(struct mount *mp, int waitfor, kauth_cred_t uc)
+{
+
+	return 0;
+}
+
+static int
+autofs_loadvnode(struct mount *mp, struct vnode *vp,
+    const void *key, size_t key_len, const void **new_key)
+{
+	struct autofs_node *anp;
+
+	KASSERT(key_len == sizeof(anp));
+	memcpy(&anp, key, key_len);
+	KASSERT(!anp->an_vnode);
+
+	vp->v_tag = VT_AUTOFS;
+	vp->v_type = VDIR;
+	vp->v_op = autofs_vnodeop_p;
+	vp->v_data = anp;
+
+	if (anp->an_ino == AUTOFS_ROOTINO)
+		vp->v_vflag |= VV_ROOT;
+
+	anp->an_vnode = vp;
+	uvm_vnp_setsize(vp, 0);
+
+	*new_key = &vp->v_data;
+
+	return 0;
+}
+
+static const struct vnodeopv_desc * const autofs_vnodeopv_descs[] = {
+	&autofs_vnodeop_opv_desc,
+	NULL,
+};
+
+static struct vfsops autofs_vfsops = {
+	.vfs_name = MOUNT_AUTOFS,
+	.vfs_min_mount_data = sizeof(struct autofs_args),
+	.vfs_mount = autofs_mount,
+	.vfs_start = autofs_start,
+	.vfs_unmount = autofs_unmount,
+	.vfs_root = autofs_root,
+	.vfs_quotactl = (void *)eopnotsupp,
+	.vfs_statvfs = autofs_statvfs,
+	.vfs_sync = autofs_sync,
+	.vfs_vget = (void *)eopnotsupp,
+	.vfs_loadvnode = (void *)autofs_loadvnode,
+	.vfs_newvnode = (void *)eopnotsupp,
+	.vfs_fhtovp = (void *)eopnotsupp,
+	.vfs_vptofh = (void *)eopnotsupp,
+	.vfs_init = autofs_init,
+	.vfs_reinit = (void *)eopnotsupp,
+	.vfs_done = autofs_done,
+	.vfs_mountroot = (void *)eopnotsupp,
+	.vfs_snapshot = (void *)eopnotsupp,
+	.vfs_extattrctl = (void *)eopnotsupp,
+	.vfs_suspendctl = (void *)genfs_suspendctl,
+	.vfs_renamelock_enter = (void *)eopnotsupp,
+	.vfs_renamelock_exit = (void *)eopnotsupp,
+	.vfs_fsync = (void *)eopnotsupp,
+	.vfs_opv_descs = autofs_vnodeopv_descs
+};
+
+#define AUTOFS_SYSCTL_DEBUG		1
+#define AUTOFS_SYSCTL_MOUNT_ON_STAT	2
+#define AUTOFS_SYSCTL_TIMEOUT		3
+#define AUTOFS_SYSCTL_CACHE		4
+#define AUTOFS_SYSCTL_RETRY_ATTEMPTS	5
+#define AUTOFS_SYSCTL_RETRY_DELAY	6
+#define AUTOFS_SYSCTL_INTERRUPTIBLE	7
+
+static struct sysctllog *autofs_sysctl_log;
+
+static int
+autofs_sysctl_create(void)
+{
+	int error;
+
+	/*
+	 * XXX the "33" below could be dynamic, thereby eliminating one
+	 * more instance of the "number to vfs" mapping problem, but
+	 * "33" is the order as taken from sys/mount.h
+	 */
+	error = sysctl_createv(&autofs_sysctl_log, 0, NULL, NULL,
+	    CTLFLAG_PERMANENT,
+	    CTLTYPE_NODE, "autofs",
+	    SYSCTL_DESCR("Automounter filesystem"),
+	    NULL, 0, NULL, 0,
+	    CTL_VFS, 33, CTL_EOL);
+	if (error)
+		goto fail;
+
+	error = sysctl_createv(&autofs_sysctl_log, 0, NULL, NULL,
+	    CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
+	    CTLTYPE_INT, "autofs_debug",
+	    SYSCTL_DESCR("Enable debug messages"),
+	    NULL, 0, &autofs_debug, 0,
+	    CTL_VFS, 33, AUTOFS_SYSCTL_DEBUG, CTL_EOL);
+	if (error)
+		goto fail;
+
+	error = sysctl_createv(&autofs_sysctl_log, 0, NULL, NULL,
+	    CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
+	    CTLTYPE_INT, "autofs_mount_on_stat",
+	    SYSCTL_DESCR("Trigger mount on stat(2) on mountpoint"),
+	    NULL, 0, &autofs_mount_on_stat, 0,
+	    CTL_VFS, 33, AUTOFS_SYSCTL_MOUNT_ON_STAT, CTL_EOL);
+	if (error)
+		goto fail;
+
+	error = sysctl_createv(&autofs_sysctl_log, 0, NULL, NULL,
+	    CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
+	    CTLTYPE_INT, "autofs_timeout",
+	    SYSCTL_DESCR("Number of seconds to wait for automountd(8)"),
+	    NULL, 0, &autofs_timeout, 0,
+	    CTL_VFS, 33, AUTOFS_SYSCTL_TIMEOUT, CTL_EOL);
+	if (error)
+		goto fail;
+
+	error = sysctl_createv(&autofs_sysctl_log, 0, NULL, NULL,
+	    CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
+	    CTLTYPE_INT, "autofs_cache",
+	    SYSCTL_DESCR("Number of seconds to wait before reinvoking"),
+	    NULL, 0, &autofs_cache, 0,
+	    CTL_VFS, 33, AUTOFS_SYSCTL_CACHE, CTL_EOL);
+	if (error)
+		goto fail;
+
+	error = sysctl_createv(&autofs_sysctl_log, 0, NULL, NULL,
+	    CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
+	    CTLTYPE_INT, "autofs_retry_attempts",
+	    SYSCTL_DESCR("Number of attempts before failing mount"),
+	    NULL, 0, &autofs_retry_attempts, 0,
+	    CTL_VFS, 33, AUTOFS_SYSCTL_RETRY_ATTEMPTS, CTL_EOL);
+	if (error)
+		goto fail;
+
+	error = sysctl_createv(&autofs_sysctl_log, 0, NULL, NULL,
+	    CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
+	    CTLTYPE_INT, "autofs_retry_delay",
+	    SYSCTL_DESCR("Number of seconds before retrying"),
+	    NULL, 0, &autofs_retry_delay, 0,
+	    CTL_VFS, 33, AUTOFS_SYSCTL_RETRY_DELAY, CTL_EOL);
+	if (error)
+		goto fail;
+
+	error = sysctl_createv(&autofs_sysctl_log, 0, NULL, NULL,
+	    CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
+	    CTLTYPE_INT, "autofs_interruptible",
+	    SYSCTL_DESCR("Allow requests to be interrupted by signal"),
+	    NULL, 0, &autofs_interruptible, 0,
+	    CTL_VFS, 33, AUTOFS_SYSCTL_INTERRUPTIBLE, CTL_EOL);
+	if (error)
+		goto fail;
+
+	return 0;
+fail:
+	AUTOFS_WARN("sysctl_createv failed with error %d", error);
+
+	return error;
+}
+
+static int
+autofs_modcmd(modcmd_t cmd, void *arg)
+{
+#ifdef _MODULE
+	devmajor_t bmajor = NODEVMAJOR, cmajor = NODEVMAJOR;
+#endif
+	int error = 0;
+
+	switch (cmd) {
+	case MODULE_CMD_INIT:
+		error = vfs_attach(&autofs_vfsops);
+		if (error)
+			break;
+#ifdef _MODULE
+		error = devsw_attach("autofs", NULL, &bmajor, &autofs_ops,
+		    &cmajor);
+		if (error) {
+			vfs_detach(&autofs_vfsops);
+			break;
+		}
+#endif
+		error = workqueue_create(&autofs_tmo_wq, "autofstmo",
+		    autofs_timeout_wq, NULL, 0, 0, WQ_MPSAFE);
+		if (error) {
+			devsw_detach(NULL, &autofs_ops);
+			vfs_detach(&autofs_vfsops);
+			break;
+		}
+
+		error = autofs_sysctl_create();
+		if (error) {
+			workqueue_destroy(autofs_tmo_wq);
+			devsw_detach(NULL, &autofs_ops);
+			vfs_detach(&autofs_vfsops);
+			break;
+		}
+		break;
+	case MODULE_CMD_FINI:
+		KASSERT(autofs_softc);
+		mutex_enter(&autofs_softc->sc_lock);
+		if (autofs_softc->sc_dev_opened) {
+			mutex_exit(&autofs_softc->sc_lock);
+			error = EBUSY;
+			break;
+		}
+		mutex_exit(&autofs_softc->sc_lock);
+
+		workqueue_destroy(autofs_tmo_wq);
+#ifdef _MODULE
+		error = devsw_detach(NULL, &autofs_ops);
+		if (error)
+			break;
+#endif
+		error = vfs_detach(&autofs_vfsops);
+		if (error)
+			break;
+		break;
+	default:
+		error = ENOTTY;
+		break;
+	}
+
+	return error;
+}
diff --git a/sys/fs/autofs/autofs_vnops.c b/sys/fs/autofs/autofs_vnops.c
new file mode 100644
index 000000000000..f693d025a9b3
--- /dev/null
+++ b/sys/fs/autofs/autofs_vnops.c
@@ -0,0 +1,696 @@
+/*-
+ * 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 <miscfs/genfs/genfs.h>
+
+static int	autofs_trigger_vn(struct vnode *vp, const char *path,
+		    int pathlen, struct vnode **newvp);
+
+static int
+autofs_access(void *v)
+{
+	struct vop_access_args /* {
+		struct vnode	*a_vp;
+		int		a_mode;
+		kauth_cred_t	a_cred;
+	} */ *ap = v;
+	struct vnode *vp __diagused = 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;
+		kauth_cred_t	a_cred;
+	} */ *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);
+		if (error)
+			return error;
+		/*
+		 * Already mounted here.
+		 */
+		if (newvp) {
+			error = VOP_GETATTR(newvp, vap, ap->a_cred);
+			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_fsidx.__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_birthtime = 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 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);
+
+	mutex_enter(&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:
+	mutex_exit(&autofs_softc->sc_lock);
+	vn_lock(vp, lock_flags | LK_RETRY);
+	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_v2_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 cachefound;
+	int error;
+	const bool lastcn __diagused = (cnp->cn_flags & ISLASTCN) != 0;
+
+	KASSERT(VOP_ISLOCKED(dvp));
+
+	anp = VTOI(dvp);
+	*vpp = NULL;
+
+	/* Check accessibility of directory. */
+	KASSERT(!VOP_ACCESS(dvp, VEXEC, cnp->cn_cred));
+
+	/*
+	 * Avoid doing a linear scan of the directory if the requested
+	 * directory/name couple is already in the cache.
+	 */
+	cachefound = cache_lookup(dvp, cnp->cn_nameptr, cnp->cn_namelen,
+	    cnp->cn_nameiop, cnp->cn_flags, NULL, vpp);
+	if (cachefound && *vpp == NULLVP) {
+		/* Negative cache hit. */
+		error = ENOENT;
+		goto out;
+	} else if (cachefound) {
+		error = 0;
+		goto out;
+	}
+
+	if (cnp->cn_flags & ISDOTDOT) {
+		struct autofs_node *parent;
+		/*
+		 * Lookup of ".." case.
+		 */
+		KASSERT(!(lastcn && cnp->cn_nameiop == RENAME));
+		parent = anp->an_parent;
+		if (!parent) {
+			error = ENOENT;
+			goto out;
+		}
+
+		error = vcache_get(dvp->v_mount, &parent, sizeof(parent), vpp);
+		goto out;
+	} else if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') {
+		/*
+		 * Lookup of "." case.
+		 */
+		KASSERT(!(lastcn && cnp->cn_nameiop == RENAME));
+		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);
+		if (error)
+			return error;
+		/*
+		 * Already mounted here.
+		 */
+		if (newvp) {
+			error = VOP_LOOKUP(newvp, vpp, cnp);
+			vput(newvp);
+			return error;
+		}
+	}
+
+	mutex_enter(&amp->am_lock);
+	error = autofs_node_find(anp, cnp->cn_nameptr, cnp->cn_namelen, &child);
+	if (error) {
+		if ((cnp->cn_flags & ISLASTCN) && cnp->cn_nameiop == CREATE) {
+			mutex_exit(&amp->am_lock);
+			error = EJUSTRETURN;
+			goto done;
+		}
+
+		mutex_exit(&amp->am_lock);
+		error = ENOENT;
+		goto done;
+	}
+
+	/*
+	 * Dropping the node here is ok, because we never remove nodes.
+	 */
+	mutex_exit(&amp->am_lock);
+
+	/* Get a vnode for the matching entry. */
+	error = vcache_get(dvp->v_mount, &child, sizeof(child), vpp);
+done:
+	/*
+	 * Cache the result, unless request was for creation (as it does
+	 * not improve the performance).
+	 */
+	if (cnp->cn_nameiop != CREATE) {
+		cache_enter(dvp, *vpp, cnp->cn_nameptr, cnp->cn_namelen,
+			    cnp->cn_flags);
+	}
+out:
+	KASSERT(VOP_ISLOCKED(dvp));
+
+	return error;
+}
+
+static int
+autofs_open(void *v)
+{
+	struct vop_open_args /* {
+		struct vnode	*a_vp;
+		int		a_mode;
+		kauth_cred_t	a_cred;
+	} */ *ap = v;
+	struct vnode *vp __diagused = 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;
+		kauth_cred_t	a_cred;
+	} */ *ap = v;
+	struct vnode *vp __diagused = ap->a_vp;
+
+	KASSERT(VOP_ISLOCKED(vp));
+	return 0;
+}
+
+static int
+autofs_fsync(void *v)
+{
+	struct vop_fsync_args /* {
+		struct vnode *a_vp;
+		kauth_cred_t a_cred;
+		int a_flags;
+		off_t a_offlo;
+		off_t a_offhi;
+		struct lwp *a_l;
+	} */ *ap = v;
+	struct vnode *vp __diagused = ap->a_vp;
+
+	/* Nothing to do.  Should be up to date. */
+	KASSERT(VOP_ISLOCKED(vp));
+	return 0;
+}
+
+static int
+autofs_mkdir(void *v)
+{
+	struct vop_mkdir_v3_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)
+		return EPERM;
+
+	mutex_enter(&amp->am_lock);
+	error = autofs_node_new(anp, amp, cnp->cn_nameptr, cnp->cn_namelen,
+	    &child);
+	if (error) {
+		mutex_exit(&amp->am_lock);
+		return error;
+	}
+	mutex_exit(&amp->am_lock);
+
+	return vcache_get(amp->am_mp, &child, sizeof(child), vpp);
+}
+
+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;
+
+	(void)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;
+		kauth_cred_t	a_cred;
+		int		*a_eofflag;
+		off_t		**a_cookies;
+		int		*ncookies;
+	} */ *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;
+
+	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);
+		if (error)
+			return error;
+		/*
+		 * Already mounted here.
+		 */
+		if (newvp) {
+			error = VOP_READDIR(newvp, ap->a_uio, ap->a_cred,
+			    ap->a_eofflag, ap->a_cookies, ap->a_ncookies);
+			vput(newvp);
+			return error;
+		}
+	}
+
+	if (uio->uio_offset < 0)
+		return EINVAL;
+
+	if (ap->a_eofflag)
+		*ap->a_eofflag = FALSE;
+
+	/*
+	 * Write out the directory entry for ".".
+	 */
+	if (uio->uio_offset == 0) {
+		error = autofs_readdir_one(uio, ".", anp->an_ino, &reclen);
+		if (error)
+			goto out;
+	}
+	reclens = autofs_dirent_reclen(".");
+
+	/*
+	 * Write out the directory entry for "..".
+	 */
+	if (uio->uio_offset <= reclens) {
+		if (uio->uio_offset != reclens)
+			return EINVAL;
+		error = autofs_readdir_one(uio, "..",
+		    (anp->an_parent ? anp->an_parent->an_ino : anp->an_ino),
+		    &reclen);
+		if (error)
+			goto out;
+	}
+	reclens += autofs_dirent_reclen("..");
+
+	/*
+	 * Write out the directory entries for subdirectories.
+	 */
+	mutex_enter(&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) {
+			mutex_exit(&amp->am_lock);
+			return EINVAL;
+		}
+
+		error = autofs_readdir_one(uio, child->an_name,
+		    child->an_ino, &reclen);
+		reclens += reclen;
+		if (error) {
+			mutex_exit(&amp->am_lock);
+			goto out;
+		}
+	}
+	mutex_exit(&amp->am_lock);
+
+	if (ap->a_eofflag)
+		*ap->a_eofflag = TRUE;
+
+	return 0;
+out:
+	/*
+	 * Return error if the initial buffer was too small to do anything.
+	 */
+	if (uio->uio_resid == initial_resid)
+		return error;
+
+	/*
+	 * Don't return an error if we managed to copy out some entries.
+	 */
+	if (uio->uio_resid < reclen)
+		return 0;
+
+	return error;
+}
+
+static int
+autofs_reclaim(void *v)
+{
+	struct vop_reclaim_v2_args /* {
+		struct vnode *a_vp;
+	} */ *ap = v;
+	struct vnode *vp = ap->a_vp;
+	struct autofs_node *anp = VTOI(vp);
+
+	VOP_UNLOCK(vp);
+
+	/*
+	 * We do not free autofs_node here; instead we are
+	 * destroying them in autofs_node_delete().
+	 */
+	mutex_enter(&anp->an_vnode_lock);
+	anp->an_vnode = NULL;
+	vp->v_data = NULL;
+	mutex_exit(&anp->an_vnode_lock);
+
+	return 0;
+}
+
+int (**autofs_vnodeop_p)(void *);
+static const struct vnodeopv_entry_desc autofs_vnodeop_entries[] = {
+	{ &vop_default_desc,	vn_default_error },
+	{ &vop_lookup_desc,	autofs_lookup },
+	{ &vop_open_desc,	autofs_open },
+	{ &vop_close_desc,	autofs_close },
+	{ &vop_access_desc,	autofs_access },
+	{ &vop_getattr_desc,	autofs_getattr },
+	{ &vop_fsync_desc,	autofs_fsync },
+	{ &vop_mkdir_desc,	autofs_mkdir },
+	{ &vop_readdir_desc,	autofs_readdir },
+	{ &vop_reclaim_desc,	autofs_reclaim },
+	{ &vop_lock_desc,	genfs_lock },
+	{ &vop_unlock_desc,	genfs_unlock },
+	{ &vop_print_desc,	autofs_print },
+	{ &vop_islocked_desc,	genfs_islocked },
+	{ &vop_getpages_desc,	genfs_getpages },
+	{ &vop_putpages_desc,	genfs_putpages },
+	{ NULL, NULL }
+};
+
+const struct vnodeopv_desc autofs_vnodeop_opv_desc = {
+	&autofs_vnodeop_p, autofs_vnodeop_entries
+};
+
+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;
+
+	KASSERT(mutex_owned(&amp->am_lock));
+
+	if (parent) {
+		KASSERT(mutex_owned(&parent->an_mount->am_lock));
+		KASSERT(autofs_node_find(parent, name, namelen, NULL) ==
+		    ENOENT);
+	}
+
+	anp = pool_get(&autofs_node_pool, PR_WAITOK);
+	if (namelen >= 0)
+		anp->an_name = kstrndup(name, namelen);
+	else
+		anp->an_name = kstrdup(name);
+	anp->an_ino = amp->am_last_ino++;
+	callout_init(&anp->an_callout, 0);
+	mutex_init(&anp->an_vnode_lock, MUTEX_DEFAULT, IPL_NONE);
+	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;
+
+	KASSERT(mutex_owned(&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_AUTOFS);
+
+	return error;
+}
+
+void
+autofs_node_delete(struct autofs_node *anp)
+{
+
+	KASSERT(mutex_owned(&anp->an_mount->am_lock));
+	KASSERT(RB_EMPTY(&anp->an_children));
+
+	callout_halt(&anp->an_callout, NULL);
+
+	if (anp->an_parent)
+		RB_REMOVE(autofs_node_tree, &anp->an_parent->an_children, anp);
+
+	mutex_destroy(&anp->an_vnode_lock);
+	free(anp->an_name, M_AUTOFS);
+	pool_put(&autofs_node_pool, anp);
+}
diff --git a/sys/fs/autofs/files.autofs b/sys/fs/autofs/files.autofs
new file mode 100644
index 000000000000..7ab24188b1b1
--- /dev/null
+++ b/sys/fs/autofs/files.autofs
@@ -0,0 +1,7 @@
+deffs	AUTOFS
+defpseudo	autofs
+
+define	autofs: vfs
+file	fs/autofs/autofs.c		autofs
+file	fs/autofs/autofs_vfsops.c	autofs
+file	fs/autofs/autofs_vnops.c	autofs
diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c
index 12a1a43f1208..68e1b90f3781 100644
--- a/sys/kern/kern_event.c
+++ b/sys/kern/kern_event.c
@@ -106,6 +106,9 @@ static void	filt_timerexpire(void *x);
 static int	filt_timerattach(struct knote *);
 static void	filt_timerdetach(struct knote *);
 static int	filt_timer(struct knote *, long hint);
+static int	filt_fsattach(struct knote *kn);
+static void	filt_fsdetach(struct knote *kn);
+static int	filt_fs(struct knote *kn, long hint);
 
 static const struct fileops kqueueops = {
 	.fo_name = "kqueue",
@@ -148,6 +151,13 @@ static const struct filterops timer_filtops = {
 	.f_event = filt_timer,
 };
 
+static const struct filterops fs_filtops = {
+	.f_isfd = 0,
+	.f_attach = filt_fsattach,
+	.f_detach = filt_fsdetach,
+	.f_event = filt_fs,
+};
+
 static u_int	kq_ncallouts = 0;
 static int	kq_calloutmax = (4 * 1024);
 
@@ -181,6 +191,7 @@ static struct kfilter sys_kfilters[] = {
 	{ "EVFILT_PROC",	EVFILT_PROC,	0, &proc_filtops, 0 },
 	{ "EVFILT_SIGNAL",	EVFILT_SIGNAL,	0, &sig_filtops, 0 },
 	{ "EVFILT_TIMER",	EVFILT_TIMER,	0, &timer_filtops, 0 },
+	{ "EVFILT_FS",		EVFILT_FS,	0, &fs_filtops, 0 },
 	{ NULL,			0,		0, NULL, 0 },
 };
 
@@ -719,6 +730,45 @@ filt_timer(struct knote *kn, long hint)
 	return rv;
 }
 
+/*
+ * Filter event method for EVFILT_FS.
+ */
+struct klist fs_klist = SLIST_HEAD_INITIALIZER(&fs_klist);
+
+static int
+filt_fsattach(struct knote *kn)
+{
+
+	mutex_enter(&kqueue_misc_lock);
+	kn->kn_flags |= EV_CLEAR;
+	SLIST_INSERT_HEAD(&fs_klist, kn, kn_selnext);
+	mutex_exit(&kqueue_misc_lock);
+
+	return 0;
+}
+
+static void
+filt_fsdetach(struct knote *kn)
+{
+
+	mutex_enter(&kqueue_misc_lock);
+	SLIST_REMOVE(&fs_klist, kn, knote, kn_selnext);
+	mutex_exit(&kqueue_misc_lock);
+}
+
+static int
+filt_fs(struct knote *kn, long hint)
+{
+	int rv;
+
+	mutex_enter(&kqueue_misc_lock);
+	kn->kn_fflags |= hint;
+	rv = (kn->kn_fflags != 0);
+	mutex_exit(&kqueue_misc_lock);
+
+	return rv;
+}
+
 /*
  * filt_seltrue:
  *
diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c
index 01db5fbed0a2..c4ac55d7fbce 100644
--- a/sys/kern/vfs_syscalls.c
+++ b/sys/kern/vfs_syscalls.c
@@ -107,6 +107,7 @@ __KERNEL_RCSID(0, "$NetBSD: vfs_syscalls.c,v 1.517 2017/11/07 15:57:38 christos
 #include <sys/atomic.h>
 #include <sys/module.h>
 #include <sys/buf.h>
+#include <sys/event.h>
 
 #include <miscfs/genfs/genfs.h>
 #include <miscfs/specfs/specdev.h>
@@ -543,6 +544,8 @@ do_sys_mount(struct lwp *l, const char *type, enum uio_seg type_seg,
 		    &data_len);
 		vfsopsrele = false;
 	}
+	if (!error)
+		KNOTE(&fs_klist, VQ_MOUNT);
 
     done:
 	if (vfsopsrele)
@@ -621,6 +624,8 @@ sys_unmount(struct lwp *l, const struct sys_unmount_args *uap, register_t *retva
 	vrele(vp);
 	error = dounmount(mp, SCARG(uap, flags), l);
 	vfs_rele(mp);
+	if (!error)
+		KNOTE(&fs_klist, VQ_UNMOUNT);
 	return error;
 }
 
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
index 8fed2b690d70..d0195550f27d 100644
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -9,6 +9,7 @@ SUBDIR+=	accf_httpready
 SUBDIR+=	adosfs
 SUBDIR+=	aio
 SUBDIR+=	audio
+SUBDIR+=	autofs
 SUBDIR+=	blowfish
 SUBDIR+=	bpf
 SUBDIR+=	bpf_filter
diff --git a/sys/modules/autofs/Makefile b/sys/modules/autofs/Makefile
new file mode 100644
index 000000000000..1fff59ff48ce
--- /dev/null
+++ b/sys/modules/autofs/Makefile
@@ -0,0 +1,11 @@
+.include "../Makefile.inc"
+
+.PATH:	${S}/fs/autofs
+
+KMOD=	autofs
+SRCS=	autofs.c autofs_vfsops.c autofs_vnops.c
+
+CPPFLAGS+=	-DDIAGNOSTIC
+DBG+=		-DDEBUG -DLOCKDEBUG
+
+.include <bsd.kmodule.mk>
diff --git a/sys/sys/event.h b/sys/sys/event.h
index 59dd9476b43a..104cad5f4b87 100644
--- a/sys/sys/event.h
+++ b/sys/sys/event.h
@@ -43,7 +43,8 @@
 #define	EVFILT_PROC		4U	/* attached to struct proc */
 #define	EVFILT_SIGNAL		5U	/* attached to struct proc */
 #define	EVFILT_TIMER		6U	/* arbitrary timer (in ms) */
-#define	EVFILT_SYSCOUNT		7U	/* number of filters */
+#define	EVFILT_FS		7U	/* filesystem events */
+#define	EVFILT_SYSCOUNT		8U	/* number of filters */
 
 struct kevent {
 	uintptr_t	ident;		/* identifier for this event */
@@ -256,6 +257,8 @@ int	kfilter_unregister(const char *);
 int	filt_seltrue(struct knote *, long);
 extern const struct filterops seltrue_filtops;
 
+extern struct klist fs_klist;	/* EVFILT_FS */
+
 #else 	/* !_KERNEL */
 
 #include <sys/cdefs.h>
diff --git a/sys/sys/fstypes.h b/sys/sys/fstypes.h
index b337a5037785..61088718af54 100644
--- a/sys/sys/fstypes.h
+++ b/sys/sys/fstypes.h
@@ -99,12 +99,14 @@ typedef struct fhandle	fhandle_t;
 #define	MNT_EXTATTR	0x01000000	/* enable extended attributes */
 #define	MNT_LOG		0x02000000	/* Use logging */
 #define	MNT_NOATIME	0x04000000	/* Never update access times in fs */
+#define	MNT_AUTOMOUNTED 0x10000000	/* mounted by automountd(8) */
 #define	MNT_SYMPERM	0x20000000	/* recognize symlink permission */
 #define	MNT_NODEVMTIME	0x40000000	/* Never update mod times for devs */
 #define	MNT_SOFTDEP	0x80000000	/* Use soft dependencies */
 
 #define	__MNT_BASIC_FLAGS \
 	{ MNT_ASYNC,		0,	"asynchronous" }, \
+	{ MNT_AUTOMOUNTED,	0,	"automounted" }, \
 	{ MNT_DISCARD,		0,	"discard" }, \
 	{ MNT_EXTATTR,		0,	"extattr" }, \
 	{ MNT_IGNORE,		0,	"hidden" }, \
@@ -122,10 +124,10 @@ typedef struct fhandle	fhandle_t;
 	{ MNT_SYNCHRONOUS,	0,	"synchronous" }, \
 	{ MNT_UNION,		0,	"union" }, \
 
-#define MNT_BASIC_FLAGS (MNT_ASYNC | MNT_DISCARD | MNT_EXTATTR | MNT_LOG | \
-    MNT_NOATIME | MNT_NOCOREDUMP | MNT_NODEV | MNT_NODEVMTIME | MNT_NOEXEC | \
-    MNT_NOSUID | MNT_RDONLY | MNT_RELATIME | MNT_SOFTDEP | MNT_SYMPERM | \
-    MNT_SYNCHRONOUS | MNT_UNION)
+#define MNT_BASIC_FLAGS (MNT_ASYNC | MNT_AUTOMOUNTED | MNT_DISCARD | \
+    MNT_EXTATTR | MNT_LOG | MNT_NOATIME | MNT_NOCOREDUMP | MNT_NODEV | \
+    MNT_NODEVMTIME | MNT_NOEXEC | MNT_NOSUID | MNT_RDONLY | MNT_RELATIME | \
+    MNT_SOFTDEP | MNT_SYMPERM | MNT_SYNCHRONOUS | MNT_UNION)
 /*
  * exported mount flags.
  */
@@ -187,7 +189,8 @@ typedef struct fhandle	fhandle_t;
      MNT_QUOTA | \
      MNT_ROOTFS | \
      MNT_LOG | \
-     MNT_EXTATTR)
+     MNT_EXTATTR | \
+     MNT_AUTOMOUNTED)
 
 /*
  * External filesystem control flags.
diff --git a/sys/sys/mount.h b/sys/sys/mount.h
index 0ba3abc7f5a3..6cbe357a863b 100644
--- a/sys/sys/mount.h
+++ b/sys/sys/mount.h
@@ -98,6 +98,7 @@
 #define MOUNT_NILFS	"nilfs"		/* NTT's NiLFS(2) logging file system */
 #define MOUNT_RUMPFS	"rumpfs"	/* rump virtual file system */
 #define	MOUNT_V7FS	"v7fs"		/* 7th Edition of Unix Filesystem */
+#define MOUNT_AUTOFS	"autofs"	/* Automounter Filesystem */
 
 /*
  * Sysctl CTL_VFS definitions.
@@ -116,6 +117,10 @@
 #define	VFS_MAGICLINKS  4		/* expand 'magic' symlinks */
 #define	VFSGEN_MAXID	5		/* number of valid vfs.generic ids */
 
+/* vfsquery flags for kqueue(2) */
+#define VQ_MOUNT	0x0001	/* new filesystem arrived */
+#define VQ_UNMOUNT	0x0002	/* filesystem has left */
+
 #ifndef _STANDALONE
 
 #if defined(_KERNEL) || defined(__EXPOSE_MOUNT)
diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h
index 92de7130be0d..19d6adf9c9d0 100644
--- a/sys/sys/vnode.h
+++ b/sys/sys/vnode.h
@@ -102,7 +102,7 @@ enum vtagtype	{
 	VT_AFS, VT_ISOFS, VT_UNION, VT_ADOSFS, VT_EXT2FS, VT_CODA,
 	VT_FILECORE, VT_NTFS, VT_VFS, VT_OVERLAY, VT_SMBFS, VT_PTYFS,
 	VT_TMPFS, VT_UDF, VT_SYSVBFS, VT_PUFFS, VT_HFS, VT_EFS, VT_ZFS,
-	VT_RUMP, VT_NILFS, VT_V7FS, VT_CHFS
+	VT_RUMP, VT_NILFS, VT_V7FS, VT_CHFS, VT_AUTOFS
 };
 
 #define	VNODE_TAGS \
@@ -111,7 +111,7 @@ enum vtagtype	{
     "VT_AFS", "VT_ISOFS", "VT_UNION", "VT_ADOSFS", "VT_EXT2FS", "VT_CODA", \
     "VT_FILECORE", "VT_NTFS", "VT_VFS", "VT_OVERLAY", "VT_SMBFS", "VT_PTYFS", \
     "VT_TMPFS", "VT_UDF", "VT_SYSVBFS", "VT_PUFFS", "VT_HFS", "VT_EFS", \
-    "VT_ZFS", "VT_RUMP", "VT_NILFS", "VT_V7FS", "VT_CHFS"
+    "VT_ZFS", "VT_RUMP", "VT_NILFS", "VT_V7FS", "VT_CHFS", "VT_AUTOFS"
 
 #if defined(_KERNEL) || defined(_KMEMUSER)
 struct vnode;
diff --git a/usr.bin/showmount/showmount.8 b/usr.bin/showmount/showmount.8
index 5aadaebbabb0..f67cbca076bd 100644
--- a/usr.bin/showmount/showmount.8
+++ b/usr.bin/showmount/showmount.8
@@ -32,7 +32,7 @@
 .\"
 .\"     @(#)showmount.8	8.3 (Berkeley) 3/29/95
 .\"
-.Dd March 29, 1995
+.Dd November 25, 2017
 .Dt SHOWMOUNT 8
 .Os
 .Sh NAME
@@ -40,7 +40,7 @@
 .Nd show remote NFS mounts on host
 .Sh SYNOPSIS
 .Nm
-.Op Fl ade3
+.Op Fl adEe3
 .Op Ar host
 .Sh DESCRIPTION
 .Nm
@@ -67,6 +67,12 @@ List all mount points in the form:
 .Ed
 .It Fl d
 List directory paths of mount points instead of hosts
+.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.
 .It Fl e
 Show the
 .Ar host Ns 's
diff --git a/usr.bin/showmount/showmount.c b/usr.bin/showmount/showmount.c
index d1de68dab103..04d1f327c93d 100644
--- a/usr.bin/showmount/showmount.c
+++ b/usr.bin/showmount/showmount.c
@@ -59,6 +59,8 @@ __RCSID("$NetBSD: showmount.c,v 1.22 2016/01/26 16:23:27 christos Exp $");
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <err.h>
+#include <vis.h>
 
 /* Constant defs */
 #define	ALL	1
@@ -66,6 +68,7 @@ __RCSID("$NetBSD: showmount.c,v 1.22 2016/01/26 16:23:27 christos Exp $");
 
 #define	DODUMP		0x1
 #define	DOEXPORTS	0x2
+#define	DOPARSABLEEXPORTS	0x4
 
 struct mountlist {
 	struct mountlist *ml_left;
@@ -112,8 +115,10 @@ main(int argc, char **argv)
 	const char *host;
 	int ch;
 	int len;
+	int nbytes;
+	char strvised[1024 * 4 + 1];
 
-	while ((ch = getopt(argc, argv, "ade3")) != -1)
+	while ((ch = getopt(argc, argv, "adEe3")) != -1)
 		switch((char)ch) {
 		case 'a':
 			if (type == 0) {
@@ -129,6 +134,9 @@ main(int argc, char **argv)
 			} else
 				usage();
 			break;
+		case 'E':
+			rpcs |= DOPARSABLEEXPORTS;
+			break;
 		case 'e':
 			rpcs |= DOEXPORTS;
 			break;
@@ -142,6 +150,13 @@ main(int argc, char **argv)
 	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
@@ -158,7 +173,7 @@ main(int argc, char **argv)
 			clnt_perrno(estat);
 			exit(1);
 		}
-	if (rpcs & DOEXPORTS)
+	if (rpcs & (DOEXPORTS | DOPARSABLEEXPORTS))
 		if ((estat = tcp_callrpc(host, RPCPROG_MNT, mntvers,
 			 RPCMNT_EXPORT, (xdrproc_t)xdr_void, NULL,
 			 (xdrproc_t)xdr_exports, (char *)&exports)) != 0) {
@@ -202,7 +217,17 @@ main(int argc, char **argv)
 			exp = exp->ex_next;
 		}
 	}
-
+	if (rpcs & DOPARSABLEEXPORTS) {
+		exp = exports;
+		while (exp) {
+			nbytes = strnvis(strvised, sizeof(strvised),
+			    exp->ex_dirp, VIS_GLOB | VIS_NL);
+			if (nbytes == -1)
+				err(1, "strsnvis");
+			printf("%s\n", strvised);
+			exp = exp->ex_next;
+		}
+	}
 	exit(0);
 }
 
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index 31e532cb920e..312b6f43ae73 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -3,12 +3,12 @@
 
 .include <bsd.own.mk>
 
-SUBDIR=	ac accton acpitools altq apm apmd arp \
+SUBDIR=	ac accton acpitools altq apm apmd arp autofs \
 	bad144 bootp bta2dpd btattach btconfig btdevctl bthcid btpand catman \
 	chroot cnwctl cpuctl crash \
 	dev_mkdb diskpart dumpfs dumplfs \
 	edquota eeprom envstat eshconfig etcupdate extattrctl \
-	flashctl fssconfig fusermount fwctl \
+	flashctl fssconfig fstyp fusermount fwctl \
 	gpioctl grfconfig gspa \
 	hdaudioctl \
 	i2cscan ifwatchd inetd installboot intrctl iopctl iostat ipwctl irdaattach \
diff --git a/usr.sbin/autofs/Makefile b/usr.sbin/autofs/Makefile
new file mode 100644
index 000000000000..f6007876d1eb
--- /dev/null
+++ b/usr.sbin/autofs/Makefile
@@ -0,0 +1,15 @@
+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 --git a/usr.sbin/autofs/auto_master.5 b/usr.sbin/autofs/auto_master.5
new file mode 100644
index 000000000000..8c3676945849
--- /dev/null
+++ b/usr.sbin/autofs/auto_master.5
@@ -0,0 +1,392 @@
+.\" 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 November 25, 2017
+.Dt AUTO_MASTER 5
+.Os
+.Sh NAME
+.Nm auto_master
+.Nd auto_master and map file format
+.Sh DESCRIPTION
+The automounter configuration consists of the
+.Nm
+configuration file, which assigns filesystem paths to map names,
+and maps, which contain actual mount information.
+The
+.Nm
+configuration file is used by the
+.Xr automount 8
+command.
+Map files are read by the
+.Xr automountd 8
+daemon.
+.Sh AUTO_MASTER SYNTAX
+The
+.Nm
+file consists of lines with two or three entries separated by whitespace
+and terminated by newline character:
+.Bd -literal -offset indent
+.Pa mountpoint Pa map_name Op Ar -options
+.Ed
+.Pp
+.Pa mountpoint
+is either a fully specified path, or
+.Li /- .
+When
+.Pa mountpoint
+is a full path,
+.Pa map_name
+must reference an indirect map.
+Otherwise,
+.Pa map_name
+must reference a direct map.
+See
+.Sx "MAP SYNTAX"
+below.
+.Pp
+.Pa map_name
+specifies map to use.
+If
+.Pa map_name
+begins with
+.Li - ,
+it specifies a special map.
+See
+.Sx "MAP SYNTAX"
+below.
+If
+.Pa map_name
+is not a fully specified path
+.Pq it does not start with Li / ,
+.Xr automountd 8
+will search for that name in
+.Li /etc .
+Otherwise it will use the path as given.
+If the file indicated by
+.Pa map_name
+is executable,
+.Xr automountd 8
+will assume it is an executable map.
+See
+.Sx "MAP SYNTAX"
+below.
+Otherwise, the file is opened and the contents parsed.
+.Pp
+.Pa -options
+is an optional field that starts with
+.Li -
+and can contain generic filesystem mount options.
+.Pp
+The following example specifies that the /etc/auto_example indirect map
+will be mounted on /example.
+.Bd -literal -offset indent
+/example auto_example
+.Ed
+.Sh MAP SYNTAX
+Map files consist of lines with a number of entries separated by whitespace
+and terminated by newline character:
+.Bd -literal -offset indent
+.Pa key Oo Ar -options Oc Oo Ar mountpoint Oo -options Oc Oc Ar location Op ...
+.Ed
+.Pp
+In most cases, it can be simplified to:
+.Bd -literal -offset indent
+.Pa key Oo Ar -options Oc Ar location
+.Ed
+.Pp
+.Pa key
+is the path component used by
+.Xr automountd 8
+to find the right map entry to use.
+It is also used to form the final mountpoint.
+A wildcard
+.Pq Ql *
+can be used for the key.
+It matches every directory that does not match other keys.
+Those directories will not be visible to the user
+until accessed.
+.Pp
+The
+.Ar options
+field, if present, must begin with
+.Li - .
+When mounting the filesystem, options supplied to
+.Nm
+and options specified in the map entry are concatenated together.
+The special option
+.Li fstype
+is used to specify filesystem type.
+It is not passed to the mount program as an option.
+Instead, it is passed as an argument to
+.Cm "mount -t".
+The default
+.Li fstype
+is
+.Ql nfs .
+The special option
+.Li nobrowse
+is used to disable creation of top-level directories for special
+and executable maps.
+.Pp
+The optional
+.Pa mountpoint
+field is used to specify multiple mount points
+for a single key.
+.Pp
+The
+.Ar location
+field specifies the filesystem to be mounted.
+Ampersands
+.Pq Ql &
+in the
+.Ar location
+field are replaced with the value of
+.Ar key .
+This is typically used with wildcards, like:
+.Bd -literal -offset indent
+.Li *	192.168.1.1:/share/&
+.Ed
+.Pp
+The
+.Ar location
+field may contain references to variables, like:
+.Bd -literal -offset indent
+.Li sys	192.168.1.1:/sys/${OSNAME}
+.Ed
+.Pp
+Defined variables are:
+.Pp
+.Bl -tag -width "-OSNAME" -compact
+.It Li ARCH
+Expands to the output of
+.Li "uname -p" .
+.It Li CPU
+Same as ARCH.
+.It Li HOST
+Expands to the output of
+.Li "uname -n" .
+.It Li OSNAME
+Expands to the output of
+.Li "uname -s" .
+.It Li OSREL
+Expands to the output of
+.Li "uname -r" .
+.It Li OSVERS
+Expands to the output of
+.Li "uname -v" .
+.El
+.Pp
+Additional variables can be defined with the
+.Fl D
+option of
+.Xr automount 8
+and
+.Xr automountd 8 .
+.Pp
+To pass a location that begins with
+.Li / ,
+prefix it with a colon.
+For example,
+.Li :/dev/cd0 .
+.Pp
+This example, when put into
+.Pa /etc/auto_example ,
+and with
+.Nm
+referring to the map as described above, specifies that the NFS share
+.Li 192.168.1.1:/share/example/x
+will be mounted on
+.Pa /example/x/
+when any process attempts to access that mountpoint, with
+.Li intr
+and
+.Li nfsv4
+mount options, described in
+.Xr mount_nfs 8 :
+.Bd -literal -offset indent
+.Li x -intr,nfsv4 192.168.1.1:/share/example/x
+.Ed
+.Pp
+Automatically mount an SMB share on access, as a guest user,
+without prompting for a password:
+.Bd -literal -offset indent
+.Li share -fstype=smbfs,-N ://@server/share
+.Ed
+.Pp
+Automatically mount the CD drive on access:
+.Bd -literal -offset indent
+.Li cd -fstype=cd9660 :/dev/cd0
+.Ed
+.Sh SPECIAL MAPS
+Special maps have names beginning with
+.Li - .
+Supported special maps are:
+.Pp
+.Bl -tag -width "-hosts" -compact
+.It Li -hosts
+Query the remote NFS server and map exported shares.
+This map is traditionally mounted on
+.Pa /net .
+Access to files on a remote NFS server is provided through the
+.Pf /net/ Ar nfs-server-ip Ns / Ns Ar share-name Ns/
+directory without any additional configuration.
+Directories for individual NFS servers are not present until the first access,
+when they are automatically created.
+.It Li -media
+Query devices that are not yet mounted, but contain valid filesystems.
+Generally used to access files on removable media.
+.It Li -noauto
+Mount filesystems configured in
+.Xr fstab 5
+as "noauto".
+This needs to be set up as a direct map.
+.It Li -null
+Prevent
+.Xr automountd 8
+from mounting anything on the mountpoint.
+.El
+.Pp
+It is possible to add custom special maps by adding them, as executable
+maps named
+.Pa special_foo ,
+to the
+.Pa /etc/autofs/
+directory.
+.Sh EXECUTABLE MAPS
+If the map file specified in
+.Nm
+has the execute bit set,
+.Xr automountd 8
+will execute it and parse the standard output instead of parsing
+the file contents.
+When called without command line arguments, the executable is
+expected to output a list of available map keys separated by
+newline characters.
+Otherwise, the executable will be called with a key name as
+a command line argument.
+Output from the executable is expected to be the entry for that key,
+not including the key itself.
+.Sh INDIRECT VERSUS DIRECT MAPS
+Indirect maps are referred to in
+.Nm
+by entries with a fully qualified path as a mount point, and must contain only
+relative paths as keys.
+Direct maps are referred to in
+.Nm
+by entries with
+.Li /-
+as the mountpoint, and must contain only fully qualified paths as keys.
+For indirect maps, the final mount point is determined by concatenating the
+.Nm
+mountpoint with the map entry key and optional map entry mountpoint.
+For direct maps, the final mount point is determined by concatenating
+the map entry key with the optional map entry mountpoint.
+.Pp
+The example above could be rewritten using direct map, by placing this in
+.Nm :
+.Bd -literal -offset indent
+.Li /- auto_example
+.Ed
+.Pp
+and this in
+.Li /etc/auto_example
+map file:
+.Bd -literal -offset indent
+.Li /example/x -intr,nfsv4 192.168.1.1:/share/example/x
+.Li /example/share -fstype=smbfs,-N ://@server/share
+.Li /example/cd -fstype=cd9660 :/dev/cd0
+.Ed
+.Sh DIRECTORY SERVICES
+Both
+.Nm
+and maps may contain entries consisting of a plus sign and map name:
+.Bd -literal -offset indent
+.Li +auto_master
+.Ed
+.Pp
+Those entries cause
+.Xr automountd 8
+daemon to retrieve the named map from directory services (like LDAP)
+and include it where the entry was.
+.Pp
+If the file containing the map referenced in
+.Nm
+is not found, the map will be retrieved from directory services instead.
+.Pp
+To retrieve entries from directory services,
+.Xr automountd 8
+daemon runs
+.Pa /etc/autofs/include ,
+which is usually a shell script, with map name as the only command line
+parameter.
+The script should output entries formatted according to
+.Nm
+or automounter map syntax to standard output.
+An example script to use LDAP is included in
+.Pa /etc/autofs/include_ldap .
+It can be symlinked to
+.Pa /etc/autofs/include .
+.Sh FILES
+.Bl -tag -width ".Pa /etc/auto_master" -compact
+.It Pa /etc/auto_master
+The default location of the
+.Pa auto_master
+file.
+.It Pa /etc/autofs/
+Directory containing shell scripts to implement special maps and directory
+services.
+.El
+.Sh SEE ALSO
+.Xr autofs 5 ,
+.Xr automount 8 ,
+.Xr automountd 8 ,
+.Xr autounmountd 8
+.Sh AUTHORS
+The
+.Nm
+configuration file functionality was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
+.Pp
+The
+.Nm
+configuration file functionality was ported to
+.Dx
+and
+.Nx
+by
+.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com .
+.Sh BUGS
+The -media special map is currently unsupported on
+.Nx .
diff --git a/usr.sbin/autofs/automount.8 b/usr.sbin/autofs/automount.8
new file mode 100644
index 000000000000..007eec087cb4
--- /dev/null
+++ b/usr.sbin/autofs/automount.8
@@ -0,0 +1,133 @@
+.\" 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 November 25, 2017
+.Dt AUTOMOUNT 8
+.Os
+.Sh NAME
+.Nm automount
+.Nd update autofs mounts
+.Sh SYNOPSIS
+.Nm
+.Op Fl D Ar name=value
+.Op Fl L
+.Op Fl c
+.Op Fl f
+.Op Fl o Ar options
+.Op Fl v
+.Op Fl u
+.Sh DESCRIPTION
+When called without options, the
+.Nm
+command parses the
+.Xr auto_master 5
+configuration file and any direct maps that it references, and mounts
+or unmounts
+.Xr autofs 5
+filesystems to match.
+These options are available:
+.Bl -tag -width ".Fl v"
+.It Fl D
+Define a variable.
+It is only useful with
+.Fl L .
+.It Fl L
+Do not mount or unmount anything.
+Instead parse
+.Xr auto_master 5
+and any direct maps, then print them to standard output.
+When specified more than once, all the maps, including indirect ones,
+will be parsed and shown.
+This is useful when debugging configuration problems.
+.It Fl c
+Flush caches, discarding possibly stale information obtained from maps
+and directory services.
+.It Fl f
+Force unmount, to be used with
+.Fl u .
+.It Fl o
+Specify mount options to be used along with the ones specified in the maps.
+It is only useful with
+.Fl L .
+.It Fl u
+Try to unmount filesystems mounted by
+.Xr automountd 8 .
+.Xr autofs 5
+mounts are not unmounted.
+To unmount all
+.Xr autofs
+mounts, use
+.Cm "umount -At autofs".
+.It Fl v
+Increase verbosity.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Unmount all filesystems mounted by
+.Xr automountd 8 :
+.Dl Nm Fl u
+.Sh SEE ALSO
+.Xr autofs 5 ,
+.Xr auto_master 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 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
+.Pp
+The
+.Nm
+was ported to
+.Dx
+and
+.Nx
+by
+.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com .
diff --git a/usr.sbin/autofs/automount.c b/usr.sbin/autofs/automount.c
new file mode 100644
index 000000000000..f8c92f43ee06
--- /dev/null
+++ b/usr.sbin/autofs/automount.c
@@ -0,0 +1,346 @@
+/*-
+ * 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/statvfs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fs/autofs/autofs_mount.h>
+
+#include "common.h"
+
+static int
+unmount_by_statfs(const struct statvfs *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 statvfs *
+find_statfs(const struct statvfs *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, sizeof(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 statvfs *mntbuf, int nitems)
+{
+	const struct statvfs *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 statvfs *mntbuf;
+	struct node *n, *n2;
+	int i, nitems;
+
+	nitems = getmntinfo(&mntbuf, MNT_WAIT);
+	if (nitems <= 0)
+		log_err(1, "getmntinfo");
+
+	log_debugx("unmounting stale autofs mounts");
+
+	for (i = 0; i < nitems; i++) {
+		if (strcmp(mntbuf[i].f_fstypename, "autofs") != 0) {
+			log_debugx("skipping %s, filesystem type is not autofs",
+			    mntbuf[i].f_mntonname);
+			continue;
+		}
+
+		n = node_find(root, mntbuf[i].f_mntonname);
+		if (n != NULL) {
+			log_debugx("leaving autofs mounted on %s",
+			    mntbuf[i].f_mntonname);
+			continue;
+		}
+
+		log_debugx("autofs mounted on %s not found "
+		    "in new configuration; unmounting", mntbuf[i].f_mntonname);
+		unmount_by_statfs(&(mntbuf[i]), false);
+	}
+
+	log_debugx("mounting new autofs mounts");
+
+	TAILQ_FOREACH(n, &root->n_children, n_next) {
+		if (!node_is_direct_map(n)) {
+			mount_if_not_already(n, n->n_map, n->n_options,
+			    n->n_key, mntbuf, nitems);
+			continue;
+		}
+
+		TAILQ_FOREACH(n2, &n->n_children, n_next) {
+			mount_if_not_already(n2, n->n_map, n->n_options,
+			    "/", mntbuf, nitems);
+		}
+	}
+}
+
+static void
+flush_autofs(const char *fspath)
+{
+	int error;
+
+	log_debugx("flushing %s", fspath);
+
+	error = mount("autofs", fspath, MNT_UPDATE, NULL, 0);
+	if (error != 0)
+		log_err(1, "cannot flush %s", fspath);
+}
+
+static void
+flush_caches(void)
+{
+	struct statvfs *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 statvfs *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_flag & MNT_AUTOMOUNTED) == 0) {
+			log_debugx("skipping %s, not automounted",
+			    mntbuf[i].f_mntonname);
+			continue;
+		}
+
+		unmount_by_statfs(&(mntbuf[i]), force);
+	}
+}
+
+static void
+usage_automount(void)
+{
+
+	fprintf(stderr, "usage: automount [-D name=value][-o opts][-Lcfuv]\n");
+	exit(1);
+}
+
+int
+main_automount(int argc, char **argv)
+{
+	struct node *root;
+	int ch, debug = 0, show_maps = 0;
+	char *options = NULL;
+	bool do_unmount = false, force_unmount = false, flush = false;
+
+	/*
+	 * Note that in automount(8), the only purpose of variable
+	 * handling is to aid in debugging maps (automount -L).
+	 */
+	defined_init();
+
+	while ((ch = getopt(argc, argv, "D:Lfco:uv")) != -1) {
+		switch (ch) {
+		case 'D':
+			defined_parse_and_add(optarg);
+			break;
+		case 'L':
+			show_maps++;
+			break;
+		case 'c':
+			flush = true;
+			break;
+		case 'f':
+			force_unmount = true;
+			break;
+		case 'o':
+			options = concat(options, ',', optarg);
+			break;
+		case 'u':
+			do_unmount = true;
+			break;
+		case 'v':
+			debug++;
+			break;
+		case '?':
+		default:
+			usage_automount();
+		}
+	}
+	argc -= optind;
+	if (argc != 0)
+		usage_automount();
+
+	if (force_unmount && !do_unmount)
+		usage_automount();
+
+	log_init(debug);
+
+	if (flush) {
+		flush_caches();
+		return (0);
+	}
+
+	if (do_unmount) {
+		unmount_automounted(force_unmount);
+		return (0);
+	}
+
+	root = node_new_root();
+	parse_master(root, AUTO_MASTER_PATH);
+
+	if (show_maps) {
+		if (show_maps > 1) {
+			node_expand_indirect_maps(root);
+			node_expand_ampersand(root, NULL);
+		}
+		node_expand_defined(root);
+		node_print(root, options);
+		return (0);
+	}
+
+	mount_unmount(root);
+
+	return (0);
+}
diff --git a/usr.sbin/autofs/automountd.8 b/usr.sbin/autofs/automountd.8
new file mode 100644
index 000000000000..f4f1134ee38d
--- /dev/null
+++ b/usr.sbin/autofs/automountd.8
@@ -0,0 +1,126 @@
+.\" 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 November 25, 2017
+.Dt AUTOMOUNTD 8
+.Os
+.Sh NAME
+.Nm automountd
+.Nd daemon handling autofs mount requests
+.Sh SYNOPSIS
+.Nm
+.Op Fl D Ar name=value
+.Op Fl i
+.Op Fl m Ar maxproc
+.Op Fl o Ar options
+.Op Fl d
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+daemon is responsible for handling
+.Xr autofs 5
+mount requests, parsing maps,
+and mounting filesystems they specify.
+On startup,
+.Nm
+forks into background and waits for kernel requests.
+When a request is received,
+.Nm
+forks a child process.
+The child process parses the appropriate map and mounts filesystems accordingly.
+Then it signals the kernel to release blocked processes that were waiting
+for the mount.
+.Bl -tag -width ".Fl v"
+.It Fl D
+Define a variable.
+.It Fl i
+For indirect mounts, only create subdirectories if there are no wildcard
+entries.
+Without
+.Fl i ,
+.Nm
+creates all the subdirectories it can.
+Users may not realize that the wildcard map entry makes it possible to access
+directories that have not yet been created.
+.It Fl m Ar maxproc
+Limit the number of forked
+.Nm
+processes, and thus the number of mount requests being handled in parallel.
+The default is 30.
+.It Fl d
+Debug mode: increase verbosity and do not daemonize.
+.It Fl o Ar options
+Specify mount options.
+Options specified here will be overridden by options entered in maps or
+.Xr auto_master 5 .
+.It Fl v
+Increase verbosity.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr autofs 5 ,
+.Xr auto_master 5 ,
+.Xr hammer 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 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
+.Pp
+The
+.Nm
+was ported to
+.Dx
+and
+.Nx
+by
+.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com .
diff --git a/usr.sbin/autofs/automountd.c b/usr.sbin/autofs/automountd.c
new file mode 100644
index 000000000000..ba7194ef2dca
--- /dev/null
+++ b/usr.sbin/autofs/automountd.c
@@ -0,0 +1,598 @@
+/*-
+ * 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/module.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 <fs/autofs/autofs_ioctl.h>
+
+#include "common.h"
+
+#define AUTOMOUNTD_PIDFILE	"automountd"
+
+static int nchildren = 0;
+static int autofs_fd;
+static int request_id;
+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;
+
+	tofree = *optionsp;
+
+	newoptions = calloc(1, strlen(*optionsp) + 1);
+	if (newoptions == NULL)
+		log_err(1, "calloc");
+
+	while ((pair = strsep(optionsp, ",")) != NULL) {
+		/*
+		 * XXX: strncasecmp(3) perhaps?
+		 */
+		if (strncmp(pair, option, strlen(option)) == 0) {
+			picked = checked_strdup(pair + strlen(option));
+		} else {
+			if (first == false)
+				strcat(newoptions, ",");
+			else
+				first = false;
+			strcat(newoptions, pair);
+		}
+	}
+
+	free(tofree);
+	*optionsp = newoptions;
+
+	return (picked);
+}
+
+static void
+create_subtree(const struct node *node, bool incomplete)
+{
+	const struct node *child;
+	char *path;
+	bool wildcard_found = false;
+
+	/*
+	 * Skip wildcard nodes.
+	 */
+	if (strcmp(node->n_key, "*") == 0)
+		return;
+
+	path = node_path(node);
+	log_debugx("creating subtree at %s", path);
+	create_directory(path);
+
+	if (incomplete) {
+		TAILQ_FOREACH(child, &node->n_children, n_next) {
+			if (strcmp(child->n_key, "*") == 0) {
+				wildcard_found = true;
+				break;
+			}
+		}
+
+		if (wildcard_found) {
+			log_debugx("node %s contains wildcard entry; "
+			    "not creating its subdirectories due to -d flag",
+			    path);
+			free(path);
+			return;
+		}
+	}
+
+	free(path);
+
+	TAILQ_FOREACH(child, &node->n_children, n_next)
+		create_subtree(child, incomplete);
+}
+
+static void
+exit_callback(void)
+{
+
+	done(EIO, true);
+}
+
+static void
+handle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
+    bool incomplete_hierarchy)
+{
+	const char *map;
+	struct node *root, *parent, *node;
+	FILE *f;
+	char *key, *options, *fstype, *nobrowse, *retrycnt, *tmp;
+	int error;
+	bool wildcards;
+
+	log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
+	    "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
+	    adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
+
+	/*
+	 * Try to notify the kernel about any problems.
+	 */
+	request_id = adr->adr_id;
+	atexit(exit_callback);
+
+	if (strncmp(adr->adr_from, "map ", 4) != 0) {
+		log_errx(1, "invalid mountfrom \"%s\"; failing request",
+		    adr->adr_from);
+	}
+
+	map = adr->adr_from + 4; /* 4 for strlen("map "); */
+	root = node_new_root();
+	if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
+		/*
+		 * Direct map.  autofs(4) doesn't have a way to determine
+		 * correct map key, but since it's a direct map, we can just
+		 * use adr_path instead.
+		 */
+		parent = root;
+		key = checked_strdup(adr->adr_path);
+	} else {
+		/*
+		 * Indirect map.
+		 */
+		parent = node_new_map(root, checked_strdup(adr->adr_prefix),
+		    NULL,  checked_strdup(map),
+		    checked_strdup("[kernel request]"), lineno);
+
+		if (adr->adr_key[0] == '\0')
+			key = NULL;
+		else
+			key = checked_strdup(adr->adr_key);
+	}
+
+	/*
+	 * "Wildcards" here actually means "make autofs(4) request
+	 * automountd(8) action if the node being looked up does not
+	 * exist, even though the parent is marked as cached".  This
+	 * needs to be done for maps with wildcard entries, but also
+	 * for special and executable maps.
+	 */
+	parse_map(parent, map, key, &wildcards);
+	if (!wildcards)
+		wildcards = node_has_wildcards(parent);
+	if (wildcards)
+		log_debugx("map may contain wildcard entries");
+	else
+		log_debugx("map does not contain wildcard entries");
+
+	if (key != NULL)
+		node_expand_wildcard(root, key);
+
+	node = node_find(root, adr->adr_path);
+	if (node == NULL) {
+		log_errx(1, "map %s does not contain key for \"%s\"; "
+		    "failing mount", map, adr->adr_path);
+	}
+
+	options = node_options(node);
+
+	/*
+	 * Append options from auto_master.
+	 */
+	options = concat(options, ',', adr->adr_options);
+
+	/*
+	 * Prepend options passed via automountd(8) command line.
+	 */
+	options = concat(cmdline_options, ',', options);
+
+	if (node->n_location == NULL) {
+		log_debugx("found node defined at %s:%d; not a mountpoint",
+		    node->n_config_file, node->n_config_line);
+
+		nobrowse = pick_option("nobrowse", &options);
+		if (nobrowse != NULL && key == NULL) {
+			log_debugx("skipping map %s due to \"nobrowse\" "
+			    "option; exiting", map);
+			done(0, true);
+
+			/*
+			 * Exit without calling exit_callback().
+			 */
+			quick_exit(0);
+		}
+
+		/*
+		 * Not a mountpoint; create directories in the autofs mount
+		 * and complete the request.
+		 */
+		create_subtree(node, incomplete_hierarchy);
+
+		if (incomplete_hierarchy && key != NULL) {
+			/*
+			 * We still need to create the single subdirectory
+			 * user is trying to access.
+			 */
+			tmp = concat(adr->adr_path, '/', key);
+			node = node_find(root, tmp);
+			if (node != NULL)
+				create_subtree(node, false);
+		}
+
+		log_debugx("nothing to mount; exiting");
+		done(0, wildcards);
+
+		/*
+		 * Exit without calling exit_callback().
+		 */
+		quick_exit(0);
+	}
+
+	log_debugx("found node defined at %s:%d; it is a mountpoint",
+	    node->n_config_file, node->n_config_line);
+
+	if (key != NULL)
+		node_expand_ampersand(node, key);
+	error = node_expand_defined(node);
+	if (error != 0) {
+		log_errx(1, "variable expansion failed for %s; "
+		    "failing mount", adr->adr_path);
+	}
+
+	/*
+	 * Append "automounted".
+	 */
+	options = concat(options, ',', "automounted");
+
+	/*
+	 * Remove "nobrowse", mount(8) doesn't understand it.
+	 */
+	pick_option("nobrowse", &options);
+
+	/*
+	 * Figure out fstype.
+	 */
+	fstype = pick_option("fstype=", &options);
+	if (fstype == NULL) {
+		log_debugx("fstype not specified in options; "
+		    "defaulting to \"nfs\"");
+		fstype = checked_strdup("nfs");
+	}
+
+	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 doesn't have -o retrycnt=... option which is available
+	 * on FreeBSD and DragonFlyBSD, 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(0);
+}
+
+static void
+sigchld_handler(int dummy __unused)
+{
+
+	/*
+	 * The only purpose of this handler is to make SIGCHLD
+	 * interrupt the AUTOFSREQUEST ioctl(2), so we can call
+	 * wait_for_children().
+	 */
+}
+
+static void
+register_sigchld(void)
+{
+	struct sigaction sa;
+	int error;
+
+	bzero(&sa, sizeof(sa));
+	sa.sa_handler = sigchld_handler;
+	sigfillset(&sa.sa_mask);
+	error = sigaction(SIGCHLD, &sa, NULL);
+	if (error != 0)
+		log_err(1, "sigaction");
+}
+
+
+static int
+wait_for_children(bool block)
+{
+	pid_t pid;
+	int status;
+	int num = 0;
+
+	for (;;) {
+		/*
+		 * If "block" is true, wait for at least one process.
+		 */
+		if (block && num == 0)
+			pid = wait4(-1, &status, 0, NULL);
+		else
+			pid = wait4(-1, &status, WNOHANG, NULL);
+		if (pid <= 0)
+			break;
+		if (WIFSIGNALED(status)) {
+			log_warnx("child process %d terminated with signal %d",
+			    pid, WTERMSIG(status));
+		} else if (WEXITSTATUS(status) != 0) {
+			log_debugx("child process %d terminated with exit status %d",
+			    pid, WEXITSTATUS(status));
+		} else {
+			log_debugx("child process %d terminated gracefully", pid);
+		}
+		num++;
+	}
+
+	return (num);
+}
+
+static void
+usage_automountd(void)
+{
+
+	fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]"
+	    "[-o opts][-Tidv]\n");
+	exit(1);
+}
+
+static int
+load_autofs(void)
+{
+	modctl_load_t args = {
+		.ml_filename = "autofs",
+		.ml_flags = MODCTL_NO_PROP,
+		.ml_props = NULL,
+		.ml_propslen = 0
+	};
+	int error;
+
+	error = modctl(MODCTL_LOAD, &args);
+	if (error && errno != EEXIST)
+		log_warn("failed to load %s: %s", args.ml_filename,
+		    strerror(errno));
+
+	return error;
+}
+
+int
+main_automountd(int argc, char **argv)
+{
+	pid_t pid;
+	const char *pidfile_path = AUTOMOUNTD_PIDFILE;
+	char *options = NULL;
+	struct autofs_daemon_request request;
+	int ch, debug = 0, error, maxproc = 30, saved_errno;
+	bool dont_daemonize = false, incomplete_hierarchy = false;
+
+	defined_init();
+
+	while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
+		switch (ch) {
+		case 'D':
+			defined_parse_and_add(optarg);
+			break;
+		case 'T':
+			/*
+			 * For compatibility with other implementations,
+			 * such as OS X.
+			 */
+			debug++;
+			break;
+		case 'd':
+			dont_daemonize = true;
+			debug++;
+			break;
+		case 'i':
+			incomplete_hierarchy = true;
+			break;
+		case 'm':
+			maxproc = atoi(optarg);
+			break;
+		case 'o':
+			options = concat(options, ',', optarg);
+			break;
+		case 'v':
+			debug++;
+			break;
+		case '?':
+		default:
+			usage_automountd();
+		}
+	}
+	argc -= optind;
+	if (argc != 0)
+		usage_automountd();
+
+	log_init(debug);
+
+	/*
+	 * XXX: Workaround for NetBSD.
+	 * load_autofs() should be needed only if open(2) failed with ENXIO.
+	 * We attempt to load autofs before open(2) to suppress below warning
+	 * "module error: incompatible module class for `autofs' (3 != 2)",
+	 * which comes from sys/miscfs/specfs/spec_vnops.c:spec_open().
+	 * spec_open() tries to load autofs as MODULE_CLASS_DRIVER while autofs
+	 * is of MODULE_CLASS_VFS.
+	 */
+	load_autofs();
+
+	/*
+	 * NetBSD needs to check ENXIO here, but might not need ENOENT.
+	 */
+	autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
+	if (autofs_fd < 0 && (errno == ENOENT || errno == ENXIO)) {
+		saved_errno = errno;
+		if (!load_autofs())
+			autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
+		else
+			errno = saved_errno;
+	}
+	if (autofs_fd < 0)
+		log_err(1, "failed to open %s", AUTOFS_PATH);
+
+	if (dont_daemonize == false) {
+		if (daemon(0, 0) == -1) {
+			log_warn("cannot daemonize");
+			pidfile_clean();
+			exit(1);
+		}
+	} else {
+		lesser_daemon();
+	}
+
+	/*
+	 * Call pidfile(3) after daemon(3).
+	 */
+	if (pidfile(pidfile_path) == -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 \"%s\"", pidfile_path);
+	}
+	if (pidfile_lock(pidfile_path) == -1)
+		log_err(1, "cannot lock pidfile \"%s\"", pidfile_path);
+
+	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);
+	}
+
+	pidfile_clean();
+
+	return (0);
+}
+
diff --git a/usr.sbin/autofs/autounmountd.8 b/usr.sbin/autofs/autounmountd.8
new file mode 100644
index 000000000000..ec21e3921115
--- /dev/null
+++ b/usr.sbin/autofs/autounmountd.8
@@ -0,0 +1,110 @@
+.\" 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 November 25, 2017
+.Dt AUTOUNMOUNTD 8
+.Os
+.Sh NAME
+.Nm autounmountd
+.Nd daemon unmounting automounted filesystems
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl r Ar time
+.Op Fl t Ar time
+.Op Fl v
+.Sh DESCRIPTION
+The
+.Nm
+daemon is responsible for unmounting filesystems mounted by
+.Xr automountd 8 .
+On startup,
+.Nm
+retrieves a list of filesystems that have the
+.Li automounted
+mount option set.
+The list is updated every time a filesystem is mounted or unmounted.
+After a specified time passes,
+.Nm
+attempts to unmount a filesystem, retrying after some time if necessary.
+.Pp
+These options are available:
+.Bl -tag -width ".Fl v"
+.It Fl d
+Debug mode: increase verbosity and do not daemonize.
+.It Fl r
+Number of seconds to wait before trying to unmount an expired filesystem
+after a previous attempt failed, possibly due to filesystem being busy.
+The default value is 600, or ten minutes.
+.It Fl t
+Number of seconds to wait before trying to unmount a filesystem.
+The default value is 600, or ten minutes.
+.It Fl v
+Increase verbosity.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr autofs 5 ,
+.Xr auto_master 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 .
+.Sh AUTHORS
+The
+.Nm
+was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
+.Pp
+The
+.Nm
+was ported to
+.Dx
+and
+.Nx
+by
+.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com .
diff --git a/usr.sbin/autofs/autounmountd.c b/usr.sbin/autofs/autounmountd.c
new file mode 100644
index 000000000000..dd359ec61d61
--- /dev/null
+++ b/usr.sbin/autofs/autounmountd.c
@@ -0,0 +1,350 @@
+/*-
+ * 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 <sys/fstypes.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"
+
+#define AUTOUNMOUNTD_PIDFILE	"autounmountd"
+
+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.__fsid_val[0] == fsid.__fsid_val[0] &&
+		    af->af_fsid.__fsid_val[1] == fsid.__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 statvfs *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_flag & MNT_AUTOMOUNTED) == 0) {
+			log_debugx("skipping %s, not automounted",
+			    mntbuf[i].f_mntonname);
+			continue;
+		}
+
+		af = automounted_find(mntbuf[i].f_fsidx);
+		if (af == NULL) {
+			log_debugx("new automounted filesystem found on %s "
+			    "(FSID:%d:%d)", mntbuf[i].f_mntonname,
+			    mntbuf[i].f_fsidx.__fsid_val[0],
+			    mntbuf[i].f_fsidx.__fsid_val[1]);
+			af = automounted_add(mntbuf[i].f_fsidx,
+			    mntbuf[i].f_mntonname);
+		} else {
+			log_debugx("already known automounted filesystem "
+			    "found on %s (FSID:%d:%d)", mntbuf[i].f_mntonname,
+			    mntbuf[i].f_fsidx.__fsid_val[0],
+			    mntbuf[i].f_fsidx.__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.__fsid_val[0],
+		    af->af_fsid.__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.__fsid_val[0],
+			    af->af_fsid.__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.__fsid_val[0],
+		    af->af_fsid.__fsid_val[1], mounted_for);
+		error = do_unmount(af->af_fsid, af->af_mountpoint);
+		if (error != 0) {
+			if (mounted_for > mounted_max)
+				mounted_max = mounted_for;
+		}
+	}
+
+	return (mounted_max);
+}
+
+static void
+usage_autounmountd(void)
+{
+
+	fprintf(stderr, "usage: autounmountd [-r time][-t time][-dv]\n");
+	exit(1);
+}
+
+static void
+do_wait(int kq, double sleep_time)
+{
+	struct timespec timeout;
+	struct kevent unused;
+	int nevents;
+
+	if (sleep_time != -1.0) {
+		assert(sleep_time > 0.0);
+		timeout.tv_sec = sleep_time;
+		timeout.tv_nsec = 0;
+
+		log_debugx("waiting for filesystem event for %.0f seconds", sleep_time);
+		nevents = kevent(kq, NULL, 0, &unused, 1, &timeout);
+	} else {
+		log_debugx("waiting for filesystem event");
+		nevents = kevent(kq, NULL, 0, &unused, 1, NULL);
+	}
+	if (nevents < 0) {
+		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;
+	const char *pidfile_path = AUTOUNMOUNTD_PIDFILE;
+	int ch, debug = 0, error, kq;
+	double expiration_time = 600, retry_time = 600, mounted_max, sleep_time;
+	bool dont_daemonize = false;
+
+	while ((ch = getopt(argc, argv, "dr:t:v")) != -1) {
+		switch (ch) {
+		case 'd':
+			dont_daemonize = true;
+			debug++;
+			break;
+		case 'r':
+			retry_time = atoi(optarg);
+			break;
+		case 't':
+			expiration_time = atoi(optarg);
+			break;
+		case 'v':
+			debug++;
+			break;
+		case '?':
+		default:
+			usage_autounmountd();
+		}
+	}
+	argc -= optind;
+	if (argc != 0)
+		usage_autounmountd();
+
+	if (retry_time <= 0)
+		log_errx(1, "retry time must be greater than zero");
+	if (expiration_time <= 0)
+		log_errx(1, "expiration time must be greater than zero");
+
+	log_init(debug);
+
+	if (dont_daemonize == false) {
+		if (daemon(0, 0) == -1) {
+			log_warn("cannot daemonize");
+			pidfile_clean();
+			exit(1);
+		}
+	}
+
+	/*
+	 * Call pidfile(3) after daemon(3).
+	 */
+	if (pidfile(pidfile_path) == -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 \"%s\"", pidfile_path);
+	}
+	if (pidfile_lock(pidfile_path) == -1)
+		log_err(1, "cannot lock pidfile \"%s\"", pidfile_path);
+
+	TAILQ_INIT(&automounted);
+
+	kq = kqueue();
+	if (kq < 0)
+		log_err(1, "kqueue");
+
+	EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, (intptr_t)NULL);
+	error = kevent(kq, &event, 1, NULL, 0, NULL);
+	if (error < 0)
+		log_err(1, "kevent");
+
+	for (;;) {
+		refresh_automounted();
+		mounted_max = expire_automounted(expiration_time);
+		if (mounted_max == -1.0) {
+			sleep_time = mounted_max;
+			log_debugx("no filesystems to expire");
+		} else if (mounted_max < expiration_time) {
+			sleep_time = difftime(expiration_time, mounted_max);
+			log_debugx("some filesystems expire in %.0f seconds",
+			    sleep_time);
+		} else {
+			sleep_time = retry_time;
+			log_debugx("some expired filesystems remain mounted, "
+			    "will retry in %.0f seconds", sleep_time);
+		}
+
+		do_wait(kq, sleep_time);
+	}
+
+	return (0);
+}
diff --git a/usr.sbin/autofs/common.c b/usr.sbin/autofs/common.c
new file mode 100644
index 000000000000..752ccb16510b
--- /dev/null
+++ b/usr.sbin/autofs/common.c
@@ -0,0 +1,1211 @@
+/*-
+ * 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 <fs/autofs/autofs_ioctl.h>
+
+#include "common.h"
+
+extern FILE *yyin;
+extern char *yytext;
+extern int yylex(void);
+
+static void	parse_master_yyin(struct node *root, const char *master);
+static void	parse_map_yyin(struct node *parent, const char *map,
+		    const char *executable_key);
+
+char *
+checked_strdup(const char *s)
+{
+	char *c;
+
+	assert(s != NULL);
+
+	c = strdup(s);
+	if (c == NULL)
+		log_err(1, "strdup");
+	return (c);
+}
+
+/*
+ * Concatenate two strings, inserting separator between them, unless not needed.
+ */
+char *
+concat(const char *s1, char separator, const char *s2)
+{
+	char *result;
+	char s1last, s2first;
+	int ret;
+
+	if (s1 == NULL)
+		s1 = "";
+	if (s2 == NULL)
+		s2 = "";
+
+	if (s1[0] == '\0')
+		s1last = '\0';
+	else
+		s1last = s1[strlen(s1) - 1];
+
+	s2first = s2[0];
+
+	if (s1last == separator && s2first == separator) {
+		/*
+		 * If s1 ends with the separator and s2 begins with
+		 * it - skip the latter; otherwise concatenating "/"
+		 * and "/foo" would end up returning "//foo".
+		 */
+		ret = asprintf(&result, "%s%s", s1, s2 + 1);
+	} else if (s1last == separator || s2first == separator ||
+	    s1[0] == '\0' || s2[0] == '\0') {
+		ret = asprintf(&result, "%s%s", s1, s2);
+	} else {
+		ret = asprintf(&result, "%s%c%s", s1, separator, s2);
+	}
+	if (ret < 0)
+		log_err(1, "asprintf");
+
+	//log_debugx("%s: got %s and %s, returning %s", __func__, s1, s2, result);
+
+	return (result);
+}
+
+void
+create_directory(const char *path)
+{
+	char *component, *copy, *tofree, *partial, *tmp;
+	int error;
+
+	assert(path[0] == '/');
+
+	/*
+	 * +1 to skip the leading slash.
+	 */
+	copy = tofree = checked_strdup(path + 1);
+
+	partial = checked_strdup("");
+	for (;;) {
+		component = strsep(&copy, "/");
+		if (component == NULL)
+			break;
+		tmp = concat(partial, '/', component);
+		free(partial);
+		partial = tmp;
+		//log_debugx("creating \"%s\"", partial);
+		error = mkdir(partial, 0755);
+		if (error != 0 && errno != EEXIST) {
+			log_warn("cannot create %s", partial);
+			return;
+		}
+	}
+
+	free(tofree);
+}
+
+struct node *
+node_new_root(void)
+{
+	struct node *n;
+
+	n = calloc(1, sizeof(*n));
+	if (n == NULL)
+		log_err(1, "calloc");
+	// XXX
+	n->n_key = checked_strdup("/");
+	n->n_options = checked_strdup("");
+
+	TAILQ_INIT(&n->n_children);
+
+	return (n);
+}
+
+struct node *
+node_new(struct node *parent, char *key, char *options, char *location,
+    const char *config_file, int config_line)
+{
+	struct node *n;
+
+	n = calloc(1, sizeof(*n));
+	if (n == NULL)
+		log_err(1, "calloc");
+
+	TAILQ_INIT(&n->n_children);
+	assert(key != NULL);
+	assert(key[0] != '\0');
+	n->n_key = key;
+	if (options != NULL)
+		n->n_options = options;
+	else
+		n->n_options = strdup("");
+	n->n_location = location;
+	assert(config_file != NULL);
+	n->n_config_file = config_file;
+	assert(config_line >= 0);
+	n->n_config_line = config_line;
+
+	assert(parent != NULL);
+	n->n_parent = parent;
+	TAILQ_INSERT_TAIL(&parent->n_children, n, n_next);
+
+	return (n);
+}
+
+struct node *
+node_new_map(struct node *parent, char *key, char *options, char *map,
+    const char *config_file, int config_line)
+{
+	struct node *n;
+
+	n = calloc(1, sizeof(*n));
+	if (n == NULL)
+		log_err(1, "calloc");
+
+	TAILQ_INIT(&n->n_children);
+	assert(key != NULL);
+	assert(key[0] != '\0');
+	n->n_key = key;
+	if (options != NULL)
+		n->n_options = options;
+	else
+		n->n_options = strdup("");
+	n->n_map = map;
+	assert(config_file != NULL);
+	n->n_config_file = config_file;
+	assert(config_line >= 0);
+	n->n_config_line = config_line;
+
+	assert(parent != NULL);
+	n->n_parent = parent;
+	TAILQ_INSERT_TAIL(&parent->n_children, n, n_next);
+
+	return (n);
+}
+
+static struct node *
+node_duplicate(const struct node *o, struct node *parent)
+{
+	const struct node *child;
+	struct node *n;
+
+	if (parent == NULL)
+		parent = o->n_parent;
+
+	n = node_new(parent, o->n_key, o->n_options, o->n_location,
+	    o->n_config_file, o->n_config_line);
+
+	TAILQ_FOREACH(child, &o->n_children, n_next)
+		node_duplicate(child, n);
+
+	return (n);
+}
+
+static void
+node_delete(struct node *n)
+{
+	struct node *child, *tmp;
+
+	assert (n != NULL);
+
+	TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp)
+		node_delete(child);
+
+	if (n->n_parent != NULL)
+		TAILQ_REMOVE(&n->n_parent->n_children, n, n_next);
+
+	free(n);
+}
+
+/*
+ * Move (reparent) node 'n' to make it sibling of 'previous', placed
+ * just after it.
+ */
+static void
+node_move_after(struct node *n, struct node *previous)
+{
+
+	TAILQ_REMOVE(&n->n_parent->n_children, n, n_next);
+	n->n_parent = previous->n_parent;
+	TAILQ_INSERT_AFTER(&previous->n_parent->n_children, previous, n, n_next);
+}
+
+static void
+node_expand_includes(struct node *root, bool is_master)
+{
+	struct node *n, *n2, *tmp, *tmp2, *tmproot;
+	int error;
+
+	TAILQ_FOREACH_SAFE(n, &root->n_children, n_next, tmp) {
+		if (n->n_key[0] != '+')
+			continue;
+
+		error = access(AUTO_INCLUDE_PATH, F_OK);
+		if (error != 0) {
+			log_errx(1, "directory services not configured; "
+			    "%s does not exist", AUTO_INCLUDE_PATH);
+		}
+
+		/*
+		 * "+1" to skip leading "+".
+		 */
+		yyin = auto_popen(AUTO_INCLUDE_PATH, n->n_key + 1, NULL);
+		assert(yyin != NULL);
+
+		tmproot = node_new_root();
+		if (is_master)
+			parse_master_yyin(tmproot, n->n_key);
+		else
+			parse_map_yyin(tmproot, n->n_key, NULL);
+
+		error = auto_pclose(yyin);
+		yyin = NULL;
+		if (error != 0) {
+			log_errx(1, "failed to handle include \"%s\"",
+			    n->n_key);
+		}
+
+		/*
+		 * Entries to be included are now in tmproot.  We need to merge
+		 * them with the rest, preserving their place and ordering.
+		 */
+		TAILQ_FOREACH_REVERSE_SAFE(n2,
+		    &tmproot->n_children, nodehead, n_next, tmp2) {
+			node_move_after(n2, n);
+		}
+
+		node_delete(n);
+		node_delete(tmproot);
+	}
+}
+
+static char *
+expand_ampersand(char *string, const char *key)
+{
+	char c, *expanded;
+	int i, ret, before_len = 0;
+	bool backslashed = false;
+
+	assert(key[0] != '\0');
+
+	expanded = checked_strdup(string);
+
+	for (i = 0; string[i] != '\0'; i++) {
+		c = string[i];
+		if (c == '\\' && backslashed == false) {
+			backslashed = true;
+			continue;
+		}
+		if (backslashed) {
+			backslashed = false;
+			continue;
+		}
+		backslashed = false;
+		if (c != '&')
+			continue;
+
+		/*
+		 * The 'before_len' variable contains the number
+		 * of characters before the '&'.
+		 */
+		before_len = i;
+		//assert(i + 1 < (int)strlen(string));
+
+		ret = asprintf(&expanded, "%.*s%s%s",
+		    before_len, string, key, string + before_len + 1);
+		if (ret < 0)
+			log_err(1, "asprintf");
+
+		//log_debugx("\"%s\" expanded with key \"%s\" to \"%s\"",
+		//    string, key, expanded);
+
+		/*
+		 * Figure out where to start searching for next variable.
+		 */
+		string = expanded;
+		i = before_len + strlen(key);
+		backslashed = false;
+		//assert(i < (int)strlen(string));
+	}
+
+	return (expanded);
+}
+
+/*
+ * Expand "&" in n_location.  If the key is NULL, try to use
+ * key from map entries themselves.  Keep in mind that maps
+ * consist of tho levels of node structures, the key is one
+ * level up.
+ *
+ * Variant with NULL key is for "automount -LL".
+ */
+void
+node_expand_ampersand(struct node *n, const char *key)
+{
+	struct node *child;
+
+	if (n->n_location != NULL) {
+		if (key == NULL) {
+			if (n->n_parent != NULL &&
+			    strcmp(n->n_parent->n_key, "*") != 0) {
+				n->n_location = expand_ampersand(n->n_location,
+				    n->n_parent->n_key);
+			}
+		} else {
+			n->n_location = expand_ampersand(n->n_location, key);
+		}
+	}
+
+	TAILQ_FOREACH(child, &n->n_children, n_next)
+		node_expand_ampersand(child, key);
+}
+
+/*
+ * Expand "*" in n_key.
+ */
+void
+node_expand_wildcard(struct node *n, const char *key)
+{
+	struct node *child, *expanded;
+
+	assert(key != NULL);
+
+	if (strcmp(n->n_key, "*") == 0) {
+		expanded = node_duplicate(n, NULL);
+		expanded->n_key = checked_strdup(key);
+		node_move_after(expanded, n);
+	}
+
+	TAILQ_FOREACH(child, &n->n_children, n_next)
+		node_expand_wildcard(child, key);
+}
+
+int
+node_expand_defined(struct node *n)
+{
+	struct node *child;
+	int error, cumulated_error = 0;
+
+	if (n->n_location != NULL) {
+		n->n_location = defined_expand(n->n_location);
+		if (n->n_location == NULL) {
+			log_warnx("failed to expand location for %s",
+			    node_path(n));
+			return (EINVAL);
+		}
+	}
+
+	TAILQ_FOREACH(child, &n->n_children, n_next) {
+		error = node_expand_defined(child);
+		if (error != 0 && cumulated_error == 0)
+			cumulated_error = error;
+	}
+
+	return (cumulated_error);
+}
+
+static bool
+node_is_direct_key(const struct node *n)
+{
+
+	if (n->n_parent != NULL && n->n_parent->n_parent == NULL &&
+	    strcmp(n->n_key, "/-") == 0) {
+		return (true);
+	}
+
+	return (false);
+}
+
+bool
+node_is_direct_map(const struct node *n)
+{
+
+	for (;;) {
+		assert(n->n_parent != NULL);
+		if (n->n_parent->n_parent == NULL)
+			break;
+		n = n->n_parent;
+	}
+
+	return (node_is_direct_key(n));
+}
+
+bool
+node_has_wildcards(const struct node *n)
+{
+	const struct node *child;
+
+	TAILQ_FOREACH(child, &n->n_children, n_next) {
+		if (strcmp(child->n_key, "*") == 0)
+			return (true);
+	}
+
+	return (false);
+}
+
+static void
+node_expand_maps(struct node *n, bool indirect)
+{
+	struct node *child, *tmp;
+
+	TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) {
+		if (node_is_direct_map(child)) {
+			if (indirect)
+				continue;
+		} else {
+			if (indirect == false)
+				continue;
+		}
+
+		/*
+		 * This is the first-level map node; the one that contains
+		 * the key and subnodes with mountpoints and actual map names.
+		 */
+		if (child->n_map == NULL)
+			continue;
+
+		if (indirect) {
+			log_debugx("map \"%s\" is an indirect map, parsing",
+			    child->n_map);
+		} else {
+			log_debugx("map \"%s\" is a direct map, parsing",
+			    child->n_map);
+		}
+		parse_map(child, child->n_map, NULL, NULL);
+	}
+}
+
+static void
+node_expand_direct_maps(struct node *n)
+{
+
+	node_expand_maps(n, false);
+}
+
+void
+node_expand_indirect_maps(struct node *n)
+{
+
+	node_expand_maps(n, true);
+}
+
+static char *
+node_path_x(const struct node *n, char *x)
+{
+	char *path;
+
+	if (n->n_parent == NULL)
+		return (x);
+
+	/*
+	 * Return "/-" for direct maps only if we were asked for path
+	 * to the "/-" node itself, not to any of its subnodes.
+	 */
+	if (node_is_direct_key(n) && x[0] != '\0')
+		return (x);
+
+	assert(n->n_key[0] != '\0');
+	path = concat(n->n_key, '/', x);
+	free(x);
+
+	return (node_path_x(n->n_parent, path));
+}
+
+/*
+ * Return full path for node, consisting of concatenated
+ * paths of node itself and all its parents, up to the root.
+ */
+char *
+node_path(const struct node *n)
+{
+	char *path;
+	size_t len;
+
+	path = node_path_x(n, checked_strdup(""));
+
+	/*
+	 * Strip trailing slash, unless the whole path is "/".
+	 */
+	len = strlen(path);
+	if (len > 1 && path[len - 1] == '/')
+		path[len - 1] = '\0';
+
+	return (path);
+}
+
+static char *
+node_options_x(const struct node *n, char *x)
+{
+	char *options;
+
+	if (n == NULL)
+		return (x);
+
+	options = concat(x, ',', n->n_options);
+	free(x);
+
+	return (node_options_x(n->n_parent, options));
+}
+
+/*
+ * Return options for node, consisting of concatenated
+ * options from the node itself and all its parents,
+ * up to the root.
+ */
+char *
+node_options(const struct node *n)
+{
+
+	return (node_options_x(n, checked_strdup("")));
+}
+
+static void
+node_print_indent(const struct node *n, const char *cmdline_options,
+    int indent)
+{
+	const struct node *child, *first_child;
+	char *path, *options, *tmp;
+
+	path = node_path(n);
+	tmp = node_options(n);
+	options = concat(cmdline_options, ',', tmp);
+	free(tmp);
+
+	/*
+	 * Do not show both parent and child node if they have the same
+	 * mountpoint; only show the child node.  This means the typical,
+	 * "key location", map entries are shown in a single line;
+	 * the "key mountpoint1 location2 mountpoint2 location2" entries
+	 * take multiple lines.
+	 */
+	first_child = TAILQ_FIRST(&n->n_children);
+	if (first_child == NULL || TAILQ_NEXT(first_child, n_next) != NULL ||
+	    strcmp(path, node_path(first_child)) != 0) {
+		assert(n->n_location == NULL || n->n_map == NULL);
+		printf("%*.s%-*s %s%-*s %-*s # %s map %s at %s:%d\n",
+		    indent, "",
+		    25 - indent,
+		    path,
+		    options[0] != '\0' ? "-" : " ",
+		    20,
+		    options[0] != '\0' ? options : "",
+		    20,
+		    n->n_location != NULL ? n->n_location : n->n_map != NULL ? n->n_map : "",
+		    node_is_direct_map(n) ? "direct" : "indirect",
+		    indent == 0 ? "referenced" : "defined",
+		    n->n_config_file, n->n_config_line);
+	}
+
+	free(path);
+	free(options);
+
+	TAILQ_FOREACH(child, &n->n_children, n_next)
+		node_print_indent(child, cmdline_options, indent + 2);
+}
+
+/*
+ * Recursively print node with all its children.  The cmdline_options
+ * argument is used for additional options to be prepended to all the
+ * others - usually those are the options passed by command line.
+ */
+void
+node_print(const struct node *n, const char *cmdline_options)
+{
+	const struct node *child;
+
+	TAILQ_FOREACH(child, &n->n_children, n_next)
+		node_print_indent(child, cmdline_options, 0);
+}
+
+static struct node *
+node_find_x(struct node *node, const char *path)
+{
+	struct node *child, *found;
+	char *tmp;
+	size_t tmplen;
+
+	//log_debugx("looking up %s in %s", path, node_path(node));
+
+	if (!node_is_direct_key(node)) {
+		tmp = node_path(node);
+		tmplen = strlen(tmp);
+		if (strncmp(tmp, path, tmplen) != 0) {
+			free(tmp);
+			return (NULL);
+		}
+		if (path[tmplen] != '/' && path[tmplen] != '\0') {
+			/*
+			 * If we have two map entries like 'foo' and 'foobar', make
+			 * sure the search for 'foobar' won't match 'foo' instead.
+			 */
+			free(tmp);
+			return (NULL);
+		}
+		free(tmp);
+	}
+
+	TAILQ_FOREACH(child, &node->n_children, n_next) {
+		found = node_find_x(child, path);
+		if (found != NULL)
+			return (found);
+	}
+
+	if (node->n_parent == NULL || node_is_direct_key(node))
+		return (NULL);
+
+	return (node);
+}
+
+struct node *
+node_find(struct node *root, const char *path)
+{
+	struct node *node;
+
+	assert(root->n_parent == NULL);
+
+	node = node_find_x(root, path);
+	if (node != NULL)
+		assert(node != root);
+
+	return (node);
+}
+
+/*
+ * Canonical form of a map entry looks like this:
+ *
+ * key [-options] [ [/mountpoint] [-options2] location ... ]
+ *
+ * Entries for executable maps are slightly different, as they
+ * lack the 'key' field and are always single-line; the key field
+ * for those maps is taken from 'executable_key' argument.
+ *
+ * We parse it in such a way that a map always has two levels - first
+ * for key, and the second, for the mountpoint.
+ */
+static void
+parse_map_yyin(struct node *parent, const char *map, const char *executable_key)
+{
+	char *key = NULL, *options = NULL, *mountpoint = NULL,
+	    *options2 = NULL, *location = NULL;
+	int ret;
+	struct node *node;
+
+	lineno = 1;
+
+	if (executable_key != NULL)
+		key = checked_strdup(executable_key);
+
+	for (;;) {
+		ret = yylex();
+		if (ret == 0 || ret == NEWLINE) {
+			/*
+			 * In case of executable map, the key is always
+			 * non-NULL, even if the map is empty.  So, make sure
+			 * we don't fail empty maps here.
+			 */
+			if ((key != NULL && executable_key == NULL) ||
+			    options != NULL) {
+				log_errx(1, "truncated entry at %s, line %d",
+				    map, lineno);
+			}
+			if (ret == 0 || executable_key != NULL) {
+				/*
+				 * End of file.
+				 */
+				break;
+			} else {
+				key = options = NULL;
+				continue;
+			}
+		}
+		if (key == NULL) {
+			key = checked_strdup(yytext);
+			if (key[0] == '+') {
+				node_new(parent, key, NULL, NULL, map, lineno);
+				key = options = NULL;
+				continue;
+			}
+			continue;
+		} else if (yytext[0] == '-') {
+			if (options != NULL) {
+				log_errx(1, "duplicated options at %s, line %d",
+				    map, lineno);
+			}
+			/*
+			 * +1 to skip leading "-".
+			 */
+			options = checked_strdup(yytext + 1);
+			continue;
+		}
+
+		/*
+		 * We cannot properly handle a situation where the map key
+		 * is "/".  Ignore such entries.
+		 *
+		 * XXX: According to Piete Brooks, Linux automounter uses
+		 *	"/" as a wildcard character in LDAP maps.  Perhaps
+		 *	we should work around this braindamage by substituting
+		 *	"*" for "/"?
+		 */
+		if (strcmp(key, "/") == 0) {
+			log_warnx("nonsensical map key \"/\" at %s, line %d; "
+			    "ignoring map entry ", map, lineno);
+
+			/*
+			 * Skip the rest of the entry.
+			 */
+			do {
+				ret = yylex();
+			} while (ret != 0 && ret != NEWLINE);
+
+			key = options = NULL;
+			continue;
+		}
+
+		//log_debugx("adding map node, %s", key);
+		node = node_new(parent, key, options, NULL, map, lineno);
+		key = options = NULL;
+
+		for (;;) {
+			if (yytext[0] == '/') {
+				if (mountpoint != NULL) {
+					log_errx(1, "duplicated mountpoint "
+					    "in %s, line %d", map, lineno);
+				}
+				if (options2 != NULL || location != NULL) {
+					log_errx(1, "mountpoint out of order "
+					    "in %s, line %d", map, lineno);
+				}
+				mountpoint = checked_strdup(yytext);
+				goto again;
+			}
+
+			if (yytext[0] == '-') {
+				if (options2 != NULL) {
+					log_errx(1, "duplicated options "
+					    "in %s, line %d", map, lineno);
+				}
+				if (location != NULL) {
+					log_errx(1, "options out of order "
+					    "in %s, line %d", map, lineno);
+				}
+				options2 = checked_strdup(yytext + 1);
+				goto again;
+			}
+
+			if (location != NULL) {
+				log_errx(1, "too many arguments "
+				    "in %s, line %d", map, lineno);
+			}
+
+			/*
+			 * If location field starts with colon, e.g. ":/dev/cd0",
+			 * then strip it.
+			 */
+			if (yytext[0] == ':') {
+				location = checked_strdup(yytext + 1);
+				if (location[0] == '\0') {
+					log_errx(1, "empty location in %s, "
+					    "line %d", map, lineno);
+				}
+			} else {
+				location = checked_strdup(yytext);
+			}
+
+			if (mountpoint == NULL)
+				mountpoint = checked_strdup("/");
+			if (options2 == NULL)
+				options2 = checked_strdup("");
+
+#if 0
+			log_debugx("adding map node, %s %s %s",
+			    mountpoint, options2, location);
+#endif
+			node_new(node, mountpoint, options2, location,
+			    map, lineno);
+			mountpoint = options2 = location = NULL;
+again:
+			ret = yylex();
+			if (ret == 0 || ret == NEWLINE) {
+				if (mountpoint != NULL || options2 != NULL ||
+				    location != NULL) {
+					log_errx(1, "truncated entry "
+					    "in %s, line %d", map, lineno);
+				}
+				break;
+			}
+		}
+	}
+}
+
+/*
+ * Parse output of a special map called without argument.  It is a list
+ * of keys, separated by newlines.  They can contain whitespace, so use
+ * getline(3) instead of lexer used for maps.
+ */
+static void
+parse_map_keys_yyin(struct node *parent, const char *map)
+{
+	char *line = NULL, *key;
+	size_t linecap = 0;
+	ssize_t linelen;
+
+	lineno = 1;
+
+	for (;;) {
+		linelen = getline(&line, &linecap, yyin);
+		if (linelen < 0) {
+			/*
+			 * End of file.
+			 */
+			break;
+		}
+		if (linelen <= 1) {
+			/*
+			 * Empty line, consisting of just the newline.
+			 */
+			continue;
+		}
+
+		/*
+		 * "-1" to strip the trailing newline.
+		 */
+		key = strndup(line, linelen - 1);
+
+		log_debugx("adding key \"%s\"", key);
+		node_new(parent, key, NULL, NULL, map, lineno);
+		lineno++;
+	}
+	free(line);
+}
+
+static bool
+file_is_executable(const char *path)
+{
+	struct stat sb;
+	int error;
+
+	error = stat(path, &sb);
+	if (error != 0)
+		log_err(1, "cannot stat %s", path);
+	if ((sb.st_mode & S_IXUSR) || (sb.st_mode & S_IXGRP) ||
+	    (sb.st_mode & S_IXOTH))
+		return (true);
+	return (false);
+}
+
+/*
+ * Parse a special map, e.g. "-hosts".
+ */
+static void
+parse_special_map(struct node *parent, const char *map, const char *key)
+{
+	char *path;
+	int error, ret;
+
+	assert(map[0] == '-');
+
+	/*
+	 * +1 to skip leading "-" in map name.
+	 */
+	ret = asprintf(&path, "%s/special_%s", AUTO_SPECIAL_PREFIX, map + 1);
+	if (ret < 0)
+		log_err(1, "asprintf");
+
+	yyin = auto_popen(path, key, NULL);
+	assert(yyin != NULL);
+
+	if (key == NULL) {
+		parse_map_keys_yyin(parent, map);
+	} else {
+		parse_map_yyin(parent, map, key);
+	}
+
+	error = auto_pclose(yyin);
+	yyin = NULL;
+	if (error != 0)
+		log_errx(1, "failed to handle special map \"%s\"", map);
+
+	node_expand_includes(parent, false);
+	node_expand_direct_maps(parent);
+
+	free(path);
+}
+
+/*
+ * Retrieve and parse map from directory services, e.g. LDAP.
+ * Note that it is different from executable maps, in that
+ * the include script outputs the whole map to standard output
+ * (as opposed to executable maps that only output a single
+ * entry, without the key), and it takes the map name as an
+ * argument, instead of key.
+ */
+static void
+parse_included_map(struct node *parent, const char *map)
+{
+	int error;
+
+	assert(map[0] != '-');
+	assert(map[0] != '/');
+
+	error = access(AUTO_INCLUDE_PATH, F_OK);
+	if (error != 0) {
+		log_errx(1, "directory services not configured;"
+		    " %s does not exist", AUTO_INCLUDE_PATH);
+	}
+
+	yyin = auto_popen(AUTO_INCLUDE_PATH, map, NULL);
+	assert(yyin != NULL);
+
+	parse_map_yyin(parent, map, NULL);
+
+	error = auto_pclose(yyin);
+	yyin = NULL;
+	if (error != 0)
+		log_errx(1, "failed to handle remote map \"%s\"", map);
+
+	node_expand_includes(parent, false);
+	node_expand_direct_maps(parent);
+}
+
+void
+parse_map(struct node *parent, const char *map, const char *key,
+    bool *wildcards)
+{
+	char *path = NULL;
+	int error, ret;
+	bool executable;
+
+	assert(map != NULL);
+	assert(map[0] != '\0');
+
+	log_debugx("parsing map \"%s\"", map);
+
+	if (wildcards != NULL)
+		*wildcards = false;
+
+	if (map[0] == '-') {
+		if (wildcards != NULL)
+			*wildcards = true;
+		return (parse_special_map(parent, map, key));
+	}
+
+	if (map[0] == '/') {
+		path = checked_strdup(map);
+	} else {
+		ret = asprintf(&path, "%s/%s", AUTO_MAP_PREFIX, map);
+		if (ret < 0)
+			log_err(1, "asprintf");
+		log_debugx("map \"%s\" maps to \"%s\"", map, path);
+
+		/*
+		 * See if the file exists.  If not, try to obtain the map
+		 * from directory services.
+		 */
+		error = access(path, F_OK);
+		if (error != 0) {
+			log_debugx("map file \"%s\" does not exist; falling "
+			    "back to directory services", path);
+			return (parse_included_map(parent, map));
+		}
+	}
+
+	executable = file_is_executable(path);
+
+	if (executable) {
+		log_debugx("map \"%s\" is executable", map);
+
+		if (wildcards != NULL)
+			*wildcards = true;
+
+		if (key != NULL) {
+			yyin = auto_popen(path, key, NULL);
+		} else {
+			yyin = auto_popen(path, NULL);
+		}
+		assert(yyin != NULL);
+	} else {
+		yyin = fopen(path, "r");
+		if (yyin == NULL)
+			log_err(1, "unable to open \"%s\"", path);
+	}
+
+	free(path);
+	path = NULL;
+
+	parse_map_yyin(parent, map, executable ? key : NULL);
+
+	if (executable) {
+		error = auto_pclose(yyin);
+		yyin = NULL;
+		if (error != 0) {
+			log_errx(1, "failed to handle executable map \"%s\"",
+			    map);
+		}
+	} else {
+		fclose(yyin);
+	}
+	yyin = NULL;
+
+	log_debugx("done parsing map \"%s\"", map);
+
+	node_expand_includes(parent, false);
+	node_expand_direct_maps(parent);
+}
+
+static void
+parse_master_yyin(struct node *root, const char *master)
+{
+	char *mountpoint = NULL, *map = NULL, *options = NULL;
+	int ret;
+
+	/*
+	 * XXX: 1 gives incorrect values; wtf?
+	 */
+	lineno = 0;
+
+	for (;;) {
+		ret = yylex();
+		if (ret == 0 || ret == NEWLINE) {
+			if (mountpoint != NULL) {
+				//log_debugx("adding map for %s", mountpoint);
+				node_new_map(root, mountpoint, options, map,
+				    master, lineno);
+			}
+			if (ret == 0) {
+				break;
+			} else {
+				mountpoint = map = options = NULL;
+				continue;
+			}
+		}
+		if (mountpoint == NULL) {
+			mountpoint = checked_strdup(yytext);
+		} else if (map == NULL) {
+			map = checked_strdup(yytext);
+		} else if (options == NULL) {
+			/*
+			 * +1 to skip leading "-".
+			 */
+			options = checked_strdup(yytext + 1);
+		} else {
+			log_errx(1, "too many arguments at %s, line %d",
+			    master, lineno);
+		}
+	}
+}
+
+void
+parse_master(struct node *root, const char *master)
+{
+
+	log_debugx("parsing auto_master file at \"%s\"", master);
+
+	yyin = fopen(master, "r");
+	if (yyin == NULL)
+		err(1, "unable to open %s", master);
+
+	parse_master_yyin(root, master);
+
+	fclose(yyin);
+	yyin = NULL;
+
+	log_debugx("done parsing \"%s\"", master);
+
+	node_expand_includes(root, true);
+	node_expand_direct_maps(root);
+}
+
+/*
+ * Two things daemon(3) does, that we actually also want to do
+ * when running in foreground, is closing the stdin and chdiring
+ * to "/".  This is what we do here.
+ */
+void
+lesser_daemon(void)
+{
+	int error, fd;
+
+	error = chdir("/");
+	if (error != 0)
+		log_warn("chdir");
+
+	fd = open(_PATH_DEVNULL, O_RDWR, 0);
+	if (fd < 0) {
+		log_warn("cannot open %s", _PATH_DEVNULL);
+		return;
+	}
+
+	error = dup2(fd, STDIN_FILENO);
+	if (error != 0)
+		log_warn("dup2");
+
+	error = close(fd);
+	if (error != 0) {
+		/* Bloody hell. */
+		log_warn("close");
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	char *cmdname;
+
+	if (argv[0] == NULL)
+		log_errx(1, "NULL command name");
+
+	cmdname = basename(argv[0]);
+
+	if (strcmp(cmdname, "automount") == 0)
+		return (main_automount(argc, argv));
+	else if (strcmp(cmdname, "automountd") == 0)
+		return (main_automountd(argc, argv));
+	else if (strcmp(cmdname, "autounmountd") == 0)
+		return (main_autounmountd(argc, argv));
+	else
+		log_errx(1, "binary name should be either \"automount\", "
+		    "\"automountd\", or \"autounmountd\"");
+}
diff --git a/usr.sbin/autofs/common.h b/usr.sbin/autofs/common.h
new file mode 100644
index 000000000000..4359c7c76cee
--- /dev/null
+++ b/usr.sbin/autofs/common.h
@@ -0,0 +1,119 @@
+/*-
+ * 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 level);
+void	log_set_peer_name(const char *name);
+void	log_set_peer_addr(const char *addr);
+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 *s1, char separator, const char *s2);
+void	create_directory(const char *path);
+
+struct node	*node_new_root(void);
+struct node	*node_new(struct node *parent, char *key, char *options,
+		    char *location, const char *config_file, int config_line);
+struct node	*node_new_map(struct node *parent, char *key, char *options,
+		    char *map, const char *config_file, int config_line);
+struct node	*node_find(struct node *root, const char *mountpoint);
+bool		node_is_direct_map(const struct node *n);
+bool		node_has_wildcards(const struct node *n);
+char	*node_path(const struct node *n);
+char	*node_options(const struct node *n);
+void	node_expand_ampersand(struct node *root, const char *key);
+void	node_expand_wildcard(struct node *root, const char *key);
+int	node_expand_defined(struct node *root);
+void	node_expand_indirect_maps(struct node *n);
+void	node_print(const struct node *n, const char *cmdline_options);
+void	parse_master(struct node *root, const char *path);
+void	parse_map(struct node *parent, const char *map, const char *args,
+	    bool *wildcards);
+char	*defined_expand(const char *string);
+void	defined_init(void);
+void	defined_parse_and_add(char *def);
+void	lesser_daemon(void);
+
+int	main_automount(int argc, char **argv);
+int	main_automountd(int argc, char **argv);
+int	main_autounmountd(int argc, char **argv);
+
+FILE	*auto_popen(const char *argv0, ...);
+int	auto_pclose(FILE *iop);
+
+/*
+ * lex(1) stuff.
+ */
+extern int lineno;
+
+#define	STR	1
+#define	NEWLINE	2
+
+#endif /* !AUTOMOUNTD_H */
diff --git a/usr.sbin/autofs/defined.c b/usr.sbin/autofs/defined.c
new file mode 100644
index 000000000000..e275f3ec0833
--- /dev/null
+++ b/usr.sbin/autofs/defined.c
@@ -0,0 +1,257 @@
+/*-
+ * 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 i, ret, before_len = 0, name_off = 0, name_len = 0, after_off = 0;
+	bool backslashed = false, bracketed = false;
+
+	expanded = checked_strdup(string);
+
+	for (i = 0; string[i] != '\0'; i++) {
+		c = string[i];
+		if (c == '\\' && backslashed == false) {
+			backslashed = true;
+			continue;
+		}
+		if (backslashed) {
+			backslashed = false;
+			continue;
+		}
+		backslashed = false;
+		if (c != '$')
+			continue;
+
+		/*
+		 * The 'before_len' variable contains the number
+		 * of characters before the '$'.
+		 */
+		before_len = i;
+		assert(i + 1 < (int)strlen(string));
+		if (string[i + 1] == '{')
+			bracketed = true;
+
+		if (string[i + 1] == '\0') {
+			log_warnx("truncated variable");
+			return (NULL);
+		}
+
+		/*
+		 * Skip '$'.
+		 */
+		i++;
+
+		if (bracketed) {
+			if (string[i + 1] == '\0') {
+				log_warnx("truncated variable");
+				return (NULL);
+			}
+
+			/*
+			 * Skip '{'.
+			 */
+			i++;
+		}
+
+		/*
+		 * The 'name_off' variable contains the number
+		 * of characters before the variable name,
+		 * including the "$" or "${".
+		 */
+		name_off = i;
+
+		for (; string[i] != '\0'; i++) {
+			c = string[i];
+			/*
+			 * XXX: Decide on the set of characters that can be
+			 *	used in a variable name.
+			 */
+			if (isalnum((int)c) || c == '_')
+				continue;
+
+			/*
+			 * End of variable name.
+			 */
+			if (bracketed) {
+				if (c != '}')
+					continue;
+
+				/*
+				 * The 'after_off' variable contains the number
+				 * of characters before the rest of the string,
+				 * i.e. after the variable name.
+				 */
+				after_off = i + 1;
+				assert(i > 1);
+				assert(i - 1 > name_off);
+				name_len = i - name_off;
+				break;
+			}
+
+			after_off = i;
+			assert(i > 1);
+			assert(i > name_off);
+			name_len = i - name_off;
+			break;
+		}
+
+		name = strndup(string + name_off, name_len);
+		if (name == NULL)
+			log_err(1, "strndup");
+		value = defined_find(name);
+		if (value == NULL) {
+			log_warnx("undefined variable ${%s}", name);
+			return (NULL);
+		}
+
+		/*
+		 * Concatenate it back.
+		 */
+		ret = asprintf(&expanded, "%.*s%s%s",
+		    before_len, string, value, string + after_off);
+		if (ret < 0)
+			log_err(1, "asprintf");
+
+		//log_debugx("\"%s\" expanded to \"%s\"", string, expanded);
+		free(name);
+
+		/*
+		 * Figure out where to start searching for next variable.
+		 */
+		string = expanded;
+		i = before_len + strlen(value);
+		backslashed = bracketed = false;
+		before_len = name_off = name_len = after_off = 0;
+		assert(i <= (int)strlen(string));
+	}
+
+	if (before_len != 0 || name_off != 0 || name_len != 0 || after_off != 0) {
+		log_warnx("truncated variable");
+		return (NULL);
+	}
+
+	return (expanded);
+}
+
+static void
+defined_add(const char *name, const char *value)
+{
+	struct defined_value *d;
+	const char *found;
+
+	found = defined_find(name);
+	if (found != NULL)
+		log_errx(1, "variable %s already defined", name);
+
+	log_debugx("defining variable %s=%s", name, value);
+
+	d = calloc(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 --git a/usr.sbin/autofs/log.c b/usr.sbin/autofs/log.c
new file mode 100644
index 000000000000..e66e8cc37704
--- /dev/null
+++ b/usr.sbin/autofs/log.c
@@ -0,0 +1,203 @@
+/*-
+ * 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)
+{
+
+	/*
+	 * XXX: Turn it into assertion?
+	 */
+	if (peer_name != NULL)
+		log_errx(1, "%s called twice", __func__);
+	if (peer_addr == NULL)
+		log_errx(1, "%s called before log_set_peer_addr", __func__);
+
+	peer_name = checked_strdup(name);
+}
+
+void
+log_set_peer_addr(const char *addr)
+{
+
+	/*
+	 * XXX: Turn it into assertion?
+	 */
+	if (peer_addr != NULL)
+		log_errx(1, "%s called twice", __func__);
+
+	peer_addr = checked_strdup(addr);
+}
+
+static void
+log_common(int priority, int log_errno, const char *fmt, va_list ap)
+{
+	static char msgbuf[MSGBUF_LEN];
+	static char msgbuf_strvised[MSGBUF_LEN * 4 + 1];
+	char *errstr;
+	int ret;
+
+	ret = vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+	if (ret < 0) {
+		fprintf(stderr, "%s: snprintf failed", getprogname());
+		syslog(LOG_CRIT, "snprintf failed");
+		exit(1);
+	}
+
+	ret = strnvis(msgbuf_strvised, sizeof(msgbuf_strvised), msgbuf, VIS_NL);
+	if (ret < 0) {
+		fprintf(stderr, "%s: strnvis failed", getprogname());
+		syslog(LOG_CRIT, "strnvis failed");
+		exit(1);
+	}
+
+	if (log_errno == -1) {
+		if (peer_name != NULL) {
+			fprintf(stderr, "%s: %s (%s): %s\n", getprogname(),
+			    peer_addr, peer_name, msgbuf_strvised);
+			syslog(priority, "%s (%s): %s",
+			    peer_addr, peer_name, msgbuf_strvised);
+		} else if (peer_addr != NULL) {
+			fprintf(stderr, "%s: %s: %s\n", getprogname(),
+			    peer_addr, msgbuf_strvised);
+			syslog(priority, "%s: %s",
+			    peer_addr, msgbuf_strvised);
+		} else {
+			fprintf(stderr, "%s: %s\n", getprogname(), msgbuf_strvised);
+			syslog(priority, "%s", msgbuf_strvised);
+		}
+
+	} else {
+		errstr = strerror(log_errno);
+
+		if (peer_name != NULL) {
+			fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(),
+			    peer_addr, peer_name, msgbuf_strvised, errstr);
+			syslog(priority, "%s (%s): %s: %s",
+			    peer_addr, peer_name, msgbuf_strvised, errstr);
+		} else if (peer_addr != NULL) {
+			fprintf(stderr, "%s: %s: %s: %s\n", getprogname(),
+			    peer_addr, msgbuf_strvised, errstr);
+			syslog(priority, "%s: %s: %s",
+			    peer_addr, msgbuf_strvised, errstr);
+		} else {
+			fprintf(stderr, "%s: %s: %s\n", getprogname(),
+			    msgbuf_strvised, errstr);
+			syslog(priority, "%s: %s",
+			    msgbuf_strvised, errstr);
+		}
+	}
+}
+
+void
+log_err(int eval, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	log_common(LOG_CRIT, errno, fmt, ap);
+	va_end(ap);
+
+	exit(eval);
+}
+
+void
+log_errx(int eval, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	log_common(LOG_CRIT, -1, fmt, ap);
+	va_end(ap);
+
+	exit(eval);
+}
+
+void
+log_warn(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	log_common(LOG_WARNING, errno, fmt, ap);
+	va_end(ap);
+}
+
+void
+log_warnx(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	log_common(LOG_WARNING, -1, fmt, ap);
+	va_end(ap);
+}
+
+void
+log_debugx(const char *fmt, ...)
+{
+	va_list ap;
+
+	if (log_level == 0)
+		return;
+
+	va_start(ap, fmt);
+	log_common(LOG_DEBUG, -1, fmt, ap);
+	va_end(ap);
+}
diff --git a/usr.sbin/autofs/popen.c b/usr.sbin/autofs/popen.c
new file mode 100644
index 000000000000..5a147653eb67
--- /dev/null
+++ b/usr.sbin/autofs/popen.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 1988, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * 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 --git a/usr.sbin/autofs/token.l b/usr.sbin/autofs/token.l
new file mode 100644
index 000000000000..5f3b3e165e09
--- /dev/null
+++ b/usr.sbin/autofs/token.l
@@ -0,0 +1,59 @@
+%{
+/*-
+ * Copyright (c) 2016 The DragonFly Project
+ * Copyright (c) 2014 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <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 --git a/usr.sbin/fstyp/Makefile b/usr.sbin/fstyp/Makefile
new file mode 100644
index 000000000000..8d6460468df3
--- /dev/null
+++ b/usr.sbin/fstyp/Makefile
@@ -0,0 +1,26 @@
+.include <bsd.own.mk>
+
+PROG=	fstyp
+SRCS=	cd9660.c ext2fs.c fstyp.c msdosfs.c ntfs.c ufs.c
+
+.if (${MKZFS} != "no")
+SRCS+=	zfs.c
+.endif
+
+MAN=	fstyp.8
+
+WARNS?=	2
+
+.if (${MKZFS} != "no")
+CFLAGS+=	-DHAVE_ZFS
+CFLAGS+=	-Wno-unknown-pragmas
+CFLAGS+=	-I${NETBSDSRCDIR}/external
+CFLAGS+=	-I${NETBSDSRCDIR}/external/cddl/osnet/include
+CFLAGS+=	-I${NETBSDSRCDIR}/external/cddl/osnet/sys
+CFLAGS+=	-I${NETBSDSRCDIR}/external/cddl/osnet/dist/uts/common
+CFLAGS+=	-I${NETBSDSRCDIR}/external/cddl/osnet/dist/uts/common/fs/zfs
+CFLAGS+=	-I${NETBSDSRCDIR}/external/cddl/osnet/dist/lib/libnvpair
+LDADD+=	-lnvpair -lzfs
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/fstyp/cd9660.c b/usr.sbin/fstyp/cd9660.c
new file mode 100644
index 000000000000..d21654e7c28a
--- /dev/null
+++ b/usr.sbin/fstyp/cd9660.c
@@ -0,0 +1,65 @@
+/*-
+ * 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 --git a/usr.sbin/fstyp/ext2fs.c b/usr.sbin/fstyp/ext2fs.c
new file mode 100644
index 000000000000..d1632f9f9088
--- /dev/null
+++ b/usr.sbin/fstyp/ext2fs.c
@@ -0,0 +1,87 @@
+/*-
+ * 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 = (e2sb_t *)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 --git a/usr.sbin/fstyp/fstyp.8 b/usr.sbin/fstyp/fstyp.8
new file mode 100644
index 000000000000..d7c9f2bda386
--- /dev/null
+++ b/usr.sbin/fstyp/fstyp.8
@@ -0,0 +1,148 @@
+.\" 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 November 25, 2017
+.Dt FSTYP 8
+.Os
+.Sh NAME
+.Nm fstyp
+.Nd determine filesystem type
+.Sh SYNOPSIS
+.Nm
+.Op Fl l
+.Op Fl s
+.Op Fl u
+.Ar special
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to determine the filesystem type on a given device.
+It can recognize ISO-9660, Ext2, FAT, NTFS, and UFS filesystems.
+When the
+.Fl u
+flag is specified,
+.Nm
+also recognizes certain additional metadata formats that cannot be
+handled using
+.Xr mount 8 ,
+such as
+ZFS pools.
+.Pp
+The filesystem name is printed to the standard output
+as, respectively:
+.Bl -item -offset indent -compact
+.It
+cd9660
+.It
+ext2fs
+.It
+msdosfs
+.It
+ntfs
+.It
+ufs
+.It
+zfs
+.El
+.Pp
+Because
+.Nm
+is built specifically to detect filesystem types, it differs from
+.Xr file 1
+in several ways.
+The output is machine-parsable, filesystem labels are supported,
+and does not try to recognize any file format other than filesystems.
+.Pp
+These options are available:
+.Bl -tag -width ".Fl l"
+.It Fl l
+In addition to filesystem type, print filesystem 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 filesystems 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 filesystem
+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 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility was developed by
+.An Edward Tomasz Napierala Aq Mt trasz@FreeBSD.org
+under sponsorship from the FreeBSD Foundation.
+ZFS and GELI support was added by
+.An Allan Jude Aq Mt allanjude@FreeBSD.org .
+.Pp
+The
+.Nm
+utility was ported to
+.Dx
+and
+.Nx
+by
+.An Tomohiro Kusumi Aq Mt kusumi.tomohiro@gmail.com .
+.Sh BUGS
+.Xr geli 8
+and
+.Xr hammer 8
+are currently unsupported on
+.Nx .
diff --git a/usr.sbin/fstyp/fstyp.c b/usr.sbin/fstyp/fstyp.c
new file mode 100644
index 000000000000..844787b4f4cb
--- /dev/null
+++ b/usr.sbin/fstyp/fstyp.c
@@ -0,0 +1,257 @@
+/*-
+ * 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/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 <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 },
+	{ "ext2fs", &fstyp_ext2fs, false },
+	{ "msdosfs", &fstyp_msdosfs, false },
+	{ "ntfs", &fstyp_ntfs, false },
+	{ "ufs", &fstyp_ufs, false },
+#ifdef HAVE_ZFS
+	{ "zfs", &fstyp_zfs, true },
+#endif
+	{ NULL, NULL, NULL }
+};
+
+static struct {
+	const char	*name;
+	fsvtyp_function	function;
+	bool		unmountable;
+} fsvtypes[] = {
+	{ NULL, NULL, NULL }
+};
+
+void *
+read_buf(FILE *fp, off_t off, size_t len)
+{
+	int error;
+	size_t nread;
+	void *buf;
+
+	error = fseek(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(1, "strdup");
+	return (c);
+}
+
+void
+rtrim(char *label, size_t size)
+{
+	ptrdiff_t i;
+
+	for (i = size - 1; i >= 0; i--) {
+		if (label[i] == '\0')
+			continue;
+		else if (label[i] == ' ')
+			label[i] = '\0';
+		else
+			break;
+	}
+}
+
+static void
+usage(void)
+{
+
+	fprintf(stderr, "usage: fstyp [-l] [-s] [-u] special\n");
+	exit(1);
+}
+
+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(1, "%s: fstat", path);
+
+	if (S_ISREG(sb.st_mode))
+		return;
+
+	error = ioctl(fd, DIOCGDINFO, &dl);
+	if (error != 0)
+		errx(1, "%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 == false && fstypes[i].unmountable == true)
+			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 == false && fsvtypes[i].unmountable == true)
+			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;
+		}
+	}
+
+	warnx("%s: filesystem not recognized", path);
+	return (1);
+done:
+	if (show_label && label[0] != '\0') {
+		/*
+		 * XXX: I'd prefer VIS_HTTPSTYLE, but it unconditionally
+		 *      encodes spaces.
+		 */
+		nbytes = strsnvis(strvised, sizeof(strvised), label,
+		    VIS_GLOB | VIS_NL, "\"'$");
+		if (nbytes == -1)
+			err(1, "strsnvis");
+
+		printf("%s %s\n", name, strvised);
+	} else {
+		printf("%s\n", name);
+	}
+
+	return (0);
+}
diff --git a/usr.sbin/fstyp/fstyp.h b/usr.sbin/fstyp/fstyp.h
new file mode 100644
index 000000000000..b7908c65b9ac
--- /dev/null
+++ b/usr.sbin/fstyp/fstyp.h
@@ -0,0 +1,53 @@
+/*-
+ * 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 *fp, off_t off, size_t len);
+char	*checked_strdup(const char *s);
+void	rtrim(char *label, size_t size);
+
+int	fstyp_cd9660(FILE *fp, char *label, size_t size);
+int	fstyp_ext2fs(FILE *fp, char *label, size_t size);
+int	fstyp_msdosfs(FILE *fp, char *label, size_t size);
+int	fstyp_ntfs(FILE *fp, char *label, size_t size);
+int	fstyp_ufs(FILE *fp, char *label, size_t size);
+#ifdef HAVE_ZFS
+int	fstyp_zfs(FILE *fp, char *label, size_t size);
+#endif
+
+#endif /* !FSTYP_H */
diff --git a/usr.sbin/fstyp/msdosfs.c b/usr.sbin/fstyp/msdosfs.c
new file mode 100644
index 000000000000..2edf7139f058
--- /dev/null
+++ b/usr.sbin/fstyp/msdosfs.c
@@ -0,0 +1,178 @@
+/*-
+ * 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 = (uint8_t *)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);
+
+		//    fat_FirstDataSector, fat_BytesPerSector);
+
+		for (offset = fat_BytesPerSector * fat_FirstDataSector;;
+		    offset += fat_BytesPerSector) {
+			sector = (uint8_t *)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 --git a/usr.sbin/fstyp/msdosfs.h b/usr.sbin/fstyp/msdosfs.h
new file mode 100644
index 000000000000..69968693a1dd
--- /dev/null
+++ b/usr.sbin/fstyp/msdosfs.h
@@ -0,0 +1,145 @@
+/*-
+ * 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 --git a/usr.sbin/fstyp/ntfs.c b/usr.sbin/fstyp/ntfs.c
new file mode 100644
index 000000000000..3fa47805afca
--- /dev/null
+++ b/usr.sbin/fstyp/ntfs.c
@@ -0,0 +1,165 @@
+/*-
+ * 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;
+	int recsize, j;
+
+	filerecp = NULL;
+
+	bf = (struct ntfs_bootfile *)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) ? (mftrecsz * bf->bf_bps * bf->bf_spc) : (1 << -mftrecsz);
+
+	voloff = 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 --git a/usr.sbin/fstyp/ufs.c b/usr.sbin/fstyp/ufs.c
new file mode 100644
index 000000000000..e0d680ecf990
--- /dev/null
+++ b/usr.sbin/fstyp/ufs.c
@@ -0,0 +1,110 @@
+/*-
+ * 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);
+}
diff --git a/usr.sbin/fstyp/zfs.c b/usr.sbin/fstyp/zfs.c
new file mode 100644
index 000000000000..095734199e01
--- /dev/null
+++ b/usr.sbin/fstyp/zfs.c
@@ -0,0 +1,79 @@
+/*-
+ * Copyright (c) 2017 The NetBSD Foundation, Inc.
+ * Copyright (c) 2015 Allan Jude <allanjude@FreeBSD.org>
+ * Copyright (c) 2015 Xin LI <delphij@FreeBSD.org>
+ * 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.
+ */
+
+#include <sys/types.h>
+#include <cddl/osnet/sys/sys/types.h>
+#include <sys/time.h>
+#include <cddl/osnet/sys/sys/time.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libnvpair.h>
+#include <sys/vdev_impl.h>
+
+#include "fstyp.h"
+
+int
+fstyp_zfs(FILE *fp, char *label, size_t labelsize)
+{
+	vdev_label_t *vdev_label = NULL;
+	vdev_phys_t *vdev_phys;
+	char *zpool_name = NULL;
+	nvlist_t *config = NULL;
+	int err = 0;
+
+	/*
+	 * Read in the first ZFS vdev label ("L0"), located at the beginning
+	 * of the vdev and extract the pool name from it.
+	 *
+	 * TODO: the checksum of label should be validated.
+	 */
+	vdev_label = (vdev_label_t *)read_buf(fp, 0, sizeof(*vdev_label));
+	if (vdev_label == NULL)
+		return (1);
+
+	vdev_phys = &(vdev_label->vl_vdev_phys);
+
+	if ((nvlist_unpack(vdev_phys->vp_nvlist, sizeof(vdev_phys->vp_nvlist),
+	    &config, 0)) == 0 &&
+	    (nvlist_lookup_string(config, "name", &zpool_name) == 0)) {
+		strlcpy(label, zpool_name, labelsize);
+	} else
+		err = 1;
+
+	nvlist_free(config);
+	free(vdev_label);
+
+	return (err);
+}
diff --git a/usr.sbin/mountd/mountd.c b/usr.sbin/mountd/mountd.c
index 90e945d1d6e7..4c5fd0c683cc 100644
--- a/usr.sbin/mountd/mountd.c
+++ b/usr.sbin/mountd/mountd.c
@@ -964,6 +964,10 @@ parse_directory(const char *line, size_t lineno, struct grouplist *tgrp,
 		return 0;
 	}
 
+	if ((fsp->f_flag & MNT_AUTOMOUNTED) != 0)
+		syslog(LOG_ERR, "\"%s\", line %ld: Warning: exporting of "
+		    "automounted fs %s not supported",
+		    line, (unsigned long)lineno, cp);
 	if (got_nondir) {
 		syslog(LOG_ERR,
 		    "\"%s\", line %ld: Directories must precede files",
-- 
2.14.1

