Retry removal of busy minors
[zfs.git] / lib / libzfs / libzfs_dataset.c
index 089be7e..661f9b5 100644 (file)
@@ -21,6 +21,8 @@
 
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2011 by Delphix. All rights reserved.
  */
 
 #include <ctype.h>
 #include <grp.h>
 #include <stddef.h>
 #include <ucred.h>
+#ifdef HAVE_IDMAP
 #include <idmap.h>
 #include <aclutils.h>
 #include <directory.h>
+#endif /* HAVE_IDMAP */
 
 #include <sys/dnode.h>
 #include <sys/spa.h>
@@ -55,6 +59,7 @@
 #include "libzfs_impl.h"
 #include "zfs_deleg.h"
 
+static int zvol_create_link_common(libzfs_handle_t *, const char *, int);
 static int userquota_propname_decode(const char *propname, boolean_t zoned,
     zfs_userquota_prop_t *typep, char *domain, int domainlen, uint64_t *ridp);
 
@@ -72,55 +77,14 @@ zfs_type_to_name(zfs_type_t type)
                return (dgettext(TEXT_DOMAIN, "snapshot"));
        case ZFS_TYPE_VOLUME:
                return (dgettext(TEXT_DOMAIN, "volume"));
+       default:
+               break;
        }
 
        return (NULL);
 }
 
 /*
- * Given a path and mask of ZFS types, return a string describing this dataset.
- * This is used when we fail to open a dataset and we cannot get an exact type.
- * We guess what the type would have been based on the path and the mask of
- * acceptable types.
- */
-static const char *
-path_to_str(const char *path, int types)
-{
-       /*
-        * When given a single type, always report the exact type.
-        */
-       if (types == ZFS_TYPE_SNAPSHOT)
-               return (dgettext(TEXT_DOMAIN, "snapshot"));
-       if (types == ZFS_TYPE_FILESYSTEM)
-               return (dgettext(TEXT_DOMAIN, "filesystem"));
-       if (types == ZFS_TYPE_VOLUME)
-               return (dgettext(TEXT_DOMAIN, "volume"));
-
-       /*
-        * The user is requesting more than one type of dataset.  If this is the
-        * case, consult the path itself.  If we're looking for a snapshot, and
-        * a '@' is found, then report it as "snapshot".  Otherwise, remove the
-        * snapshot attribute and try again.
-        */
-       if (types & ZFS_TYPE_SNAPSHOT) {
-               if (strchr(path, '@') != NULL)
-                       return (dgettext(TEXT_DOMAIN, "snapshot"));
-               return (path_to_str(path, types & ~ZFS_TYPE_SNAPSHOT));
-       }
-
-       /*
-        * The user has requested either filesystems or volumes.
-        * We have no way of knowing a priori what type this would be, so always
-        * report it as "filesystem" or "volume", our two primitive types.
-        */
-       if (types & ZFS_TYPE_FILESYSTEM)
-               return (dgettext(TEXT_DOMAIN, "filesystem"));
-
-       assert(types & ZFS_TYPE_VOLUME);
-       return (dgettext(TEXT_DOMAIN, "volume"));
-}
-
-/*
  * Validate a ZFS path.  This is used even before trying to open the dataset, to
  * provide a more meaningful error message.  We call zfs_error_aux() to
  * explain exactly why the name was not valid.
@@ -132,6 +96,7 @@ zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
        namecheck_err_t why;
        char what;
 
+       (void) zfs_prop_get_table();
        if (dataset_namecheck(path, &why, &what) != 0) {
                if (hdl != NULL) {
                        switch (why) {
@@ -180,6 +145,8 @@ zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                                    "reserved disk name"));
                                break;
+                       default:
+                               break;
                        }
                }
 
@@ -446,6 +413,9 @@ make_dataset_handle_common(zfs_handle_t *zhp, zfs_cmd_t *zc)
                zhp->zfs_head_type = ZFS_TYPE_VOLUME;
        else if (zhp->zfs_dmustats.dds_type == DMU_OST_ZFS)
                zhp->zfs_head_type = ZFS_TYPE_FILESYSTEM;
+       else if (zhp->zfs_dmustats.dds_type == DMU_OST_OTHER)
+               return (-1); /* zpios' and other testing datasets are
+                               of this type, ignore if encountered */
        else
                abort();
 
@@ -619,7 +589,7 @@ libzfs_mnttab_fini(libzfs_handle_t *hdl)
        void *cookie = NULL;
        mnttab_node_t *mtn;
 
-       while (mtn = avl_destroy_nodes(&hdl->libzfs_mnttab_cache, &cookie)) {
+       while ((mtn = avl_destroy_nodes(&hdl->libzfs_mnttab_cache, &cookie))) {
                free(mtn->mtn_mt.mnt_special);
                free(mtn->mtn_mt.mnt_mountp);
                free(mtn->mtn_mt.mnt_fstype);
@@ -691,7 +661,7 @@ libzfs_mnttab_remove(libzfs_handle_t *hdl, const char *fsname)
        mnttab_node_t *ret;
 
        find.mtn_mt.mnt_special = (char *)fsname;
-       if (ret = avl_find(&hdl->libzfs_mnttab_cache, (void *)&find, NULL)) {
+       if ((ret = avl_find(&hdl->libzfs_mnttab_cache, (void *)&find, NULL))) {
                avl_remove(&hdl->libzfs_mnttab_cache, ret);
                free(ret->mtn_mt.mnt_special);
                free(ret->mtn_mt.mnt_mountp);
@@ -941,6 +911,7 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
 
                case ZFS_PROP_MLSLABEL:
                {
+#ifdef HAVE_MLSLABEL
                        /*
                         * Verify the mlslabel string and convert to
                         * internal hex label string.
@@ -990,7 +961,12 @@ badlabel:
                        (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
                        m_label_free(new_sl);   /* OK if null */
                        goto error;
-
+#else
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "mlslabels are unsupported"));
+                       (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+                       goto error;
+#endif /* HAVE_MLSLABEL */
                }
 
                case ZFS_PROP_MOUNTPOINT:
@@ -1015,6 +991,8 @@ badlabel:
                                            "component of '%s' is too long"),
                                            propname);
                                        break;
+                               default:
+                                       break;
                                }
                                (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
                                goto error;
@@ -1139,6 +1117,8 @@ badlabel:
                case ZFS_PROP_NORMALIZE:
                        chosen_normal = (int)intval;
                        break;
+               default:
+                       break;
                }
 
                /*
@@ -1187,6 +1167,8 @@ badlabel:
                                        goto error;
                                }
                                break;
+                       default:
+                               break;
                        }
                }
        }
@@ -1337,6 +1319,25 @@ zfs_setprop_error(libzfs_handle_t *hdl, zfs_prop_t prop, int err,
        }
 }
 
+static boolean_t
+zfs_is_namespace_prop(zfs_prop_t prop)
+{
+       switch (prop) {
+
+       case ZFS_PROP_ATIME:
+       case ZFS_PROP_DEVICES:
+       case ZFS_PROP_EXEC:
+       case ZFS_PROP_SETUID:
+       case ZFS_PROP_READONLY:
+       case ZFS_PROP_XATTR:
+       case ZFS_PROP_NBMAND:
+               return (B_TRUE);
+
+       default:
+               return (B_FALSE);
+       }
+}
+
 /*
  * Given a property name and value, set the property for the given dataset.
  */
@@ -1352,7 +1353,7 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval)
        zfs_prop_t prop;
        boolean_t do_prefix;
        uint64_t idx;
-       int added_resv;
+       int added_resv = 0;
 
        (void) snprintf(errbuf, sizeof (errbuf),
            dgettext(TEXT_DOMAIN, "cannot set property for '%s'"),
@@ -1432,12 +1433,22 @@ zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval)
                if (do_prefix)
                        ret = changelist_postfix(cl);
 
-               /*
-                * Refresh the statistics so the new property value
-                * is reflected.
-                */
-               if (ret == 0)
+               if (ret == 0) {
+                       /*
+                        * Refresh the statistics so the new property
+                        * value is reflected.
+                        */
                        (void) get_stats(zhp);
+
+                       /*
+                        * Remount the filesystem to propagate the change
+                        * if one of the options handled by the generic
+                        * Linux namespace layer has been modified.
+                        */
+                       if (zfs_is_namespace_prop(prop) &&
+                           zfs_is_mounted(zhp, NULL))
+                               ret = zfs_mount(zhp, MNTOPT_REMOUNT, 0);
+               }
        }
 
 error:
@@ -1554,7 +1565,7 @@ error:
  * True DSL properties are stored in an nvlist.  The following two functions
  * extract them appropriately.
  */
-static uint64_t
+uint64_t
 getprop_uint64(zfs_handle_t *zhp, zfs_prop_t prop, char **source)
 {
        nvlist_t *nv;
@@ -1622,7 +1633,7 @@ zfs_unset_recvd_props_mode(zfs_handle_t *zhp, uint64_t *cookie)
  * zfs_prop_get_int() are built using this interface.
  *
  * Certain properties can be overridden using 'mount -o'.  In this case, scan
- * the contents of the /etc/mnttab entry, searching for the appropriate options.
+ * the contents of the /etc/mtab entry, searching for the appropriate options.
  * If they differ from the on-disk values, report the current values and mark
  * the source "temporary".
  */
@@ -1674,11 +1685,13 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src,
                mntopt_on = MNTOPT_NBMAND;
                mntopt_off = MNTOPT_NONBMAND;
                break;
+       default:
+               break;
        }
 
        /*
         * Because looking up the mount options is potentially expensive
-        * (iterating over all of /etc/mnttab), we defer its calculation until
+        * (iterating over all of /etc/mtab), we defer its calculation until
         * we're looking up a property which requires its presence.
         */
        if (!zhp->zfs_mntcheck &&
@@ -2018,6 +2031,7 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                }
                break;
 
+       case ZFS_PROP_REFRATIO:
        case ZFS_PROP_COMPRESSRATIO:
                if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
                        return (-1);
@@ -2070,6 +2084,7 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
 
        case ZFS_PROP_MLSLABEL:
                {
+#ifdef HAVE_MLSLABEL
                        m_label_t *new_sl = NULL;
                        char *ascii = NULL;     /* human readable label */
 
@@ -2103,6 +2118,10 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
 
                        (void) strlcpy(propbuf, ascii, proplen);
                        free(ascii);
+#else
+                       (void) strlcpy(propbuf,
+                           getprop_string(zhp, prop, &source), proplen);
+#endif /* HAVE_MLSLABEL */
                }
                break;
 
@@ -2197,6 +2216,7 @@ zfs_prop_get_numeric(zfs_handle_t *zhp, zfs_prop_t prop, uint64_t *value,
        return (0);
 }
 
+#ifdef HAVE_IDMAP
 static int
 idmap_id_to_numeric_domain_rid(uid_t id, boolean_t isuser,
     char **domainp, idmap_rid_t *ridp)
@@ -2226,20 +2246,25 @@ out:
                idmap_get_destroy(get_hdl);
        return (err);
 }
+#endif /* HAVE_IDMAP */
 
 /*
  * convert the propname into parameters needed by kernel
  * Eg: userquota@ahrens -> ZFS_PROP_USERQUOTA, "", 126829
  * Eg: userused@matt@domain -> ZFS_PROP_USERUSED, "S-1-123-456", 789
+ * Eg: groupquota@staff -> ZFS_PROP_GROUPQUOTA, "", 1234
+ * Eg: groupused@staff -> ZFS_PROP_GROUPUSED, "", 1234
  */
 static int
 userquota_propname_decode(const char *propname, boolean_t zoned,
     zfs_userquota_prop_t *typep, char *domain, int domainlen, uint64_t *ridp)
 {
        zfs_userquota_prop_t type;
-       char *cp, *end;
-       char *numericsid = NULL;
+       char *cp;
        boolean_t isuser;
+       boolean_t isgroup;
+       struct passwd *pw;
+       struct group *gr;
 
        domain[0] = '\0';
 
@@ -2253,17 +2278,29 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                return (EINVAL);
        *typep = type;
 
-       isuser = (type == ZFS_PROP_USERQUOTA ||
-           type == ZFS_PROP_USERUSED);
+       isuser = (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_USERUSED);
+       isgroup = (type == ZFS_PROP_GROUPQUOTA || type == ZFS_PROP_GROUPUSED);
 
        cp = strchr(propname, '@') + 1;
 
-       if (strchr(cp, '@')) {
+       if (isuser && (pw = getpwnam(cp)) != NULL) {
+               if (zoned && getzoneid() == GLOBAL_ZONEID)
+                       return (ENOENT);
+               *ridp = pw->pw_uid;
+       } else if (isgroup && (gr = getgrnam(cp)) != NULL) {
+               if (zoned && getzoneid() == GLOBAL_ZONEID)
+                       return (ENOENT);
+               *ridp = gr->gr_gid;
+       } else if (strchr(cp, '@')) {
+#ifdef HAVE_IDMAP
                /*
                 * It's a SID name (eg "user@domain") that needs to be
                 * turned into S-1-domainID-RID.
                 */
                directory_error_t e;
+               char *numericsid = NULL;
+               char *end;
+
                if (zoned && getzoneid() == GLOBAL_ZONEID)
                        return (ENOENT);
                if (isuser) {
@@ -2280,11 +2317,6 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                if (numericsid == NULL)
                        return (ENOENT);
                cp = numericsid;
-               /* will be further decoded below */
-       }
-
-       if (strncmp(cp, "S-1-", 4) == 0) {
-               /* It's a numeric SID (eg "S-1-234-567-89") */
                (void) strlcpy(domain, cp, domainlen);
                cp = strrchr(domain, '-');
                *cp = '\0';
@@ -2292,38 +2324,22 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
 
                errno = 0;
                *ridp = strtoull(cp, &end, 10);
-               if (numericsid) {
-                       free(numericsid);
-                       numericsid = NULL;
-               }
+               free(numericsid);
+
                if (errno != 0 || *end != '\0')
                        return (EINVAL);
-       } else if (!isdigit(*cp)) {
-               /*
-                * It's a user/group name (eg "user") that needs to be
-                * turned into a uid/gid
-                */
-               if (zoned && getzoneid() == GLOBAL_ZONEID)
-                       return (ENOENT);
-               if (isuser) {
-                       struct passwd *pw;
-                       pw = getpwnam(cp);
-                       if (pw == NULL)
-                               return (ENOENT);
-                       *ridp = pw->pw_uid;
-               } else {
-                       struct group *gr;
-                       gr = getgrnam(cp);
-                       if (gr == NULL)
-                               return (ENOENT);
-                       *ridp = gr->gr_gid;
-               }
+#else
+               return (ENOSYS);
+#endif /* HAVE_IDMAP */
        } else {
+#ifdef HAVE_IDMAP
                /* It's a user/group ID (eg "12345"). */
-               uid_t id = strtoul(cp, &end, 10);
+               uid_t id;
                idmap_rid_t rid;
                char *mapdomain;
+               char *end;
 
+               id = strtoul(cp, &end, 10);
                if (*end != '\0')
                        return (EINVAL);
                if (id > MAXUID) {
@@ -2336,9 +2352,11 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                } else {
                        *ridp = id;
                }
+#else
+               return (ENOSYS);
+#endif /* HAVE_IDMAP */
        }
 
-       ASSERT3P(numericsid, ==, NULL);
        return (0);
 }
 
@@ -2722,7 +2740,7 @@ create_parents(libzfs_handle_t *hdl, char *target, int prefixlen)
         * up to the prefixlen-long one.
         */
        for (cp = target + prefixlen + 1;
-           cp = strchr(cp, '/'); *cp = '/', cp++) {
+           (cp = strchr(cp, '/')); *cp = '/', cp++) {
                char *logstr;
 
                *cp = '\0';
@@ -2779,7 +2797,7 @@ zfs_create_ancestors(libzfs_handle_t *hdl, const char *path)
 {
        int prefix;
        char *path_copy;
-       int rc;
+       int rc = 0;
 
        if (check_parents(hdl, path, NULL, B_TRUE, &prefix) != 0)
                return (-1);
@@ -2895,6 +2913,18 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
        /* create the dataset */
        ret = zfs_ioctl(hdl, ZFS_IOC_CREATE, &zc);
 
+       if (ret == 0 && type == ZFS_TYPE_VOLUME) {
+               ret = zvol_create_link(hdl, path);
+               if (ret) {
+                       (void) zfs_standard_error(hdl, errno,
+                           dgettext(TEXT_DOMAIN,
+                           "Volume successfully created, but device links "
+                           "were not created"));
+                       zcmd_free_nvlists(&zc);
+                       return (-1);
+               }
+       }
+
        zcmd_free_nvlists(&zc);
 
        /* check for failure */
@@ -2957,6 +2987,9 @@ zfs_destroy(zfs_handle_t *zhp, boolean_t defer)
        (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 
        if (ZFS_IS_VOLUME(zhp)) {
+               if (zvol_remove_link(zhp->zfs_hdl, zhp->zfs_name) != 0)
+                       return (-1);
+
                zc.zc_objset_type = DMU_OST_ZVOL;
        } else {
                zc.zc_objset_type = DMU_OST_ZFS;
@@ -2999,9 +3032,17 @@ zfs_check_snap_cb(zfs_handle_t *zhp, void *arg)
                zfs_close(szhp);
        }
 
+       if (zhp->zfs_type == ZFS_TYPE_VOLUME) {
+               (void) zvol_remove_link(zhp->zfs_hdl, name);
+               /*
+                * NB: this is simply a best-effort.  We don't want to
+                * return an error, because then we wouldn't visit all
+                * the volumes.
+                */
+       }
+
        dd->closezhp = B_TRUE;
-       if (!dd->gotone)
-               rv = zfs_iter_filesystems(zhp, zfs_check_snap_cb, arg);
+       rv = zfs_iter_filesystems(zhp, zfs_check_snap_cb, arg);
        if (closezhp)
                zfs_close(zhp);
        return (rv);
@@ -3136,11 +3177,70 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props)
                        return (zfs_standard_error(zhp->zfs_hdl, errno,
                            errbuf));
                }
+       } else if (ZFS_IS_VOLUME(zhp)) {
+               ret = zvol_create_link(zhp->zfs_hdl, target);
        }
 
        return (ret);
 }
 
+typedef struct promote_data {
+       char cb_mountpoint[MAXPATHLEN];
+       const char *cb_target;
+       const char *cb_errbuf;
+       uint64_t cb_pivot_txg;
+} promote_data_t;
+
+static int
+promote_snap_cb(zfs_handle_t *zhp, void *data)
+{
+       promote_data_t *pd = data;
+       zfs_handle_t *szhp;
+       char snapname[MAXPATHLEN];
+       int rv = 0;
+
+       /* We don't care about snapshots after the pivot point */
+       if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) > pd->cb_pivot_txg) {
+               zfs_close(zhp);
+               return (0);
+       }
+
+       /* Remove the device link if it's a zvol. */
+       if (ZFS_IS_VOLUME(zhp))
+               (void) zvol_remove_link(zhp->zfs_hdl, zhp->zfs_name);
+
+       /* Check for conflicting names */
+       (void) strlcpy(snapname, pd->cb_target, sizeof (snapname));
+       (void) strlcat(snapname, strchr(zhp->zfs_name, '@'), sizeof (snapname));
+       szhp = make_dataset_handle(zhp->zfs_hdl, snapname);
+       if (szhp != NULL) {
+               zfs_close(szhp);
+               zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+                   "snapshot name '%s' from origin \n"
+                   "conflicts with '%s' from target"),
+                   zhp->zfs_name, snapname);
+               rv = zfs_error(zhp->zfs_hdl, EZFS_EXISTS, pd->cb_errbuf);
+       }
+       zfs_close(zhp);
+       return (rv);
+}
+
+static int
+promote_snap_done_cb(zfs_handle_t *zhp, void *data)
+{
+       promote_data_t *pd = data;
+
+       /* We don't care about snapshots after the pivot point */
+       if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) <= pd->cb_pivot_txg) {
+               /* Create the device link if it's a zvol. */
+               if (ZFS_IS_VOLUME(zhp))
+                       (void) zvol_create_link(zhp->zfs_hdl, zhp->zfs_name);
+       }
+
+       zfs_close(zhp);
+       return (0);
+}
+
 /*
  * Promotes the given clone fs to be the clone parent.
  */
@@ -3150,7 +3250,10 @@ zfs_promote(zfs_handle_t *zhp)
        libzfs_handle_t *hdl = zhp->zfs_hdl;
        zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        char parent[MAXPATHLEN];
+       char *cp;
        int ret;
+       zfs_handle_t *pzhp;
+       promote_data_t pd;
        char errbuf[1024];
 
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
@@ -3168,7 +3271,29 @@ zfs_promote(zfs_handle_t *zhp)
                    "not a cloned filesystem"));
                return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
        }
+       cp = strchr(parent, '@');
+       *cp = '\0';
 
+       /* Walk the snapshots we will be moving */
+       pzhp = zfs_open(hdl, zhp->zfs_dmustats.dds_origin, ZFS_TYPE_SNAPSHOT);
+       if (pzhp == NULL)
+               return (-1);
+       pd.cb_pivot_txg = zfs_prop_get_int(pzhp, ZFS_PROP_CREATETXG);
+       zfs_close(pzhp);
+       pd.cb_target = zhp->zfs_name;
+       pd.cb_errbuf = errbuf;
+       pzhp = zfs_open(hdl, parent, ZFS_TYPE_DATASET);
+       if (pzhp == NULL)
+               return (-1);
+       (void) zfs_prop_get(pzhp, ZFS_PROP_MOUNTPOINT, pd.cb_mountpoint,
+           sizeof (pd.cb_mountpoint), NULL, NULL, 0, FALSE);
+       ret = zfs_iter_snapshots(pzhp, promote_snap_cb, &pd);
+       if (ret != 0) {
+               zfs_close(pzhp);
+               return (-1);
+       }
+
+       /* issue the ioctl */
        (void) strlcpy(zc.zc_value, zhp->zfs_dmustats.dds_origin,
            sizeof (zc.zc_value));
        (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
@@ -3177,9 +3302,16 @@ zfs_promote(zfs_handle_t *zhp)
        if (ret != 0) {
                int save_errno = errno;
 
+               (void) zfs_iter_snapshots(pzhp, promote_snap_done_cb, &pd);
+               zfs_close(pzhp);
+
                switch (save_errno) {
                case EEXIST:
-                       /* There is a conflicting snapshot name. */
+                       /*
+                        * There is a conflicting snapshot name.  We
+                        * should have caught this above, but they could
+                        * have renamed something in the mean time.
+                        */
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "conflicting snapshot '%s' from parent '%s'"),
                            zc.zc_string, parent);
@@ -3188,7 +3320,44 @@ zfs_promote(zfs_handle_t *zhp)
                default:
                        return (zfs_standard_error(hdl, save_errno, errbuf));
                }
+       } else {
+               (void) zfs_iter_snapshots(zhp, promote_snap_done_cb, &pd);
        }
+
+       zfs_close(pzhp);
+       return (ret);
+}
+
+struct createdata {
+       const char *cd_snapname;
+       int cd_ifexists;
+};
+
+static int
+zfs_create_link_cb(zfs_handle_t *zhp, void *arg)
+{
+       struct createdata *cd = arg;
+       int ret;
+
+       if (zhp->zfs_type == ZFS_TYPE_VOLUME) {
+               char name[MAXPATHLEN];
+
+               (void) strlcpy(name, zhp->zfs_name, sizeof (name));
+               (void) strlcat(name, "@", sizeof (name));
+               (void) strlcat(name, cd->cd_snapname, sizeof (name));
+               (void) zvol_create_link_common(zhp->zfs_hdl, name,
+                   cd->cd_ifexists);
+               /*
+                * NB: this is simply a best-effort.  We don't want to
+                * return an error, because then we wouldn't visit all
+                * the volumes.
+                */
+       }
+
+       ret = zfs_iter_filesystems(zhp, zfs_create_link_cb, cd);
+
+       zfs_close(zhp);
+
        return (ret);
 }
 
@@ -3252,12 +3421,32 @@ zfs_snapshot(libzfs_handle_t *hdl, const char *path, boolean_t recursive,
         * if it was recursive, the one that actually failed will be in
         * zc.zc_name.
         */
-       if (ret != 0) {
+       if (ret != 0)
                (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
                    "cannot create snapshot '%s@%s'"), zc.zc_name, zc.zc_value);
-               (void) zfs_standard_error(hdl, errno, errbuf);
+
+       if (ret == 0 && recursive) {
+               struct createdata cd;
+
+               cd.cd_snapname = delim + 1;
+               cd.cd_ifexists = B_FALSE;
+               (void) zfs_iter_filesystems(zhp, zfs_create_link_cb, &cd);
+       }
+       if (ret == 0 && zhp->zfs_type == ZFS_TYPE_VOLUME) {
+               ret = zvol_create_link(zhp->zfs_hdl, path);
+               if (ret != 0) {
+                       (void) zfs_standard_error(hdl, errno,
+                           dgettext(TEXT_DOMAIN,
+                           "Volume successfully snapshotted, but device links "
+                           "were not created"));
+                       zfs_close(zhp);
+                       return (-1);
+               }
        }
 
+       if (ret != 0)
+               (void) zfs_standard_error(hdl, errno, errbuf);
+
        zfs_close(zhp);
 
        return (ret);
@@ -3336,8 +3525,8 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
        int err;
        zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        boolean_t restore_resv = 0;
-       uint64_t old_volsize, new_volsize;
-       zfs_prop_t resv_prop;
+       uint64_t old_volsize = 0, new_volsize;
+       zfs_prop_t resv_prop = { 0 };
 
        assert(zhp->zfs_type == ZFS_TYPE_FILESYSTEM ||
            zhp->zfs_type == ZFS_TYPE_VOLUME);
@@ -3359,6 +3548,8 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
         */
 
        if (zhp->zfs_type == ZFS_TYPE_VOLUME) {
+               if (zvol_remove_link(zhp->zfs_hdl, zhp->zfs_name) != 0)
+                       return (-1);
                if (zfs_which_resv_prop(zhp, &resv_prop) < 0)
                        return (-1);
                old_volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE);
@@ -3396,6 +3587,10 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
         */
        if ((zhp->zfs_type == ZFS_TYPE_VOLUME) &&
            (zhp = make_dataset_handle(zhp->zfs_hdl, zhp->zfs_name))) {
+               if ((err = zvol_create_link(zhp->zfs_hdl, zhp->zfs_name))) {
+                       zfs_close(zhp);
+                       return (err);
+               }
                if (restore_resv) {
                        new_volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE);
                        if (old_volsize != new_volsize)
@@ -3544,6 +3739,7 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
        }
 
        if (recursive) {
+               struct destroydata dd;
 
                parentname = zfs_strdup(zhp->zfs_hdl, zhp->zfs_name);
                if (parentname == NULL) {
@@ -3558,6 +3754,15 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
                        goto error;
                }
 
+               dd.snapname = delim + 1;
+               dd.gotone = B_FALSE;
+               dd.closezhp = B_TRUE;
+
+               /* We remove any zvol links prior to renaming them */
+               ret = zfs_iter_filesystems(zhrp, zfs_check_snap_cb, &dd);
+               if (ret) {
+                       goto error;
+               }
        } else {
                if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0, 0)) == NULL)
                        return (-1);
@@ -3567,6 +3772,7 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
                            "child dataset with inherited mountpoint is used "
                            "in a non-global zone"));
                        (void) zfs_error(hdl, EZFS_ZONED, errbuf);
+                       ret = -1;
                        goto error;
                }
 
@@ -3605,10 +3811,27 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive)
                 * On failure, we still want to remount any filesystems that
                 * were previously mounted, so we don't alter the system state.
                 */
-               if (!recursive)
+               if (recursive) {
+                       struct createdata cd;
+
+                       /* only create links for datasets that had existed */
+                       cd.cd_snapname = delim + 1;
+                       cd.cd_ifexists = B_TRUE;
+                       (void) zfs_iter_filesystems(zhrp, zfs_create_link_cb,
+                           &cd);
+               } else {
                        (void) changelist_postfix(cl);
+               }
        } else {
-               if (!recursive) {
+               if (recursive) {
+                       struct createdata cd;
+
+                       /* only create links for datasets that had existed */
+                       cd.cd_snapname = strchr(target, '@') + 1;
+                       cd.cd_ifexists = B_TRUE;
+                       ret = zfs_iter_filesystems(zhrp, zfs_create_link_cb,
+                           &cd);
+               } else {
                        changelist_rename(cl, zfs_get_name(zhp), target);
                        ret = changelist_postfix(cl);
                }
@@ -3627,19 +3850,122 @@ error:
        return (ret);
 }
 
-nvlist_t *
-zfs_get_user_props(zfs_handle_t *zhp)
+/*
+ * Given a zvol dataset, issue the ioctl to create the appropriate minor node,
+ * and wait briefly for udev to create the /dev link.
+ */
+int
+zvol_create_link(libzfs_handle_t *hdl, const char *dataset)
 {
-       return (zhp->zfs_user_props);
+       return (zvol_create_link_common(hdl, dataset, B_FALSE));
+}
+
+static int
+zvol_create_link_common(libzfs_handle_t *hdl, const char *dataset, int ifexists)
+{
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       char path[MAXPATHLEN];
+       int error;
+
+       (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
+
+       /*
+        * Issue the appropriate ioctl.
+        */
+       if (ioctl(hdl->libzfs_fd, ZFS_IOC_CREATE_MINOR, &zc) != 0) {
+               switch (errno) {
+               case EEXIST:
+                       /*
+                        * Silently ignore the case where the link already
+                        * exists.  This allows 'zfs volinit' to be run multiple
+                        * times without errors.
+                        */
+                       return (0);
+
+               case ENOENT:
+                       /*
+                        * Dataset does not exist in the kernel.  If we
+                        * don't care (see zfs_rename), then ignore the
+                        * error quietly.
+                        */
+                       if (ifexists) {
+                               return (0);
+                       }
+
+                       /* FALLTHROUGH */
+
+               default:
+                       return (zfs_standard_error_fmt(hdl, errno,
+                           dgettext(TEXT_DOMAIN, "cannot create device links "
+                           "for '%s'"), dataset));
+               }
+       }
+
+       /*
+        * Wait up to 10 seconds for udev to create the device.
+        */
+       (void) snprintf(path, sizeof (path), "%s/%s", ZVOL_DIR, dataset);
+       error = zpool_label_disk_wait(path, 10000);
+       if (error)
+               (void) printf(gettext("%s may not be immediately "
+                   "available\n"), path);
+
+       return (0);
+}
+
+/*
+ * Remove a minor node for the given zvol and the associated /dev links.
+ */
+int
+zvol_remove_link(libzfs_handle_t *hdl, const char *dataset)
+{
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       int timeout = 3000; /* in milliseconds */
+       int error = 0;
+       int i;
+
+       (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
+
+       /*
+        * Due to concurrent updates by udev the device may be reported as
+        * busy.  In this case don't immediately fail.  Instead briefly delay
+        * and retry the ioctl() which is now likely to succeed.  If unable
+        * remove the link after timeout milliseconds return the failure.
+        */
+       for (i = 0; i < timeout; i++) {
+               error = ioctl(hdl->libzfs_fd, ZFS_IOC_REMOVE_MINOR, &zc);
+               if (error && errno == EBUSY) {
+                       usleep(1000);
+                       continue;
+               } else {
+                       break;
+               }
+       }
+
+       if (error) {
+               switch (errno) {
+               case ENXIO:
+                       /*
+                        * Silently ignore the case where the link no longer
+                        * exists, so that 'zfs volfini' can be run multiple
+                        * times without errors.
+                        */
+                       return (0);
+
+               default:
+                       return (zfs_standard_error_fmt(hdl, errno,
+                           dgettext(TEXT_DOMAIN, "cannot remove device "
+                           "links for '%s'"), dataset));
+               }
+       }
+
+       return (0);
 }
 
 nvlist_t *
-zfs_get_recvd_props(zfs_handle_t *zhp)
+zfs_get_user_props(zfs_handle_t *zhp)
 {
-       if (zhp->zfs_recvd_props == NULL)
-               if (get_recvd_props_ioctl(zhp) != 0)
-                       return (NULL);
-       return (zhp->zfs_recvd_props);
+       return (zhp->zfs_user_props);
 }
 
 /*
@@ -3751,26 +4077,6 @@ zfs_expand_proplist(zfs_handle_t *zhp, zprop_list_t **plp, boolean_t received)
        return (0);
 }
 
-int
-zfs_deleg_share_nfs(libzfs_handle_t *hdl, char *dataset, char *path,
-    char *resource, void *export, void *sharetab,
-    int sharemax, zfs_share_op_t operation)
-{
-       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
-       int error;
-
-       (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
-       (void) strlcpy(zc.zc_value, path, sizeof (zc.zc_value));
-       if (resource)
-               (void) strlcpy(zc.zc_string, resource, sizeof (zc.zc_string));
-       zc.zc_share.z_sharedata = (uint64_t)(uintptr_t)sharetab;
-       zc.zc_share.z_exportdata = (uint64_t)(uintptr_t)export;
-       zc.zc_share.z_sharetype = operation;
-       zc.zc_share.z_sharemax = sharemax;
-       error = ioctl(hdl->libzfs_fd, ZFS_IOC_SHARE, &zc);
-       return (error);
-}
-
 void
 zfs_prune_proplist(zfs_handle_t *zhp, uint8_t *props)
 {
@@ -3817,7 +4123,7 @@ zfs_smb_acl_mgmt(libzfs_handle_t *hdl, char *dataset, char *path,
        if (cmd == ZFS_SMB_ACL_RENAME) {
                if (nvlist_alloc(&nvlist, NV_UNIQUE_NAME, 0) != 0) {
                        (void) no_memory(hdl);
-                       return (NULL);
+                       return (-1);
                }
        }
 
@@ -4020,6 +4326,193 @@ zfs_release(zfs_handle_t *zhp, const char *snapname, const char *tag,
        return (0);
 }
 
+int
+zfs_get_fsacl(zfs_handle_t *zhp, nvlist_t **nvl)
+{
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       libzfs_handle_t *hdl = zhp->zfs_hdl;
+       int nvsz = 2048;
+       void *nvbuf;
+       int err = 0;
+       char errbuf[ZFS_MAXNAMELEN+32];
+
+       assert(zhp->zfs_type == ZFS_TYPE_VOLUME ||
+           zhp->zfs_type == ZFS_TYPE_FILESYSTEM);
+
+tryagain:
+
+       nvbuf = malloc(nvsz);
+       if (nvbuf == NULL) {
+               err = (zfs_error(hdl, EZFS_NOMEM, strerror(errno)));
+               goto out;
+       }
+
+       zc.zc_nvlist_dst_size = nvsz;
+       zc.zc_nvlist_dst = (uintptr_t)nvbuf;
+
+       (void) strlcpy(zc.zc_name, zhp->zfs_name, ZFS_MAXNAMELEN);
+
+       if (zfs_ioctl(hdl, ZFS_IOC_GET_FSACL, &zc) != 0) {
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN, "cannot get permissions on '%s'"),
+                   zc.zc_name);
+               switch (errno) {
+               case ENOMEM:
+                       free(nvbuf);
+                       nvsz = zc.zc_nvlist_dst_size;
+                       goto tryagain;
+
+               case ENOTSUP:
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "pool must be upgraded"));
+                       err = zfs_error(hdl, EZFS_BADVERSION, errbuf);
+                       break;
+               case EINVAL:
+                       err = zfs_error(hdl, EZFS_BADTYPE, errbuf);
+                       break;
+               case ENOENT:
+                       err = zfs_error(hdl, EZFS_NOENT, errbuf);
+                       break;
+               default:
+                       err = zfs_standard_error_fmt(hdl, errno, errbuf);
+                       break;
+               }
+       } else {
+               /* success */
+               int rc = nvlist_unpack(nvbuf, zc.zc_nvlist_dst_size, nvl, 0);
+               if (rc) {
+                       (void) snprintf(errbuf, sizeof (errbuf), dgettext(
+                           TEXT_DOMAIN, "cannot get permissions on '%s'"),
+                           zc.zc_name);
+                       err = zfs_standard_error_fmt(hdl, rc, errbuf);
+               }
+       }
+
+       free(nvbuf);
+out:
+       return (err);
+}
+
+int
+zfs_set_fsacl(zfs_handle_t *zhp, boolean_t un, nvlist_t *nvl)
+{
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       libzfs_handle_t *hdl = zhp->zfs_hdl;
+       char *nvbuf;
+       char errbuf[ZFS_MAXNAMELEN+32];
+       size_t nvsz;
+       int err;
+
+       assert(zhp->zfs_type == ZFS_TYPE_VOLUME ||
+           zhp->zfs_type == ZFS_TYPE_FILESYSTEM);
+
+       err = nvlist_size(nvl, &nvsz, NV_ENCODE_NATIVE);
+       assert(err == 0);
+
+       nvbuf = malloc(nvsz);
+
+       err = nvlist_pack(nvl, &nvbuf, &nvsz, NV_ENCODE_NATIVE, 0);
+       assert(err == 0);
+
+       zc.zc_nvlist_src_size = nvsz;
+       zc.zc_nvlist_src = (uintptr_t)nvbuf;
+       zc.zc_perm_action = un;
+
+       (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+
+       if (zfs_ioctl(hdl, ZFS_IOC_SET_FSACL, &zc) != 0) {
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN, "cannot set permissions on '%s'"),
+                   zc.zc_name);
+               switch (errno) {
+               case ENOTSUP:
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "pool must be upgraded"));
+                       err = zfs_error(hdl, EZFS_BADVERSION, errbuf);
+                       break;
+               case EINVAL:
+                       err = zfs_error(hdl, EZFS_BADTYPE, errbuf);
+                       break;
+               case ENOENT:
+                       err = zfs_error(hdl, EZFS_NOENT, errbuf);
+                       break;
+               default:
+                       err = zfs_standard_error_fmt(hdl, errno, errbuf);
+                       break;
+               }
+       }
+
+       free(nvbuf);
+
+       return (err);
+}
+
+int
+zfs_get_holds(zfs_handle_t *zhp, nvlist_t **nvl)
+{
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
+       libzfs_handle_t *hdl = zhp->zfs_hdl;
+       int nvsz = 2048;
+       void *nvbuf;
+       int err = 0;
+       char errbuf[ZFS_MAXNAMELEN+32];
+
+       assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
+
+tryagain:
+
+       nvbuf = malloc(nvsz);
+       if (nvbuf == NULL) {
+               err = (zfs_error(hdl, EZFS_NOMEM, strerror(errno)));
+               goto out;
+       }
+
+       zc.zc_nvlist_dst_size = nvsz;
+       zc.zc_nvlist_dst = (uintptr_t)nvbuf;
+
+       (void) strlcpy(zc.zc_name, zhp->zfs_name, ZFS_MAXNAMELEN);
+
+       if (zfs_ioctl(hdl, ZFS_IOC_GET_HOLDS, &zc) != 0) {
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN, "cannot get holds for '%s'"),
+                   zc.zc_name);
+               switch (errno) {
+               case ENOMEM:
+                       free(nvbuf);
+                       nvsz = zc.zc_nvlist_dst_size;
+                       goto tryagain;
+
+               case ENOTSUP:
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "pool must be upgraded"));
+                       err = zfs_error(hdl, EZFS_BADVERSION, errbuf);
+                       break;
+               case EINVAL:
+                       err = zfs_error(hdl, EZFS_BADTYPE, errbuf);
+                       break;
+               case ENOENT:
+                       err = zfs_error(hdl, EZFS_NOENT, errbuf);
+                       break;
+               default:
+                       err = zfs_standard_error_fmt(hdl, errno, errbuf);
+                       break;
+               }
+       } else {
+               /* success */
+               int rc = nvlist_unpack(nvbuf, zc.zc_nvlist_dst_size, nvl, 0);
+               if (rc) {
+                       (void) snprintf(errbuf, sizeof (errbuf),
+                           dgettext(TEXT_DOMAIN, "cannot get holds for '%s'"),
+                           zc.zc_name);
+                       err = zfs_standard_error_fmt(hdl, rc, errbuf);
+               }
+       }
+
+       free(nvbuf);
+out:
+       return (err);
+}
+
 uint64_t
 zvol_volsize_to_reservation(uint64_t volsize, nvlist_t *props)
 {