--- /dev/null
+From 9dbfc5d36211b9bfd3e7a770567903ca14995743 Mon Sep 17 00:00:00 2001
+From: Brian Behlendorf <behlendorf1@llnl.gov>
+Date: Fri, 11 Nov 2011 12:45:53 +0530
+Subject: [PATCH 8/8] Add .zfs control directory
+
+Add support for the .zfs control directory. This was accomplished
+by leveraging as much of the existing ZFS infrastructure as posible
+and updating it for Linux as required. The bulk of the core
+functionality is now all there with the following limitations.
+
+*) The .zfs/snapshot directory automount support requires a 2.6.37
+ or newer kernel. The exception is RHEL6.2 which has backported
+ the d_automount patches.
+
+*) Creating/destroying/renaming snapshots with mkdir/rmdir/mv
+ in the .zfs/snapshot directory works as expected. However,
+ this functionality is only available to root until zfs
+ delegations are finished.
+
+ * mkdir - create a snapshot
+ * rmdir - destroy a snapshot
+ * mv - rename a snapshot
+
+The following issues are known defeciences, but we expect them to
+be addressed by future commits.
+
+*) Add automount support for kernels older the 2.6.37. This should
+ be possible using follow_link() which is what Linux did before.
+
+*) Accessing the the .zfs/snapshot directory via NFS is not yet
+ possible. The majority of the ground work for this is complete.
+ However, finishing this work will require resolving some lingering
+ integration issues with the Linux NFS kernel server.
+
+*) The .zfs/shares directory exists but no futher smb functionality
+ has yet been implemented.
+
+Contributions-by: Rohan Puri <rohan.puri15@gmail.com>
+Contributiobs-by: Andrew Barnes <barnes333@gmail.com>
+Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
+Issue #173
+
+Conflicts:
+
+ dracut/90zfs/Makefile.in
+ dracut/Makefile.in
+ etc/init.d/Makefile.in
+---
+ Makefile.in | 1 +
+ cmd/Makefile.in | 1 +
+ cmd/mount_zfs/Makefile.in | 1 +
+ cmd/sas_switch_id/Makefile.in | 1 +
+ cmd/zdb/Makefile.in | 1 +
+ cmd/zfs/Makefile.in | 1 +
+ cmd/zinject/Makefile.in | 1 +
+ cmd/zpios/Makefile.in | 1 +
+ cmd/zpool/Makefile.in | 1 +
+ cmd/zpool_id/Makefile.in | 1 +
+ cmd/zpool_layout/Makefile.in | 1 +
+ cmd/ztest/Makefile.in | 1 +
+ cmd/zvol_id/Makefile.in | 1 +
+ config/kernel-automount.m4 | 23 +
+ config/kernel.m4 | 1 +
+ configure | 136 ++++
+ etc/Makefile.in | 1 +
+ etc/zfs/Makefile.in | 1 +
+ include/Makefile.in | 1 +
+ include/linux/Makefile.in | 1 +
+ include/linux/dcache_compat.h | 1 +
+ include/sys/Makefile.am | 1 +
+ include/sys/Makefile.in | 4 +
+ include/sys/dmu.h | 1 +
+ include/sys/fm/Makefile.in | 1 +
+ include/sys/fm/fs/Makefile.in | 1 +
+ include/sys/fs/Makefile.in | 1 +
+ include/sys/zfs_ctldir.h | 113 ++++
+ include/sys/zfs_vfsops.h | 21 +-
+ include/sys/zfs_znode.h | 1 +
+ include/sys/zpl.h | 18 +
+ lib/Makefile.in | 1 +
+ lib/libavl/Makefile.in | 1 +
+ lib/libefi/Makefile.in | 1 +
+ lib/libnvpair/Makefile.in | 1 +
+ lib/libshare/Makefile.in | 1 +
+ lib/libspl/Makefile.in | 1 +
+ lib/libspl/asm-generic/Makefile.in | 1 +
+ lib/libspl/asm-i386/Makefile.in | 1 +
+ lib/libspl/asm-x86_64/Makefile.in | 1 +
+ lib/libspl/include/Makefile.in | 1 +
+ lib/libspl/include/ia32/Makefile.in | 1 +
+ lib/libspl/include/ia32/sys/Makefile.in | 1 +
+ lib/libspl/include/rpc/Makefile.in | 1 +
+ lib/libspl/include/sys/Makefile.in | 1 +
+ lib/libspl/include/sys/dktp/Makefile.in | 1 +
+ lib/libspl/include/sys/sysevent/Makefile.in | 1 +
+ lib/libspl/include/util/Makefile.in | 1 +
+ lib/libunicode/Makefile.in | 1 +
+ lib/libuutil/Makefile.in | 1 +
+ lib/libzfs/Makefile.in | 1 +
+ lib/libzpool/Makefile.am | 2 +
+ lib/libzpool/Makefile.in | 3 +
+ man/Makefile.in | 1 +
+ man/man8/Makefile.in | 1 +
+ module/zfs/Makefile.in | 2 +
+ module/zfs/dmu_objset.c | 35 +
+ module/zfs/dsl_dataset.c | 3 +-
+ module/zfs/zfs_ctldir.c | 957 +++++++++++++++++++++++++++
+ module/zfs/zfs_dir.c | 11 +-
+ module/zfs/zfs_ioctl.c | 88 ++--
+ module/zfs/zfs_vfsops.c | 47 +-
+ module/zfs/zfs_vnops.c | 10 +-
+ module/zfs/zfs_znode.c | 9 +
+ module/zfs/zpl_ctldir.c | 519 +++++++++++++++
+ module/zfs/zpl_export.c | 6 +-
+ module/zfs/zpl_inode.c | 15 +-
+ module/zfs/zpl_super.c | 24 +-
+ scripts/Makefile.in | 1 +
+ scripts/zpios-profile/Makefile.in | 1 +
+ scripts/zpios-test/Makefile.in | 1 +
+ scripts/zpool-config/Makefile.in | 1 +
+ scripts/zpool-layout/Makefile.in | 1 +
+ udev/Makefile.in | 1 +
+ udev/rules.d/Makefile.in | 1 +
+ zfs_config.h.in | 3 +
+ 76 files changed, 2003 insertions(+), 100 deletions(-)
+ create mode 100644 config/kernel-automount.m4
+ create mode 100644 include/sys/zfs_ctldir.h
+ create mode 100644 module/zfs/zfs_ctldir.c
+ create mode 100644 module/zfs/zpl_ctldir.c
+
+diff --git a/Makefile.in b/Makefile.in
+index bb9bc1d..077d318 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -62,6 +62,7 @@ subdir = .
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/Makefile.in b/cmd/Makefile.in
+index a6ab6cc..76a5731 100644
+--- a/cmd/Makefile.in
++++ b/cmd/Makefile.in
+@@ -39,6 +39,7 @@ DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/mount_zfs/Makefile.in b/cmd/mount_zfs/Makefile.in
+index 94d9d05..ecc61d1 100644
+--- a/cmd/mount_zfs/Makefile.in
++++ b/cmd/mount_zfs/Makefile.in
+@@ -42,6 +42,7 @@ subdir = cmd/mount_zfs
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/sas_switch_id/Makefile.in b/cmd/sas_switch_id/Makefile.in
+index ba0535f..375c65e 100644
+--- a/cmd/sas_switch_id/Makefile.in
++++ b/cmd/sas_switch_id/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(dist_udev_SCRIPTS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/zdb/Makefile.in b/cmd/zdb/Makefile.in
+index 0029e5d..ae4e2c9 100644
+--- a/cmd/zdb/Makefile.in
++++ b/cmd/zdb/Makefile.in
+@@ -42,6 +42,7 @@ subdir = cmd/zdb
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/zfs/Makefile.in b/cmd/zfs/Makefile.in
+index e0ba1e5..4452a96 100644
+--- a/cmd/zfs/Makefile.in
++++ b/cmd/zfs/Makefile.in
+@@ -42,6 +42,7 @@ subdir = cmd/zfs
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/zinject/Makefile.in b/cmd/zinject/Makefile.in
+index 14f1b2a..5e200d1 100644
+--- a/cmd/zinject/Makefile.in
++++ b/cmd/zinject/Makefile.in
+@@ -42,6 +42,7 @@ subdir = cmd/zinject
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/zpios/Makefile.in b/cmd/zpios/Makefile.in
+index b74b113..c7507a1 100644
+--- a/cmd/zpios/Makefile.in
++++ b/cmd/zpios/Makefile.in
+@@ -42,6 +42,7 @@ subdir = cmd/zpios
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/zpool/Makefile.in b/cmd/zpool/Makefile.in
+index bb17ef5..5775a4f 100644
+--- a/cmd/zpool/Makefile.in
++++ b/cmd/zpool/Makefile.in
+@@ -42,6 +42,7 @@ subdir = cmd/zpool
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/zpool_id/Makefile.in b/cmd/zpool_id/Makefile.in
+index 4b6f04c..3d51566 100644
+--- a/cmd/zpool_id/Makefile.in
++++ b/cmd/zpool_id/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(dist_udev_SCRIPTS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/zpool_layout/Makefile.in b/cmd/zpool_layout/Makefile.in
+index 95e802c..077f1d2 100644
+--- a/cmd/zpool_layout/Makefile.in
++++ b/cmd/zpool_layout/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(dist_bin_SCRIPTS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/ztest/Makefile.in b/cmd/ztest/Makefile.in
+index d1c8029..405e496 100644
+--- a/cmd/ztest/Makefile.in
++++ b/cmd/ztest/Makefile.in
+@@ -42,6 +42,7 @@ subdir = cmd/ztest
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/cmd/zvol_id/Makefile.in b/cmd/zvol_id/Makefile.in
+index 5d96d2b..6f6688f 100644
+--- a/cmd/zvol_id/Makefile.in
++++ b/cmd/zvol_id/Makefile.in
+@@ -42,6 +42,7 @@ subdir = cmd/zvol_id
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/config/kernel-automount.m4 b/config/kernel-automount.m4
+new file mode 100644
+index 0000000..972b09b
+--- /dev/null
++++ b/config/kernel-automount.m4
+@@ -0,0 +1,23 @@
++dnl #
++dnl # 2.6.37 API change
++dnl # The dops->d_automount() dentry operation was added as a clean
++dnl # solution to handling automounts. Prior to this cifs/nfs clients
++dnl # which required automount support would abuse the follow_link()
++dnl # operation on directories for this purpose.
++dnl #
++AC_DEFUN([ZFS_AC_KERNEL_AUTOMOUNT], [
++ AC_MSG_CHECKING([whether dops->d_automount() exists])
++ ZFS_LINUX_TRY_COMPILE([
++ #include <linux/dcache.h>
++ ],[
++ struct vfsmount *(*d_automount) (struct path *) = NULL;
++ struct dentry_operations dops __attribute__ ((unused)) = {
++ .d_automount = d_automount,
++ };
++ ],[
++ AC_MSG_RESULT(yes)
++ AC_DEFINE(HAVE_AUTOMOUNT, 1, [dops->automount() exists])
++ ],[
++ AC_MSG_RESULT(no)
++ ])
++])
+diff --git a/config/kernel.m4 b/config/kernel.m4
+index bf43a75..2cb2e8c 100644
+--- a/config/kernel.m4
++++ b/config/kernel.m4
+@@ -45,6 +45,7 @@ AC_DEFUN([ZFS_AC_CONFIG_KERNEL], [
+ ZFS_AC_KERNEL_NR_CACHED_OBJECTS
+ ZFS_AC_KERNEL_FREE_CACHED_OBJECTS
+ ZFS_AC_KERNEL_FALLOCATE
++ ZFS_AC_KERNEL_AUTOMOUNT
+ ZFS_AC_KERNEL_INSERT_INODE_LOCKED
+ ZFS_AC_KERNEL_D_OBTAIN_ALIAS
+ ZFS_AC_KERNEL_CHECK_DISK_SIZE_CHANGE
+diff --git a/configure b/configure
+index 14c1c4d..da28b47 100755
+--- a/configure
++++ b/configure
+@@ -15681,6 +15681,74 @@ fi
+
+
+
++ { $as_echo "$as_me:$LINENO: checking whether dops->d_automount() exists" >&5
++$as_echo_n "checking whether dops->d_automount() exists... " >&6; }
++
++
++cat confdefs.h - <<_ACEOF >conftest.c
++/* confdefs.h. */
++_ACEOF
++cat confdefs.h >>conftest.$ac_ext
++cat >>conftest.$ac_ext <<_ACEOF
++/* end confdefs.h. */
++
++
++ #include <linux/dcache.h>
++
++int
++main (void)
++{
++
++ struct vfsmount *(*d_automount) (struct path *) = NULL;
++ struct dentry_operations dops __attribute__ ((unused)) = {
++ .d_automount = d_automount,
++ };
++
++ ;
++ return 0;
++}
++
++_ACEOF
++
++
++ rm -Rf build && mkdir -p build
++ echo "obj-m := conftest.o" >build/Makefile
++ if { ac_try='cp conftest.c build && make modules -C $LINUX_OBJ EXTRA_CFLAGS="-Werror-implicit-function-declaration $EXTRA_KCFLAGS" $ARCH_UM M=$PWD/build'
++ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
++ (eval $ac_try) 2>&5
++ ac_status=$?
++ $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5
++ (exit $ac_status); }; } >/dev/null && { ac_try='test -s build/conftest.o'
++ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
++ (eval $ac_try) 2>&5
++ ac_status=$?
++ $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5
++ (exit $ac_status); }; }; then
++
++ { $as_echo "$as_me:$LINENO: result: yes" >&5
++$as_echo "yes" >&6; }
++
++cat >>confdefs.h <<\_ACEOF
++#define HAVE_AUTOMOUNT 1
++_ACEOF
++
++
++else
++ $as_echo "$as_me: failed program was:" >&5
++sed 's/^/| /' conftest.$ac_ext >&5
++
++ { $as_echo "$as_me:$LINENO: result: no" >&5
++$as_echo "no" >&6; }
++
++
++
++fi
++
++ rm -Rf build
++
++
++
++
+ { $as_echo "$as_me:$LINENO: checking whether symbol insert_inode_locked is exported" >&5
+ $as_echo_n "checking whether symbol insert_inode_locked is exported... " >&6; }
+ grep -q -E '[[:space:]]insert_inode_locked[[:space:]]' \
+@@ -21568,6 +21636,74 @@ fi
+
+
+
++ { $as_echo "$as_me:$LINENO: checking whether dops->d_automount() exists" >&5
++$as_echo_n "checking whether dops->d_automount() exists... " >&6; }
++
++
++cat confdefs.h - <<_ACEOF >conftest.c
++/* confdefs.h. */
++_ACEOF
++cat confdefs.h >>conftest.$ac_ext
++cat >>conftest.$ac_ext <<_ACEOF
++/* end confdefs.h. */
++
++
++ #include <linux/dcache.h>
++
++int
++main (void)
++{
++
++ struct vfsmount *(*d_automount) (struct path *) = NULL;
++ struct dentry_operations dops __attribute__ ((unused)) = {
++ .d_automount = d_automount,
++ };
++
++ ;
++ return 0;
++}
++
++_ACEOF
++
++
++ rm -Rf build && mkdir -p build
++ echo "obj-m := conftest.o" >build/Makefile
++ if { ac_try='cp conftest.c build && make modules -C $LINUX_OBJ EXTRA_CFLAGS="-Werror-implicit-function-declaration $EXTRA_KCFLAGS" $ARCH_UM M=$PWD/build'
++ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
++ (eval $ac_try) 2>&5
++ ac_status=$?
++ $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5
++ (exit $ac_status); }; } >/dev/null && { ac_try='test -s build/conftest.o'
++ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
++ (eval $ac_try) 2>&5
++ ac_status=$?
++ $as_echo "$as_me:$LINENO: \$? = $ac_status" >&5
++ (exit $ac_status); }; }; then
++
++ { $as_echo "$as_me:$LINENO: result: yes" >&5
++$as_echo "yes" >&6; }
++
++cat >>confdefs.h <<\_ACEOF
++#define HAVE_AUTOMOUNT 1
++_ACEOF
++
++
++else
++ $as_echo "$as_me: failed program was:" >&5
++sed 's/^/| /' conftest.$ac_ext >&5
++
++ { $as_echo "$as_me:$LINENO: result: no" >&5
++$as_echo "no" >&6; }
++
++
++
++fi
++
++ rm -Rf build
++
++
++
++
+ { $as_echo "$as_me:$LINENO: checking whether symbol insert_inode_locked is exported" >&5
+ $as_echo_n "checking whether symbol insert_inode_locked is exported... " >&6; }
+ grep -q -E '[[:space:]]insert_inode_locked[[:space:]]' \
+diff --git a/etc/Makefile.in b/etc/Makefile.in
+index c8c8821..1187d0a 100644
+--- a/etc/Makefile.in
++++ b/etc/Makefile.in
+@@ -39,6 +39,7 @@ DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/etc/zfs/Makefile.in b/etc/zfs/Makefile.in
+index 0382b0c..73025a1 100644
+--- a/etc/zfs/Makefile.in
++++ b/etc/zfs/Makefile.in
+@@ -40,6 +40,7 @@ DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/include/Makefile.in b/include/Makefile.in
+index ad01d2b..e181712 100644
+--- a/include/Makefile.in
++++ b/include/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(am__kernel_HEADERS_DIST) $(am__libzfs_HEADERS_DIST) \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/include/linux/Makefile.in b/include/linux/Makefile.in
+index 5dfa3de..3d47867 100644
+--- a/include/linux/Makefile.in
++++ b/include/linux/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(am__kernel_HEADERS_DIST) $(libzfs_HEADERS) \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/include/linux/dcache_compat.h b/include/linux/dcache_compat.h
+index a624d4d..5f7d5e9 100644
+--- a/include/linux/dcache_compat.h
++++ b/include/linux/dcache_compat.h
+@@ -29,5 +29,6 @@
+ #include <linux/dcache.h>
+
+ #define dname(dentry) ((char *)((dentry)->d_name.name))
++#define dlen(dentry) ((int)((dentry)->d_name.len))
+
+ #endif /* _ZFS_DCACHE_H */
+diff --git a/include/sys/Makefile.am b/include/sys/Makefile.am
+index 083e121..651e68b 100644
+--- a/include/sys/Makefile.am
++++ b/include/sys/Makefile.am
+@@ -55,6 +55,7 @@ COMMON_H = \
+ $(top_srcdir)/include/sys/zap_leaf.h \
+ $(top_srcdir)/include/sys/zfs_acl.h \
+ $(top_srcdir)/include/sys/zfs_context.h \
++ $(top_srcdir)/include/sys/zfs_ctldir.h \
+ $(top_srcdir)/include/sys/zfs_debug.h \
+ $(top_srcdir)/include/sys/zfs_dir.h \
+ $(top_srcdir)/include/sys/zfs_fuid.h \
+diff --git a/include/sys/Makefile.in b/include/sys/Makefile.in
+index a2afe14..a179576 100644
+--- a/include/sys/Makefile.in
++++ b/include/sys/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(am__kernel_HEADERS_DIST) $(am__libzfs_HEADERS_DIST) \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+@@ -170,6 +171,7 @@ am__kernel_HEADERS_DIST = $(top_srcdir)/include/sys/arc.h \
+ $(top_srcdir)/include/sys/zap_leaf.h \
+ $(top_srcdir)/include/sys/zfs_acl.h \
+ $(top_srcdir)/include/sys/zfs_context.h \
++ $(top_srcdir)/include/sys/zfs_ctldir.h \
+ $(top_srcdir)/include/sys/zfs_debug.h \
+ $(top_srcdir)/include/sys/zfs_dir.h \
+ $(top_srcdir)/include/sys/zfs_fuid.h \
+@@ -266,6 +268,7 @@ am__libzfs_HEADERS_DIST = $(top_srcdir)/include/sys/arc.h \
+ $(top_srcdir)/include/sys/zap_leaf.h \
+ $(top_srcdir)/include/sys/zfs_acl.h \
+ $(top_srcdir)/include/sys/zfs_context.h \
++ $(top_srcdir)/include/sys/zfs_ctldir.h \
+ $(top_srcdir)/include/sys/zfs_debug.h \
+ $(top_srcdir)/include/sys/zfs_dir.h \
+ $(top_srcdir)/include/sys/zfs_fuid.h \
+@@ -550,6 +553,7 @@ COMMON_H = \
+ $(top_srcdir)/include/sys/zap_leaf.h \
+ $(top_srcdir)/include/sys/zfs_acl.h \
+ $(top_srcdir)/include/sys/zfs_context.h \
++ $(top_srcdir)/include/sys/zfs_ctldir.h \
+ $(top_srcdir)/include/sys/zfs_debug.h \
+ $(top_srcdir)/include/sys/zfs_dir.h \
+ $(top_srcdir)/include/sys/zfs_fuid.h \
+diff --git a/include/sys/dmu.h b/include/sys/dmu.h
+index da1aa30..3f9c711 100644
+--- a/include/sys/dmu.h
++++ b/include/sys/dmu.h
+@@ -643,6 +643,7 @@ extern uint64_t dmu_objset_syncprop(objset_t *os);
+ extern uint64_t dmu_objset_logbias(objset_t *os);
+ extern int dmu_snapshot_list_next(objset_t *os, int namelen, char *name,
+ uint64_t *id, uint64_t *offp, boolean_t *case_conflict);
++extern int dmu_snapshot_id(objset_t *os, const char *snapname, uint64_t *idp);
+ extern int dmu_snapshot_realname(objset_t *os, char *name, char *real,
+ int maxlen, boolean_t *conflict);
+ extern int dmu_dir_list_next(objset_t *os, int namelen, char *name,
+diff --git a/include/sys/fm/Makefile.in b/include/sys/fm/Makefile.in
+index 9fe283a..5ae85a3 100644
+--- a/include/sys/fm/Makefile.in
++++ b/include/sys/fm/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(am__kernel_HEADERS_DIST) $(am__libzfs_HEADERS_DIST) \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/include/sys/fm/fs/Makefile.in b/include/sys/fm/fs/Makefile.in
+index bc2db28..5976245 100644
+--- a/include/sys/fm/fs/Makefile.in
++++ b/include/sys/fm/fs/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(am__kernel_HEADERS_DIST) $(am__libzfs_HEADERS_DIST) \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/include/sys/fs/Makefile.in b/include/sys/fs/Makefile.in
+index 00ed240..61f3125 100644
+--- a/include/sys/fs/Makefile.in
++++ b/include/sys/fs/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(am__kernel_HEADERS_DIST) $(am__libzfs_HEADERS_DIST) \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/include/sys/zfs_ctldir.h b/include/sys/zfs_ctldir.h
+new file mode 100644
+index 0000000..e598529
+--- /dev/null
++++ b/include/sys/zfs_ctldir.h
+@@ -0,0 +1,113 @@
++/*
++ * CDDL HEADER START
++ *
++ * The contents of this file are subject to the terms of the
++ * Common Development and Distribution License (the "License").
++ * You may not use this file except in compliance with the License.
++ *
++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
++ * or http://www.opensolaris.org/os/licensing.
++ * See the License for the specific language governing permissions
++ * and limitations under the License.
++ *
++ * When distributing Covered Code, include this CDDL HEADER in each
++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
++ * If applicable, add the following below this CDDL HEADER, with the
++ * fields enclosed by brackets "[]" replaced with your own identifying
++ * information: Portions Copyright [yyyy] [name of copyright owner]
++ *
++ * CDDL HEADER END
++ */
++/*
++ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
++ * Copyright (C) 2011 Lawrence Livermore National Security, LLC.
++ * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
++ * LLNL-CODE-403049.
++ * Rewritten for Linux by:
++ * Rohan Puri <rohan.puri15@gmail.com>
++ * Brian Behlendorf <behlendorf1@llnl.gov>
++ */
++
++#ifndef _ZFS_CTLDIR_H
++#define _ZFS_CTLDIR_H
++
++#include <sys/vnode.h>
++#include <sys/zfs_vfsops.h>
++#include <sys/zfs_znode.h>
++
++#define ZFS_CTLDIR_NAME ".zfs"
++#define ZFS_SNAPDIR_NAME "snapshot"
++#define ZFS_SHAREDIR_NAME "shares"
++
++#define zfs_has_ctldir(zdp) \
++ ((zdp)->z_id == ZTOZSB(zdp)->z_root && \
++ (ZTOZSB(zdp)->z_ctldir != NULL))
++#define zfs_show_ctldir(zdp) \
++ (zfs_has_ctldir(zdp) && \
++ (ZTOZSB(zdp)->z_show_ctldir))
++
++typedef struct {
++ char *se_name;
++ char *se_path;
++ struct inode *se_inode;
++ struct delayed_work se_work;
++ avl_node_t se_node;
++} zfs_snapentry_t;
++
++/* zfsctl generic functions */
++extern int snapentry_compare(const void *a, const void *b);
++extern boolean_t zfsctl_is_node(struct inode *ip);
++extern boolean_t zfsctl_is_snapdir(struct inode *ip);
++extern void zfsctl_inode_inactive(struct inode *ip);
++extern void zfsctl_inode_destroy(struct inode *ip);
++extern int zfsctl_create(zfs_sb_t *zsb);
++extern void zfsctl_destroy(zfs_sb_t *zsb);
++extern struct inode *zfsctl_root(znode_t *zp);
++extern int zfsctl_fid(struct inode *ip, fid_t *fidp);
++
++/* zfsctl '.zfs' functions */
++extern int zfsctl_root_lookup(struct inode *dip, char *name,
++ struct inode **ipp, int flags, cred_t *cr, int *direntflags,
++ pathname_t *realpnp);
++
++/* zfsctl '.zfs/snapshot' functions */
++extern int zfsctl_snapdir_lookup(struct inode *dip, char *name,
++ struct inode **ipp, int flags, cred_t *cr, int *direntflags,
++ pathname_t *realpnp);
++extern int zfsctl_snapdir_rename(struct inode *sdip, char *sname,
++ struct inode *tdip, char *tname, cred_t *cr, int flags);
++extern int zfsctl_snapdir_remove(struct inode *dip, char *name, cred_t *cr,
++ int flags);
++extern int zfsctl_snapdir_mkdir(struct inode *dip, char *dirname, vattr_t *vap,
++ struct inode **ipp, cred_t *cr, int flags);
++extern void zfsctl_snapdir_inactive(struct inode *ip);
++extern int zfsctl_unmount_snapshot(zfs_sb_t *zsb, char *name, int flags);
++extern int zfsctl_unmount_snapshots(zfs_sb_t *zsb, int flags, int *count);
++extern int zfsctl_mount_snapshot(struct path *path, int flags);
++extern int zfsctl_lookup_objset(struct super_block *sb, uint64_t objsetid,
++ zfs_sb_t **zsb);
++
++/* zfsctl '.zfs/shares' functions */
++extern int zfsctl_shares_lookup(struct inode *dip, char *name,
++ struct inode **ipp, int flags, cred_t *cr, int *direntflags,
++ pathname_t *realpnp);
++
++/* zfsctl_init/fini functions */
++extern void zfsctl_init(void);
++extern void zfsctl_fini(void);
++
++/*
++ * These inodes numbers are reserved for the .zfs control directory.
++ * It is important that they be no larger that 48-bits because only
++ * 6 bytes are reserved in the NFS file handle for the object number.
++ * However, they should be as large as possible to avoid conflicts
++ * with the objects which are assigned monotonically by the dmu.
++ */
++#define ZFSCTL_INO_ROOT 0x0000FFFFFFFFFFFF
++#define ZFSCTL_INO_SHARES 0x0000FFFFFFFFFFFE
++#define ZFSCTL_INO_SNAPDIR 0x0000FFFFFFFFFFFD
++#define ZFSCTL_INO_SNAPDIRS 0x0000FFFFFFFFFFFC
++
++#define ZFSCTL_EXPIRE_SNAPSHOT 300
++
++#endif /* _ZFS_CTLDIR_H */
+diff --git a/include/sys/zfs_vfsops.h b/include/sys/zfs_vfsops.h
+index 7b70f32..33ecf72 100644
+--- a/include/sys/zfs_vfsops.h
++++ b/include/sys/zfs_vfsops.h
+@@ -71,6 +71,8 @@ typedef struct zfs_sb {
+ uint64_t z_nr_znodes; /* number of znodes in the fs */
+ kmutex_t z_znodes_lock; /* lock for z_all_znodes */
+ struct inode *z_ctldir; /* .zfs directory inode */
++ avl_tree_t z_ctldir_snaps; /* .zfs/snapshot entries */
++ kmutex_t z_ctldir_lock; /* .zfs ctldir lock */
+ boolean_t z_show_ctldir; /* expose .zfs in the root dir */
+ boolean_t z_issnap; /* true if this is a snapshot */
+ boolean_t z_vscan; /* virus scan on/off */
+@@ -93,24 +95,6 @@ typedef struct zfs_sb {
+
+ #define ZSB_XATTR 0x0001 /* Enable user xattrs */
+
+-
+-/*
+- * Minimal snapshot helpers, the bulk of the Linux snapshot implementation
+- * lives in the zpl_snap.c file which is part of the zpl source.
+- */
+-#define ZFS_CTLDIR_NAME ".zfs"
+-
+-#define zfs_has_ctldir(zdp) \
+- ((zdp)->z_id == ZTOZSB(zdp)->z_root && \
+- (ZTOZSB(zdp)->z_ctldir != NULL))
+-#define zfs_show_ctldir(zdp) \
+- (zfs_has_ctldir(zdp) && \
+- (ZTOZSB(zdp)->z_show_ctldir))
+-
+-#define ZFSCTL_INO_ROOT 0x1
+-#define ZFSCTL_INO_SNAPDIR 0x2
+-#define ZFSCTL_INO_SHARES 0x3
+-
+ /*
+ * Allow a maximum number of links. While ZFS does not internally limit
+ * this the inode->i_nlink member is defined as an unsigned int. To be
+@@ -195,6 +179,7 @@ extern boolean_t zfs_is_readonly(zfs_sb_t *zsb);
+ extern int zfs_register_callbacks(zfs_sb_t *zsb);
+ extern void zfs_unregister_callbacks(zfs_sb_t *zsb);
+ extern int zfs_domount(struct super_block *sb, void *data, int silent);
++extern void zfs_preumount(struct super_block *sb);
+ extern int zfs_umount(struct super_block *sb);
+ extern int zfs_remount(struct super_block *sb, int *flags, char *data);
+ extern int zfs_root(zfs_sb_t *zsb, struct inode **ipp);
+diff --git a/include/sys/zfs_znode.h b/include/sys/zfs_znode.h
+index 6903ad4..8af5798 100644
+--- a/include/sys/zfs_znode.h
++++ b/include/sys/zfs_znode.h
+@@ -214,6 +214,7 @@ typedef struct znode {
+ boolean_t z_is_sa; /* are we native sa? */
+ boolean_t z_is_zvol; /* are we used by the zvol */
+ boolean_t z_is_mapped; /* are we mmap'ed */
++ boolean_t z_is_ctldir; /* are we .zfs entry */
+ struct inode z_inode; /* generic vfs inode */
+ } znode_t;
+
+diff --git a/include/sys/zpl.h b/include/sys/zpl.h
+index cfc8679..aa4f41f 100644
+--- a/include/sys/zpl.h
++++ b/include/sys/zpl.h
+@@ -33,6 +33,9 @@
+ #include <linux/falloc.h>
+
+ /* zpl_inode.c */
++extern void zpl_vap_init(vattr_t *vap, struct inode *dir,
++ struct dentry *dentry, mode_t mode, cred_t *cr);
++
+ extern const struct inode_operations zpl_inode_operations;
+ extern const struct inode_operations zpl_dir_inode_operations;
+ extern const struct inode_operations zpl_symlink_inode_operations;
+@@ -69,4 +72,19 @@ extern int zpl_xattr_security_init(struct inode *ip, struct inode *dip,
+
+ extern xattr_handler_t *zpl_xattr_handlers[];
+
++/* zpl_ctldir.c */
++extern const struct file_operations zpl_fops_root;
++extern const struct inode_operations zpl_ops_root;
++
++extern const struct file_operations zpl_fops_snapdir;
++extern const struct inode_operations zpl_ops_snapdir;
++#ifdef HAVE_AUTOMOUNT
++extern const struct dentry_operations zpl_dops_snapdirs;
++#else
++extern const struct inode_operations zpl_ops_snapdirs;
++#endif /* HAVE_AUTOMOUNT */
++
++extern const struct file_operations zpl_fops_shares;
++extern const struct inode_operations zpl_ops_shares;
++
+ #endif /* _SYS_ZPL_H */
+diff --git a/lib/Makefile.in b/lib/Makefile.in
+index 8d57296..f40801f 100644
+--- a/lib/Makefile.in
++++ b/lib/Makefile.in
+@@ -39,6 +39,7 @@ DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libavl/Makefile.in b/lib/libavl/Makefile.in
+index 8c2be9a..5e31cc5 100644
+--- a/lib/libavl/Makefile.in
++++ b/lib/libavl/Makefile.in
+@@ -41,6 +41,7 @@ subdir = lib/libavl
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libefi/Makefile.in b/lib/libefi/Makefile.in
+index 4f95441..a2f4255 100644
+--- a/lib/libefi/Makefile.in
++++ b/lib/libefi/Makefile.in
+@@ -41,6 +41,7 @@ subdir = lib/libefi
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libnvpair/Makefile.in b/lib/libnvpair/Makefile.in
+index c736a50..7732784 100644
+--- a/lib/libnvpair/Makefile.in
++++ b/lib/libnvpair/Makefile.in
+@@ -41,6 +41,7 @@ subdir = lib/libnvpair
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libshare/Makefile.in b/lib/libshare/Makefile.in
+index 65133e1..e1c1a2c 100644
+--- a/lib/libshare/Makefile.in
++++ b/lib/libshare/Makefile.in
+@@ -41,6 +41,7 @@ subdir = lib/libshare
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/Makefile.in b/lib/libspl/Makefile.in
+index 15c6720..2f94b5d 100644
+--- a/lib/libspl/Makefile.in
++++ b/lib/libspl/Makefile.in
+@@ -41,6 +41,7 @@ subdir = lib/libspl
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/asm-generic/Makefile.in b/lib/libspl/asm-generic/Makefile.in
+index 6ad9971..66ec77b 100644
+--- a/lib/libspl/asm-generic/Makefile.in
++++ b/lib/libspl/asm-generic/Makefile.in
+@@ -40,6 +40,7 @@ subdir = lib/libspl/asm-generic
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/asm-i386/Makefile.in b/lib/libspl/asm-i386/Makefile.in
+index e68762c..4c9f37f 100644
+--- a/lib/libspl/asm-i386/Makefile.in
++++ b/lib/libspl/asm-i386/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/asm-x86_64/Makefile.in b/lib/libspl/asm-x86_64/Makefile.in
+index 6b7f4be..845e2ab 100644
+--- a/lib/libspl/asm-x86_64/Makefile.in
++++ b/lib/libspl/asm-x86_64/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/include/Makefile.in b/lib/libspl/include/Makefile.in
+index 480b998..d9f6944 100644
+--- a/lib/libspl/include/Makefile.in
++++ b/lib/libspl/include/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(libspl_HEADERS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/include/ia32/Makefile.in b/lib/libspl/include/ia32/Makefile.in
+index 6344158..abb9a7b 100644
+--- a/lib/libspl/include/ia32/Makefile.in
++++ b/lib/libspl/include/ia32/Makefile.in
+@@ -39,6 +39,7 @@ DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/include/ia32/sys/Makefile.in b/lib/libspl/include/ia32/sys/Makefile.in
+index c00dc4d..a7e3f3c 100644
+--- a/lib/libspl/include/ia32/sys/Makefile.in
++++ b/lib/libspl/include/ia32/sys/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(libspl_HEADERS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/include/rpc/Makefile.in b/lib/libspl/include/rpc/Makefile.in
+index 3513cb8..6c3174d 100644
+--- a/lib/libspl/include/rpc/Makefile.in
++++ b/lib/libspl/include/rpc/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(libspl_HEADERS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/include/sys/Makefile.in b/lib/libspl/include/sys/Makefile.in
+index c02b3ea..b226c8d 100644
+--- a/lib/libspl/include/sys/Makefile.in
++++ b/lib/libspl/include/sys/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(libspl_HEADERS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/include/sys/dktp/Makefile.in b/lib/libspl/include/sys/dktp/Makefile.in
+index cadff82..aa63471 100644
+--- a/lib/libspl/include/sys/dktp/Makefile.in
++++ b/lib/libspl/include/sys/dktp/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(libspl_HEADERS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/include/sys/sysevent/Makefile.in b/lib/libspl/include/sys/sysevent/Makefile.in
+index 3e5f219..2986138 100644
+--- a/lib/libspl/include/sys/sysevent/Makefile.in
++++ b/lib/libspl/include/sys/sysevent/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(libspl_HEADERS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libspl/include/util/Makefile.in b/lib/libspl/include/util/Makefile.in
+index 3d49a92..bb71a90 100644
+--- a/lib/libspl/include/util/Makefile.in
++++ b/lib/libspl/include/util/Makefile.in
+@@ -41,6 +41,7 @@ DIST_COMMON = $(libspl_HEADERS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libunicode/Makefile.in b/lib/libunicode/Makefile.in
+index d05c40a..0b99b01 100644
+--- a/lib/libunicode/Makefile.in
++++ b/lib/libunicode/Makefile.in
+@@ -41,6 +41,7 @@ subdir = lib/libunicode
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libuutil/Makefile.in b/lib/libuutil/Makefile.in
+index 4aca160..5e87e01 100644
+--- a/lib/libuutil/Makefile.in
++++ b/lib/libuutil/Makefile.in
+@@ -41,6 +41,7 @@ subdir = lib/libuutil
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libzfs/Makefile.in b/lib/libzfs/Makefile.in
+index 720cf07..5fc0ed9 100644
+--- a/lib/libzfs/Makefile.in
++++ b/lib/libzfs/Makefile.in
+@@ -41,6 +41,7 @@ subdir = lib/libzfs
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/lib/libzpool/Makefile.am b/lib/libzpool/Makefile.am
+index d445d34..cbe3acd 100644
+--- a/lib/libzpool/Makefile.am
++++ b/lib/libzpool/Makefile.am
+@@ -97,6 +97,7 @@ libzpool_la_LDFLAGS = -pthread -version-info 1:1:0
+ EXTRA_DIST = \
+ $(top_srcdir)/module/zfs/vdev_disk.c \
+ $(top_srcdir)/module/zfs/zfs_acl.c \
++ $(top_srcdir)/module/zfs/zfs_ctldir.c \
+ $(top_srcdir)/module/zfs/zfs_dir.c \
+ $(top_srcdir)/module/zfs/zfs_ioctl.c \
+ $(top_srcdir)/module/zfs/zfs_log.c \
+@@ -105,6 +106,7 @@ EXTRA_DIST = \
+ $(top_srcdir)/module/zfs/zfs_rlock.c \
+ $(top_srcdir)/module/zfs/zfs_vfsops.c \
+ $(top_srcdir)/module/zfs/zfs_vnops.c \
++ $(top_srcdir)/module/zfs/zpl_ctldir.c \
+ $(top_srcdir)/module/zfs/zpl_export.c \
+ $(top_srcdir)/module/zfs/zpl_file.c \
+ $(top_srcdir)/module/zfs/zpl_inode.c \
+diff --git a/lib/libzpool/Makefile.in b/lib/libzpool/Makefile.in
+index 67130c3..d357fdd 100644
+--- a/lib/libzpool/Makefile.in
++++ b/lib/libzpool/Makefile.in
+@@ -41,6 +41,7 @@ subdir = lib/libzpool
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+@@ -455,6 +456,7 @@ libzpool_la_LDFLAGS = -pthread -version-info 1:1:0
+ EXTRA_DIST = \
+ $(top_srcdir)/module/zfs/vdev_disk.c \
+ $(top_srcdir)/module/zfs/zfs_acl.c \
++ $(top_srcdir)/module/zfs/zfs_ctldir.c \
+ $(top_srcdir)/module/zfs/zfs_dir.c \
+ $(top_srcdir)/module/zfs/zfs_ioctl.c \
+ $(top_srcdir)/module/zfs/zfs_log.c \
+@@ -463,6 +465,7 @@ EXTRA_DIST = \
+ $(top_srcdir)/module/zfs/zfs_rlock.c \
+ $(top_srcdir)/module/zfs/zfs_vfsops.c \
+ $(top_srcdir)/module/zfs/zfs_vnops.c \
++ $(top_srcdir)/module/zfs/zpl_ctldir.c \
+ $(top_srcdir)/module/zfs/zpl_export.c \
+ $(top_srcdir)/module/zfs/zpl_file.c \
+ $(top_srcdir)/module/zfs/zpl_inode.c \
+diff --git a/man/Makefile.in b/man/Makefile.in
+index 5a065b7..fb691d0 100644
+--- a/man/Makefile.in
++++ b/man/Makefile.in
+@@ -39,6 +39,7 @@ DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/man/man8/Makefile.in b/man/man8/Makefile.in
+index 5b4ca61..c8f015d 100644
+--- a/man/man8/Makefile.in
++++ b/man/man8/Makefile.in
+@@ -39,6 +39,7 @@ DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/module/zfs/Makefile.in b/module/zfs/Makefile.in
+index b303168..5ec75a0 100644
+--- a/module/zfs/Makefile.in
++++ b/module/zfs/Makefile.in
+@@ -64,6 +64,7 @@ $(MODULE)-objs += @top_srcdir@/module/zfs/zap_leaf.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zap_micro.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zfs_acl.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zfs_byteswap.o
++$(MODULE)-objs += @top_srcdir@/module/zfs/zfs_ctldir.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zfs_debug.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zfs_dir.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zfs_fm.o
+@@ -83,6 +84,7 @@ $(MODULE)-objs += @top_srcdir@/module/zfs/zio_checksum.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zio_compress.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zio_inject.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zle.o
++$(MODULE)-objs += @top_srcdir@/module/zfs/zpl_ctldir.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zpl_export.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zpl_file.o
+ $(MODULE)-objs += @top_srcdir@/module/zfs/zpl_inode.o
+diff --git a/module/zfs/dmu_objset.c b/module/zfs/dmu_objset.c
+index 0703a94..1d0b461 100644
+--- a/module/zfs/dmu_objset.c
++++ b/module/zfs/dmu_objset.c
+@@ -1584,6 +1584,41 @@ dmu_snapshot_list_next(objset_t *os, int namelen, char *name,
+ return (0);
+ }
+
++/*
++ * Determine the objset id for a given snapshot name.
++ */
++int
++dmu_snapshot_id(objset_t *os, const char *snapname, uint64_t *idp)
++{
++ dsl_dataset_t *ds = os->os_dsl_dataset;
++ zap_cursor_t cursor;
++ zap_attribute_t attr;
++ int error;
++
++ if (ds->ds_phys->ds_snapnames_zapobj == 0)
++ return (ENOENT);
++
++ zap_cursor_init(&cursor, ds->ds_dir->dd_pool->dp_meta_objset,
++ ds->ds_phys->ds_snapnames_zapobj);
++
++ error = zap_cursor_move_to_key(&cursor, snapname, MT_EXACT);
++ if (error) {
++ zap_cursor_fini(&cursor);
++ return (error);
++ }
++
++ error = zap_cursor_retrieve(&cursor, &attr);
++ if (error) {
++ zap_cursor_fini(&cursor);
++ return (error);
++ }
++
++ *idp = attr.za_first_integer;
++ zap_cursor_fini(&cursor);
++
++ return (0);
++}
++
+ int
+ dmu_dir_list_next(objset_t *os, int namelen, char *name,
+ uint64_t *idp, uint64_t *offp)
+diff --git a/module/zfs/dsl_dataset.c b/module/zfs/dsl_dataset.c
+index 718c3ad..2deec8c 100644
+--- a/module/zfs/dsl_dataset.c
++++ b/module/zfs/dsl_dataset.c
+@@ -2373,8 +2373,7 @@ dsl_snapshot_rename_one(const char *name, void *arg)
+ return (err == ENOENT ? 0 : err);
+ }
+
+-/* XXX: Ignore for SPL version until mounting the FS is supported */
+-#if defined(_KERNEL) && !defined(HAVE_SPL)
++#ifdef _KERNEL
+ /*
+ * For all filesystems undergoing rename, we'll need to unmount it.
+ */
+diff --git a/module/zfs/zfs_ctldir.c b/module/zfs/zfs_ctldir.c
+new file mode 100644
+index 0000000..d04c88c
+--- /dev/null
++++ b/module/zfs/zfs_ctldir.c
+@@ -0,0 +1,957 @@
++/*
++ * CDDL HEADER START
++ *
++ * The contents of this file are subject to the terms of the
++ * Common Development and Distribution License (the "License").
++ * You may not use this file except in compliance with the License.
++ *
++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
++ * or http://www.opensolaris.org/os/licensing.
++ * See the License for the specific language governing permissions
++ * and limitations under the License.
++ *
++ * When distributing Covered Code, include this CDDL HEADER in each
++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
++ * If applicable, add the following below this CDDL HEADER, with the
++ * fields enclosed by brackets "[]" replaced with your own identifying
++ * information: Portions Copyright [yyyy] [name of copyright owner]
++ *
++ * CDDL HEADER END
++ */
++/*
++ *
++ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
++ * Copyright (C) 2011 Lawrence Livermore National Security, LLC.
++ * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
++ * LLNL-CODE-403049.
++ * Rewritten for Linux by:
++ * Rohan Puri <rohan.puri15@gmail.com>
++ * Brian Behlendorf <behlendorf1@llnl.gov>
++ */
++
++/*
++ * ZFS control directory (a.k.a. ".zfs")
++ *
++ * This directory provides a common location for all ZFS meta-objects.
++ * Currently, this is only the 'snapshot' and 'shares' directory, but this may
++ * expand in the future. The elements are built dynamically, as the hierarchy
++ * does not actually exist on disk.
++ *
++ * For 'snapshot', we don't want to have all snapshots always mounted, because
++ * this would take up a huge amount of space in /etc/mnttab. We have three
++ * types of objects:
++ *
++ * ctldir ------> snapshotdir -------> snapshot
++ * |
++ * |
++ * V
++ * mounted fs
++ *
++ * The 'snapshot' node contains just enough information to lookup '..' and act
++ * as a mountpoint for the snapshot. Whenever we lookup a specific snapshot, we
++ * perform an automount of the underlying filesystem and return the
++ * corresponding inode.
++ *
++ * All mounts are handled automatically by an user mode helper which invokes
++ * the mount mount procedure. Unmounts are handled by allowing the mount
++ * point to expire so the kernel may automatically unmount it.
++ *
++ * The '.zfs', '.zfs/snapshot', and all directories created under
++ * '.zfs/snapshot' (ie: '.zfs/snapshot/<snapname>') all share the same
++ * share the same zfs_sb_t as the head filesystem (what '.zfs' lives under).
++ *
++ * File systems mounted on top of the '.zfs/snapshot/<snapname>' paths
++ * (ie: snapshots) are complete ZFS filesystems and have their own unique
++ * zfs_sb_t. However, the fsid reported by these mounts will be the same
++ * as that used by the parent zfs_sb_t to make NFS happy.
++ */
++
++#include <sys/types.h>
++#include <sys/param.h>
++#include <sys/time.h>
++#include <sys/systm.h>
++#include <sys/sysmacros.h>
++#include <sys/pathname.h>
++#include <sys/vfs.h>
++#include <sys/vfs_opreg.h>
++#include <sys/zfs_ctldir.h>
++#include <sys/zfs_ioctl.h>
++#include <sys/zfs_vfsops.h>
++#include <sys/zfs_vnops.h>
++#include <sys/stat.h>
++#include <sys/dmu.h>
++#include <sys/dsl_deleg.h>
++#include <sys/mount.h>
++#include <sys/zpl.h>
++#include "zfs_namecheck.h"
++
++/*
++ * Control Directory Tunables (.zfs)
++ */
++int zfs_expire_snapshot = ZFSCTL_EXPIRE_SNAPSHOT;
++
++static zfs_snapentry_t *
++zfsctl_sep_alloc(void)
++{
++ return kmem_zalloc(sizeof (zfs_snapentry_t), KM_SLEEP);
++}
++
++void
++zfsctl_sep_free(zfs_snapentry_t *sep)
++{
++ kmem_free(sep->se_name, MAXNAMELEN);
++ kmem_free(sep->se_path, PATH_MAX);
++ kmem_free(sep, sizeof (zfs_snapentry_t));
++}
++
++/*
++ * Attempt to expire an automounted snapshot, unmounts are attempted every
++ * 'zfs_expire_snapshot' seconds until they succeed. The work request is
++ * responsible for rescheduling itself and freeing the zfs_expire_snapshot_t.
++ */
++static void
++zfsctl_expire_snapshot(void *data)
++{
++ zfs_snapentry_t *sep;
++ zfs_sb_t *zsb;
++ int error;
++
++ sep = spl_get_work_data(data, zfs_snapentry_t, se_work.work);
++ zsb = ITOZSB(sep->se_inode);
++
++ error = zfsctl_unmount_snapshot(zsb, sep->se_name, MNT_EXPIRE);
++ if (error == EBUSY)
++ schedule_delayed_work(&sep->se_work, zfs_expire_snapshot * HZ);
++}
++
++int
++snapentry_compare(const void *a, const void *b)
++{
++ const zfs_snapentry_t *sa = a;
++ const zfs_snapentry_t *sb = b;
++ int ret = strcmp(sa->se_name, sb->se_name);
++
++ if (ret < 0)
++ return (-1);
++ else if (ret > 0)
++ return (1);
++ else
++ return (0);
++}
++
++boolean_t
++zfsctl_is_node(struct inode *ip)
++{
++ return (ITOZ(ip)->z_is_ctldir);
++}
++
++boolean_t
++zfsctl_is_snapdir(struct inode *ip)
++{
++ return (zfsctl_is_node(ip) && (ip->i_ino <= ZFSCTL_INO_SNAPDIRS));
++}
++
++/*
++ * Allocate a new inode with the passed id and ops.
++ */
++static struct inode *
++zfsctl_inode_alloc(zfs_sb_t *zsb, unsigned long id,
++ const struct file_operations *fops, const struct inode_operations *ops)
++{
++ struct inode *ip;
++ znode_t *zp;
++
++ ip = new_inode(zsb->z_sb);
++ if (ip == NULL)
++ return (NULL);
++
++ zp = ITOZ(ip);
++ memset(zp, 0, sizeof (znode_t) - sizeof (struct inode));
++ zp->z_is_ctldir = B_TRUE;
++ zp->z_id = id;
++ ip->i_ino = id;
++ ip->i_mode = (S_IFDIR | S_IRUGO | S_IXUGO);
++ ip->i_uid = 0;
++ ip->i_gid = 0;
++ ip->i_blkbits = SPA_MINBLOCKSHIFT;
++ ip->i_fop = fops;
++ ip->i_op = ops;
++
++ if (insert_inode_locked(ip)) {
++ unlock_new_inode(ip);
++ iput(ip);
++ return (NULL);
++ }
++
++ mutex_enter(&zsb->z_znodes_lock);
++ list_insert_tail(&zsb->z_all_znodes, zp);
++ membar_producer();
++ mutex_exit(&zsb->z_znodes_lock);
++
++ unlock_new_inode(ip);
++
++ return (ip);
++}
++
++/*
++ * Lookup the inode with given id, it will be allocated if needed.
++ */
++static struct inode *
++zfsctl_inode_lookup(zfs_sb_t *zsb, unsigned long id,
++ const struct file_operations *fops, const struct inode_operations *ops)
++{
++ struct inode *ip = NULL;
++
++ while (ip == NULL) {
++ ip = ilookup(zsb->z_sb, id);
++ if (ip)
++ break;
++
++ /* May fail due to concurrent zfsctl_inode_alloc() */
++ ip = zfsctl_inode_alloc(zsb, id, fops, ops);
++ }
++
++ return (ip);
++}
++
++/*
++ * Free zfsctl inode specific structures, currently there are none.
++ */
++void
++zfsctl_inode_destroy(struct inode *ip)
++{
++ return;
++}
++
++/*
++ * An inode is being evicted from the cache.
++ */
++void
++zfsctl_inode_inactive(struct inode *ip)
++{
++ if (zfsctl_is_snapdir(ip))
++ zfsctl_snapdir_inactive(ip);
++}
++
++/*
++ * Create the '.zfs' directory. This directory is cached as part of the VFS
++ * structure. This results in a hold on the zfs_sb_t. The code in zfs_umount()
++ * therefore checks against a vfs_count of 2 instead of 1. This reference
++ * is removed when the ctldir is destroyed in the unmount. All other entities
++ * under the '.zfs' directory are created dynamically as needed.
++ */
++int
++zfsctl_create(zfs_sb_t *zsb)
++{
++ ASSERT(zsb->z_ctldir == NULL);
++
++ zsb->z_ctldir = zfsctl_inode_alloc(zsb, ZFSCTL_INO_ROOT,
++ &zpl_fops_root, &zpl_ops_root);
++ if (zsb->z_ctldir == NULL)
++ return (ENOENT);
++
++ return (0);
++}
++
++/*
++ * Destroy the '.zfs' directory. Only called when the filesystem is unmounted.
++ */
++void
++zfsctl_destroy(zfs_sb_t *zsb)
++{
++ iput(zsb->z_ctldir);
++ zsb->z_ctldir = NULL;
++}
++
++/*
++ * Given a root znode, retrieve the associated .zfs directory.
++ * Add a hold to the vnode and return it.
++ */
++struct inode *
++zfsctl_root(znode_t *zp)
++{
++ ASSERT(zfs_has_ctldir(zp));
++ igrab(ZTOZSB(zp)->z_ctldir);
++ return (ZTOZSB(zp)->z_ctldir);
++}
++
++/*ARGSUSED*/
++int
++zfsctl_fid(struct inode *ip, fid_t *fidp)
++{
++ znode_t *zp = ITOZ(ip);
++ zfs_sb_t *zsb = ITOZSB(ip);
++ uint64_t object = zp->z_id;
++ zfid_short_t *zfid;
++ int i;
++
++ ZFS_ENTER(zsb);
++
++ if (fidp->fid_len < SHORT_FID_LEN) {
++ fidp->fid_len = SHORT_FID_LEN;
++ ZFS_EXIT(zsb);
++ return (ENOSPC);
++ }
++
++ zfid = (zfid_short_t *)fidp;
++
++ zfid->zf_len = SHORT_FID_LEN;
++
++ for (i = 0; i < sizeof (zfid->zf_object); i++)
++ zfid->zf_object[i] = (uint8_t)(object >> (8 * i));
++
++ /* .zfs znodes always have a generation number of 0 */
++ for (i = 0; i < sizeof (zfid->zf_gen); i++)
++ zfid->zf_gen[i] = 0;
++
++ ZFS_EXIT(zsb);
++ return (0);
++}
++
++static int
++zfsctl_snapshot_zname(struct inode *ip, const char *name, int len, char *zname)
++{
++ objset_t *os = ITOZSB(ip)->z_os;
++
++ if (snapshot_namecheck(name, NULL, NULL) != 0)
++ return (EILSEQ);
++
++ dmu_objset_name(os, zname);
++ if ((strlen(zname) + 1 + strlen(name)) >= len)
++ return (ENAMETOOLONG);
++
++ (void) strcat(zname, "@");
++ (void) strcat(zname, name);
++
++ return (0);
++}
++
++static int
++zfsctl_snapshot_zpath(struct path *path, int len, char *zpath)
++{
++ char *path_buffer, *path_ptr;
++ int path_len, error = 0;
++
++ path_buffer = kmem_alloc(len, KM_SLEEP);
++
++ path_ptr = d_path(path, path_buffer, len);
++ if (IS_ERR(path_ptr)) {
++ error = -PTR_ERR(path_ptr);
++ goto out;
++ }
++
++ path_len = path_buffer + len - 1 - path_ptr;
++ if (path_len > len) {
++ error = EFAULT;
++ goto out;
++ }
++
++ memcpy(zpath, path_ptr, path_len);
++ zpath[path_len] = '\0';
++out:
++ kmem_free(path_buffer, len);
++
++ return (error);
++}
++
++/*
++ * Special case the handling of "..".
++ */
++/* ARGSUSED */
++int
++zfsctl_root_lookup(struct inode *dip, char *name, struct inode **ipp,
++ int flags, cred_t *cr, int *direntflags, pathname_t *realpnp)
++{
++ zfs_sb_t *zsb = ITOZSB(dip);
++ int error = 0;
++
++ ZFS_ENTER(zsb);
++
++ if (strcmp(name, "..") == 0) {
++ *ipp = dip->i_sb->s_root->d_inode;
++ } else if (strcmp(name, ZFS_SNAPDIR_NAME) == 0) {
++ *ipp = zfsctl_inode_lookup(zsb, ZFSCTL_INO_SNAPDIR,
++ &zpl_fops_snapdir, &zpl_ops_snapdir);
++ } else if (strcmp(name, ZFS_SHAREDIR_NAME) == 0) {
++ *ipp = zfsctl_inode_lookup(zsb, ZFSCTL_INO_SHARES,
++ &zpl_fops_shares, &zpl_ops_shares);
++ } else {
++ *ipp = NULL;
++ }
++
++ if (*ipp == NULL)
++ error = ENOENT;
++
++ ZFS_EXIT(zsb);
++
++ return (error);
++}
++
++/*
++ * Lookup entry point for the 'snapshot' directory. Try to open the
++ * snapshot if it exist, creating the pseudo filesystem inode as necessary.
++ * Perform a mount of the associated dataset on top of the inode.
++ */
++/* ARGSUSED */
++int
++zfsctl_snapdir_lookup(struct inode *dip, char *name, struct inode **ipp,
++ int flags, cred_t *cr, int *direntflags, pathname_t *realpnp)
++{
++ zfs_sb_t *zsb = ITOZSB(dip);
++ uint64_t id;
++ int error;
++
++ ZFS_ENTER(zsb);
++
++ error = dmu_snapshot_id(zsb->z_os, name, &id);
++ if (error) {
++ ZFS_EXIT(zsb);
++ return (error);
++ }
++
++ *ipp = zfsctl_inode_lookup(zsb, ZFSCTL_INO_SNAPDIRS - id,
++ &simple_dir_operations, &simple_dir_inode_operations);
++ if (*ipp) {
++#ifdef HAVE_AUTOMOUNT
++ (*ipp)->i_flags |= S_AUTOMOUNT;
++#endif /* HAVE_AUTOMOUNT */
++ } else {
++ error = ENOENT;
++ }
++
++ ZFS_EXIT(zsb);
++
++ return (error);
++}
++
++static void
++zfsctl_rename_snap(zfs_sb_t *zsb, zfs_snapentry_t *sep, const char *name)
++{
++ avl_index_t where;
++
++ ASSERT(MUTEX_HELD(&zsb->z_ctldir_lock));
++ ASSERT(sep != NULL);
++
++ /*
++ * Change the name in the AVL tree.
++ */
++ avl_remove(&zsb->z_ctldir_snaps, sep);
++ (void) strcpy(sep->se_name, name);
++ VERIFY(avl_find(&zsb->z_ctldir_snaps, sep, &where) == NULL);
++ avl_insert(&zsb->z_ctldir_snaps, sep, where);
++}
++
++/*
++ * Renaming a directory under '.zfs/snapshot' will automatically trigger
++ * a rename of the snapshot to the new given name. The rename is confined
++ * to the '.zfs/snapshot' directory snapshots cannot be moved elsewhere.
++ */
++/*ARGSUSED*/
++int
++zfsctl_snapdir_rename(struct inode *sdip, char *sname,
++ struct inode *tdip, char *tname, cred_t *cr, int flags)
++{
++ zfs_sb_t *zsb = ITOZSB(sdip);
++ zfs_snapentry_t search, *sep;
++ avl_index_t where;
++ char *to, *from, *real;
++ int error;
++
++ ZFS_ENTER(zsb);
++
++ to = kmem_alloc(MAXNAMELEN, KM_SLEEP);
++ from = kmem_alloc(MAXNAMELEN, KM_SLEEP);
++ real = kmem_alloc(MAXNAMELEN, KM_SLEEP);
++
++ if (zsb->z_case == ZFS_CASE_INSENSITIVE) {
++ error = dmu_snapshot_realname(zsb->z_os, sname, real,
++ MAXNAMELEN, NULL);
++ if (error == 0) {
++ sname = real;
++ } else if (error != ENOTSUP) {
++ goto out;
++ }
++ }
++
++ error = zfsctl_snapshot_zname(sdip, sname, MAXNAMELEN, from);
++ if (!error)
++ error = zfsctl_snapshot_zname(tdip, tname, MAXNAMELEN, to);
++ if (!error)
++ error = zfs_secpolicy_rename_perms(from, to, cr);
++ if (error)
++ goto out;
++
++ /*
++ * Cannot move snapshots out of the snapdir.
++ */
++ if (sdip != tdip) {
++ error = EINVAL;
++ goto out;
++ }
++
++ /*
++ * No-op when names are identical.
++ */
++ if (strcmp(sname, tname) == 0) {
++ error = 0;
++ goto out;
++ }
++
++ mutex_enter(&zsb->z_ctldir_lock);
++
++ error = dmu_objset_rename(from, to, B_FALSE);
++ if (error)
++ goto out_unlock;
++
++ search.se_name = (char *)sname;
++ sep = avl_find(&zsb->z_ctldir_snaps, &search, &where);
++ if (sep)
++ zfsctl_rename_snap(zsb, sep, tname);
++
++out_unlock:
++ mutex_exit(&zsb->z_ctldir_lock);
++out:
++ kmem_free(from, MAXNAMELEN);
++ kmem_free(to, MAXNAMELEN);
++ kmem_free(real, MAXNAMELEN);
++
++ ZFS_EXIT(zsb);
++
++ return (error);
++}
++
++/*
++ * Removing a directory under '.zfs/snapshot' will automatically trigger
++ * the removal of the snapshot with the given name.
++ */
++/* ARGSUSED */
++int
++zfsctl_snapdir_remove(struct inode *dip, char *name, cred_t *cr, int flags)
++{
++ zfs_sb_t *zsb = ITOZSB(dip);
++ char *snapname, *real;
++ int error;
++
++ ZFS_ENTER(zsb);
++
++ snapname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
++ real = kmem_alloc(MAXNAMELEN, KM_SLEEP);
++
++ if (zsb->z_case == ZFS_CASE_INSENSITIVE) {
++ error = dmu_snapshot_realname(zsb->z_os, name, real,
++ MAXNAMELEN, NULL);
++ if (error == 0) {
++ name = real;
++ } else if (error != ENOTSUP) {
++ goto out;
++ }
++ }
++
++ error = zfsctl_snapshot_zname(dip, name, MAXNAMELEN, snapname);
++ if (!error)
++ error = zfs_secpolicy_destroy_perms(snapname, cr);
++ if (error)
++ goto out;
++
++ error = zfsctl_unmount_snapshot(zsb, name, MNT_FORCE);
++ if ((error == 0) || (error == ENOENT))
++ error = dmu_objset_destroy(snapname, B_FALSE);
++out:
++ kmem_free(snapname, MAXNAMELEN);
++ kmem_free(real, MAXNAMELEN);
++
++ ZFS_EXIT(zsb);
++
++ return (error);
++}
++
++/*
++ * Creating a directory under '.zfs/snapshot' will automatically trigger
++ * the creation of a new snapshot with the given name.
++ */
++/* ARGSUSED */
++int
++zfsctl_snapdir_mkdir(struct inode *dip, char *dirname, vattr_t *vap,
++ struct inode **ipp, cred_t *cr, int flags)
++{
++ zfs_sb_t *zsb = ITOZSB(dip);
++ char *dsname;
++ int error;
++
++ dsname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
++
++ if (snapshot_namecheck(dirname, NULL, NULL) != 0) {
++ error = EILSEQ;
++ goto out;
++ }
++
++ dmu_objset_name(zsb->z_os, dsname);
++
++ error = zfs_secpolicy_snapshot_perms(dsname, cr);
++ if (error)
++ goto out;
++
++ if (error == 0) {
++ error = dmu_objset_snapshot(dsname, dirname,
++ NULL, NULL, B_FALSE, B_FALSE, -1);
++ if (error)
++ goto out;
++
++ error = zfsctl_snapdir_lookup(dip, dirname, ipp,
++ 0, cr, NULL, NULL);
++ }
++out:
++ kmem_free(dsname, MAXNAMELEN);
++
++ return (error);
++}
++
++/*
++ * When a .zfs/snapshot/<snapshot> inode is evicted they must be removed
++ * from the snapshot list. This will normally happen as part of the auto
++ * unmount, however in the case of a manual snapshot unmount this will be
++ * the only notification we receive.
++ */
++void
++zfsctl_snapdir_inactive(struct inode *ip)
++{
++ zfs_sb_t *zsb = ITOZSB(ip);
++ zfs_snapentry_t *sep, *next;
++
++ mutex_enter(&zsb->z_ctldir_lock);
++
++ sep = avl_first(&zsb->z_ctldir_snaps);
++ while (sep != NULL) {
++ next = AVL_NEXT(&zsb->z_ctldir_snaps, sep);
++
++ if (sep->se_inode == ip) {
++ avl_remove(&zsb->z_ctldir_snaps, sep);
++ cancel_delayed_work_sync(&sep->se_work);
++ zfsctl_sep_free(sep);
++ break;
++ }
++ sep = next;
++ }
++
++ mutex_exit(&zsb->z_ctldir_lock);
++}
++
++/*
++ * Attempt to unmount a snapshot by making a call to user space.
++ * There is no assurance that this can or will succeed, is just a
++ * best effort. In the case where it does fail, perhaps because
++ * it's in use, the unmount will fail harmlessly.
++ */
++#define SET_UNMOUNT_CMD \
++ "exec 0</dev/null " \
++ " 1>/dev/null " \
++ " 2>/dev/null; " \
++ "umount -t zfs -n %s%s"
++
++static int
++__zfsctl_unmount_snapshot(zfs_snapentry_t *sep, int flags)
++{
++ char *argv[] = { "/bin/sh", "-c", NULL, NULL };
++ char *envp[] = { NULL };
++ int error;
++
++ argv[2] = kmem_asprintf(SET_UNMOUNT_CMD,
++ flags & MNT_FORCE ? "-f " : "", sep->se_path);
++ error = call_usermodehelper(argv[0], argv, envp, 1);
++ strfree(argv[2]);
++
++ /*
++ * The umount system utility will return 256 on error. We must
++ * assume this error is because the file system is busy so it is
++ * converted to the more sensible EBUSY.
++ */
++ if (error)
++ error = EBUSY;
++
++ /*
++ * This was the result of a manual unmount, cancel the delayed work
++ * to prevent zfsctl_expire_snapshot() from attempting a unmount.
++ */
++ if ((error == 0) && !(flags & MNT_EXPIRE))
++ cancel_delayed_work(&sep->se_work);
++
++ return (error);
++}
++
++int
++zfsctl_unmount_snapshot(zfs_sb_t *zsb, char *name, int flags)
++{
++ zfs_snapentry_t search;
++ zfs_snapentry_t *sep;
++ int error = 0;
++
++ mutex_enter(&zsb->z_ctldir_lock);
++
++ search.se_name = name;
++ sep = avl_find(&zsb->z_ctldir_snaps, &search, NULL);
++ if (sep) {
++ avl_remove(&zsb->z_ctldir_snaps, sep);
++ error = __zfsctl_unmount_snapshot(sep, flags);
++ if (error == EBUSY)
++ avl_add(&zsb->z_ctldir_snaps, sep);
++ else
++ zfsctl_sep_free(sep);
++ } else {
++ error = ENOENT;
++ }
++
++ mutex_exit(&zsb->z_ctldir_lock);
++ ASSERT3S(error, >=, 0);
++
++ return (error);
++}
++
++/*
++ * Traverse all mounted snapshots and attempt to unmount them. This
++ * is best effort, on failure EEXIST is returned and count will be set
++ * to the number of file snapshots which could not be unmounted.
++ */
++int
++zfsctl_unmount_snapshots(zfs_sb_t *zsb, int flags, int *count)
++{
++ zfs_snapentry_t *sep, *next;
++ int error = 0;
++
++ *count = 0;
++
++ ASSERT(zsb->z_ctldir != NULL);
++ mutex_enter(&zsb->z_ctldir_lock);
++
++ sep = avl_first(&zsb->z_ctldir_snaps);
++ while (sep != NULL) {
++ next = AVL_NEXT(&zsb->z_ctldir_snaps, sep);
++ avl_remove(&zsb->z_ctldir_snaps, sep);
++ error = __zfsctl_unmount_snapshot(sep, flags);
++ if (error == EBUSY) {
++ avl_add(&zsb->z_ctldir_snaps, sep);
++ (*count)++;
++ } else {
++ zfsctl_sep_free(sep);
++ }
++
++ sep = next;
++ }
++
++ mutex_exit(&zsb->z_ctldir_lock);
++
++ return ((*count > 0) ? EEXIST : 0);
++}
++
++#define SET_MOUNT_CMD \
++ "exec 0</dev/null " \
++ " 1>/dev/null " \
++ " 2>/dev/null; " \
++ "mount -t zfs -n %s %s"
++
++int
++zfsctl_mount_snapshot(struct path *path, int flags)
++{
++ struct dentry *dentry = path->dentry;
++ struct inode *ip = dentry->d_inode;
++ zfs_sb_t *zsb = ITOZSB(ip);
++ char *full_name, *full_path;
++ zfs_snapentry_t *sep;
++ zfs_snapentry_t search;
++ char *argv[] = { "/bin/sh", "-c", NULL, NULL };
++ char *envp[] = { NULL };
++ int error;
++
++ ZFS_ENTER(zsb);
++
++ full_name = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
++ full_path = kmem_zalloc(PATH_MAX, KM_SLEEP);
++
++ error = zfsctl_snapshot_zname(ip, dname(dentry), MAXNAMELEN, full_name);
++ if (error)
++ goto error;
++
++ error = zfsctl_snapshot_zpath(path, PATH_MAX, full_path);
++ if (error)
++ goto error;
++
++ /*
++ * Attempt to mount the snapshot from user space. Normally this
++ * would be done using the vfs_kern_mount() function, however that
++ * function is marked GPL-only and cannot be used. On error we
++ * careful to log the real error to the console and return EISDIR
++ * to safely abort the automount. This should be very rare.
++ */
++ argv[2] = kmem_asprintf(SET_MOUNT_CMD, full_name, full_path);
++ error = call_usermodehelper(argv[0], argv, envp, 1);
++ strfree(argv[2]);
++ if (error) {
++ printk("ZFS: Unable to automount %s at %s: %d\n",
++ full_name, full_path, error);
++ error = EISDIR;
++ goto error;
++ }
++
++ mutex_enter(&zsb->z_ctldir_lock);
++
++ /*
++ * Ensure a previous entry does not exist, if it does safely remove
++ * it any cancel the outstanding expiration. This can occur when a
++ * snapshot is manually unmounted and then an automount is triggered.
++ */
++ search.se_name = full_name;
++ sep = avl_find(&zsb->z_ctldir_snaps, &search, NULL);
++ if (sep) {
++ avl_remove(&zsb->z_ctldir_snaps, sep);
++ cancel_delayed_work_sync(&sep->se_work);
++ zfsctl_sep_free(sep);
++ }
++
++ sep = zfsctl_sep_alloc();
++ sep->se_name = full_name;
++ sep->se_path = full_path;
++ sep->se_inode = ip;
++ avl_add(&zsb->z_ctldir_snaps, sep);
++
++ spl_init_delayed_work(&sep->se_work, zfsctl_expire_snapshot, sep);
++ schedule_delayed_work(&sep->se_work, zfs_expire_snapshot * HZ);
++
++ mutex_exit(&zsb->z_ctldir_lock);
++error:
++ if (error) {
++ kmem_free(full_name, MAXNAMELEN);
++ kmem_free(full_path, PATH_MAX);
++ }
++
++ ZFS_EXIT(zsb);
++
++ return (error);
++}
++
++/*
++ * Check if this super block has a matching objset id.
++ */
++static int
++zfsctl_test_super(struct super_block *sb, void *objsetidp)
++{
++ zfs_sb_t *zsb = sb->s_fs_info;
++ uint64_t objsetid = *(uint64_t *)objsetidp;
++
++ return (dmu_objset_id(zsb->z_os) == objsetid);
++}
++
++/*
++ * Prevent a new super block from being allocated if an existing one
++ * could not be located. We only want to preform a lookup operation.
++ */
++static int
++zfsctl_set_super(struct super_block *sb, void *objsetidp)
++{
++ return (-EEXIST);
++}
++
++int
++zfsctl_lookup_objset(struct super_block *sb, uint64_t objsetid, zfs_sb_t **zsbp)
++{
++ zfs_sb_t *zsb = sb->s_fs_info;
++ struct super_block *sbp;
++ zfs_snapentry_t *sep;
++ uint64_t id;
++ int error;
++
++ ASSERT(zsb->z_ctldir != NULL);
++
++ mutex_enter(&zsb->z_ctldir_lock);
++
++ /*
++ * Verify that the snapshot is mounted.
++ */
++ sep = avl_first(&zsb->z_ctldir_snaps);
++ while (sep != NULL) {
++ error = dmu_snapshot_id(zsb->z_os, sep->se_name, &id);
++ if (error)
++ goto out;
++
++ if (id == objsetid)
++ break;
++
++ sep = AVL_NEXT(&zsb->z_ctldir_snaps, sep);
++ }
++
++ if (sep != NULL) {
++ /*
++ * Lookup the mounted root rather than the covered mount
++ * point. This may fail if the snapshot has just been
++ * unmounted by an unrelated user space process. This
++ * race cannot occur to an expired mount point because
++ * we hold the zsb->z_ctldir_lock to prevent the race.
++ */
++ sbp = sget(&zpl_fs_type, zfsctl_test_super,
++ zfsctl_set_super, &id);
++ if (IS_ERR(sbp)) {
++ error = -PTR_ERR(sbp);
++ } else {
++ *zsbp = sbp->s_fs_info;
++ deactivate_super(sbp);
++ }
++ } else {
++ error = EINVAL;
++ }
++out:
++ mutex_exit(&zsb->z_ctldir_lock);
++ ASSERT3S(error, >=, 0);
++
++ return (error);
++}
++
++/* ARGSUSED */
++int
++zfsctl_shares_lookup(struct inode *dip, char *name, struct inode **ipp,
++ int flags, cred_t *cr, int *direntflags, pathname_t *realpnp)
++{
++ zfs_sb_t *zsb = ITOZSB(dip);
++ struct inode *ip;
++ znode_t *dzp;
++ int error;
++
++ ZFS_ENTER(zsb);
++
++ if (zsb->z_shares_dir == 0) {
++ ZFS_EXIT(zsb);
++ return (-ENOTSUP);
++ }
++
++ error = zfs_zget(zsb, zsb->z_shares_dir, &dzp);
++ if (error) {
++ ZFS_EXIT(zsb);
++ return (error);
++ }
++
++ error = zfs_lookup(ZTOI(dzp), name, &ip, 0, cr, NULL, NULL);
++
++ iput(ZTOI(dzp));
++ ZFS_EXIT(zsb);
++
++ return (error);
++}
++
++
++/*
++ * Initialize the various pieces we'll need to create and manipulate .zfs
++ * directories. Currently this is unused but available.
++ */
++void
++zfsctl_init(void)
++{
++}
++
++/*
++ * Cleanup the various pieces we needed for .zfs directories. In particular
++ * ensure the expiry timer is canceled safely.
++ */
++void
++zfsctl_fini(void)
++{
++}
++
++module_param(zfs_expire_snapshot, int, 0644);
++MODULE_PARM_DESC(zfs_expire_snapshot, "Seconds to expire .zfs/snapshot");
+diff --git a/module/zfs/zfs_dir.c b/module/zfs/zfs_dir.c
+index 8f1a0c2..6cd9c85 100644
+--- a/module/zfs/zfs_dir.c
++++ b/module/zfs/zfs_dir.c
+@@ -50,6 +50,7 @@
+ #include <sys/zap.h>
+ #include <sys/dmu.h>
+ #include <sys/atomic.h>
++#include <sys/zfs_ctldir.h>
+ #include <sys/zfs_fuid.h>
+ #include <sys/sa.h>
+ #include <sys/zfs_sa.h>
+@@ -415,28 +416,24 @@ zfs_dirlook(znode_t *dzp, char *name, struct inode **ipp, int flags,
+
+ /*
+ * If we are a snapshot mounted under .zfs, return
+- * the vp for the snapshot directory.
++ * the inode pointer for the snapshot directory.
+ */
+ if ((error = sa_lookup(dzp->z_sa_hdl,
+ SA_ZPL_PARENT(zsb), &parent, sizeof (parent))) != 0)
+ return (error);
+-#ifdef HAVE_SNAPSHOT
++
+ if (parent == dzp->z_id && zsb->z_parent != zsb) {
+ error = zfsctl_root_lookup(zsb->z_parent->z_ctldir,
+- "snapshot", ipp, NULL, 0, NULL, kcred,
+- NULL, NULL, NULL);
++ "snapshot", ipp, 0, kcred, NULL, NULL);
+ return (error);
+ }
+-#endif /* HAVE_SNAPSHOT */
+ rw_enter(&dzp->z_parent_lock, RW_READER);
+ error = zfs_zget(zsb, parent, &zp);
+ if (error == 0)
+ *ipp = ZTOI(zp);
+ rw_exit(&dzp->z_parent_lock);
+-#ifdef HAVE_SNAPSHOT
+ } else if (zfs_has_ctldir(dzp) && strcmp(name, ZFS_CTLDIR_NAME) == 0) {
+ *ipp = zfsctl_root(dzp);
+-#endif /* HAVE_SNAPSHOT */
+ } else {
+ int zf;
+
+diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c
+index 532f17a..d2ad1af 100644
+--- a/module/zfs/zfs_ioctl.c
++++ b/module/zfs/zfs_ioctl.c
+@@ -58,6 +58,7 @@
+ #include <sys/mount.h>
+ #include <sys/sdt.h>
+ #include <sys/fs/zfs.h>
++#include <sys/zfs_ctldir.h>
+ #include <sys/zfs_dir.h>
+ #include <sys/zfs_onexit.h>
+ #include <sys/zvol.h>
+@@ -2690,33 +2691,6 @@ zfs_ioc_get_fsacl(zfs_cmd_t *zc)
+ return (error);
+ }
+
+-#ifdef HAVE_SNAPSHOT
+-/*
+- * Search the vfs list for a specified resource. Returns a pointer to it
+- * or NULL if no suitable entry is found. The caller of this routine
+- * is responsible for releasing the returned vfs pointer.
+- */
+-static vfs_t *
+-zfs_get_vfs(const char *resource)
+-{
+- struct vfs *vfsp;
+- struct vfs *vfs_found = NULL;
+-
+- vfs_list_read_lock();
+- vfsp = rootvfs;
+- do {
+- if (strcmp(refstr_value(vfsp->vfs_resource), resource) == 0) {
+- mntget(vfsp);
+- vfs_found = vfsp;
+- break;
+- }
+- vfsp = vfsp->vfs_next;
+- } while (vfsp != rootvfs);
+- vfs_list_unlock();
+- return (vfs_found);
+-}
+-#endif /* HAVE_SNAPSHOT */
+-
+ /* ARGSUSED */
+ static void
+ zfs_create_cb(objset_t *os, void *arg, cred_t *cr, dmu_tx_t *tx)
+@@ -3067,38 +3041,52 @@ out:
+ return (error);
+ }
+
++/*
++ * inputs:
++ * name dataset name, or when 'arg == NULL' the full snapshot name
++ * arg short snapshot name (i.e. part after the '@')
++ */
+ int
+ zfs_unmount_snap(const char *name, void *arg)
+ {
+-#ifdef HAVE_SNAPSHOT
+- vfs_t *vfsp = NULL;
++ zfs_sb_t *zsb = NULL;
++ char *dsname;
++ char *snapname;
++ char *fullname;
++ char *ptr;
++ int error;
+
+ if (arg) {
+- char *snapname = arg;
+- char *fullname = kmem_asprintf("%s@%s", name, snapname);
+- vfsp = zfs_get_vfs(fullname);
+- strfree(fullname);
+- } else if (strchr(name, '@')) {
+- vfsp = zfs_get_vfs(name);
++ dsname = strdup(name);
++ snapname = strdup(arg);
++ } else {
++ ptr = strchr(name, '@');
++ if (ptr) {
++ dsname = strdup(name);
++ dsname[ptr - name] = '\0';
++ snapname = strdup(ptr + 1);
++ } else {
++ return (0);
++ }
+ }
+
+- if (vfsp) {
+- /*
+- * Always force the unmount for snapshots.
+- */
+- int flag = MS_FORCE;
+- int err;
++ fullname = kmem_asprintf("%s@%s", dsname, snapname);
+
+- if ((err = vn_vfswlock(vfsp->vfs_vnodecovered)) != 0) {
+- mntput(vfsp);
+- return (err);
+- }
+- mntput(vfsp);
+- if ((err = dounmount(vfsp, flag, kcred)) != 0)
+- return (err);
++ error = zfs_sb_hold(dsname, FTAG, &zsb, B_FALSE);
++ if (error == 0) {
++ error = zfsctl_unmount_snapshot(zsb, fullname, MNT_FORCE);
++ zfs_sb_rele(zsb, FTAG);
++
++ /* Allow ENOENT for consistency with upstream */
++ if (error == ENOENT)
++ error = 0;
+ }
+-#endif /* HAVE_SNAPSHOT */
+- return (0);
++
++ strfree(dsname);
++ strfree(snapname);
++ strfree(fullname);
++
++ return (error);
+ }
+
+ /*
+diff --git a/module/zfs/zfs_vfsops.c b/module/zfs/zfs_vfsops.c
+index fb319a5..f895f5c 100644
+--- a/module/zfs/zfs_vfsops.c
++++ b/module/zfs/zfs_vfsops.c
+@@ -56,6 +56,7 @@
+ #include <sys/modctl.h>
+ #include <sys/refstr.h>
+ #include <sys/zfs_ioctl.h>
++#include <sys/zfs_ctldir.h>
+ #include <sys/zfs_fuid.h>
+ #include <sys/bootconf.h>
+ #include <sys/sunddi.h>
+@@ -710,6 +711,10 @@ zfs_sb_create(const char *osname, zfs_sb_t **zsbp)
+ for (i = 0; i != ZFS_OBJ_MTX_SZ; i++)
+ mutex_init(&zsb->z_hold_mtx[i], NULL, MUTEX_DEFAULT, NULL);
+
++ avl_create(&zsb->z_ctldir_snaps, snapentry_compare,
++ sizeof (zfs_snapentry_t), offsetof(zfs_snapentry_t, se_node));
++ mutex_init(&zsb->z_ctldir_lock, NULL, MUTEX_DEFAULT, NULL);
++
+ *zsbp = zsb;
+ return (0);
+
+@@ -819,6 +824,8 @@ zfs_sb_free(zfs_sb_t *zsb)
+ rw_destroy(&zsb->z_fuid_lock);
+ for (i = 0; i != ZFS_OBJ_MTX_SZ; i++)
+ mutex_destroy(&zsb->z_hold_mtx[i]);
++ mutex_destroy(&zsb->z_ctldir_lock);
++ avl_destroy(&zsb->z_ctldir_snaps);
+ kmem_free(zsb, sizeof (zfs_sb_t));
+ }
+ EXPORT_SYMBOL(zfs_sb_free);
+@@ -1183,11 +1190,11 @@ zfs_domount(struct super_block *sb, void *data, int silent)
+ mutex_exit(&zsb->z_os->os_user_ptr_lock);
+ } else {
+ error = zfs_sb_setup(zsb, B_TRUE);
+-#ifdef HAVE_SNAPSHOT
+- (void) zfs_snap_create(zsb);
+-#endif /* HAVE_SNAPSHOT */
+ }
+
++ if (!zsb->z_issnap)
++ zfsctl_create(zsb);
++
+ /* Allocate a root inode for the filesystem. */
+ error = zfs_root(zsb, &root_inode);
+ if (error) {
+@@ -1212,6 +1219,27 @@ out:
+ }
+ EXPORT_SYMBOL(zfs_domount);
+
++/*
++ * Called when an unmount is requested and certain sanity checks have
++ * already passed. At this point no dentries or inodes have been reclaimed
++ * from their respective caches. We drop the extra reference on the .zfs
++ * control directory to allow everything to be reclaimed. All snapshots
++ * must already have been unmounted to reach this point.
++ */
++void
++zfs_preumount(struct super_block *sb)
++{
++ zfs_sb_t *zsb = sb->s_fs_info;
++
++ if (zsb->z_ctldir != NULL)
++ zfsctl_destroy(zsb);
++}
++EXPORT_SYMBOL(zfs_preumount);
++
++/*
++ * Called once all other unmount released tear down has occurred.
++ * It is our responsibility to release any remaining infrastructure.
++ */
+ /*ARGSUSED*/
+ int
+ zfs_umount(struct super_block *sb)
+@@ -1288,11 +1316,10 @@ zfs_vget(struct super_block *sb, struct inode **ipp, fid_t *fidp)
+
+ ZFS_EXIT(zsb);
+
+-#ifdef HAVE_SNAPSHOT
+- err = zfsctl_lookup_objset(vfsp, objsetid, &zsb);
++ err = zfsctl_lookup_objset(sb, objsetid, &zsb);
+ if (err)
+ return (EINVAL);
+-#endif /* HAVE_SNAPSHOT */
++
+ ZFS_ENTER(zsb);
+ }
+
+@@ -1309,22 +1336,20 @@ zfs_vget(struct super_block *sb, struct inode **ipp, fid_t *fidp)
+ return (EINVAL);
+ }
+
+-#ifdef HAVE_SNAPSHOT
+ /* A zero fid_gen means we are in the .zfs control directories */
+ if (fid_gen == 0 &&
+ (object == ZFSCTL_INO_ROOT || object == ZFSCTL_INO_SNAPDIR)) {
+ *ipp = zsb->z_ctldir;
+ ASSERT(*ipp != NULL);
+ if (object == ZFSCTL_INO_SNAPDIR) {
+- VERIFY(zfsctl_root_lookup(*ipp, "snapshot", ipp, NULL,
+- 0, NULL, NULL, NULL, NULL, NULL) == 0);
++ VERIFY(zfsctl_root_lookup(*ipp, "snapshot", ipp,
++ 0, kcred, NULL, NULL) == 0);
+ } else {
+ igrab(*ipp);
+ }
+ ZFS_EXIT(zsb);
+ return (0);
+ }
+-#endif /* HAVE_SNAPSHOT */
+
+ gen_mask = -1ULL >> (64 - 8 * i);
+
+@@ -1550,6 +1575,7 @@ EXPORT_SYMBOL(zfs_get_zplprop);
+ void
+ zfs_init(void)
+ {
++ zfsctl_init();
+ zfs_znode_init();
+ dmu_objset_register_type(DMU_OST_ZFS, zfs_space_delta_cb);
+ register_filesystem(&zpl_fs_type);
+@@ -1561,4 +1587,5 @@ zfs_fini(void)
+ {
+ unregister_filesystem(&zpl_fs_type);
+ zfs_znode_fini();
++ zfsctl_fini();
+ }
+diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c
+index 74b96b8..2da5fec 100644
+--- a/module/zfs/zfs_vnops.c
++++ b/module/zfs/zfs_vnops.c
+@@ -63,6 +63,7 @@
+ #include <sys/sid.h>
+ #include <sys/mode.h>
+ #include "fs/fs_subr.h"
++#include <sys/zfs_ctldir.h>
+ #include <sys/zfs_fuid.h>
+ #include <sys/zfs_sa.h>
+ #include <sys/zfs_vnops.h>
+@@ -2045,7 +2046,7 @@ zfs_readdir(struct inode *ip, void *dirent, filldir_t filldir,
+ dmu_prefetch(os, objnum, 0, 0);
+ }
+
+- if (*pos >= 2) {
++ if (*pos > 2 || (*pos == 2 && !zfs_show_ctldir(zp))) {
+ zap_cursor_advance(&zc);
+ *pos = zap_cursor_serialize(&zc);
+ } else {
+@@ -3876,9 +3877,10 @@ zfs_inactive(struct inode *ip)
+ zfs_sb_t *zsb = ITOZSB(ip);
+ int error;
+
+-#ifdef HAVE_SNAPSHOT
+- /* Early return for snapshot inode? */
+-#endif /* HAVE_SNAPSHOT */
++ if (zfsctl_is_node(ip)) {
++ zfsctl_inode_inactive(ip);
++ return;
++ }
+
+ rw_enter(&zsb->z_teardown_inactive_lock, RW_READER);
+ if (zp->z_sa_hdl == NULL) {
+diff --git a/module/zfs/zfs_znode.c b/module/zfs/zfs_znode.c
+index 709ae74..629c851 100644
+--- a/module/zfs/zfs_znode.c
++++ b/module/zfs/zfs_znode.c
+@@ -52,6 +52,7 @@
+ #include <sys/zfs_rlock.h>
+ #include <sys/zfs_fuid.h>
+ #include <sys/zfs_vnops.h>
++#include <sys/zfs_ctldir.h>
+ #include <sys/dnode.h>
+ #include <sys/fs/zfs.h>
+ #include <sys/kidmap.h>
+@@ -267,6 +268,9 @@ zfs_inode_destroy(struct inode *ip)
+ znode_t *zp = ITOZ(ip);
+ zfs_sb_t *zsb = ZTOZSB(zp);
+
++ if (zfsctl_is_node(ip))
++ zfsctl_inode_destroy(ip);
++
+ mutex_enter(&zsb->z_znodes_lock);
+ list_remove(&zsb->z_all_znodes, zp);
+ zsb->z_nr_znodes--;
+@@ -363,6 +367,7 @@ zfs_znode_alloc(zfs_sb_t *zsb, dmu_buf_t *db, int blksz,
+ zp->z_seq = 0x7A4653;
+ zp->z_sync_cnt = 0;
+ zp->z_is_zvol = 0;
++ zp->z_is_ctldir = 0;
+
+ zfs_znode_sa_init(zsb, zp, db, obj_type, hdl);
+
+@@ -434,6 +439,10 @@ zfs_inode_update(znode_t *zp)
+ zsb = ZTOZSB(zp);
+ ip = ZTOI(zp);
+
++ /* Skip .zfs control nodes which do not exist on disk. */
++ if (zfsctl_is_node(ip))
++ return;
++
+ sa_lookup(zp->z_sa_hdl, SA_ZPL_ATIME(zsb), &atime, 16);
+ sa_lookup(zp->z_sa_hdl, SA_ZPL_MTIME(zsb), &mtime, 16);
+ sa_lookup(zp->z_sa_hdl, SA_ZPL_CTIME(zsb), &ctime, 16);
+diff --git a/module/zfs/zpl_ctldir.c b/module/zfs/zpl_ctldir.c
+new file mode 100644
+index 0000000..6c742c9
+--- /dev/null
++++ b/module/zfs/zpl_ctldir.c
+@@ -0,0 +1,519 @@
++/*
++ * CDDL HEADER START
++ *
++ * The contents of this file are subject to the terms of the
++ * Common Development and Distribution License (the "License").
++ * You may not use this file except in compliance with the License.
++ *
++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
++ * or http://www.opensolaris.org/os/licensing.
++ * See the License for the specific language governing permissions
++ * and limitations under the License.
++ *
++ * When distributing Covered Code, include this CDDL HEADER in each
++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
++ * If applicable, add the following below this CDDL HEADER, with the
++ * fields enclosed by brackets "[]" replaced with your own identifying
++ * information: Portions Copyright [yyyy] [name of copyright owner]
++ *
++ * CDDL HEADER END
++ */
++/*
++ * Copyright (C) 2011 Lawrence Livermore National Security, LLC.
++ * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
++ * LLNL-CODE-403049.
++ * Rewritten for Linux by:
++ * Rohan Puri <rohan.puri15@gmail.com>
++ * Brian Behlendorf <behlendorf1@llnl.gov>
++ */
++
++#include <sys/zfs_vfsops.h>
++#include <sys/zfs_vnops.h>
++#include <sys/zfs_znode.h>
++#include <sys/zfs_ctldir.h>
++#include <sys/zpl.h>
++
++/*
++ * Common open routine. Disallow any write access.
++ */
++/* ARGSUSED */
++static int
++zpl_common_open(struct inode *ip, struct file *filp)
++{
++ if (filp->f_mode & FMODE_WRITE)
++ return (-EACCES);
++
++ return generic_file_open(ip, filp);
++}
++
++static int
++zpl_common_readdir(struct file *filp, void *dirent, filldir_t filldir)
++{
++ struct dentry *dentry = filp->f_path.dentry;
++ struct inode *ip = dentry->d_inode;
++ int error = 0;
++
++ switch (filp->f_pos) {
++ case 0:
++ error = filldir(dirent, ".", 1, 0, ip->i_ino, DT_DIR);
++ if (error)
++ break;
++
++ filp->f_pos++;
++ /* fall-thru */
++ case 1:
++ error = filldir(dirent, "..", 2, 1, parent_ino(dentry), DT_DIR);
++ if (error)
++ break;
++
++ filp->f_pos++;
++ /* fall-thru */
++ default:
++ break;
++ }
++
++ return (error);
++}
++
++/*
++ * Get root directory contents.
++ */
++static int
++zpl_root_readdir(struct file *filp, void *dirent, filldir_t filldir)
++{
++ struct dentry *dentry = filp->f_path.dentry;
++ struct inode *ip = dentry->d_inode;
++ zfs_sb_t *zsb = ITOZSB(ip);
++ int error = 0;
++
++ ZFS_ENTER(zsb);
++
++ switch (filp->f_pos) {
++ case 0:
++ error = filldir(dirent, ".", 1, 0, ip->i_ino, DT_DIR);
++ if (error)
++ goto out;
++
++ filp->f_pos++;
++ /* fall-thru */
++ case 1:
++ error = filldir(dirent, "..", 2, 1, parent_ino(dentry), DT_DIR);
++ if (error)
++ goto out;
++
++ filp->f_pos++;
++ /* fall-thru */
++ case 2:
++ error = filldir(dirent, ZFS_SNAPDIR_NAME,
++ strlen(ZFS_SNAPDIR_NAME), 2, ZFSCTL_INO_SNAPDIR, DT_DIR);
++ if (error)
++ goto out;
++
++ filp->f_pos++;
++ /* fall-thru */
++ case 3:
++ error = filldir(dirent, ZFS_SHAREDIR_NAME,
++ strlen(ZFS_SHAREDIR_NAME), 3, ZFSCTL_INO_SHARES, DT_DIR);
++ if (error)
++ goto out;
++
++ filp->f_pos++;
++ /* fall-thru */
++ }
++out:
++ ZFS_EXIT(zsb);
++
++ return (error);
++}
++
++/*
++ * Get root directory attributes.
++ */
++/* ARGSUSED */
++static int
++zpl_root_getattr(struct vfsmount *mnt, struct dentry *dentry,
++ struct kstat *stat)
++{
++ int error;
++
++ error = simple_getattr(mnt, dentry, stat);
++ stat->atime = CURRENT_TIME;
++
++ return (error);
++}
++
++static struct dentry *
++zpl_root_lookup(struct inode *dip, struct dentry *dentry, struct nameidata *nd)
++{
++ cred_t *cr = CRED();
++ struct inode *ip;
++ int error;
++
++ crhold(cr);
++ error = -zfsctl_root_lookup(dip, dname(dentry), &ip, 0, cr, NULL, NULL);
++ ASSERT3S(error, <=, 0);
++ crfree(cr);
++
++ if (error) {
++ if (error == -ENOENT)
++ return d_splice_alias(NULL, dentry);
++ else
++ return ERR_PTR(error);
++ }
++
++ return d_splice_alias(ip, dentry);
++}
++
++/*
++ * The '.zfs' control directory file and inode operations.
++ */
++const struct file_operations zpl_fops_root = {
++ .open = zpl_common_open,
++ .llseek = generic_file_llseek,
++ .read = generic_read_dir,
++ .readdir = zpl_root_readdir,
++};
++
++const struct inode_operations zpl_ops_root = {
++ .lookup = zpl_root_lookup,
++ .getattr = zpl_root_getattr,
++};
++
++static struct dentry *
++zpl_snapdir_lookup(struct inode *dip, struct dentry *dentry,
++ struct nameidata *nd)
++{
++ cred_t *cr = CRED();
++ struct inode *ip;
++ int error;
++
++ crhold(cr);
++ error = -zfsctl_snapdir_lookup(dip, dname(dentry), &ip,
++ 0, cr, NULL, NULL);
++ ASSERT3S(error, <=, 0);
++ crfree(cr);
++
++ if (error) {
++ if (error == -ENOENT)
++ return d_splice_alias(NULL, dentry);
++ else
++ return ERR_PTR(error);
++ }
++
++ /*
++ * Auto mounting of snapshots is only supported for 2.6.37 and
++ * newer kernels. Prior to this kernel the ops->follow_link()
++ * callback was used as a hack to trigger the mount. The
++ * resulting vfsmount was then explicitly grafted in to the
++ * name space. While it might be possible to add compatibility
++ * code to accomplish this it would require considerable care.
++ */
++#ifdef HAVE_AUTOMOUNT
++ dentry->d_op = &zpl_dops_snapdirs;
++#endif /* HAVE_AUTOMOUNT */
++
++ return d_splice_alias(ip, dentry);
++}
++
++/* ARGSUSED */
++static int
++zpl_snapdir_readdir(struct file *filp, void *dirent, filldir_t filldir)
++{
++ struct dentry *dentry = filp->f_path.dentry;
++ struct inode *dip = dentry->d_inode;
++ zfs_sb_t *zsb = ITOZSB(dip);
++ char snapname[MAXNAMELEN];
++ uint64_t id, cookie;
++ boolean_t case_conflict;
++ int error = 0;
++
++ ZFS_ENTER(zsb);
++
++ cookie = filp->f_pos;
++ switch (filp->f_pos) {
++ case 0:
++ error = filldir(dirent, ".", 1, 0, dip->i_ino, DT_DIR);
++ if (error)
++ goto out;
++
++ filp->f_pos++;
++ /* fall-thru */
++ case 1:
++ error = filldir(dirent, "..", 2, 1, parent_ino(dentry), DT_DIR);
++ if (error)
++ goto out;
++
++ filp->f_pos++;
++ /* fall-thru */
++ default:
++ while (error == 0) {
++ error = -dmu_snapshot_list_next(zsb->z_os, MAXNAMELEN,
++ snapname, &id, &cookie, &case_conflict);
++ if (error)
++ goto out;
++
++ error = filldir(dirent, snapname, strlen(snapname),
++ filp->f_pos, ZFSCTL_INO_SHARES - id, DT_DIR);
++ if (error)
++ goto out;
++
++ filp->f_pos = cookie;
++ }
++ }
++out:
++ ZFS_EXIT(zsb);
++
++ if (error == -ENOENT)
++ return (0);
++
++ return (error);
++}
++
++int
++zpl_snapdir_rename(struct inode *sdip, struct dentry *sdentry,
++ struct inode *tdip, struct dentry *tdentry)
++{
++ cred_t *cr = CRED();
++ int error;
++
++ crhold(cr);
++ error = -zfsctl_snapdir_rename(sdip, dname(sdentry),
++ tdip, dname(tdentry), cr, 0);
++ ASSERT3S(error, <=, 0);
++ crfree(cr);
++
++ return (error);
++}
++
++static int
++zpl_snapdir_rmdir(struct inode *dip, struct dentry *dentry)
++{
++ cred_t *cr = CRED();
++ int error;
++
++ crhold(cr);
++ error = -zfsctl_snapdir_remove(dip, dname(dentry), cr, 0);
++ ASSERT3S(error, <=, 0);
++ crfree(cr);
++
++ return (error);
++}
++
++static int
++zpl_snapdir_mkdir(struct inode *dip, struct dentry *dentry, int mode)
++{
++ cred_t *cr = CRED();
++ vattr_t *vap;
++ struct inode *ip;
++ int error;
++
++ crhold(cr);
++ vap = kmem_zalloc(sizeof(vattr_t), KM_SLEEP);
++ zpl_vap_init(vap, dip, dentry, mode | S_IFDIR, cr);
++
++ error = -zfsctl_snapdir_mkdir(dip, dname(dentry), vap, &ip, cr, 0);
++ if (error == 0) {
++#ifdef HAVE_AUTOMOUNT
++ dentry->d_op = &zpl_dops_snapdirs;
++#endif /* HAVE_AUTOMOUNT */
++ d_instantiate(dentry, ip);
++ }
++
++ kmem_free(vap, sizeof(vattr_t));
++ ASSERT3S(error, <=, 0);
++ crfree(cr);
++
++ return (error);
++}
++
++#ifdef HAVE_AUTOMOUNT
++static struct vfsmount *
++zpl_snapdir_automount(struct path *path)
++{
++ struct dentry *dentry = path->dentry;
++ int error;
++
++ /*
++ * We must briefly disable automounts for this dentry because the
++ * user space mount utility will trigger another lookup on this
++ * directory. That will result in zpl_snapdir_automount() being
++ * called repeatedly. The DCACHE_NEED_AUTOMOUNT flag can be
++ * safely reset once the mount completes.
++ */
++ dentry->d_flags &= ~DCACHE_NEED_AUTOMOUNT;
++ error = -zfsctl_mount_snapshot(path, 0);
++ dentry->d_flags |= DCACHE_NEED_AUTOMOUNT;
++ if (error)
++ return ERR_PTR(error);
++
++ /*
++ * Rather than returning the new vfsmount for the snapshot we must
++ * return NULL to indicate a mount collision. This is done because
++ * the user space mount calls do_add_mount() which adds the vfsmount
++ * to the name space. If we returned the new mount here it would be
++ * added again to the vfsmount list resulting in list corruption.
++ */
++ return (NULL);
++}
++#endif /* HAVE_AUTOMOUNT */
++
++/*
++ * Get snapshot directory attributes.
++ */
++/* ARGSUSED */
++static int
++zpl_snapdir_getattr(struct vfsmount *mnt, struct dentry *dentry,
++ struct kstat *stat)
++{
++ zfs_sb_t *zsb = ITOZSB(dentry->d_inode);
++ int error;
++
++ ZFS_ENTER(zsb);
++ error = simple_getattr(mnt, dentry, stat);
++ stat->nlink = stat->size = avl_numnodes(&zsb->z_ctldir_snaps) + 2;
++ stat->ctime = stat->mtime = dmu_objset_snap_cmtime(zsb->z_os);
++ stat->atime = CURRENT_TIME;
++ ZFS_EXIT(zsb);
++
++ return (error);
++}
++
++/*
++ * The '.zfs/snapshot' directory file operations. These mainly control
++ * generating the list of available snapshots when doing an 'ls' in the
++ * directory. See zpl_snapdir_readdir().
++ */
++const struct file_operations zpl_fops_snapdir = {
++ .open = zpl_common_open,
++ .llseek = generic_file_llseek,
++ .read = generic_read_dir,
++ .readdir = zpl_snapdir_readdir,
++};
++
++/*
++ * The '.zfs/snapshot' directory inode operations. These mainly control
++ * creating an inode for a snapshot directory and initializing the needed
++ * infrastructure to automount the snapshot. See zpl_snapdir_lookup().
++ */
++const struct inode_operations zpl_ops_snapdir = {
++ .lookup = zpl_snapdir_lookup,
++ .getattr = zpl_snapdir_getattr,
++ .rename = zpl_snapdir_rename,
++ .rmdir = zpl_snapdir_rmdir,
++ .mkdir = zpl_snapdir_mkdir,
++};
++
++#ifdef HAVE_AUTOMOUNT
++const struct dentry_operations zpl_dops_snapdirs = {
++ .d_automount = zpl_snapdir_automount,
++};
++#endif /* HAVE_AUTOMOUNT */
++
++static struct dentry *
++zpl_shares_lookup(struct inode *dip, struct dentry *dentry,
++ struct nameidata *nd)
++{
++ cred_t *cr = CRED();
++ struct inode *ip = NULL;
++ int error;
++
++ crhold(cr);
++ error = -zfsctl_shares_lookup(dip, dname(dentry), &ip,
++ 0, cr, NULL, NULL);
++ ASSERT3S(error, <=, 0);
++ crfree(cr);
++
++ if (error) {
++ if (error == -ENOENT)
++ return d_splice_alias(NULL, dentry);
++ else
++ return ERR_PTR(error);
++ }
++
++ return d_splice_alias(ip, dentry);
++}
++
++/* ARGSUSED */
++static int
++zpl_shares_readdir(struct file *filp, void *dirent, filldir_t filldir)
++{
++ cred_t *cr = CRED();
++ struct dentry *dentry = filp->f_path.dentry;
++ struct inode *ip = dentry->d_inode;
++ zfs_sb_t *zsb = ITOZSB(ip);
++ znode_t *dzp;
++ int error;
++
++ ZFS_ENTER(zsb);
++
++ if (zsb->z_shares_dir == 0) {
++ error = zpl_common_readdir(filp, dirent, filldir);
++ ZFS_EXIT(zsb);
++ return (error);
++ }
++
++ error = -zfs_zget(zsb, zsb->z_shares_dir, &dzp);
++ if (error) {
++ ZFS_EXIT(zsb);
++ return (error);
++ }
++
++ crhold(cr);
++ error = -zfs_readdir(ZTOI(dzp), dirent, filldir, &filp->f_pos, cr);
++ crfree(cr);
++
++ iput(ZTOI(dzp));
++ ZFS_EXIT(zsb);
++ ASSERT3S(error, <=, 0);
++
++ return (error);
++}
++
++/* ARGSUSED */
++static int
++zpl_shares_getattr(struct vfsmount *mnt, struct dentry *dentry,
++ struct kstat *stat)
++{
++ struct inode *ip = dentry->d_inode;
++ zfs_sb_t *zsb = ITOZSB(ip);
++ znode_t *dzp;
++ int error;
++
++ ZFS_ENTER(zsb);
++
++ if (zsb->z_shares_dir == 0) {
++ error = simple_getattr(mnt, dentry, stat);
++ stat->nlink = stat->size = 2;
++ stat->atime = CURRENT_TIME;
++ ZFS_EXIT(zsb);
++ return (error);
++ }
++
++ error = -zfs_zget(zsb, zsb->z_shares_dir, &dzp);
++ if (error == 0)
++ error = -zfs_getattr_fast(dentry->d_inode, stat);
++
++ iput(ZTOI(dzp));
++ ZFS_EXIT(zsb);
++ ASSERT3S(error, <=, 0);
++
++ return (error);
++}
++
++/*
++ * The '.zfs/shares' directory file operations.
++ */
++const struct file_operations zpl_fops_shares = {
++ .open = zpl_common_open,
++ .llseek = generic_file_llseek,
++ .read = generic_read_dir,
++ .readdir = zpl_shares_readdir,
++};
++
++/*
++ * The '.zfs/shares' directory inode operations.
++ */
++const struct inode_operations zpl_ops_shares = {
++ .lookup = zpl_shares_lookup,
++ .getattr = zpl_shares_getattr,
++};
+diff --git a/module/zfs/zpl_export.c b/module/zfs/zpl_export.c
+index 4fe9984..f82ee30 100644
+--- a/module/zfs/zpl_export.c
++++ b/module/zfs/zpl_export.c
+@@ -25,6 +25,7 @@
+
+ #include <sys/zfs_vnops.h>
+ #include <sys/zfs_znode.h>
++#include <sys/zfs_ctldir.h>
+ #include <sys/zpl.h>
+
+
+@@ -42,7 +43,10 @@ zpl_encode_fh(struct dentry *dentry, __u32 *fh, int *max_len, int connectable)
+
+ fid->fid_len = len_bytes - offsetof(fid_t, fid_data);
+
+- rc = zfs_fid(ip, fid);
++ if (zfsctl_is_node(ip))
++ rc = zfsctl_fid(ip, fid);
++ else
++ rc = zfs_fid(ip, fid);
+
+ len_bytes = offsetof(fid_t, fid_data) + fid->fid_len;
+ *max_len = roundup(len_bytes, sizeof (__u32)) / sizeof (__u32);
+diff --git a/module/zfs/zpl_inode.c b/module/zfs/zpl_inode.c
+index 9b55337..d9b918b 100644
+--- a/module/zfs/zpl_inode.c
++++ b/module/zfs/zpl_inode.c
+@@ -25,6 +25,7 @@
+
+ #include <sys/zfs_vfsops.h>
+ #include <sys/zfs_vnops.h>
++#include <sys/zfs_znode.h>
+ #include <sys/vfs.h>
+ #include <sys/zpl.h>
+
+@@ -51,7 +52,7 @@ zpl_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
+ return d_splice_alias(ip, dentry);
+ }
+
+-static void
++void
+ zpl_vap_init(vattr_t *vap, struct inode *dir, struct dentry *dentry,
+ mode_t mode, cred_t *cr)
+ {
+@@ -171,8 +172,20 @@ zpl_rmdir(struct inode * dir, struct dentry *dentry)
+ static int
+ zpl_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
+ {
++ boolean_t issnap = ITOZSB(dentry->d_inode)->z_issnap;
+ int error;
+
++ /*
++ * Ensure MNT_SHRINKABLE is set on snapshots to ensure they are
++ * unmounted automatically with the parent file system. This
++ * is done on the first getattr because it's not easy to get the
++ * vfsmount structure at mount time. This call path is explicitly
++ * marked unlikely to avoid any performance impact. FWIW, ext4
++ * resorts to a similar trick for sysadmin convenience.
++ */
++ if (unlikely(issnap && !(mnt->mnt_flags & MNT_SHRINKABLE)))
++ mnt->mnt_flags |= MNT_SHRINKABLE;
++
+ error = -zfs_getattr_fast(dentry->d_inode, stat);
+ ASSERT3S(error, <=, 0);
+
+diff --git a/module/zfs/zpl_super.c b/module/zfs/zpl_super.c
+index 0e6e936..98d0a03 100644
+--- a/module/zfs/zpl_super.c
++++ b/module/zfs/zpl_super.c
+@@ -26,6 +26,7 @@
+ #include <sys/zfs_vfsops.h>
+ #include <sys/zfs_vnops.h>
+ #include <sys/zfs_znode.h>
++#include <sys/zfs_ctldir.h>
+ #include <sys/zpl.h>
+
+
+@@ -139,6 +140,20 @@ zpl_remount_fs(struct super_block *sb, int *flags, char *data)
+ return (error);
+ }
+
++static void
++zpl_umount_begin(struct super_block *sb)
++{
++ zfs_sb_t *zsb = sb->s_fs_info;
++ int count;
++
++ /*
++ * Best effort to unmount snapshots in .zfs/snapshot/. Normally this
++ * isn't required because snapshots have the MNT_SHRINKABLE flag set.
++ */
++ if (zsb->z_ctldir)
++ (void) zfsctl_unmount_snapshots(zsb, MNT_FORCE, &count);
++}
++
+ /*
+ * The Linux VFS automatically handles the following flags:
+ * MNT_NOSUID, MNT_NODEV, MNT_NOEXEC, MNT_NOATIME, MNT_READONLY
+@@ -199,13 +214,7 @@ zpl_get_sb(struct file_system_type *fs_type, int flags,
+ static void
+ zpl_kill_sb(struct super_block *sb)
+ {
+-#ifdef HAVE_SNAPSHOT
+- zfs_sb_t *zsb = sb->s_fs_info;
+-
+- if (zsb && dmu_objset_is_snapshot(zsb->z_os))
+- zfs_snap_destroy(zsb);
+-#endif /* HAVE_SNAPSHOT */
+-
++ zfs_preumount(sb);
+ kill_anon_super(sb);
+ }
+
+@@ -306,6 +315,7 @@ const struct super_operations zpl_super_operations = {
+ .sync_fs = zpl_sync_fs,
+ .statfs = zpl_statfs,
+ .remount_fs = zpl_remount_fs,
++ .umount_begin = zpl_umount_begin,
+ .show_options = zpl_show_options,
+ .show_stats = NULL,
+ #ifdef HAVE_NR_CACHED_OBJECTS
+diff --git a/scripts/Makefile.in b/scripts/Makefile.in
+index 5c81ae6..c6a49f6 100644
+--- a/scripts/Makefile.in
++++ b/scripts/Makefile.in
+@@ -40,6 +40,7 @@ DIST_COMMON = $(dist_pkglibexec_SCRIPTS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/scripts/zpios-profile/Makefile.in b/scripts/zpios-profile/Makefile.in
+index 3ee1ec3..684530d 100644
+--- a/scripts/zpios-profile/Makefile.in
++++ b/scripts/zpios-profile/Makefile.in
+@@ -40,6 +40,7 @@ DIST_COMMON = $(dist_pkglibexec_SCRIPTS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/scripts/zpios-test/Makefile.in b/scripts/zpios-test/Makefile.in
+index ee88174..c9cb7c2 100644
+--- a/scripts/zpios-test/Makefile.in
++++ b/scripts/zpios-test/Makefile.in
+@@ -40,6 +40,7 @@ DIST_COMMON = $(dist_pkglibexec_SCRIPTS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/scripts/zpool-config/Makefile.in b/scripts/zpool-config/Makefile.in
+index eed1573..db4b29c 100644
+--- a/scripts/zpool-config/Makefile.in
++++ b/scripts/zpool-config/Makefile.in
+@@ -40,6 +40,7 @@ DIST_COMMON = $(dist_pkglibexec_SCRIPTS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/scripts/zpool-layout/Makefile.in b/scripts/zpool-layout/Makefile.in
+index 75593b8..036f48c 100644
+--- a/scripts/zpool-layout/Makefile.in
++++ b/scripts/zpool-layout/Makefile.in
+@@ -40,6 +40,7 @@ DIST_COMMON = $(dist_pkglibexec_SCRIPTS) $(srcdir)/Makefile.am \
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/udev/Makefile.in b/udev/Makefile.in
+index b4033bb..49ca53a 100644
+--- a/udev/Makefile.in
++++ b/udev/Makefile.in
+@@ -39,6 +39,7 @@ DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/udev/rules.d/Makefile.in b/udev/rules.d/Makefile.in
+index aba04ee..84693ad 100644
+--- a/udev/rules.d/Makefile.in
++++ b/udev/rules.d/Makefile.in
+@@ -40,6 +40,7 @@ DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+ am__aclocal_m4_deps = \
+ $(top_srcdir)/config/always-no-unused-but-set-variable.m4 \
++ $(top_srcdir)/config/kernel-automount.m4 \
+ $(top_srcdir)/config/kernel-bdev-block-device-operations.m4 \
+ $(top_srcdir)/config/kernel-bdev-logical-size.m4 \
+ $(top_srcdir)/config/kernel-bdi-setup-and-register.m4 \
+diff --git a/zfs_config.h.in b/zfs_config.h.in
+index f51bd18..234e4e4 100644
+--- a/zfs_config.h.in
++++ b/zfs_config.h.in
+@@ -9,6 +9,9 @@
+ /* security_inode_init_security wants 6 args */
+ #undef HAVE_6ARGS_SECURITY_INODE_INIT_SECURITY
+
++/* dops->automount() exists */
++#undef HAVE_AUTOMOUNT
++
+ /* struct block_device_operations use bdevs */
+ #undef HAVE_BDEV_BLOCK_DEVICE_OPERATIONS
+
+--
+1.7.5.4
+