Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 84 additions & 12 deletions cmd/zfs/zfs_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -410,11 +410,14 @@ get_usage(zfs_help_t idx)
"\tproject -C [-k] [-r] <directory ...>\n"
"\tproject [-p id] [-r] [-s] <directory ...>\n"));
case HELP_HOLD:
return (gettext("\thold [-r] <tag> <snapshot> ...\n"));
return (gettext("\thold [-r] <tag> "
"<filesystem|volume|snapshot> ...\n"));
case HELP_HOLDS:
return (gettext("\tholds [-rHp] <snapshot> ...\n"));
return (gettext("\tholds [-rHp] "
"<filesystem|volume|snapshot> ...\n"));
case HELP_RELEASE:
return (gettext("\trelease [-r] <tag> <snapshot> ...\n"));
return (gettext("\trelease [-r] <tag> "
"<filesystem|volume|snapshot> ...\n"));
case HELP_DIFF:
return (gettext("\tdiff [-FHth] <snapshot> "
"[snapshot|filesystem]\n"));
Expand Down Expand Up @@ -6677,6 +6680,63 @@ zfs_do_unallow(int argc, char **argv)
return (zfs_do_allow_unallow_impl(argc, argv, B_TRUE));
}

typedef struct dataset_hold_arg {
nvlist_t *nvl;
const char *tag;
boolean_t holding;
boolean_t recursive;
} dataset_hold_arg_t;

static int
zfs_dataset_hold_rele_one(zfs_handle_t *zhp, void *arg)
{
dataset_hold_arg_t *ha = arg;
int rv = 0;
const char *name = zfs_get_name(zhp);

if (ha->holding) {
fnvlist_add_string(ha->nvl, name, ha->tag);
} else {
nvlist_t *torelease = fnvlist_alloc();
fnvlist_add_boolean(torelease, ha->tag);
fnvlist_add_nvlist(ha->nvl, name, torelease);
fnvlist_free(torelease);
}

if (ha->recursive)
rv = zfs_iter_filesystems_v2(zhp, 0,
zfs_dataset_hold_rele_one, ha);
zfs_close(zhp);
return (rv);
}

static int
zfs_dataset_hold_rele_recurse(zfs_handle_t *zhp, const char *tag,
boolean_t holding, boolean_t recursive)
{
dataset_hold_arg_t ha = {
.nvl = fnvlist_alloc(),
.tag = tag,
.holding = holding,
.recursive = recursive,
};
int ret;

ret = zfs_dataset_hold_rele_one(zfs_handle_dup(zhp), &ha);
if (ret != 0) {
fnvlist_free(ha.nvl);
return (ret);
}

if (holding)
ret = zfs_hold_nvl(zhp, -1, ha.nvl);
else
ret = lzc_release(ha.nvl, NULL);

fnvlist_free(ha.nvl);
return (ret);
}

static int
zfs_do_hold_rele_impl(int argc, char **argv, boolean_t holding)
{
Expand Down Expand Up @@ -6725,9 +6785,16 @@ zfs_do_hold_rele_impl(int argc, char **argv, boolean_t holding)

delim = strchr(path, '@');
if (delim == NULL) {
(void) fprintf(stderr,
gettext("'%s' is not a snapshot\n"), path);
++errors;
zhp = zfs_open(g_zfs, path,
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
if (zhp == NULL) {
++errors;
continue;
}
if (zfs_dataset_hold_rele_recurse(zhp, tag, holding,
recursive) != 0)
++errors;
zfs_close(zhp);
continue;
}
(void) strlcpy(parent, path, MIN(sizeof (parent),
Expand Down Expand Up @@ -6865,7 +6932,7 @@ holds_callback(zfs_handle_t *zhp, void *data)
const char *zname = zfs_get_name(zhp);
size_t znamelen = strlen(zname);

if (cbp->cb_recursive) {
if (cbp->cb_recursive && cbp->cb_snapname != NULL) {
const char *snapname;
const char *delim = strchr(zname, '@');
if (delim == NULL)
Expand Down Expand Up @@ -6893,9 +6960,9 @@ holds_callback(zfs_handle_t *zhp, void *data)
}

/*
* zfs holds [-rHp] <snap> ...
* zfs holds [-rHp] <filesystem|volume|snapshot> ...
*
* -r Lists holds that are set on the named snapshots recursively.
* -r Lists holds that are set on the named datasets recursively.
* -H Scripted mode; elide headers and separate columns by tabs.
* -p Display values in parsable (literal) format.
*/
Expand Down Expand Up @@ -6955,9 +7022,14 @@ zfs_do_holds(int argc, char **argv)

delim = strchr(snapshot, '@');
if (delim == NULL) {
(void) fprintf(stderr,
gettext("'%s' is not a snapshot\n"), snapshot);
errors = B_TRUE;
cb.cb_recursive = recursive;
cb.cb_snapname = NULL;
cb.cb_nvlp = &nvl;
ret = zfs_for_each(1, argv + i, flags,
ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
NULL, NULL, limit, holds_callback, &cb);
if (ret != 0)
errors = B_TRUE;
continue;
}
snapname = delim + 1;
Expand Down
46 changes: 23 additions & 23 deletions man/man8/zfs-hold.8
Original file line number Diff line number Diff line change
Expand Up @@ -30,61 +30,61 @@
.\" Copyright 2018 Nexenta Systems, Inc.
.\" Copyright 2019 Joyent, Inc.
.\"
.Dd November 8, 2022
.Dd May 9, 2026
.Dt ZFS-HOLD 8
.Os
.
.Sh NAME
.Nm zfs-hold
.Nd hold ZFS snapshots to prevent their removal
.Nd hold datasets to prevent their removal
.Sh SYNOPSIS
.Nm zfs
.Cm hold
.Op Fl r
.Ar tag Ar snapshot Ns …
.Ar tag Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot Ns …
.Nm zfs
.Cm holds
.Op Fl rHp
.Ar snapshot Ns …
.Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot Ns …
.Nm zfs
.Cm release
.Op Fl r
.Ar tag Ar snapshot Ns …
.Ar tag Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot Ns …
.
.Sh DESCRIPTION
.Bl -tag -width ""
.It Xo
.Nm zfs
.Cm hold
.Op Fl r
.Ar tag Ar snapshot Ns …
.Ar tag Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot Ns …
.Xc
Adds a single reference, named with the
.Ar tag
argument, to the specified snapshots.
Each snapshot has its own tag namespace, and tags must be unique within that
argument, to the specified datasets.
Each target has its own tag namespace, and tags must be unique within that
space.
.Pp
If a hold exists on a snapshot, attempts to destroy that snapshot by using the
If a hold exists on a dataset, attempts to destroy it with
.Nm zfs Cm destroy
command return
return
.Sy EBUSY .
.Bl -tag -width "-r"
.It Fl r
Specifies that a hold with the given tag is applied recursively to the snapshots
of all descendent file systems.
Apply the hold recursively to the named target and all descendent
file systems
.Pq for snapshots, to the same-named snapshot of each descendent .
.El
.It Xo
.Nm zfs
.Cm holds
.Op Fl rHp
.Ar snapshot Ns …
.Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot Ns …
.Xc
Lists all existing user references for the given snapshot or snapshots.
Lists all existing user references for the given datasets.
.Bl -tag -width "-r"
.It Fl r
Lists the holds that are set on the named descendent snapshots, in addition to
listing the holds on the named snapshot.
List holds set on the named target and on all descendent file systems.
.It Fl H
Do not print headers, use tab-delimited output.
.It Fl p
Expand All @@ -94,20 +94,20 @@ Prints holds timestamps as Unix epoch timestamps.
.Nm zfs
.Cm release
.Op Fl r
.Ar tag Ar snapshot Ns …
.Ar tag Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot Ns …
.Xc
Removes a single reference, named with the
.Ar tag
argument, from the specified snapshot or snapshots.
The tag must already exist for each snapshot.
If a hold exists on a snapshot, attempts to destroy that snapshot by using the
argument, from the specified datasets.
The tag must already exist for each target.
If a hold exists on a dataset, attempts to destroy it with
.Nm zfs Cm destroy
command return
return
.Sy EBUSY .
.Bl -tag -width "-r"
.It Fl r
Recursively releases a hold with the given tag on the snapshots of all
descendent file systems.
Recursively release the hold from the named target and all descendent
file systems.
.El
.El
.
Expand Down
6 changes: 3 additions & 3 deletions man/man8/zfs.8
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,10 @@ Creates snapshots with the given names.
.It Xr zfs-rollback 8
Roll back the given dataset to a previous snapshot.
.It Xr zfs-hold 8 Ns / Ns Xr zfs-release 8
Add or remove a hold reference to the specified snapshot or snapshots.
If a hold exists on a snapshot, attempts to destroy that snapshot by using the
Add or remove a hold reference on the specified snapshots or datasets.
If a hold exists, attempts to destroy the held target with
.Nm zfs Cm destroy
command return
return
.Sy EBUSY .
.It Xr zfs-diff 8
Display the difference between a snapshot of a given filesystem and another
Expand Down
15 changes: 8 additions & 7 deletions module/zfs/dsl_dataset.c
Original file line number Diff line number Diff line change
Expand Up @@ -676,13 +676,14 @@ dsl_dataset_hold_obj(dsl_pool_t *dp, uint64_t dsobj, const void *tag,
} else {
if (zfs_flags & ZFS_DEBUG_SNAPNAMES)
err = dsl_dataset_get_snapname(ds);
if (err == 0 &&
dsl_dataset_phys(ds)->ds_userrefs_obj != 0) {
err = zap_count(
ds->ds_dir->dd_pool->dp_meta_objset,
dsl_dataset_phys(ds)->ds_userrefs_obj,
&ds->ds_userrefs);
}
}

if (err == 0 &&
dsl_dataset_phys(ds)->ds_userrefs_obj != 0) {
err = zap_count(
ds->ds_dir->dd_pool->dp_meta_objset,
dsl_dataset_phys(ds)->ds_userrefs_obj,
&ds->ds_userrefs);
}

if (err == 0 && !ds->ds_is_snapshot) {
Expand Down
8 changes: 7 additions & 1 deletion module/zfs/dsl_destroy.c
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,9 @@ dsl_destroy_head_check_impl(dsl_dataset_t *ds, int expected_holds)
if (zfs_refcount_count(&ds->ds_longholds) != expected_holds)
return (SET_ERROR(EBUSY));

if (ds->ds_userrefs > 0)
return (SET_ERROR(EBUSY));

ASSERT0(ds->ds_dir->dd_activity_waiters);

mos = ds->ds_dir->dd_pool->dp_meta_objset;
Expand Down Expand Up @@ -1159,7 +1162,10 @@ dsl_destroy_head_sync_impl(dsl_dataset_t *ds, dmu_tx_t *tx)

ASSERT0(dsl_dataset_phys(ds)->ds_next_clones_obj);
ASSERT0(dsl_dataset_phys(ds)->ds_props_obj);
ASSERT0(dsl_dataset_phys(ds)->ds_userrefs_obj);
if (dsl_dataset_phys(ds)->ds_userrefs_obj != 0) {
VERIFY0(zap_destroy(mos,
dsl_dataset_phys(ds)->ds_userrefs_obj, tx));
}
dsl_dir_rele(ds->ds_dir, ds);
ds->ds_dir = NULL;
dmu_object_free_zapified(mos, obj, tx);
Expand Down
10 changes: 1 addition & 9 deletions module/zfs/dsl_userhold.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,8 @@ dsl_dataset_user_hold_check(void *arg, dmu_tx_t *tx)
int error = 0;
const char *htag, *name;

/* must be a snapshot */
name = nvpair_name(pair);
if (strchr(name, '@') == NULL)
error = SET_ERROR(EINVAL);

if (error == 0)
error = nvpair_value_string(pair, &htag);
error = nvpair_value_string(pair, &htag);

if (error == 0)
error = dsl_dataset_hold(dp, name, FTAG, &ds);
Expand Down Expand Up @@ -375,9 +370,6 @@ dsl_dataset_user_release_check_one(dsl_dataset_user_release_arg_t *ddura,
objset_t *mos;
int numholds;

if (!ds->ds_is_snapshot)
return (SET_ERROR(EINVAL));

if (nvlist_empty(holds))
return (0);

Expand Down
Loading
Loading