diff options
author | Brian Behlendorf <[email protected]> | 2009-08-18 11:43:27 -0700 |
---|---|---|
committer | Brian Behlendorf <[email protected]> | 2009-08-18 11:43:27 -0700 |
commit | 45d1cae3b8c949ecc391dd7a5b81963b34c71c29 (patch) | |
tree | 69b1f860eb1f9b1ebdef392760814c5cc089f345 /lib | |
parent | 9babb37438b58e77bad04e820d5702e15b79e6a6 (diff) |
Rebase master to b121
Diffstat (limited to 'lib')
-rw-r--r-- | lib/libzfs/include/libzfs.h | 9 | ||||
-rw-r--r-- | lib/libzfs/libzfs_changelist.c | 36 | ||||
-rw-r--r-- | lib/libzfs/libzfs_dataset.c | 141 | ||||
-rw-r--r-- | lib/libzfs/libzfs_sendrecv.c | 56 | ||||
-rw-r--r-- | lib/libzfs/libzfs_status.c | 15 | ||||
-rw-r--r-- | lib/libzfs/libzfs_util.c | 5 |
6 files changed, 212 insertions, 50 deletions
diff --git a/lib/libzfs/include/libzfs.h b/lib/libzfs/include/libzfs.h index 8d00a3151..f19e398f6 100644 --- a/lib/libzfs/include/libzfs.h +++ b/lib/libzfs/include/libzfs.h @@ -117,6 +117,8 @@ enum { EZFS_NOTSUP, /* ops not supported on this dataset */ EZFS_ACTIVE_SPARE, /* pool has active shared spare devices */ EZFS_UNPLAYED_LOGS, /* log device has unplayed logs */ + EZFS_REFTAG_RELE, /* snapshot release: tag not found */ + EZFS_REFTAG_HOLD, /* snapshot hold: tag already exists */ EZFS_UNKNOWN }; @@ -286,6 +288,7 @@ typedef enum { ZPOOL_STATUS_VERSION_OLDER, /* older on-disk version */ ZPOOL_STATUS_RESILVERING, /* device being resilvered */ ZPOOL_STATUS_OFFLINE_DEV, /* device online */ + ZPOOL_STATUS_REMOVED_DEV, /* removed device */ /* * Finally, the following indicates a healthy pool. @@ -454,8 +457,8 @@ extern int zfs_iter_snapshots(zfs_handle_t *, zfs_iter_f, void *); extern int zfs_create(libzfs_handle_t *, const char *, zfs_type_t, nvlist_t *); extern int zfs_create_ancestors(libzfs_handle_t *, const char *); -extern int zfs_destroy(zfs_handle_t *); -extern int zfs_destroy_snaps(zfs_handle_t *, char *); +extern int zfs_destroy(zfs_handle_t *, boolean_t); +extern int zfs_destroy_snaps(zfs_handle_t *, char *, boolean_t); extern int zfs_clone(zfs_handle_t *, const char *, nvlist_t *); extern int zfs_snapshot(libzfs_handle_t *, const char *, boolean_t, nvlist_t *); extern int zfs_rollback(zfs_handle_t *, zfs_handle_t *, boolean_t); @@ -463,6 +466,8 @@ extern int zfs_rename(zfs_handle_t *, const char *, boolean_t); extern int zfs_send(zfs_handle_t *, const char *, const char *, boolean_t, boolean_t, boolean_t, boolean_t, int); extern int zfs_promote(zfs_handle_t *); +extern int zfs_hold(zfs_handle_t *, const char *, const char *, boolean_t); +extern int zfs_release(zfs_handle_t *, const char *, const char *, boolean_t); typedef int (*zfs_userspace_cb_t)(void *arg, const char *domain, uid_t rid, uint64_t space); diff --git a/lib/libzfs/libzfs_changelist.c b/lib/libzfs/libzfs_changelist.c index 6fa196710..ff438b3d7 100644 --- a/lib/libzfs/libzfs_changelist.c +++ b/lib/libzfs/libzfs_changelist.c @@ -508,6 +508,14 @@ change_one(zfs_handle_t *zhp, void *data) &idx); uu_list_insert(clp->cl_list, cn, idx); } else { + /* + * Add this child to beginning of the list. Children + * below this one in the hierarchy will get added above + * this one in the list. This produces a list in + * reverse dataset name order. + * This is necessary when the original mountpoint + * is legacy or none. + */ ASSERT(!clp->cl_alldependents); verify(uu_list_insert_before(clp->cl_list, uu_list_first(clp->cl_list), cn) == 0); @@ -574,6 +582,7 @@ changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags, zfs_handle_t *temp; char property[ZFS_MAXPROPLEN]; uu_compare_fn_t *compare = NULL; + boolean_t legacy = B_FALSE; if ((clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t))) == NULL) return (NULL); @@ -586,8 +595,19 @@ changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags, if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED || prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS || prop == ZFS_PROP_SHARESMB) { - compare = compare_mountpoints; - clp->cl_sorted = B_TRUE; + + if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, + property, sizeof (property), + NULL, NULL, 0, B_FALSE) == 0 && + (strcmp(property, "legacy") == 0 || + strcmp(property, "none") == 0)) { + + legacy = B_TRUE; + } + if (!legacy) { + compare = compare_mountpoints; + clp->cl_sorted = B_TRUE; + } } clp->cl_pool = uu_list_pool_create("changelist_pool", @@ -695,6 +715,12 @@ changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags, (void) uu_list_find(clp->cl_list, cn, NULL, &idx); uu_list_insert(clp->cl_list, cn, idx); } else { + /* + * Add the target dataset to the end of the list. + * The list is not really unsorted. The list will be + * in reverse dataset name order. This is necessary + * when the original mountpoint is legacy or none. + */ verify(uu_list_insert_after(clp->cl_list, uu_list_last(clp->cl_list), cn) == 0); } @@ -703,11 +729,7 @@ changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags, * If the mountpoint property was previously 'legacy', or 'none', * record it as the behavior of changelist_postfix() will be different. */ - if ((clp->cl_prop == ZFS_PROP_MOUNTPOINT) && - (zfs_prop_get(zhp, prop, property, sizeof (property), - NULL, NULL, 0, B_FALSE) == 0 && - (strcmp(property, "legacy") == 0 || - strcmp(property, "none") == 0))) { + if ((clp->cl_prop == ZFS_PROP_MOUNTPOINT) && legacy) { /* * do not automatically mount ex-legacy datasets if * we specifically set canmount to noauto diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index ac9122605..ab9ba6b80 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -47,6 +47,7 @@ #include <ucred.h> #include <idmap.h> #include <aclutils.h> +#include <directory.h> #include <sys/spa.h> #include <sys/zap.h> @@ -1674,21 +1675,13 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src, (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_OBJSET_ZPLPROPS, &zc)) { zcmd_free_nvlists(&zc); - zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, - "unable to get %s property"), - zfs_prop_to_name(prop)); - return (zfs_error(zhp->zfs_hdl, EZFS_BADVERSION, - dgettext(TEXT_DOMAIN, "internal error"))); + return (-1); } if (zcmd_read_dst_nvlist(zhp->zfs_hdl, &zc, &zplprops) != 0 || nvlist_lookup_uint64(zplprops, zfs_prop_to_name(prop), val) != 0) { zcmd_free_nvlists(&zc); - zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, - "unable to get %s property"), - zfs_prop_to_name(prop)); - return (zfs_error(zhp->zfs_hdl, EZFS_NOMEM, - dgettext(TEXT_DOMAIN, "internal error"))); + return (-1); } if (zplprops) nvlist_free(zplprops); @@ -2074,6 +2067,7 @@ userquota_propname_decode(const char *propname, boolean_t zoned, { zfs_userquota_prop_t type; char *cp, *end; + char *numericsid = NULL; boolean_t isuser; domain[0] = '\0'; @@ -2096,33 +2090,41 @@ userquota_propname_decode(const char *propname, boolean_t zoned, if (strchr(cp, '@')) { /* * It's a SID name (eg "user@domain") that needs to be - * turned into S-1-domainID-RID. There should be a - * better way to do this, but for now just translate it - * to the (possibly ephemeral) uid and then back to the - * SID. This is like getsidname(noresolve=TRUE). + * turned into S-1-domainID-RID. */ - uid_t id; - idmap_rid_t rid; - char *mapdomain; - + directory_error_t e; if (zoned && getzoneid() == GLOBAL_ZONEID) return (ENOENT); - if (sid_to_id(cp, isuser, &id) != 0) + if (isuser) { + e = directory_sid_from_user_name(NULL, + cp, &numericsid); + } else { + e = directory_sid_from_group_name(NULL, + cp, &numericsid); + } + if (e != NULL) { + directory_error_free(e); return (ENOENT); - if (idmap_id_to_numeric_domain_rid(id, isuser, - &mapdomain, &rid) != 0) + } + if (numericsid == NULL) return (ENOENT); - (void) strlcpy(domain, mapdomain, domainlen); - *ridp = rid; - } else if (strncmp(cp, "S-1-", 4) == 0) { + 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) strcpy(domain, cp); + (void) strlcpy(domain, cp, domainlen); cp = strrchr(domain, '-'); *cp = '\0'; cp++; errno = 0; *ridp = strtoull(cp, &end, 10); + if (numericsid) { + free(numericsid); + numericsid = NULL; + } if (errno != 0 || *end != '\0') return (EINVAL); } else if (!isdigit(*cp)) { @@ -2158,13 +2160,14 @@ userquota_propname_decode(const char *propname, boolean_t zoned, if (idmap_id_to_numeric_domain_rid(id, isuser, &mapdomain, &rid) != 0) return (ENOENT); - (void) strcpy(domain, mapdomain); + (void) strlcpy(domain, mapdomain, domainlen); *ridp = rid; } else { *ridp = id; } } + ASSERT3P(numericsid, ==, NULL); return (0); } @@ -2763,7 +2766,7 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type, * isn't mounted, and that there are no active dependents. */ int -zfs_destroy(zfs_handle_t *zhp) +zfs_destroy(zfs_handle_t *zhp, boolean_t defer) { zfs_cmd_t zc = { 0 }; @@ -2787,6 +2790,7 @@ zfs_destroy(zfs_handle_t *zhp) zc.zc_objset_type = DMU_OST_ZFS; } + zc.zc_defer_destroy = defer; if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY, &zc) != 0) { return (zfs_standard_error_fmt(zhp->zfs_hdl, errno, dgettext(TEXT_DOMAIN, "cannot destroy '%s'"), @@ -2843,7 +2847,7 @@ zfs_remove_link_cb(zfs_handle_t *zhp, void *arg) * Destroys all snapshots with the given name in zhp & descendants. */ int -zfs_destroy_snaps(zfs_handle_t *zhp, char *snapname) +zfs_destroy_snaps(zfs_handle_t *zhp, char *snapname, boolean_t defer) { zfs_cmd_t zc = { 0 }; int ret; @@ -2860,6 +2864,7 @@ zfs_destroy_snaps(zfs_handle_t *zhp, char *snapname) (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value)); + zc.zc_defer_destroy = defer; ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DESTROY_SNAPS, &zc); if (ret != 0) { @@ -3275,7 +3280,7 @@ rollback_destroy(zfs_handle_t *zhp, void *data) logstr = zhp->zfs_hdl->libzfs_log_str; zhp->zfs_hdl->libzfs_log_str = NULL; - cbp->cb_error |= zfs_destroy(zhp); + cbp->cb_error |= zfs_destroy(zhp, B_FALSE); zhp->zfs_hdl->libzfs_log_str = logstr; } } else { @@ -3289,7 +3294,7 @@ rollback_destroy(zfs_handle_t *zhp, void *data) zfs_close(zhp); return (0); } - if (zfs_destroy(zhp) != 0) + if (zfs_destroy(zhp, B_FALSE) != 0) cbp->cb_error = B_TRUE; else changelist_remove(clp, zhp->zfs_name); @@ -4089,3 +4094,79 @@ zfs_userspace(zfs_handle_t *zhp, zfs_userquota_prop_t type, return (error); } + +int +zfs_hold(zfs_handle_t *zhp, const char *snapname, const char *tag, + boolean_t recursive) +{ + zfs_cmd_t zc = { 0 }; + libzfs_handle_t *hdl = zhp->zfs_hdl; + + (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value)); + (void) strlcpy(zc.zc_string, tag, sizeof (zc.zc_string)); + zc.zc_cookie = recursive; + + if (zfs_ioctl(hdl, ZFS_IOC_HOLD, &zc) != 0) { + char errbuf[ZFS_MAXNAMELEN+32]; + + /* + * if it was recursive, the one that actually failed will be in + * zc.zc_name. + */ + (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, + "cannot hold '%s@%s'"), zc.zc_name, snapname); + switch (errno) { + case ENOTSUP: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "pool must be upgraded")); + return (zfs_error(hdl, EZFS_BADVERSION, errbuf)); + case EINVAL: + return (zfs_error(hdl, EZFS_BADTYPE, errbuf)); + case EEXIST: + return (zfs_error(hdl, EZFS_REFTAG_HOLD, errbuf)); + default: + return (zfs_standard_error_fmt(hdl, errno, errbuf)); + } + } + + return (0); +} + +int +zfs_release(zfs_handle_t *zhp, const char *snapname, const char *tag, + boolean_t recursive) +{ + zfs_cmd_t zc = { 0 }; + libzfs_handle_t *hdl = zhp->zfs_hdl; + + (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value)); + (void) strlcpy(zc.zc_string, tag, sizeof (zc.zc_string)); + zc.zc_cookie = recursive; + + if (zfs_ioctl(hdl, ZFS_IOC_RELEASE, &zc) != 0) { + char errbuf[ZFS_MAXNAMELEN+32]; + + /* + * if it was recursive, the one that actually failed will be in + * zc.zc_name. + */ + (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, + "cannot release '%s@%s'"), zc.zc_name, snapname); + switch (errno) { + case ESRCH: + return (zfs_error(hdl, EZFS_REFTAG_RELE, errbuf)); + case ENOTSUP: + zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, + "pool must be upgraded")); + return (zfs_error(hdl, EZFS_BADVERSION, errbuf)); + case EINVAL: + return (zfs_error(hdl, EZFS_BADTYPE, errbuf)); + default: + return (zfs_standard_error_fmt(hdl, errno, errbuf)); + } + } + + return (0); +} diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index 612a09914..1ffb6294c 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -113,6 +113,9 @@ fsavl_destroy(avl_tree_t *avl) free(avl); } +/* + * Given an nvlist, produce an avl tree of snapshots, ordered by guid + */ static avl_tree_t * fsavl_create(nvlist_t *fss) { @@ -243,7 +246,9 @@ send_iterate_prop(zfs_handle_t *zhp, nvlist_t *nv) continue; verify(nvpair_value_nvlist(elem, &propnv) == 0); - if (prop == ZFS_PROP_QUOTA || prop == ZFS_PROP_RESERVATION) { + if (prop == ZFS_PROP_QUOTA || prop == ZFS_PROP_RESERVATION || + prop == ZFS_PROP_REFQUOTA || + prop == ZFS_PROP_REFRESERVATION) { /* these guys are modifyable, but have no source */ uint64_t value; verify(nvlist_lookup_uint64(propnv, @@ -274,6 +279,11 @@ send_iterate_prop(zfs_handle_t *zhp, nvlist_t *nv) } } +/* + * recursively generate nvlists describing datasets. See comment + * for the data structure send_data_t above for description of contents + * of the nvlist. + */ static int send_iterate_fs(zfs_handle_t *zhp, void *arg) { @@ -689,9 +699,20 @@ again: } /* - * Dumps a backup of tosnap, incremental from fromsnap if it isn't NULL. - * If 'doall', dump all intermediate snaps. - * If 'replicate', dump special header and do recursively. + * Generate a send stream for the dataset identified by the argument zhp. + * + * The content of the send stream is the snapshot identified by + * 'tosnap'. Incremental streams are requested in two ways: + * - from the snapshot identified by "fromsnap" (if non-null) or + * - from the origin of the dataset identified by zhp, which must + * be a clone. In this case, "fromsnap" is null and "fromorigin" + * is TRUE. + * + * The send stream is recursive (i.e. dumps a hierarchy of snapshots) and + * uses a special header (with a version field of DMU_BACKUP_HEADER_VERSION) + * if "replicate" is set. If "doall" is set, dump all the intermediate + * snapshots. The DMU_BACKUP_HEADER_VERSION header is used in the "doall" + * case too. */ int zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, @@ -900,11 +921,12 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname, if (err) return (err); + zc.zc_objset_type = DMU_OST_ZFS; + (void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name)); + if (tryname) { (void) strcpy(newname, tryname); - zc.zc_objset_type = DMU_OST_ZFS; - (void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name)); (void) strlcpy(zc.zc_value, tryname, sizeof (zc.zc_value)); if (flags.verbose) { @@ -959,12 +981,18 @@ recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen, int err = 0; prop_changelist_t *clp; zfs_handle_t *zhp; + boolean_t defer = B_FALSE; + int spa_version; zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET); if (zhp == NULL) return (-1); clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, flags.force ? MS_FORCE : 0); + if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT && + zfs_spa_version(zhp, &spa_version) == 0 && + spa_version >= SPA_VERSION_USERREFS) + defer = B_TRUE; zfs_close(zhp); if (clp == NULL) return (-1); @@ -973,12 +1001,12 @@ recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen, return (err); zc.zc_objset_type = DMU_OST_ZFS; + zc.zc_defer_destroy = defer; (void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name)); if (flags.verbose) (void) printf("attempting destroy %s\n", zc.zc_name); err = ioctl(hdl->libzfs_fd, ZFS_IOC_DESTROY, &zc); - if (err == 0) { if (flags.verbose) (void) printf("success\n"); @@ -988,7 +1016,12 @@ recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen, (void) changelist_postfix(clp); changelist_free(clp); - if (err != 0) + /* + * Deferred destroy should always succeed. Since we can't tell + * if it destroyed the dataset or just marked it for deferred + * destroy, always do the rename just in case. + */ + if (err != 0 || defer) err = recv_rename(hdl, name, NULL, baselen, newname, flags); return (err); @@ -1775,11 +1808,13 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, /* We can't do online recv in this case */ clp = changelist_gather(zhp, ZFS_PROP_NAME, 0, 0); if (clp == NULL) { + zfs_close(zhp); zcmd_free_nvlists(&zc); return (-1); } if (changelist_prefix(clp) != 0) { changelist_free(clp); + zfs_close(zhp); zcmd_free_nvlists(&zc); return (-1); } @@ -1936,7 +1971,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, * (if created, or if we tore them down to do an incremental * restore), and the /dev links for the new snapshot (if * created). Also mount any children of the target filesystem - * if we did an incremental receive. + * if we did a replication receive (indicated by stream_avl + * being non-NULL). */ cp = strchr(zc.zc_value, '@'); if (cp && (ioctl_err == 0 || !newfs)) { @@ -1952,7 +1988,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, if (err == 0 && ioctl_err == 0) err = zvol_create_link(hdl, zc.zc_value); - } else if (newfs) { + } else if (newfs || stream_avl) { /* * Track the first/top of hierarchy fs, * for mounting and sharing later. diff --git a/lib/libzfs/libzfs_status.c b/lib/libzfs/libzfs_status.c index c7eb04e74..44faf02af 100644 --- a/lib/libzfs/libzfs_status.c +++ b/lib/libzfs/libzfs_status.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -104,6 +104,13 @@ vdev_offlined(uint64_t state, uint64_t aux, uint64_t errs) return (state == VDEV_STATE_OFFLINE); } +/* ARGSUSED */ +static int +vdev_removed(uint64_t state, uint64_t aux, uint64_t errs) +{ + return (state == VDEV_STATE_REMOVED); +} + /* * Detect if any leaf devices that have seen errors or could not be opened. */ @@ -276,6 +283,12 @@ check_status(nvlist_t *config, boolean_t isimport) return (ZPOOL_STATUS_OFFLINE_DEV); /* + * Removed device + */ + if (find_vdev_problem(nvroot, vdev_removed)) + return (ZPOOL_STATUS_REMOVED_DEV); + + /* * Currently resilvering */ if (!vs->vs_scrub_complete && vs->vs_scrub_type == POOL_SCRUB_RESILVER) diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c index 30829d50d..4da0fb44b 100644 --- a/lib/libzfs/libzfs_util.c +++ b/lib/libzfs/libzfs_util.c @@ -213,6 +213,11 @@ libzfs_error_description(libzfs_handle_t *hdl) case EZFS_UNPLAYED_LOGS: return (dgettext(TEXT_DOMAIN, "log device has unplayed intent " "logs")); + case EZFS_REFTAG_RELE: + return (dgettext(TEXT_DOMAIN, "no such tag on this dataset")); + case EZFS_REFTAG_HOLD: + return (dgettext(TEXT_DOMAIN, "tag already exists on this " + "dataset")); case EZFS_UNKNOWN: return (dgettext(TEXT_DOMAIN, "unknown error")); default: |