ZFS: add bookmark renaming

The feature is implemented as an extension of the existing
ZFS_IOC_RENAME ioctl.  Both the userland and the DSL interfaces support
renaming only a single bookmark at a time.  As of now, there is no ZCP
interface to the new functionality.  I am going to add it once the DSL
interface passes a test of time.

This change picks up support for zfs_ioc_namecheck_t::ENTITY_NAME that
was added to ZoL as part of Redacted Send/Receive feature by Paul
Dagnelie <pcd@delphix.com>.  This is needed to allow a bookmark name in
zc_name.

Discussed with:	mahrens
Reviewed by:	bcr (man page)
Sponsored by:	CyberSecure
Differential Revision: https://reviews.freebsd.org/D21795
This commit is contained in:
avg 2019-10-03 11:08:45 +00:00
parent 23b628cbe1
commit a757da83a4
6 changed files with 237 additions and 42 deletions

View File

@ -105,6 +105,9 @@
.Ar snapshot snapshot
.Nm
.Cm rename
.Ar bookmark bookmark
.Nm
.Cm rename
.Fl u
.Op Fl p
.Ar filesystem filesystem
@ -2094,6 +2097,16 @@ 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
.Op Fl r Ns | Ns Fl d Ar depth
.Op Fl Hp

View File

@ -284,6 +284,7 @@ get_usage(zfs_help_t idx)
"<filesystem|volume|snapshot>\n"
"\trename [-f] -p <filesystem|volume> <filesystem|volume>\n"
"\trename -r <snapshot> <snapshot>\n"
"\trename <bookmark> <bookmark>\n"
"\trename -u [-p] <filesystem> <filesystem>"));
case HELP_ROLLBACK:
return (gettext("\trollback [-rRf] <snapshot>\n"));
@ -3254,6 +3255,7 @@ zfs_do_list(int argc, char **argv)
* zfs rename [-f] <fs | snap | vol> <fs | snap | vol>
* zfs rename [-f] -p <fs | vol> <fs | vol>
* zfs rename -r <snap> <snap>
* zfs rename <bmark> <bmark>
* zfs rename -u [-p] <fs> <fs>
*
* Renames the given dataset to another of the same type.
@ -3270,6 +3272,7 @@ zfs_do_rename(int argc, char **argv)
int ret = 0;
int types;
boolean_t parents = B_FALSE;
boolean_t bookmarks = B_FALSE;
char *snapshot = NULL;
/* check options */
@ -3320,7 +3323,7 @@ zfs_do_rename(int argc, char **argv)
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 @@ zfs_do_rename(int argc, char **argv)
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;

View File

@ -4291,17 +4291,18 @@ zfs_rename(zfs_handle_t *zhp, const char *source, const char *target,
/*
* 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 @@ zfs_rename(zfs_handle_t *zhp, const char *source, const char *target,
/*
* 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 @@ zfs_rename(zfs_handle_t *zhp, const char *source, const char *target,
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 @@ zfs_rename(zfs_handle_t *zhp, const char *source, const char *target,
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 @@ zfs_rename(zfs_handle_t *zhp, const char *source, const char *target,
"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);
}

View File

@ -459,3 +459,108 @@ dsl_bookmark_destroy(nvlist_t *bmarks, nvlist_t *errors)
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));
}

View File

@ -41,6 +41,7 @@ int dsl_bookmark_create(nvlist_t *, nvlist_t *);
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 *);

View File

@ -224,7 +224,8 @@ typedef int zfs_secpolicy_func_t(zfs_cmd_t *, nvlist_t *, cred_t *);
typedef enum {
NO_NAME,
POOL_NAME,
DATASET_NAME
DATASET_NAME,
ENTITY_NAME
} zfs_ioc_namecheck_t;
typedef enum {
@ -922,8 +923,21 @@ static int
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 @@ recursive_unmount(const char *fsname, void *arg)
/*
* 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 @@ zfs_ioc_rename(zfs_cmd_t *zc)
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 @@ zfs_ioc_rename(zfs_cmd_t *zc)
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 @@ zfs_ioc_rename(zfs_cmd_t *zc)
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_ioctl_init(void)
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_init(void)
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 @@ pool_status_check(const char *name, zfs_ioc_namecheck_t type,
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);
@ -6725,6 +6773,15 @@ zfsdev_ioctl(struct cdev *dev, u_long zcmd, caddr_t arg, int flag,
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:
break;
}