Retry removal of busy minors
[zfs.git] / lib / libzfs / libzfs_dataset.c
index d4b59f2..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>
@@ -94,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) {
@@ -410,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();
 
@@ -995,7 +1001,6 @@ badlabel:
 
                        /*FALLTHRU*/
 
-#ifdef HAVE_ZPL
                case ZFS_PROP_SHARESMB:
                case ZFS_PROP_SHARENFS:
                        /*
@@ -1106,7 +1111,6 @@ badlabel:
                        }
 
                        break;
-#endif /* HAVE_ZPL */
                case ZFS_PROP_UTF8ONLY:
                        chosen_utf = (int)intval;
                        break;
@@ -1315,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.
  */
@@ -1410,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:
@@ -1532,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;
@@ -1998,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);
@@ -2218,15 +2252,19 @@ out:
  * 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';
 
@@ -2240,18 +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) {
@@ -2268,14 +2317,6 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                if (numericsid == NULL)
                        return (ENOENT);
                cp = numericsid;
-               /* will be further decoded below */
-#else
-               return (ENOSYS);
-#endif /* HAVE_IDMAP */
-       }
-
-       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';
@@ -2283,39 +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) {
@@ -2333,7 +2357,6 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
 #endif /* HAVE_IDMAP */
        }
 
-       ASSERT3P(numericsid, ==, NULL);
        return (0);
 }
 
@@ -2745,7 +2768,6 @@ create_parents(libzfs_handle_t *hdl, char *target, int prefixlen)
                        goto ancestorerr;
                }
 
-#ifdef HAVE_ZPL
                if (zfs_mount(h, NULL, 0) != 0) {
                        opname = dgettext(TEXT_DOMAIN, "mount");
                        goto ancestorerr;
@@ -2755,7 +2777,6 @@ create_parents(libzfs_handle_t *hdl, char *target, int prefixlen)
                        opname = dgettext(TEXT_DOMAIN, "share");
                        goto ancestorerr;
                }
-#endif /* HAVE_ZPL */
 
                zfs_close(h);
        }
@@ -3899,10 +3920,29 @@ 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));
 
-       if (ioctl(hdl->libzfs_fd, ZFS_IOC_REMOVE_MINOR, &zc) != 0) {
+       /*
+        * 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:
                        /*
@@ -4037,28 +4077,6 @@ zfs_expand_proplist(zfs_handle_t *zhp, zprop_list_t **plp, boolean_t received)
        return (0);
 }
 
-#ifdef HAVE_ZPL
-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);
-}
-#endif /* HAVE_ZPL */
-
 void
 zfs_prune_proplist(zfs_handle_t *zhp, uint8_t *props)
 {
@@ -4308,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)
 {