]> git.proxmox.com Git - mirror_zfs-debian.git/blobdiff - module/zfs/zfs_ctldir.c
Imported Upstream version 0.6.5.8
[mirror_zfs-debian.git] / module / zfs / zfs_ctldir.c
index 55d1696543dee6938b2cbbc8e0063e56d75f31cb..05d841d4bb17b963fc485e44c6fe121c36e7246a 100644 (file)
@@ -27,6 +27,7 @@
  * Rewritten for Linux by:
  *   Rohan Puri <rohan.puri15@gmail.com>
  *   Brian Behlendorf <behlendorf1@llnl.gov>
+ * Copyright (c) 2013 by Delphix. All rights reserved.
  */
 
 /*
 #include <sys/zfs_vnops.h>
 #include <sys/stat.h>
 #include <sys/dmu.h>
+#include <sys/dmu_objset.h>
+#include <sys/dsl_destroy.h>
 #include <sys/dsl_deleg.h>
 #include <sys/mount.h>
 #include <sys/zpl.h>
 #include "zfs_namecheck.h"
 
+/*
+ * Two AVL trees are maintained which contain all currently automounted
+ * snapshots.  Every automounted snapshots maps to a single zfs_snapentry_t
+ * entry which MUST:
+ *
+ *   - be attached to both trees, and
+ *   - be unique, no duplicate entries are allowed.
+ *
+ * The zfs_snapshots_by_name tree is indexed by the full dataset name
+ * while the zfs_snapshots_by_objsetid tree is indexed by the unique
+ * objsetid.  This allows for fast lookups either by name or objsetid.
+ */
+static avl_tree_t zfs_snapshots_by_name;
+static avl_tree_t zfs_snapshots_by_objsetid;
+static krwlock_t zfs_snapshot_lock;
+
 /*
  * Control Directory Tunables (.zfs)
  */
 int zfs_expire_snapshot = ZFSCTL_EXPIRE_SNAPSHOT;
+int zfs_admin_snapshot = 0;
 
+/*
+ * Dedicated task queue for unmounting snapshots.
+ */
+static taskq_t *zfs_expire_taskq;
+
+typedef struct {
+       char            *se_name;       /* full snapshot name */
+       char            *se_path;       /* full mount path */
+       spa_t           *se_spa;        /* pool spa */
+       uint64_t        se_objsetid;    /* snapshot objset id */
+       struct dentry   *se_root_dentry; /* snapshot root dentry */
+       taskqid_t       se_taskqid;     /* scheduled unmount taskqid */
+       avl_node_t      se_node_name;   /* zfs_snapshots_by_name link */
+       avl_node_t      se_node_objsetid; /* zfs_snapshots_by_objsetid link */
+       refcount_t      se_refcount;    /* reference count */
+} zfs_snapentry_t;
+
+static void zfsctl_snapshot_unmount_delay_impl(zfs_snapentry_t *se, int delay);
+
+/*
+ * Allocate a new zfs_snapentry_t being careful to make a copy of the
+ * the snapshot name and provided mount point.  No reference is taken.
+ */
 static zfs_snapentry_t *
-zfsctl_sep_alloc(void)
+zfsctl_snapshot_alloc(char *full_name, char *full_path, spa_t *spa,
+    uint64_t objsetid, struct dentry *root_dentry)
 {
-       return kmem_zalloc(sizeof (zfs_snapentry_t), KM_SLEEP);
+       zfs_snapentry_t *se;
+
+       se = kmem_zalloc(sizeof (zfs_snapentry_t), KM_SLEEP);
+
+       se->se_name = strdup(full_name);
+       se->se_path = strdup(full_path);
+       se->se_spa = spa;
+       se->se_objsetid = objsetid;
+       se->se_root_dentry = root_dentry;
+       se->se_taskqid = -1;
+
+       refcount_create(&se->se_refcount);
+
+       return (se);
 }
 
-void
-zfsctl_sep_free(zfs_snapentry_t *sep)
+/*
+ * Free a zfs_snapentry_t the called must ensure there are no active
+ * references.
+ */
+static void
+zfsctl_snapshot_free(zfs_snapentry_t *se)
 {
-       kmem_free(sep->se_name, MAXNAMELEN);
-       kmem_free(sep->se_path, PATH_MAX);
-       kmem_free(sep, sizeof (zfs_snapentry_t));
+       refcount_destroy(&se->se_refcount);
+       strfree(se->se_name);
+       strfree(se->se_path);
+
+       kmem_free(se, 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.
+ * Hold a reference on the zfs_snapentry_t.
  */
 static void
-zfsctl_expire_snapshot(void *data)
+zfsctl_snapshot_hold(zfs_snapentry_t *se)
 {
-       zfs_snapentry_t *sep;
-       zfs_sb_t *zsb;
-       int error;
+       refcount_add(&se->se_refcount, NULL);
+}
+
+/*
+ * Release a reference on the zfs_snapentry_t.  When the number of
+ * references drops to zero the structure will be freed.
+ */
+static void
+zfsctl_snapshot_rele(zfs_snapentry_t *se)
+{
+       if (refcount_remove(&se->se_refcount, NULL) == 0)
+               zfsctl_snapshot_free(se);
+}
 
-       sep = spl_get_work_data(data, zfs_snapentry_t, se_work.work);
-       zsb = ITOZSB(sep->se_inode);
+/*
+ * Add a zfs_snapentry_t to both the zfs_snapshots_by_name and
+ * zfs_snapshots_by_objsetid trees.  While the zfs_snapentry_t is part
+ * of the trees a reference is held.
+ */
+static void
+zfsctl_snapshot_add(zfs_snapentry_t *se)
+{
+       ASSERT(RW_WRITE_HELD(&zfs_snapshot_lock));
+       refcount_add(&se->se_refcount, NULL);
+       avl_add(&zfs_snapshots_by_name, se);
+       avl_add(&zfs_snapshots_by_objsetid, se);
+}
 
-       error = zfsctl_unmount_snapshot(zsb, sep->se_name, MNT_EXPIRE);
-       if (error == EBUSY)
-               schedule_delayed_work(&sep->se_work, zfs_expire_snapshot * HZ);
+/*
+ * Remove a zfs_snapentry_t from both the zfs_snapshots_by_name and
+ * zfs_snapshots_by_objsetid trees.  Upon removal a reference is dropped,
+ * this can result in the structure being freed if that was the last
+ * remaining reference.
+ */
+static void
+zfsctl_snapshot_remove(zfs_snapentry_t *se)
+{
+       ASSERT(RW_WRITE_HELD(&zfs_snapshot_lock));
+       avl_remove(&zfs_snapshots_by_name, se);
+       avl_remove(&zfs_snapshots_by_objsetid, se);
+       zfsctl_snapshot_rele(se);
 }
 
-int
-snapentry_compare(const void *a, const void *b)
+/*
+ * Snapshot name comparison function for the zfs_snapshots_by_name.
+ */
+static int
+snapentry_compare_by_name(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);
+       const zfs_snapentry_t *se_a = a;
+       const zfs_snapentry_t *se_b = b;
+       int ret;
+
+       ret = strcmp(se_a->se_name, se_b->se_name);
 
        if (ret < 0)
                return (-1);
@@ -139,12 +236,212 @@ snapentry_compare(const void *a, const void *b)
                return (0);
 }
 
+/*
+ * Snapshot name comparison function for the zfs_snapshots_by_objsetid.
+ */
+static int
+snapentry_compare_by_objsetid(const void *a, const void *b)
+{
+       const zfs_snapentry_t *se_a = a;
+       const zfs_snapentry_t *se_b = b;
+
+       if (se_a->se_spa != se_b->se_spa)
+               return ((ulong_t)se_a->se_spa < (ulong_t)se_b->se_spa ? -1 : 1);
+
+       if (se_a->se_objsetid < se_b->se_objsetid)
+               return (-1);
+       else if (se_a->se_objsetid > se_b->se_objsetid)
+               return (1);
+       else
+               return (0);
+}
+
+/*
+ * Find a zfs_snapentry_t in zfs_snapshots_by_name.  If the snapname
+ * is found a pointer to the zfs_snapentry_t is returned and a reference
+ * taken on the structure.  The caller is responsible for dropping the
+ * reference with zfsctl_snapshot_rele().  If the snapname is not found
+ * NULL will be returned.
+ */
+static zfs_snapentry_t *
+zfsctl_snapshot_find_by_name(char *snapname)
+{
+       zfs_snapentry_t *se, search;
+
+       ASSERT(RW_LOCK_HELD(&zfs_snapshot_lock));
+
+       search.se_name = snapname;
+       se = avl_find(&zfs_snapshots_by_name, &search, NULL);
+       if (se)
+               refcount_add(&se->se_refcount, NULL);
+
+       return (se);
+}
+
+/*
+ * Find a zfs_snapentry_t in zfs_snapshots_by_objsetid given the objset id
+ * rather than the snapname.  In all other respects it behaves the same
+ * as zfsctl_snapshot_find_by_name().
+ */
+static zfs_snapentry_t *
+zfsctl_snapshot_find_by_objsetid(spa_t *spa, uint64_t objsetid)
+{
+       zfs_snapentry_t *se, search;
+
+       ASSERT(RW_LOCK_HELD(&zfs_snapshot_lock));
+
+       search.se_spa = spa;
+       search.se_objsetid = objsetid;
+       se = avl_find(&zfs_snapshots_by_objsetid, &search, NULL);
+       if (se)
+               refcount_add(&se->se_refcount, NULL);
+
+       return (se);
+}
+
+/*
+ * Rename a zfs_snapentry_t in the zfs_snapshots_by_name.  The structure is
+ * removed, renamed, and added back to the new correct location in the tree.
+ */
+static int
+zfsctl_snapshot_rename(char *old_snapname, char *new_snapname)
+{
+       zfs_snapentry_t *se;
+
+       ASSERT(RW_WRITE_HELD(&zfs_snapshot_lock));
+
+       se = zfsctl_snapshot_find_by_name(old_snapname);
+       if (se == NULL)
+               return (ENOENT);
+
+       zfsctl_snapshot_remove(se);
+       strfree(se->se_name);
+       se->se_name = strdup(new_snapname);
+       zfsctl_snapshot_add(se);
+       zfsctl_snapshot_rele(se);
+
+       return (0);
+}
+
+/*
+ * Delayed task responsible for unmounting an expired automounted snapshot.
+ */
+static void
+snapentry_expire(void *data)
+{
+       zfs_snapentry_t *se = (zfs_snapentry_t *)data;
+       spa_t *spa = se->se_spa;
+       uint64_t objsetid = se->se_objsetid;
+
+       if (zfs_expire_snapshot <= 0) {
+               zfsctl_snapshot_rele(se);
+               return;
+       }
+
+       se->se_taskqid = -1;
+       (void) zfsctl_snapshot_unmount(se->se_name, MNT_EXPIRE);
+       zfsctl_snapshot_rele(se);
+
+       /*
+        * Reschedule the unmount if the zfs_snapentry_t wasn't removed.
+        * This can occur when the snapshot is busy.
+        */
+       rw_enter(&zfs_snapshot_lock, RW_READER);
+       if ((se = zfsctl_snapshot_find_by_objsetid(spa, objsetid)) != NULL) {
+               zfsctl_snapshot_unmount_delay_impl(se, zfs_expire_snapshot);
+               zfsctl_snapshot_rele(se);
+       }
+       rw_exit(&zfs_snapshot_lock);
+}
+
+/*
+ * Cancel an automatic unmount of a snapname.  This callback is responsible
+ * for dropping the reference on the zfs_snapentry_t which was taken when
+ * during dispatch.
+ */
+static void
+zfsctl_snapshot_unmount_cancel(zfs_snapentry_t *se)
+{
+       ASSERT(RW_LOCK_HELD(&zfs_snapshot_lock));
+
+       if (taskq_cancel_id(zfs_expire_taskq, se->se_taskqid) == 0) {
+               se->se_taskqid = -1;
+               zfsctl_snapshot_rele(se);
+       }
+}
+
+/*
+ * Dispatch the unmount task for delayed handling with a hold protecting it.
+ */
+static void
+zfsctl_snapshot_unmount_delay_impl(zfs_snapentry_t *se, int delay)
+{
+       ASSERT3S(se->se_taskqid, ==, -1);
+
+       if (delay <= 0)
+               return;
+
+       zfsctl_snapshot_hold(se);
+       se->se_taskqid = taskq_dispatch_delay(zfs_expire_taskq,
+           snapentry_expire, se, TQ_SLEEP, ddi_get_lbolt() + delay * HZ);
+}
+
+/*
+ * Schedule an automatic unmount of objset id to occur in delay seconds from
+ * now.  Any previous delayed unmount will be cancelled in favor of the
+ * updated deadline.  A reference is taken by zfsctl_snapshot_find_by_name()
+ * and held until the outstanding task is handled or cancelled.
+ */
+int
+zfsctl_snapshot_unmount_delay(spa_t *spa, uint64_t objsetid, int delay)
+{
+       zfs_snapentry_t *se;
+       int error = ENOENT;
+
+       rw_enter(&zfs_snapshot_lock, RW_READER);
+       if ((se = zfsctl_snapshot_find_by_objsetid(spa, objsetid)) != NULL) {
+               zfsctl_snapshot_unmount_cancel(se);
+               zfsctl_snapshot_unmount_delay_impl(se, delay);
+               zfsctl_snapshot_rele(se);
+               error = 0;
+       }
+       rw_exit(&zfs_snapshot_lock);
+
+       return (error);
+}
+
+/*
+ * Check if snapname is currently mounted.  Returned non-zero when mounted
+ * and zero when unmounted.
+ */
+static boolean_t
+zfsctl_snapshot_ismounted(char *snapname)
+{
+       zfs_snapentry_t *se;
+       boolean_t ismounted = B_FALSE;
+
+       rw_enter(&zfs_snapshot_lock, RW_READER);
+       if ((se = zfsctl_snapshot_find_by_name(snapname)) != NULL) {
+               zfsctl_snapshot_rele(se);
+               ismounted = B_TRUE;
+       }
+       rw_exit(&zfs_snapshot_lock);
+
+       return (ismounted);
+}
+
+/*
+ * Check if the given inode is a part of the virtual .zfs directory.
+ */
 boolean_t
 zfsctl_is_node(struct inode *ip)
 {
        return (ITOZ(ip)->z_is_ctldir);
 }
 
+/*
+ * Check if the given inode is a .zfs/snapshots/snapname directory.
+ */
 boolean_t
 zfsctl_is_snapdir(struct inode *ip)
 {
@@ -189,14 +486,14 @@ zfsctl_inode_alloc(zfs_sb_t *zsb, uint64_t id,
        zp->z_gid = 0;
        zp->z_mode = 0;
        zp->z_sync_cnt = 0;
-       zp->z_is_zvol = B_FALSE;
        zp->z_is_mapped = B_FALSE;
        zp->z_is_ctldir = B_TRUE;
        zp->z_is_sa = B_FALSE;
+       zp->z_is_stale = B_FALSE;
        ip->i_ino = id;
        ip->i_mode = (S_IFDIR | S_IRUGO | S_IXUGO);
-       ip->i_uid = 0;
-       ip->i_gid = 0;
+       ip->i_uid = SUID_TO_KUID(0);
+       ip->i_gid = SGID_TO_KGID(0);
        ip->i_blkbits = SPA_MINBLOCKSHIFT;
        ip->i_atime = now;
        ip->i_mtime = now;
@@ -242,25 +539,6 @@ zfsctl_inode_lookup(zfs_sb_t *zsb, uint64_t id,
        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()
@@ -280,22 +558,38 @@ zfsctl_create(zfs_sb_t *zsb)
        zsb->z_ctldir = zfsctl_inode_alloc(zsb, ZFSCTL_INO_ROOT,
            &zpl_fops_root, &zpl_ops_root);
        if (zsb->z_ctldir == NULL)
-               return (ENOENT);
+               return (SET_ERROR(ENOENT));
 
        return (0);
 #else
-       return (EOPNOTSUPP);
+       return (SET_ERROR(EOPNOTSUPP));
 #endif /* CONFIG_64BIT */
 }
 
 /*
- * Destroy the '.zfs' directory.  Only called when the filesystem is unmounted.
+ * Destroy the '.zfs' directory or remove a snapshot from zfs_snapshots_by_name.
+ * Only called when the filesystem is unmounted.
  */
 void
 zfsctl_destroy(zfs_sb_t *zsb)
 {
-       iput(zsb->z_ctldir);
-       zsb->z_ctldir = NULL;
+       if (zsb->z_issnap) {
+               zfs_snapentry_t *se;
+               spa_t *spa = zsb->z_os->os_spa;
+               uint64_t objsetid = dmu_objset_id(zsb->z_os);
+
+               rw_enter(&zfs_snapshot_lock, RW_WRITER);
+               if ((se = zfsctl_snapshot_find_by_objsetid(spa, objsetid))
+                   != NULL) {
+                       zfsctl_snapshot_unmount_cancel(se);
+                       zfsctl_snapshot_remove(se);
+                       zfsctl_snapshot_rele(se);
+               }
+               rw_exit(&zfs_snapshot_lock);
+       } else if (zsb->z_ctldir) {
+               iput(zsb->z_ctldir);
+               zsb->z_ctldir = NULL;
+       }
 }
 
 /*
@@ -309,8 +603,45 @@ zfsctl_root(znode_t *zp)
        igrab(ZTOZSB(zp)->z_ctldir);
        return (ZTOZSB(zp)->z_ctldir);
 }
+/*
+ * Generate a long fid which includes the root object and objset of a
+ * snapshot but not the generation number.  For the root object the
+ * generation number is ignored when zero to avoid needing to open
+ * the dataset when generating fids for the snapshot names.
+ */
+static int
+zfsctl_snapdir_fid(struct inode *ip, fid_t *fidp)
+{
+       zfs_sb_t *zsb = ITOZSB(ip);
+       zfid_short_t *zfid = (zfid_short_t *)fidp;
+       zfid_long_t *zlfid = (zfid_long_t *)fidp;
+       uint32_t gen = 0;
+       uint64_t object;
+       uint64_t objsetid;
+       int i;
 
-/*ARGSUSED*/
+       object = zsb->z_root;
+       objsetid = ZFSCTL_INO_SNAPDIRS - ip->i_ino;
+       zfid->zf_len = LONG_FID_LEN;
+
+       for (i = 0; i < sizeof (zfid->zf_object); i++)
+               zfid->zf_object[i] = (uint8_t)(object >> (8 * i));
+
+       for (i = 0; i < sizeof (zfid->zf_gen); i++)
+               zfid->zf_gen[i] = (uint8_t)(gen >> (8 * i));
+
+       for (i = 0; i < sizeof (zlfid->zf_setid); i++)
+               zlfid->zf_setid[i] = (uint8_t)(objsetid >> (8 * i));
+
+       for (i = 0; i < sizeof (zlfid->zf_setgen); i++)
+               zlfid->zf_setgen[i] = 0;
+
+       return (0);
+}
+
+/*
+ * Generate an appropriate fid for an entry in the .zfs directory.
+ */
 int
 zfsctl_fid(struct inode *ip, fid_t *fidp)
 {
@@ -325,7 +656,12 @@ zfsctl_fid(struct inode *ip, fid_t *fidp)
        if (fidp->fid_len < SHORT_FID_LEN) {
                fidp->fid_len = SHORT_FID_LEN;
                ZFS_EXIT(zsb);
-               return (ENOSPC);
+               return (SET_ERROR(ENOSPC));
+       }
+
+       if (zfsctl_is_snapdir(ip)) {
+               ZFS_EXIT(zsb);
+               return (zfsctl_snapdir_fid(ip, fidp));
        }
 
        zfid = (zfid_short_t *)fidp;
@@ -343,26 +679,33 @@ zfsctl_fid(struct inode *ip, fid_t *fidp)
        return (0);
 }
 
+/*
+ * Construct a full dataset name in full_name: "pool/dataset@snap_name"
+ */
 static int
-zfsctl_snapshot_zname(struct inode *ip, const char *name, int len, char *zname)
+zfsctl_snapshot_name(zfs_sb_t *zsb, const char *snap_name, int len,
+    char *full_name)
 {
-       objset_t *os = ITOZSB(ip)->z_os;
+       objset_t *os = zsb->z_os;
 
-       if (snapshot_namecheck(name, NULL, NULL) != 0)
-               return (EILSEQ);
+       if (zfs_component_namecheck(snap_name, NULL, NULL) != 0)
+               return (SET_ERROR(EILSEQ));
 
-       dmu_objset_name(os, zname);
-       if ((strlen(zname) + 1 + strlen(name)) >= len)
-               return (ENAMETOOLONG);
+       dmu_objset_name(os, full_name);
+       if ((strlen(full_name) + 1 + strlen(snap_name)) >= len)
+               return (SET_ERROR(ENAMETOOLONG));
 
-       (void) strcat(zname, "@");
-       (void) strcat(zname, name);
+       (void) strcat(full_name, "@");
+       (void) strcat(full_name, snap_name);
 
        return (0);
 }
 
+/*
+ * Returns full path in full_path: "/pool/dataset/.zfs/snapshot/snap_name/"
+ */
 static int
-zfsctl_snapshot_zpath(struct path *path, int len, char *zpath)
+zfsctl_snapshot_path(struct path *path, int len, char *full_path)
 {
        char *path_buffer, *path_ptr;
        int path_len, error = 0;
@@ -377,22 +720,63 @@ zfsctl_snapshot_zpath(struct path *path, int len, char *zpath)
 
        path_len = path_buffer + len - 1 - path_ptr;
        if (path_len > len) {
-               error = EFAULT;
+               error = SET_ERROR(EFAULT);
                goto out;
        }
 
-       memcpy(zpath, path_ptr, path_len);
-       zpath[path_len] = '\0';
+       memcpy(full_path, path_ptr, path_len);
+       full_path[path_len] = '\0';
 out:
        kmem_free(path_buffer, len);
 
        return (error);
 }
 
+/*
+ * Returns full path in full_path: "/pool/dataset/.zfs/snapshot/snap_name/"
+ */
+static int
+zfsctl_snapshot_path_objset(zfs_sb_t *zsb, uint64_t objsetid,
+    int path_len, char *full_path)
+{
+       objset_t *os = zsb->z_os;
+       fstrans_cookie_t cookie;
+       char *snapname;
+       boolean_t case_conflict;
+       uint64_t id, pos = 0;
+       int error = 0;
+
+       if (zsb->z_mntopts->z_mntpoint == NULL)
+               return (ENOENT);
+
+       cookie = spl_fstrans_mark();
+       snapname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
+
+       while (error == 0) {
+               dsl_pool_config_enter(dmu_objset_pool(os), FTAG);
+               error = dmu_snapshot_list_next(zsb->z_os, MAXNAMELEN,
+                   snapname, &id, &pos, &case_conflict);
+               dsl_pool_config_exit(dmu_objset_pool(os), FTAG);
+               if (error)
+                       goto out;
+
+               if (id == objsetid)
+                       break;
+       }
+
+       memset(full_path, 0, path_len);
+       snprintf(full_path, path_len - 1, "%s/.zfs/snapshot/%s",
+           zsb->z_mntopts->z_mntpoint, snapname);
+out:
+       kmem_free(snapname, MAXNAMELEN);
+       spl_fstrans_unmark(cookie);
+
+       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)
@@ -415,7 +799,7 @@ zfsctl_root_lookup(struct inode *dip, char *name, struct inode **ipp,
        }
 
        if (*ipp == NULL)
-               error = ENOENT;
+               error = SET_ERROR(ENOENT);
 
        ZFS_EXIT(zsb);
 
@@ -427,7 +811,6 @@ zfsctl_root_lookup(struct inode *dip, char *name, struct inode **ipp,
  * 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)
@@ -438,7 +821,7 @@ zfsctl_snapdir_lookup(struct inode *dip, char *name, struct inode **ipp,
 
        ZFS_ENTER(zsb);
 
-       error = dmu_snapshot_id(zsb->z_os, name, &id);
+       error = dmu_snapshot_lookup(zsb->z_os, name, &id);
        if (error) {
                ZFS_EXIT(zsb);
                return (error);
@@ -446,109 +829,85 @@ zfsctl_snapdir_lookup(struct inode *dip, char *name, struct inode **ipp,
 
        *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;
-       }
+       if (*ipp == NULL)
+               error = SET_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)
+zfsctl_snapdir_rename(struct inode *sdip, char *snm,
+    struct inode *tdip, char *tnm, cred_t *cr, int flags)
 {
        zfs_sb_t *zsb = ITOZSB(sdip);
-       zfs_snapentry_t search, *sep;
-       avl_index_t where;
-       char *to, *from, *real;
+       char *to, *from, *real, *fsname;
        int error;
 
+       if (!zfs_admin_snapshot)
+               return (EACCES);
+
        ZFS_ENTER(zsb);
 
        to = kmem_alloc(MAXNAMELEN, KM_SLEEP);
        from = kmem_alloc(MAXNAMELEN, KM_SLEEP);
        real = kmem_alloc(MAXNAMELEN, KM_SLEEP);
+       fsname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
 
        if (zsb->z_case == ZFS_CASE_INSENSITIVE) {
-               error = dmu_snapshot_realname(zsb->z_os, sname, real,
+               error = dmu_snapshot_realname(zsb->z_os, snm, real,
                    MAXNAMELEN, NULL);
                if (error == 0) {
-                       sname = real;
+                       snm = 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)
+       dmu_objset_name(zsb->z_os, fsname);
+
+       error = zfsctl_snapshot_name(ITOZSB(sdip), snm, MAXNAMELEN, from);
+       if (error == 0)
+               error = zfsctl_snapshot_name(ITOZSB(tdip), tnm, MAXNAMELEN, to);
+       if (error == 0)
                error = zfs_secpolicy_rename_perms(from, to, cr);
-       if (error)
+       if (error != 0)
                goto out;
 
        /*
         * Cannot move snapshots out of the snapdir.
         */
        if (sdip != tdip) {
-               error = EINVAL;
+               error = SET_ERROR(EINVAL);
                goto out;
        }
 
        /*
         * No-op when names are identical.
         */
-       if (strcmp(sname, tname) == 0) {
+       if (strcmp(snm, tnm) == 0) {
                error = 0;
                goto out;
        }
 
-       mutex_enter(&zsb->z_ctldir_lock);
+       rw_enter(&zfs_snapshot_lock, RW_WRITER);
 
-       error = dmu_objset_rename(from, to, B_FALSE);
-       if (error)
-               goto out_unlock;
+       error = dsl_dataset_rename_snapshot(fsname, snm, tnm, B_FALSE);
+       if (error == 0)
+               (void) zfsctl_snapshot_rename(snm, tnm);
 
-       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);
+       rw_exit(&zfs_snapshot_lock);
 out:
        kmem_free(from, MAXNAMELEN);
        kmem_free(to, MAXNAMELEN);
        kmem_free(real, MAXNAMELEN);
+       kmem_free(fsname, MAXNAMELEN);
 
        ZFS_EXIT(zsb);
 
@@ -559,7 +918,6 @@ out:
  * 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)
 {
@@ -567,6 +925,9 @@ zfsctl_snapdir_remove(struct inode *dip, char *name, cred_t *cr, int flags)
        char *snapname, *real;
        int error;
 
+       if (!zfs_admin_snapshot)
+               return (EACCES);
+
        ZFS_ENTER(zsb);
 
        snapname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
@@ -582,15 +943,15 @@ zfsctl_snapdir_remove(struct inode *dip, char *name, cred_t *cr, int flags)
                }
        }
 
-       error = zfsctl_snapshot_zname(dip, name, MAXNAMELEN, snapname);
-       if (!error)
+       error = zfsctl_snapshot_name(ITOZSB(dip), name, MAXNAMELEN, snapname);
+       if (error == 0)
                error = zfs_secpolicy_destroy_perms(snapname, cr);
-       if (error)
+       if (error != 0)
                goto out;
 
-       error = zfsctl_unmount_snapshot(zsb, name, MNT_FORCE);
+       error = zfsctl_snapshot_unmount(snapname, MNT_FORCE);
        if ((error == 0) || (error == ENOENT))
-               error = dmu_objset_destroy(snapname, B_FALSE);
+               error = dsl_destroy_snapshot(snapname, B_FALSE);
 out:
        kmem_free(snapname, MAXNAMELEN);
        kmem_free(real, MAXNAMELEN);
@@ -604,7 +965,6 @@ out:
  * 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)
@@ -613,23 +973,25 @@ zfsctl_snapdir_mkdir(struct inode *dip, char *dirname, vattr_t *vap,
        char *dsname;
        int error;
 
+       if (!zfs_admin_snapshot)
+               return (EACCES);
+
        dsname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
 
-       if (snapshot_namecheck(dirname, NULL, NULL) != 0) {
-               error = EILSEQ;
+       if (zfs_component_namecheck(dirname, NULL, NULL) != 0) {
+               error = SET_ERROR(EILSEQ);
                goto out;
        }
 
        dmu_objset_name(zsb->z_os, dsname);
 
        error = zfs_secpolicy_snapshot_perms(dsname, cr);
-       if (error)
+       if (error != 0)
                goto out;
 
        if (error == 0) {
-               error = dmu_objset_snapshot(dsname, dirname,
-                   NULL, NULL, B_FALSE, B_FALSE, -1);
-               if (error)
+               error = dmu_objset_snapshot_one(dsname, dirname);
+               if (error != 0)
                        goto out;
 
                error = zfsctl_snapdir_lookup(dip, dirname, ipp,
@@ -641,221 +1003,159 @@ out:
        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 \
+#define        SET_UNMOUNT_CMD \
        "exec 0</dev/null " \
        "     1>/dev/null " \
        "     2>/dev/null; " \
-       "umount -t zfs -n %s%s"
+       "umount -t zfs -n %s'%s'"
 
-static int
-__zfsctl_unmount_snapshot(zfs_snapentry_t *sep, int flags)
+int
+zfsctl_snapshot_unmount(char *snapname, int flags)
 {
        char *argv[] = { "/bin/sh", "-c", NULL, NULL };
        char *envp[] = { NULL };
+       zfs_snapentry_t *se;
        int error;
 
+       rw_enter(&zfs_snapshot_lock, RW_READER);
+       if ((se = zfsctl_snapshot_find_by_name(snapname)) == NULL) {
+               rw_exit(&zfs_snapshot_lock);
+               return (ENOENT);
+       }
+       rw_exit(&zfs_snapshot_lock);
+
        argv[2] = kmem_asprintf(SET_UNMOUNT_CMD,
-           flags & MNT_FORCE ? "-f " : "", sep->se_path);
-       error = call_usermodehelper(argv[0], argv, envp, 1);
+           flags & MNT_FORCE ? "-f " : "", se->se_path);
+       zfsctl_snapshot_rele(se);
+       dprintf("unmount; path=%s\n", se->se_path);
+       error = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
        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);
+               error = SET_ERROR(EBUSY);
 
        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 \
+#define        MOUNT_BUSY 0x80         /* Mount failed due to EBUSY (from mntent.h) */
+#define        SET_MOUNT_CMD \
        "exec 0</dev/null " \
        "     1>/dev/null " \
        "     2>/dev/null; " \
-       "mount -t zfs -n %s %s"
+       "mount -t zfs -n '%s' '%s'"
 
 int
-zfsctl_mount_snapshot(struct path *path, int flags)
+zfsctl_snapshot_mount(struct path *path, int flags)
 {
        struct dentry *dentry = path->dentry;
        struct inode *ip = dentry->d_inode;
-       zfs_sb_t *zsb = ITOZSB(ip);
+       zfs_sb_t *zsb;
+       zfs_sb_t *snap_zsb;
+       zfs_snapentry_t *se;
        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;
+       struct path spath;
+
+       if (ip == NULL)
+               return (EISDIR);
 
+       zsb = ITOZSB(ip);
        ZFS_ENTER(zsb);
 
        full_name = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
-       full_path = kmem_zalloc(PATH_MAX, KM_SLEEP);
+       full_path = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
 
-       error = zfsctl_snapshot_zname(ip, dname(dentry), MAXNAMELEN, full_name);
+       error = zfsctl_snapshot_name(zsb, dname(dentry),
+           MAXNAMELEN, full_name);
        if (error)
                goto error;
 
-       error = zfsctl_snapshot_zpath(path, PATH_MAX, full_path);
+       error = zfsctl_snapshot_path(path, MAXPATHLEN, full_path);
        if (error)
                goto error;
 
+       /*
+        * Multiple concurrent automounts of a snapshot are never allowed.
+        * The snapshot may be manually mounted as many times as desired.
+        */
+       if (zfsctl_snapshot_ismounted(full_name)) {
+               error = 0;
+               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.
+        *
+        * If the user mode helper happens to return EBUSY, a concurrent
+        * mount is already in progress in which case the error is ignored.
+        * Take note that if the program was executed successfully the return
+        * value from call_usermodehelper() will be (exitcode << 8 + signal).
         */
+       dprintf("mount; name=%s path=%s\n", full_name, full_path);
        argv[2] = kmem_asprintf(SET_MOUNT_CMD, full_name, full_path);
-       error = call_usermodehelper(argv[0], argv, envp, 1);
+       error = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
        strfree(argv[2]);
        if (error) {
-               printk("ZFS: Unable to automount %s at %s: %d\n",
-                   full_name, full_path, error);
-               error = EISDIR;
+               if (!(error & MOUNT_BUSY << 8)) {
+                       cmn_err(CE_WARN, "Unable to automount %s/%s: %d",
+                           full_path, full_name, error);
+                       error = SET_ERROR(EISDIR);
+               } else {
+                       /*
+                        * EBUSY, this could mean a concurrent mount, or the
+                        * snapshot has already been mounted at completely
+                        * different place. We return 0 so VFS will retry. For
+                        * the latter case the VFS will retry several times
+                        * and return ELOOP, which is probably not a very good
+                        * behavior.
+                        */
+                       error = 0;
+               }
                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.
+        * Follow down in to the mounted snapshot and set MNT_SHRINKABLE
+        * to identify this as an automounted filesystem.
         */
-       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);
+       spath = *path;
+       path_get(&spath);
+       if (zpl_follow_down_one(&spath)) {
+               snap_zsb = ITOZSB(spath.dentry->d_inode);
+               snap_zsb->z_parent = zsb;
+               dentry = spath.dentry;
+               spath.mnt->mnt_flags |= MNT_SHRINKABLE;
+
+               rw_enter(&zfs_snapshot_lock, RW_WRITER);
+               se = zfsctl_snapshot_alloc(full_name, full_path,
+                   snap_zsb->z_os->os_spa, dmu_objset_id(snap_zsb->z_os),
+                   dentry);
+               zfsctl_snapshot_add(se);
+               zfsctl_snapshot_unmount_delay_impl(se, zfs_expire_snapshot);
+               rw_exit(&zfs_snapshot_lock);
        }
-
-       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);
+       path_put(&spath);
 error:
-       if (error) {
-               kmem_free(full_name, MAXNAMELEN);
-               kmem_free(full_path, PATH_MAX);
-       }
+       kmem_free(full_name, MAXNAMELEN);
+       kmem_free(full_path, MAXPATHLEN);
 
        ZFS_EXIT(zsb);
 
@@ -863,82 +1163,73 @@ 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.
+ * Given the objset id of the snapshot return its zfs_sb_t as zsbp.
  */
-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;
+       zfs_snapentry_t *se;
        int error;
-
-       ASSERT(zsb->z_ctldir != NULL);
-
-       mutex_enter(&zsb->z_ctldir_lock);
+       spa_t *spa = ((zfs_sb_t *)(sb->s_fs_info))->z_os->os_spa;
 
        /*
-        * Verify that the snapshot is mounted.
+        * Verify that the snapshot is mounted then 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 zfs_snapshot_lock to prevent the race.
         */
-       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;
+       rw_enter(&zfs_snapshot_lock, RW_READER);
+       if ((se = zfsctl_snapshot_find_by_objsetid(spa, objsetid)) != NULL) {
+               zfs_sb_t *zsb;
+
+               zsb = ITOZSB(se->se_root_dentry->d_inode);
+               ASSERT3U(dmu_objset_id(zsb->z_os), ==, objsetid);
+
+               if (time_after(jiffies, zsb->z_snap_defer_time +
+                   MAX(zfs_expire_snapshot * HZ / 2, HZ))) {
+                       zsb->z_snap_defer_time = jiffies;
+                       zfsctl_snapshot_unmount_cancel(se);
+                       zfsctl_snapshot_unmount_delay_impl(se,
+                           zfs_expire_snapshot);
+               }
 
-               sep = AVL_NEXT(&zsb->z_ctldir_snaps, sep);
+               *zsbp = zsb;
+               zfsctl_snapshot_rele(se);
+               error = SET_ERROR(0);
+       } else {
+               error = SET_ERROR(ENOENT);
        }
+       rw_exit(&zfs_snapshot_lock);
 
-       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 = zpl_sget(&zpl_fs_type, zfsctl_test_super,
-                   zfsctl_set_super, 0, &id);
-               if (IS_ERR(sbp)) {
-                       error = -PTR_ERR(sbp);
-               } else {
-                       *zsbp = sbp->s_fs_info;
-                       deactivate_super(sbp);
+       /*
+        * Automount the snapshot given the objset id by constructing the
+        * full mount point and performing a traversal.
+        */
+       if (error == ENOENT) {
+               struct path path;
+               char *mnt;
+
+               mnt = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+               error = zfsctl_snapshot_path_objset(sb->s_fs_info, objsetid,
+                   MAXPATHLEN, mnt);
+               if (error) {
+                       kmem_free(mnt, MAXPATHLEN);
+                       return (SET_ERROR(error));
                }
-       } else {
-               error = EINVAL;
+
+               error = kern_path(mnt, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
+               if (error == 0) {
+                       *zsbp = ITOZSB(path.dentry->d_inode);
+                       path_put(&path);
+               }
+
+               kmem_free(mnt, MAXPATHLEN);
        }
-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)
@@ -952,7 +1243,7 @@ zfsctl_shares_lookup(struct inode *dip, char *name, struct inode **ipp,
 
        if (zsb->z_shares_dir == 0) {
                ZFS_EXIT(zsb);
-               return (-ENOTSUP);
+               return (SET_ERROR(ENOTSUP));
        }
 
        error = zfs_zget(zsb, zsb->z_shares_dir, &dzp);
@@ -977,6 +1268,16 @@ zfsctl_shares_lookup(struct inode *dip, char *name, struct inode **ipp,
 void
 zfsctl_init(void)
 {
+       avl_create(&zfs_snapshots_by_name, snapentry_compare_by_name,
+           sizeof (zfs_snapentry_t), offsetof(zfs_snapentry_t,
+           se_node_name));
+       avl_create(&zfs_snapshots_by_objsetid, snapentry_compare_by_objsetid,
+           sizeof (zfs_snapentry_t), offsetof(zfs_snapentry_t,
+           se_node_objsetid));
+       rw_init(&zfs_snapshot_lock, NULL, RW_DEFAULT, NULL);
+
+       zfs_expire_taskq = taskq_create("z_unmount", 1, defclsyspri,
+           1, 8, TASKQ_PREPOPULATE);
 }
 
 /*
@@ -986,7 +1287,15 @@ zfsctl_init(void)
 void
 zfsctl_fini(void)
 {
+       taskq_destroy(zfs_expire_taskq);
+
+       avl_destroy(&zfs_snapshots_by_name);
+       avl_destroy(&zfs_snapshots_by_objsetid);
+       rw_destroy(&zfs_snapshot_lock);
 }
 
+module_param(zfs_admin_snapshot, int, 0644);
+MODULE_PARM_DESC(zfs_admin_snapshot, "Enable mkdir/rmdir/mv in .zfs/snapshot");
+
 module_param(zfs_expire_snapshot, int, 0644);
 MODULE_PARM_DESC(zfs_expire_snapshot, "Seconds to expire .zfs/snapshot");