X-Git-Url: https://git.camperquake.de/gitweb.cgi?a=blobdiff_plain;f=lib%2Flibzfs%2Flibzfs_dataset.c;h=554b0eadc2d3ee064c2c02fc3c500b6f49b6a0bd;hb=105afebb1529c21c3fb1ef7101918ed9fb704942;hp=e2c4c1e161fa5eb31680d07976560a1e5b20a19d;hpb=e75c13c353571efaa9b4e047f16969ec13a518f5;p=zfs.git diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index e2c4c1e..554b0ea 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -21,6 +21,10 @@ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012 DEY Storage Systems, Inc. All rights reserved. + * Copyright (c) 2012 Pawel Jakub Dawidek . + * Copyright 2012 Nexenta Systems, Inc. All rights reserved. */ #include @@ -41,9 +45,11 @@ #include #include #include +#ifdef HAVE_IDMAP #include #include #include +#endif /* HAVE_IDMAP */ #include #include @@ -55,6 +61,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); @@ -80,49 +87,6 @@ zfs_type_to_name(zfs_type_t type) } /* - * 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. @@ -134,6 +98,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) { @@ -450,6 +415,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(); @@ -497,7 +465,7 @@ make_dataset_handle(libzfs_handle_t *hdl, const char *path) return (zhp); } -static zfs_handle_t * +zfs_handle_t * make_dataset_handle_zc(libzfs_handle_t *hdl, zfs_cmd_t *zc) { zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1); @@ -514,6 +482,70 @@ make_dataset_handle_zc(libzfs_handle_t *hdl, zfs_cmd_t *zc) return (zhp); } +zfs_handle_t * +make_dataset_simple_handle_zc(zfs_handle_t *pzhp, zfs_cmd_t *zc) +{ + zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1); + + if (zhp == NULL) + return (NULL); + + zhp->zfs_hdl = pzhp->zfs_hdl; + (void) strlcpy(zhp->zfs_name, zc->zc_name, sizeof (zhp->zfs_name)); + zhp->zfs_head_type = pzhp->zfs_type; + zhp->zfs_type = ZFS_TYPE_SNAPSHOT; + zhp->zpool_hdl = zpool_handle(zhp); + + return (zhp); +} + +zfs_handle_t * +zfs_handle_dup(zfs_handle_t *zhp_orig) +{ + zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1); + + if (zhp == NULL) + return (NULL); + + zhp->zfs_hdl = zhp_orig->zfs_hdl; + zhp->zpool_hdl = zhp_orig->zpool_hdl; + (void) strlcpy(zhp->zfs_name, zhp_orig->zfs_name, + sizeof (zhp->zfs_name)); + zhp->zfs_type = zhp_orig->zfs_type; + zhp->zfs_head_type = zhp_orig->zfs_head_type; + zhp->zfs_dmustats = zhp_orig->zfs_dmustats; + if (zhp_orig->zfs_props != NULL) { + if (nvlist_dup(zhp_orig->zfs_props, &zhp->zfs_props, 0) != 0) { + (void) no_memory(zhp->zfs_hdl); + zfs_close(zhp); + return (NULL); + } + } + if (zhp_orig->zfs_user_props != NULL) { + if (nvlist_dup(zhp_orig->zfs_user_props, + &zhp->zfs_user_props, 0) != 0) { + (void) no_memory(zhp->zfs_hdl); + zfs_close(zhp); + return (NULL); + } + } + if (zhp_orig->zfs_recvd_props != NULL) { + if (nvlist_dup(zhp_orig->zfs_recvd_props, + &zhp->zfs_recvd_props, 0)) { + (void) no_memory(zhp->zfs_hdl); + zfs_close(zhp); + return (NULL); + } + } + zhp->zfs_mntcheck = zhp_orig->zfs_mntcheck; + if (zhp_orig->zfs_mntopts != NULL) { + zhp->zfs_mntopts = zfs_strdup(zhp_orig->zfs_hdl, + zhp_orig->zfs_mntopts); + } + zhp->zfs_props_table = zhp_orig->zfs_props_table; + return (zhp); +} + /* * Opens the given snapshot, filesystem, or volume. The 'types' * argument is a mask of acceptable types. The function will print an @@ -623,7 +655,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); @@ -695,7 +727,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); @@ -877,6 +909,12 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, goto error; } continue; + } else if (prop == ZPROP_INVAL && zfs_prop_written(propname)) { + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "'%s' is readonly"), + propname); + (void) zfs_error(hdl, EZFS_PROPREADONLY, errbuf); + goto error; } if (prop == ZPROP_INVAL) { @@ -945,6 +983,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. @@ -994,7 +1033,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: @@ -1347,6 +1391,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. */ @@ -1362,7 +1425,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'"), @@ -1442,12 +1505,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: @@ -1564,7 +1637,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; @@ -1632,7 +1705,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". */ @@ -1690,7 +1763,7 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src, /* * 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 && @@ -1858,8 +1931,6 @@ zfs_prop_get_recvd(zfs_handle_t *zhp, const char *propname, char *propbuf, err = zfs_prop_get(zhp, prop, propbuf, proplen, NULL, NULL, 0, literal); zfs_unset_recvd_props_mode(zhp, &cookie); - } else if (zfs_prop_userquota(propname)) { - return (-1); } else { nvlist_t *propval; char *recvdval; @@ -1874,6 +1945,120 @@ zfs_prop_get_recvd(zfs_handle_t *zhp, const char *propname, char *propbuf, return (err == 0 ? 0 : -1); } +static int +get_clones_string(zfs_handle_t *zhp, char *propbuf, size_t proplen) +{ + nvlist_t *value; + nvpair_t *pair; + + value = zfs_get_clones_nvl(zhp); + if (value == NULL) + return (-1); + + propbuf[0] = '\0'; + for (pair = nvlist_next_nvpair(value, NULL); pair != NULL; + pair = nvlist_next_nvpair(value, pair)) { + if (propbuf[0] != '\0') + (void) strlcat(propbuf, ",", proplen); + (void) strlcat(propbuf, nvpair_name(pair), proplen); + } + + return (0); +} + +struct get_clones_arg { + uint64_t numclones; + nvlist_t *value; + const char *origin; + char buf[ZFS_MAXNAMELEN]; +}; + +int +get_clones_cb(zfs_handle_t *zhp, void *arg) +{ + struct get_clones_arg *gca = arg; + + if (gca->numclones == 0) { + zfs_close(zhp); + return (0); + } + + if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, gca->buf, sizeof (gca->buf), + NULL, NULL, 0, B_TRUE) != 0) + goto out; + if (strcmp(gca->buf, gca->origin) == 0) { + if (nvlist_add_boolean(gca->value, zfs_get_name(zhp)) != 0) { + zfs_close(zhp); + return (no_memory(zhp->zfs_hdl)); + } + gca->numclones--; + } + +out: + (void) zfs_iter_children(zhp, get_clones_cb, gca); + zfs_close(zhp); + return (0); +} + +nvlist_t * +zfs_get_clones_nvl(zfs_handle_t *zhp) +{ + nvlist_t *nv, *value; + + if (nvlist_lookup_nvlist(zhp->zfs_props, + zfs_prop_to_name(ZFS_PROP_CLONES), &nv) != 0) { + struct get_clones_arg gca; + + /* + * if this is a snapshot, then the kernel wasn't able + * to get the clones. Do it by slowly iterating. + */ + if (zhp->zfs_type != ZFS_TYPE_SNAPSHOT) + return (NULL); + if (nvlist_alloc(&nv, NV_UNIQUE_NAME, 0) != 0) + return (NULL); + if (nvlist_alloc(&value, NV_UNIQUE_NAME, 0) != 0) { + nvlist_free(nv); + return (NULL); + } + + gca.numclones = zfs_prop_get_int(zhp, ZFS_PROP_NUMCLONES); + gca.value = value; + gca.origin = zhp->zfs_name; + + if (gca.numclones != 0) { + zfs_handle_t *root; + char pool[ZFS_MAXNAMELEN]; + char *cp = pool; + + /* get the pool name */ + (void) strlcpy(pool, zhp->zfs_name, sizeof (pool)); + (void) strsep(&cp, "/@"); + root = zfs_open(zhp->zfs_hdl, pool, + ZFS_TYPE_FILESYSTEM); + + (void) get_clones_cb(root, &gca); + } + + if (gca.numclones != 0 || + nvlist_add_nvlist(nv, ZPROP_VALUE, value) != 0 || + nvlist_add_nvlist(zhp->zfs_props, + zfs_prop_to_name(ZFS_PROP_CLONES), nv) != 0) { + nvlist_free(nv); + nvlist_free(value); + return (NULL); + } + nvlist_free(nv); + nvlist_free(value); + verify(0 == nvlist_lookup_nvlist(zhp->zfs_props, + zfs_prop_to_name(ZFS_PROP_CLONES), &nv)); + } + + verify(nvlist_lookup_nvlist(nv, ZPROP_VALUE, &value) == 0); + + return (value); +} + /* * Retrieve a property from the given object. If 'literal' is specified, then * numbers are left as exact values. Otherwise, numbers are converted to a @@ -2002,6 +2187,11 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, return (-1); break; + case ZFS_PROP_CLONES: + if (get_clones_string(zhp, propbuf, proplen) != 0) + return (-1); + break; + case ZFS_PROP_QUOTA: case ZFS_PROP_REFQUOTA: case ZFS_PROP_RESERVATION: @@ -2030,6 +2220,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); @@ -2082,6 +2273,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 */ @@ -2115,9 +2307,24 @@ 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; + case ZFS_PROP_GUID: + /* + * GUIDs are stored as numbers, but they are identifiers. + * We don't want them to be pretty printed, because pretty + * printing mangles the ID into a truncated and useless value. + */ + if (get_numeric_property(zhp, prop, src, &source, &val) != 0) + return (-1); + (void) snprintf(propbuf, proplen, "%llu", (u_longlong_t)val); + break; + default: switch (zfs_prop_get_type(prop)) { case PROP_TYPE_NUMBER: @@ -2209,6 +2416,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) @@ -2238,20 +2446,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'; @@ -2265,17 +2478,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) { @@ -2292,11 +2517,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'; @@ -2304,53 +2524,39 @@ 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 { /* It's a user/group ID (eg "12345"). */ - uid_t id = strtoul(cp, &end, 10); - idmap_rid_t rid; - char *mapdomain; - + uid_t id; + char *end; + id = strtoul(cp, &end, 10); if (*end != '\0') return (EINVAL); if (id > MAXUID) { +#ifdef HAVE_IDMAP /* It's an ephemeral ID. */ + idmap_rid_t rid; + char *mapdomain; + if (idmap_id_to_numeric_domain_rid(id, isuser, &mapdomain, &rid) != 0) return (ENOENT); (void) strlcpy(domain, mapdomain, domainlen); *ridp = rid; +#else + return (ENOSYS); +#endif /* HAVE_IDMAP */ } else { *ridp = id; } } - ASSERT3P(numericsid, ==, NULL); return (0); } @@ -2361,7 +2567,7 @@ zfs_prop_get_userquota_common(zfs_handle_t *zhp, const char *propname, int err; zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 }; - (void) strncpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); err = userquota_propname_decode(propname, zfs_prop_get_int(zhp, ZFS_PROP_ZONED), @@ -2414,144 +2620,96 @@ zfs_prop_get_userquota(zfs_handle_t *zhp, const char *propname, return (0); } -/* - * Returns the name of the given zfs handle. - */ -const char * -zfs_get_name(const zfs_handle_t *zhp) +int +zfs_prop_get_written_int(zfs_handle_t *zhp, const char *propname, + uint64_t *propvalue) { - return (zhp->zfs_name); -} + int err; + zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 }; + const char *snapname; -/* - * Returns the type of the given zfs handle. - */ -zfs_type_t -zfs_get_type(const zfs_handle_t *zhp) -{ - return (zhp->zfs_type); + (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + + snapname = strchr(propname, '@') + 1; + if (strchr(snapname, '@')) { + (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value)); + } else { + /* snapname is the short name, append it to zhp's fsname */ + char *cp; + + (void) strlcpy(zc.zc_value, zhp->zfs_name, + sizeof (zc.zc_value)); + cp = strchr(zc.zc_value, '@'); + if (cp != NULL) + *cp = '\0'; + (void) strlcat(zc.zc_value, "@", sizeof (zc.zc_value)); + (void) strlcat(zc.zc_value, snapname, sizeof (zc.zc_value)); + } + + err = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_SPACE_WRITTEN, &zc); + if (err) + return (err); + + *propvalue = zc.zc_cookie; + return (0); } -static int -zfs_do_list_ioctl(zfs_handle_t *zhp, int arg, zfs_cmd_t *zc) +int +zfs_prop_get_written(zfs_handle_t *zhp, const char *propname, + char *propbuf, int proplen, boolean_t literal) { - int rc; - uint64_t orig_cookie; + int err; + uint64_t propvalue; - orig_cookie = zc->zc_cookie; -top: - (void) strlcpy(zc->zc_name, zhp->zfs_name, sizeof (zc->zc_name)); - rc = ioctl(zhp->zfs_hdl->libzfs_fd, arg, zc); + err = zfs_prop_get_written_int(zhp, propname, &propvalue); - if (rc == -1) { - switch (errno) { - case ENOMEM: - /* expand nvlist memory and try again */ - if (zcmd_expand_dst_nvlist(zhp->zfs_hdl, zc) != 0) { - zcmd_free_nvlists(zc); - return (-1); - } - zc->zc_cookie = orig_cookie; - goto top; - /* - * An errno value of ESRCH indicates normal completion. - * If ENOENT is returned, then the underlying dataset - * has been removed since we obtained the handle. - */ - case ESRCH: - case ENOENT: - rc = 1; - break; - default: - rc = zfs_standard_error(zhp->zfs_hdl, errno, - dgettext(TEXT_DOMAIN, - "cannot iterate filesystems")); - break; - } + if (err) + return (err); + + if (literal) { + (void) snprintf(propbuf, proplen, "%llu", (long long unsigned int)propvalue); + } else { + zfs_nicenum(propvalue, propbuf, proplen); } - return (rc); + + return (0); } -/* - * Iterate over all child filesystems - */ int -zfs_iter_filesystems(zfs_handle_t *zhp, zfs_iter_f func, void *data) +zfs_get_snapused_int(zfs_handle_t *firstsnap, zfs_handle_t *lastsnap, + uint64_t *usedp) { + int err; zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 }; - zfs_handle_t *nzhp; - int ret; - if (zhp->zfs_type != ZFS_TYPE_FILESYSTEM) - return (0); + (void) strlcpy(zc.zc_name, lastsnap->zfs_name, sizeof (zc.zc_name)); + (void) strlcpy(zc.zc_value, firstsnap->zfs_name, sizeof (zc.zc_value)); - if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0) - return (-1); + err = ioctl(lastsnap->zfs_hdl->libzfs_fd, ZFS_IOC_SPACE_SNAPS, &zc); + if (err) + return (err); - while ((ret = zfs_do_list_ioctl(zhp, ZFS_IOC_DATASET_LIST_NEXT, - &zc)) == 0) { - /* - * Silently ignore errors, as the only plausible explanation is - * that the pool has since been removed. - */ - if ((nzhp = make_dataset_handle_zc(zhp->zfs_hdl, - &zc)) == NULL) { - continue; - } + *usedp = zc.zc_cookie; - if ((ret = func(nzhp, data)) != 0) { - zcmd_free_nvlists(&zc); - return (ret); - } - } - zcmd_free_nvlists(&zc); - return ((ret < 0) ? ret : 0); + return (0); } /* - * Iterate over all snapshots + * Returns the name of the given zfs handle. */ -int -zfs_iter_snapshots(zfs_handle_t *zhp, zfs_iter_f func, void *data) +const char * +zfs_get_name(const zfs_handle_t *zhp) { - zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 }; - zfs_handle_t *nzhp; - int ret; - - if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) - return (0); - - if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0) - return (-1); - while ((ret = zfs_do_list_ioctl(zhp, ZFS_IOC_SNAPSHOT_LIST_NEXT, - &zc)) == 0) { - - if ((nzhp = make_dataset_handle_zc(zhp->zfs_hdl, - &zc)) == NULL) { - continue; - } - - if ((ret = func(nzhp, data)) != 0) { - zcmd_free_nvlists(&zc); - return (ret); - } - } - zcmd_free_nvlists(&zc); - return ((ret < 0) ? ret : 0); + return (zhp->zfs_name); } /* - * Iterate over all children, snapshots and filesystems + * Returns the type of the given zfs handle. */ -int -zfs_iter_children(zfs_handle_t *zhp, zfs_iter_f func, void *data) +zfs_type_t +zfs_get_type(const zfs_handle_t *zhp) { - int ret; - - if ((ret = zfs_iter_filesystems(zhp, func, data)) != 0) - return (ret); - - return (zfs_iter_snapshots(zhp, func, data)); + return (zhp->zfs_type); } /* @@ -2577,18 +2735,19 @@ is_descendant(const char *ds1, const char *ds2) /* * Given a complete name, return just the portion that refers to the parent. - * Can return NULL if this is a pool. + * Will return -1 if there is no parent (path is just the name of the + * pool). */ static int parent_name(const char *path, char *buf, size_t buflen) { - char *loc; + char *slashp; - if ((loc = strrchr(path, '/')) == NULL) - return (-1); + (void) strlcpy(buf, path, buflen); - (void) strncpy(buf, path, MIN(buflen, loc - path)); - buf[loc - path] = '\0'; + if ((slashp = strrchr(buf, '/')) == NULL) + return (-1); + *slashp = '\0'; return (0); } @@ -2734,7 +2893,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'; @@ -2791,7 +2950,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); @@ -2907,6 +3066,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 */ @@ -2959,7 +3130,8 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, /* * Destroys the given dataset. The caller must make sure that the filesystem - * isn't mounted, and that there are no active dependents. + * isn't mounted, and that there are no active dependents. If the file system + * does not exist this function does nothing. */ int zfs_destroy(zfs_handle_t *zhp, boolean_t defer) @@ -2969,13 +3141,17 @@ 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; } zc.zc_defer_destroy = defer; - if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY, &zc) != 0) { + if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY, &zc) != 0 && + errno != ENOENT) { return (zfs_standard_error_fmt(zhp->zfs_hdl, errno, dgettext(TEXT_DOMAIN, "cannot destroy '%s'"), zhp->zfs_name)); @@ -2987,9 +3163,8 @@ zfs_destroy(zfs_handle_t *zhp, boolean_t defer) } struct destroydata { - char *snapname; - boolean_t gotone; - boolean_t closezhp; + nvlist_t *nvl; + const char *snapname; }; static int @@ -2998,24 +3173,28 @@ zfs_check_snap_cb(zfs_handle_t *zhp, void *arg) struct destroydata *dd = arg; zfs_handle_t *szhp; char name[ZFS_MAXNAMELEN]; - boolean_t closezhp = dd->closezhp; int rv = 0; - (void) strlcpy(name, zhp->zfs_name, sizeof (name)); - (void) strlcat(name, "@", sizeof (name)); - (void) strlcat(name, dd->snapname, sizeof (name)); + (void) snprintf(name, sizeof (name), + "%s@%s", zhp->zfs_name, dd->snapname); szhp = make_dataset_handle(zhp->zfs_hdl, name); if (szhp) { - dd->gotone = B_TRUE; + verify(nvlist_add_boolean(dd->nvl, name) == 0); zfs_close(szhp); } - dd->closezhp = B_TRUE; - if (!dd->gotone) - rv = zfs_iter_filesystems(zhp, zfs_check_snap_cb, arg); - if (closezhp) - zfs_close(zhp); + 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. + */ + } + + rv = zfs_iter_filesystems(zhp, zfs_check_snap_cb, dd); + zfs_close(zhp); return (rv); } @@ -3025,29 +3204,45 @@ zfs_check_snap_cb(zfs_handle_t *zhp, void *arg) int zfs_destroy_snaps(zfs_handle_t *zhp, char *snapname, boolean_t defer) { - zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 }; int ret; struct destroydata dd = { 0 }; dd.snapname = snapname; - (void) zfs_check_snap_cb(zhp, &dd); + verify(nvlist_alloc(&dd.nvl, NV_UNIQUE_NAME, 0) == 0); + (void) zfs_check_snap_cb(zfs_handle_dup(zhp), &dd); - if (!dd.gotone) { - return (zfs_standard_error_fmt(zhp->zfs_hdl, ENOENT, + if (nvlist_next_nvpair(dd.nvl, NULL) == NULL) { + ret = zfs_standard_error_fmt(zhp->zfs_hdl, ENOENT, dgettext(TEXT_DOMAIN, "cannot destroy '%s@%s'"), - zhp->zfs_name, snapname)); + zhp->zfs_name, snapname); + } else { + ret = zfs_destroy_snaps_nvl(zhp, dd.nvl, defer); } + nvlist_free(dd.nvl); + return (ret); +} + +/* + * Destroys all the snapshots named in the nvlist. They must be underneath + * the zhp (either snapshots of it, or snapshots of its descendants). + */ +int +zfs_destroy_snaps_nvl(zfs_handle_t *zhp, nvlist_t *snaps, boolean_t defer) +{ + int ret; + zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 }; (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); - (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value)); + if (zcmd_write_src_nvlist(zhp->zfs_hdl, &zc, snaps) != 0) + return (-1); zc.zc_defer_destroy = defer; - ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY_SNAPS, &zc); + ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY_SNAPS_NVL, &zc); if (ret != 0) { char errbuf[1024]; (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, - "cannot destroy '%s@%s'"), zc.zc_name, snapname); + "cannot destroy snapshots in %s"), zc.zc_name); switch (errno) { case EEXIST: @@ -3083,7 +3278,7 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props) (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, "cannot create '%s'"), target); - /* validate the target name */ + /* validate the target/clone name */ if (!zfs_validate_name(hdl, target, ZFS_TYPE_FILESYSTEM, B_TRUE)) return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf)); @@ -3148,11 +3343,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. */ @@ -3162,7 +3416,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, @@ -3180,7 +3437,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, B_FALSE, 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)); @@ -3189,9 +3468,17 @@ zfs_promote(zfs_handle_t *zhp) if (ret != 0) { int save_errno = errno; + (void) zfs_iter_snapshots(pzhp, B_FALSE, 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); @@ -3200,7 +3487,45 @@ zfs_promote(zfs_handle_t *zhp) default: return (zfs_standard_error(hdl, save_errno, errbuf)); } + } else { + (void) zfs_iter_snapshots(zhp, B_FALSE, 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); } @@ -3264,11 +3589,31 @@ 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); @@ -3348,14 +3693,14 @@ 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); /* - * Destroy all recent snapshots and its dependends. + * Destroy all recent snapshots and their dependents. */ cb.cb_force = force; cb.cb_target = snap->zfs_name; @@ -3371,6 +3716,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); @@ -3408,6 +3755,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) @@ -3420,46 +3771,11 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force) } /* - * Iterate over all dependents for a given dataset. This includes both - * hierarchical dependents (children) and data dependents (snapshots and - * clones). The bulk of the processing occurs in get_dependents() in - * libzfs_graph.c. - */ -int -zfs_iter_dependents(zfs_handle_t *zhp, boolean_t allowrecursion, - zfs_iter_f func, void *data) -{ - char **dependents; - size_t count; - int i; - zfs_handle_t *child; - int ret = 0; - - if (get_dependents(zhp->zfs_hdl, allowrecursion, zhp->zfs_name, - &dependents, &count) != 0) - return (-1); - - for (i = 0; i < count; i++) { - if ((child = make_dataset_handle(zhp->zfs_hdl, - dependents[i])) == NULL) - continue; - - if ((ret = func(child, data)) != 0) - break; - } - - for (i = 0; i < count; i++) - free(dependents[i]); - free(dependents); - - return (ret); -} - -/* * Renames the given dataset. */ int -zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive) +zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive, + boolean_t force_unmount) { int ret; zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 }; @@ -3556,6 +3872,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) { @@ -3570,8 +3887,18 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive) goto error; } + dd.snapname = delim + 1; + + /* We remove any zvol links prior to renaming them */ + verify(nvlist_alloc(&dd.nvl, NV_UNIQUE_NAME, 0) == 0); + ret = zfs_iter_filesystems(zhrp, zfs_check_snap_cb, &dd); + nvlist_free(dd.nvl); + if (ret) { + goto error; + } } else { - if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0, 0)) == NULL) + if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0, + force_unmount ? MS_FORCE : 0)) == NULL) return (-1); if (changelist_haszonedchild(cl)) { @@ -3579,6 +3906,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; } @@ -3617,10 +3945,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); } @@ -3639,19 +3984,130 @@ 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 ENODEV: + /* + * snapdev set to hidden : + * device creation was not permitted (see zvol.c) + * ignore error quietly + */ + 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': %s"), dataset, strerror(errno))); + } + } + + 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); } /* @@ -3763,26 +4219,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) { @@ -3829,7 +4265,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); } } @@ -3901,35 +4337,40 @@ zfs_userspace(zfs_handle_t *zhp, zfs_userquota_prop_t type, zfs_userspace_cb_t func, void *arg) { zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 }; - int error; zfs_useracct_t buf[100]; + libzfs_handle_t *hdl = zhp->zfs_hdl; + int ret; - (void) strncpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); zc.zc_objset_type = type; zc.zc_nvlist_dst = (uintptr_t)buf; - /* CONSTCOND */ - while (1) { + for (;;) { zfs_useracct_t *zua = buf; zc.zc_nvlist_dst_size = sizeof (buf); - error = ioctl(zhp->zfs_hdl->libzfs_fd, - ZFS_IOC_USERSPACE_MANY, &zc); - if (error || zc.zc_nvlist_dst_size == 0) + if (zfs_ioctl(hdl, ZFS_IOC_USERSPACE_MANY, &zc) != 0) { + char errbuf[ZFS_MAXNAMELEN + 32]; + + (void) snprintf(errbuf, sizeof (errbuf), + dgettext(TEXT_DOMAIN, + "cannot get used/quota for %s"), zc.zc_name); + return (zfs_standard_error_fmt(hdl, errno, errbuf)); + } + if (zc.zc_nvlist_dst_size == 0) break; while (zc.zc_nvlist_dst_size > 0) { - error = func(arg, zua->zu_domain, zua->zu_rid, - zua->zu_space); - if (error != 0) - return (error); + if ((ret = func(arg, zua->zu_domain, zua->zu_rid, + zua->zu_space)) != 0) + return (ret); zua++; zc.zc_nvlist_dst_size -= sizeof (zfs_useracct_t); } } - return (error); + return (0); } int @@ -4032,6 +4473,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 (ioctl(hdl->libzfs_fd, 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) {