Index: head/cddl/contrib/opensolaris/cmd/zfs/zfs.8 =================================================================== --- head/cddl/contrib/opensolaris/cmd/zfs/zfs.8 +++ head/cddl/contrib/opensolaris/cmd/zfs/zfs.8 @@ -105,6 +105,9 @@ .Ar snapshot snapshot .Nm .Cm rename +.Ar bookmark bookmark +.Nm +.Cm rename .Fl u .Op Fl p .Ar filesystem filesystem @@ -2092,6 +2095,16 @@ .Pp Recursively rename the snapshots of all descendent datasets. Snapshots are the only dataset that can be renamed recursively. +.It Xo +.Nm +.Cm rename +.Ar bookmark bookmark +.Xc +.Pp +Renames the given bookmark. +Bookmarks can only be renamed within the parent file system or volume. +When renaming a bookmark, the parent file system or volume of the bookmark +does not need to be specified as part of the second argument. .It Xo .Nm .Cm list Index: head/cddl/contrib/opensolaris/cmd/zfs/zfs_main.c =================================================================== --- head/cddl/contrib/opensolaris/cmd/zfs/zfs_main.c +++ head/cddl/contrib/opensolaris/cmd/zfs/zfs_main.c @@ -284,6 +284,7 @@ "\n" "\trename [-f] -p \n" "\trename -r \n" + "\trename \n" "\trename -u [-p] ")); case HELP_ROLLBACK: return (gettext("\trollback [-rRf] \n")); @@ -3254,6 +3255,7 @@ * zfs rename [-f] * zfs rename [-f] -p * zfs rename -r + * zfs rename * zfs rename -u [-p] * * Renames the given dataset to another of the same type. @@ -3270,6 +3272,7 @@ int ret = 0; int types; boolean_t parents = B_FALSE; + boolean_t bookmarks = B_FALSE; char *snapshot = NULL; /* check options */ @@ -3320,7 +3323,7 @@ usage(B_FALSE); } - if (flags.recurse && strchr(argv[0], '@') == 0) { + if (flags.recurse && strchr(argv[0], '@') == NULL) { (void) fprintf(stderr, gettext("source dataset for recursive " "rename must be a snapshot\n")); usage(B_FALSE); @@ -3332,10 +3335,22 @@ usage(B_FALSE); } + if (strchr(argv[0], '#') != NULL) + bookmarks = B_TRUE; + + if (bookmarks && (flags.nounmount || flags.recurse || + flags.forceunmount || parents)) { + (void) fprintf(stderr, gettext("options are not supported " + "for renaming bookmarks\n")); + usage(B_FALSE); + } + if (flags.nounmount) types = ZFS_TYPE_FILESYSTEM; else if (parents) types = ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME; + else if (bookmarks) + types = ZFS_TYPE_BOOKMARK; else types = ZFS_TYPE_DATASET; Index: head/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c =================================================================== --- head/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c +++ head/cddl/contrib/opensolaris/lib/libzfs/common/libzfs_dataset.c @@ -4291,17 +4291,18 @@ /* * Make sure the target name is valid */ - if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) { - if ((strchr(target, '@') == NULL) || - *target == '@') { + if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT || + zhp->zfs_type == ZFS_TYPE_BOOKMARK) { + const char sep = zhp->zfs_type == ZFS_TYPE_SNAPSHOT ? '@' : '#'; + + if ((strchr(target, sep) == NULL) || *target == sep) { /* * Snapshot target name is abbreviated, * reconstruct full dataset name */ - (void) strlcpy(parent, zhp->zfs_name, - sizeof (parent)); - delim = strchr(parent, '@'); - if (strchr(target, '@') == NULL) + (void) strlcpy(parent, zhp->zfs_name, sizeof (parent)); + delim = strchr(parent, sep); + if (strchr(target, sep) == NULL) *(++delim) = '\0'; else *delim = '\0'; @@ -4311,12 +4312,13 @@ /* * Make sure we're renaming within the same dataset. */ - delim = strchr(target, '@'); + delim = strchr(target, sep); if (strncmp(zhp->zfs_name, target, delim - target) - != 0 || zhp->zfs_name[delim - target] != '@') { + != 0 || zhp->zfs_name[delim - target] != sep) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, - "snapshots must be part of same " - "dataset")); + "%s must be part of same dataset"), + zhp->zfs_type == ZFS_TYPE_SNAPSHOT ? + "snapshots" : "bookmarks"); return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf)); } @@ -4379,7 +4381,6 @@ flags.nounmount = B_TRUE; } if (flags.recurse) { - parentname = zfs_strdup(zhp->zfs_hdl, zhp->zfs_name); if (parentname == NULL) { ret = -1; @@ -4392,7 +4393,8 @@ ret = -1; goto error; } - } else if (zhp->zfs_type != ZFS_TYPE_SNAPSHOT) { + } else if (zhp->zfs_type != ZFS_TYPE_SNAPSHOT && + zhp->zfs_type != ZFS_TYPE_BOOKMARK) { if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, flags.nounmount ? CL_GATHER_DONT_UNMOUNT : 0, flags.forceunmount ? MS_FORCE : 0)) == NULL) { @@ -4437,6 +4439,8 @@ "a child dataset already has a snapshot " "with the new name")); (void) zfs_error(hdl, EZFS_EXISTS, errbuf); + } else if (errno == EINVAL) { + (void) zfs_error(hdl, EZFS_INVALIDNAME, errbuf); } else { (void) zfs_standard_error(zhp->zfs_hdl, errno, errbuf); } Index: head/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/dsl_bookmark.c =================================================================== --- head/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/dsl_bookmark.c +++ head/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/dsl_bookmark.c @@ -459,3 +459,108 @@ fnvlist_free(dbda.dbda_success); return (rv); } + +typedef struct dsl_bookmark_rename_arg { + const char *dbra_fsname; + const char *dbra_oldname; + const char *dbra_newname; +} dsl_bookmark_rename_arg_t; + +static int +dsl_bookmark_rename_check(void *arg, dmu_tx_t *tx) +{ + dsl_bookmark_rename_arg_t *dbra = arg; + dsl_pool_t *dp = dmu_tx_pool(tx); + dsl_dataset_t *ds; + zfs_bookmark_phys_t bmark_phys; + int error; + + if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)) + return (SET_ERROR(ENOTSUP)); + + /* Check validity and the full length of the new bookmark name. */ + if (zfs_component_namecheck(dbra->dbra_newname, NULL, NULL)) + return (SET_ERROR(EINVAL)); + if (strlen(dbra->dbra_fsname) + strlen(dbra->dbra_newname) + 1 >= + ZFS_MAX_DATASET_NAME_LEN) + return (SET_ERROR(ENAMETOOLONG)); + + error = dsl_dataset_hold(dp, dbra->dbra_fsname, FTAG, &ds); + if (error != 0) + return (error); + if (ds->ds_is_snapshot) { + dsl_dataset_rele(ds, FTAG); + return (SET_ERROR(EINVAL)); + } + error = dsl_dataset_bmark_lookup(ds, dbra->dbra_oldname, &bmark_phys); + if (error != 0) { + dsl_dataset_rele(ds, FTAG); + return (error); + } + + error = dsl_dataset_bmark_lookup(ds, dbra->dbra_newname, &bmark_phys); + dsl_dataset_rele(ds, FTAG); + if (error == 0) + return (SET_ERROR(EEXIST)); + if (error != ESRCH) + return (error); + return (0); +} + +static void +dsl_bookmark_rename_sync(void *arg, dmu_tx_t *tx) +{ + zfs_bookmark_phys_t bmark_phys; + dsl_bookmark_rename_arg_t *dbra = arg; + dsl_pool_t *dp = dmu_tx_pool(tx); + objset_t *mos; + dsl_dataset_t *ds; + uint64_t bmark_zapobj; + uint64_t int_size, num_ints; + matchtype_t mt = 0; + int error; + + ASSERT(spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)); + VERIFY0(dsl_dataset_hold(dp, dbra->dbra_fsname, FTAG, &ds)); + + mos = ds->ds_dir->dd_pool->dp_meta_objset; + bmark_zapobj = ds->ds_bookmarks; + + if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET) + mt = MT_NORMALIZE; + + VERIFY0(zap_length(mos, bmark_zapobj, dbra->dbra_oldname, + &int_size, &num_ints)); + ASSERT3U(int_size, ==, sizeof (uint64_t)); + VERIFY0(zap_lookup_norm(mos, bmark_zapobj, dbra->dbra_oldname, int_size, + num_ints, &bmark_phys, mt, NULL, 0, NULL)); + VERIFY0(zap_remove_norm(mos, bmark_zapobj, dbra->dbra_oldname, mt, tx)); + + VERIFY0(zap_add(mos, bmark_zapobj, dbra->dbra_newname, int_size, + num_ints, &bmark_phys, tx)); + + spa_history_log_internal_ds(ds, "rename bookmark", tx, + "#%s -> #%s creation_txg=%llu", + dbra->dbra_oldname, dbra->dbra_newname, + (longlong_t)bmark_phys.zbm_creation_txg); + + dsl_dataset_rele(ds, FTAG); +} + +/* + * The bookmarks must all be in the same pool. + */ +int +dsl_bookmark_rename(const char *fsname, const char *oldbmark, + const char *newbmark) +{ + dsl_bookmark_rename_arg_t dbra; + + dbra.dbra_fsname = fsname; + dbra.dbra_oldname = oldbmark; + dbra.dbra_newname = newbmark; + + return (dsl_sync_task(fsname, dsl_bookmark_rename_check, + dsl_bookmark_rename_sync, &dbra, 1, ZFS_SPACE_CHECK_NORMAL)); +} + Index: head/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/sys/dsl_bookmark.h =================================================================== --- head/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/sys/dsl_bookmark.h +++ head/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/sys/dsl_bookmark.h @@ -41,6 +41,7 @@ int dsl_get_bookmarks(const char *, nvlist_t *, nvlist_t *); int dsl_get_bookmarks_impl(dsl_dataset_t *, nvlist_t *, nvlist_t *); int dsl_bookmark_destroy(nvlist_t *, nvlist_t *); +int dsl_bookmark_rename(const char *fs, const char *from, const char *to); int dsl_bookmark_lookup(struct dsl_pool *, const char *, struct dsl_dataset *, zfs_bookmark_phys_t *); Index: head/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c =================================================================== --- head/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c +++ head/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_ioctl.c @@ -224,7 +224,8 @@ typedef enum { NO_NAME, POOL_NAME, - DATASET_NAME + DATASET_NAME, + ENTITY_NAME } zfs_ioc_namecheck_t; typedef enum { @@ -922,8 +923,21 @@ zfs_secpolicy_rename(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr) { char *at = NULL; + char *pound; int error; + if ((pound = strchr(zc->zc_name, '#')) != NULL) { + *pound = '\0'; + error = zfs_secpolicy_write_perms(zc->zc_name, + ZFS_DELEG_PERM_RENAME, cr); + if (error == 0) { + error = zfs_secpolicy_write_perms(zc->zc_name, + ZFS_DELEG_PERM_BOOKMARK, cr); + } + *pound = '#'; + return (error); + } + if ((zc->zc_cookie & 1) != 0) { /* * This is recursive rename, so the starting snapshot might @@ -4020,8 +4034,8 @@ /* * inputs: - * zc_name old name of dataset - * zc_value new name of dataset + * zc_name old name of dataset or bookmark + * zc_value new name of dataset or bookmark * zc_cookie recursive flag (only valid for snapshots) * * outputs: none @@ -4032,7 +4046,7 @@ objset_t *os; dmu_objset_type_t ost; boolean_t recursive = zc->zc_cookie & 1; - char *at; + char *pos, *pos2; boolean_t allow_mounted = B_TRUE; int err; @@ -4040,9 +4054,34 @@ allow_mounted = (zc->zc_cookie & 2) != 0; #endif - /* "zfs rename" from and to ...%recv datasets should both fail */ zc->zc_name[sizeof (zc->zc_name) - 1] = '\0'; zc->zc_value[sizeof (zc->zc_value) - 1] = '\0'; + + pos = strchr(zc->zc_name, '#'); + if (pos != NULL) { + /* Bookmarks must be in same fs. */ + pos2 = strchr(zc->zc_value, '#'); + if (pos2 == NULL) + return (SET_ERROR(EINVAL)); + + /* Recursive flag is not supported yet. */ + if (recursive) + return (SET_ERROR(ENOTSUP)); + + *pos = '\0'; + *pos2 = '\0'; + if (strcmp(zc->zc_name, zc->zc_value) == 0) { + err = dsl_bookmark_rename(zc->zc_name, + pos + 1, pos2 + 1); + } else { + err = SET_ERROR(EXDEV); + } + *pos = '#'; + *pos2 = '#'; + return (err); + } + + /* "zfs rename" from and to ...%recv datasets should both fail */ if (dataset_namecheck(zc->zc_name, NULL, NULL) != 0 || dataset_namecheck(zc->zc_value, NULL, NULL) != 0 || strchr(zc->zc_name, '%') || strchr(zc->zc_value, '%')) @@ -4054,28 +4093,30 @@ ost = dmu_objset_type(os); dmu_objset_rele(os, FTAG); - at = strchr(zc->zc_name, '@'); - if (at != NULL) { - /* snaps must be in same fs */ - int error; - - if (strncmp(zc->zc_name, zc->zc_value, at - zc->zc_name + 1)) - return (SET_ERROR(EXDEV)); - *at = '\0'; - if (ost == DMU_OST_ZFS && !allow_mounted) { - error = dmu_objset_find(zc->zc_name, - recursive_unmount, at + 1, - recursive ? DS_FIND_CHILDREN : 0); - if (error != 0) { - *at = '@'; - return (error); + pos = strchr(zc->zc_name, '@'); + if (pos != NULL) { + /* Snapshots must be in same fs. */ + pos2 = strchr(zc->zc_value, '@'); + if (pos2 == NULL) + return (SET_ERROR(EINVAL)); + *pos = '\0'; + *pos2 = '\0'; + if (strcmp(zc->zc_name, zc->zc_value) != 0) { + err = SET_ERROR(EXDEV); + } else { + if (ost == DMU_OST_ZFS && !allow_mounted) { + err = dmu_objset_find(zc->zc_name, + recursive_unmount, pos + 1, + recursive ? DS_FIND_CHILDREN : 0); } + if (err == 0) { + err = dsl_dataset_rename_snapshot(zc->zc_name, + pos + 1, pos2 + 1, recursive); + } } - error = dsl_dataset_rename_snapshot(zc->zc_name, - at + 1, strchr(zc->zc_value, '@') + 1, recursive); - *at = '@'; - - return (error); + *pos = '@'; + *pos2 = '@'; + return (err); } else { #ifdef illumos if (ost == DMU_OST_ZVOL) @@ -6352,8 +6393,6 @@ zfs_secpolicy_none); zfs_ioctl_register_dataset_modify(ZFS_IOC_DESTROY, zfs_ioc_destroy, zfs_secpolicy_destroy); - zfs_ioctl_register_dataset_modify(ZFS_IOC_RENAME, zfs_ioc_rename, - zfs_secpolicy_rename); zfs_ioctl_register_dataset_modify(ZFS_IOC_RECV, zfs_ioc_recv, zfs_secpolicy_recv); zfs_ioctl_register_dataset_modify(ZFS_IOC_PROMOTE, zfs_ioc_promote, @@ -6363,6 +6402,14 @@ zfs_ioctl_register_dataset_modify(ZFS_IOC_SET_FSACL, zfs_ioc_set_fsacl, zfs_secpolicy_set_fsacl); + /* + * Not using zfs_ioctl_register_dataset_modify as DATASET_NAME check + * won't allow a bookmark name. + */ + zfs_ioctl_register_legacy(ZFS_IOC_RENAME, zfs_ioc_rename, + zfs_secpolicy_rename, ENTITY_NAME, B_TRUE, + POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY); + zfs_ioctl_register_dataset_nolog(ZFS_IOC_SHARE, zfs_ioc_share, zfs_secpolicy_share, POOL_CHECK_NONE); zfs_ioctl_register_dataset_nolog(ZFS_IOC_SMB_ACL, zfs_ioc_smb_acl, @@ -6392,7 +6439,8 @@ spa_t *spa; int error; - ASSERT(type == POOL_NAME || type == DATASET_NAME); + ASSERT(type == POOL_NAME || type == DATASET_NAME || + type == ENTITY_NAME); if (check & POOL_CHECK_NONE) return (0); @@ -6723,6 +6771,15 @@ else error = pool_status_check(zc->zc_name, vec->zvec_namecheck, vec->zvec_pool_check); + break; + + case ENTITY_NAME: + if (entity_namecheck(zc->zc_name, NULL, NULL) != 0) { + error = SET_ERROR(EINVAL); + } else { + error = pool_status_check(zc->zc_name, + vec->zvec_namecheck, vec->zvec_pool_check); + } break; case NO_NAME: