Add -t option to zfs destroy

Resolves #9522 by adding an optional [-t type] option gate to the
`zfs destroy` command. Checks the type of the supplied argument,
then checks if the supplied type is the same or one of the aliases
that are also used in `zfs list`. If the type doesn't match, it
prints an error to stderr and exits.

Signed-off-by: Chris Simons <chris@simons.network>
This commit is contained in:
Chris Simons 2024-03-03 18:02:24 -08:00
parent 8f2f6cd2ac
commit c3a968c2f0
4 changed files with 183 additions and 4 deletions

View File

@ -287,7 +287,8 @@ get_usage(zfs_help_t idx)
"\tcreate [-Pnpsv] [-b blocksize] [-o property=value] ... " "\tcreate [-Pnpsv] [-b blocksize] [-o property=value] ... "
"-V <size> <volume>\n")); "-V <size> <volume>\n"));
case HELP_DESTROY: case HELP_DESTROY:
return (gettext("\tdestroy [-fnpRrv] <filesystem|volume>\n" return (gettext("\tdestroy [-fnpRrv] [-t type] "
"<filesystem|volume>\n"
"\tdestroy [-dnpRrv] " "\tdestroy [-dnpRrv] "
"<filesystem|volume>@<snap>[%<snap>][,...]\n" "<filesystem|volume>@<snap>[%<snap>][,...]\n"
"\tdestroy <filesystem|volume>#<bookmark>\n")); "\tdestroy <filesystem|volume>#<bookmark>\n"));
@ -1642,9 +1643,10 @@ zfs_do_destroy(int argc, char **argv)
zfs_handle_t *zhp = NULL; zfs_handle_t *zhp = NULL;
char *at, *pound; char *at, *pound;
zfs_type_t type = ZFS_TYPE_DATASET; zfs_type_t type = ZFS_TYPE_DATASET;
char *type_filter = NULL;
/* check options */ /* check options */
while ((c = getopt(argc, argv, "vpndfrR")) != -1) { while ((c = getopt(argc, argv, "vpndfrRt:")) != -1) {
switch (c) { switch (c) {
case 'v': case 'v':
cb.cb_verbose = B_TRUE; cb.cb_verbose = B_TRUE;
@ -1670,6 +1672,9 @@ zfs_do_destroy(int argc, char **argv)
cb.cb_recurse = B_TRUE; cb.cb_recurse = B_TRUE;
cb.cb_doclones = B_TRUE; cb.cb_doclones = B_TRUE;
break; break;
case 't':
type_filter = optarg;
break;
case '?': case '?':
default: default:
(void) fprintf(stderr, gettext("invalid option '%c'\n"), (void) fprintf(stderr, gettext("invalid option '%c'\n"),
@ -1691,6 +1696,54 @@ zfs_do_destroy(int argc, char **argv)
usage(B_FALSE); usage(B_FALSE);
} }
/* check type before destroying if specified */
if (type_filter != NULL) {
zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
if (zhp != NULL) {
zfs_type_t type = zfs_get_type(zhp);
boolean_t type_matches = B_FALSE;
switch (type) {
case ZFS_TYPE_FILESYSTEM:
type_matches = (
(strcmp(type_filter, "fs") == 0) ||
(strcmp(type_filter, "filesystem") == 0));
break;
case ZFS_TYPE_VOLUME:
type_matches = (
(strcmp(type_filter, "vol") == 0) ||
(strcmp(type_filter, "volume") == 0));
break;
case ZFS_TYPE_SNAPSHOT:
type_matches = (
(strcmp(type_filter, "snap") == 0) ||
(strcmp(type_filter, "snapshot") == 0));
break;
case ZFS_TYPE_BOOKMARK:
type_matches =
(strcmp(type_filter, "bookmark") == 0);
break;
default:
type_matches = B_FALSE;
break;
}
zfs_close(zhp);
if (!type_matches) {
(void) fprintf(stderr,
gettext("cannot destroy '%s': "
"type does not match '%s'\n"),
argv[0], type_filter);
return (1);
}
} else {
return (1);
}
}
at = strchr(argv[0], '@'); at = strchr(argv[0], '@');
pound = strchr(argv[0], '#'); pound = strchr(argv[0], '#');
if (at != NULL) { if (at != NULL) {

View File

@ -40,10 +40,12 @@
.Nm zfs .Nm zfs
.Cm destroy .Cm destroy
.Op Fl Rfnprv .Op Fl Rfnprv
.Oo Fl t Ar type Ns Oc
.Ar filesystem Ns | Ns Ar volume .Ar filesystem Ns | Ns Ar volume
.Nm zfs .Nm zfs
.Cm destroy .Cm destroy
.Op Fl Rdnprv .Op Fl Rdnprv
.Oo Fl t Ar type Ns Oc
.Ar filesystem Ns | Ns Ar volume Ns @ Ns Ar snap Ns .Ar filesystem Ns | Ns Ar volume Ns @ Ns Ar snap Ns
.Oo % Ns Ar snap Ns Oo , Ns Ar snap Ns Oo % Ns Ar snap Oc Oc Oc Ns .Oo % Ns Ar snap Ns Oo , Ns Ar snap Ns Oo % Ns Ar snap Oc Oc Oc Ns
.Nm zfs .Nm zfs
@ -56,6 +58,7 @@
.Nm zfs .Nm zfs
.Cm destroy .Cm destroy
.Op Fl Rfnprv .Op Fl Rfnprv
.Oo Fl t Ar type Ns Oc
.Ar filesystem Ns | Ns Ar volume .Ar filesystem Ns | Ns Ar volume
.Xc .Xc
Destroys the given dataset. Destroys the given dataset.
@ -84,6 +87,28 @@ flags to determine what data would be deleted.
Print machine-parsable verbose information about the deleted data. Print machine-parsable verbose information about the deleted data.
.It Fl r .It Fl r
Recursively destroy all children. Recursively destroy all children.
.It Fl t Ar type
The specified type to destroy, where
.Ar type
is one of
.Sy filesystem ,
.Sy snapshot ,
.Sy volume ,
or
.Sy bookmark .
For example, specifying
.Fl t Sy snapshot
will not destroy data if the supplied argument is not a
.Sy snapshot .
.Sy fs ,
.Sy snap ,
or
.Sy vol
can be used as aliases for
.Sy filesystem ,
.Sy snapshot ,
or
.Sy volume .
.It Fl v .It Fl v
Print verbose information about the deleted data. Print verbose information about the deleted data.
.El .El
@ -98,6 +123,7 @@ behavior for mounted file systems in use.
.Nm zfs .Nm zfs
.Cm destroy .Cm destroy
.Op Fl Rdnprv .Op Fl Rdnprv
.Oo Fl t Ar type Ns Oc
.Ar filesystem Ns | Ns Ar volume Ns @ Ns Ar snap Ns .Ar filesystem Ns | Ns Ar volume Ns @ Ns Ar snap Ns
.Oo % Ns Ar snap Ns Oo , Ns Ar snap Ns Oo % Ns Ar snap Oc Oc Oc Ns .Oo % Ns Ar snap Ns Oo , Ns Ar snap Ns Oo % Ns Ar snap Oc Oc Oc Ns
.Xc .Xc
@ -155,6 +181,28 @@ Print machine-parsable verbose information about the deleted data.
Destroy Destroy
.Pq or mark for deferred deletion .Pq or mark for deferred deletion
all snapshots with this name in descendent file systems. all snapshots with this name in descendent file systems.
.It Fl t Ar type
The specified type to destroy, where
.Ar type
is one of
.Sy filesystem ,
.Sy snapshot ,
.Sy volume ,
or
.Sy bookmark .
For example, specifying
.Fl t Sy snapshot
will not destroy data if the supplied argument is not a
.Sy snapshot .
.Sy fs ,
.Sy snap ,
or
.Sy vol
can be used as aliases for
.Sy filesystem ,
.Sy snapshot ,
or
.Sy volume .
.It Fl v .It Fl v
Print verbose information about the deleted data. Print verbose information about the deleted data.
.Pp .Pp

View File

@ -210,8 +210,9 @@ tests = ['zfs_clone_livelist_condense_and_disable',
'zfs_destroy_007_neg', 'zfs_destroy_008_pos', 'zfs_destroy_009_pos', 'zfs_destroy_007_neg', 'zfs_destroy_008_pos', 'zfs_destroy_009_pos',
'zfs_destroy_010_pos', 'zfs_destroy_011_pos', 'zfs_destroy_012_pos', 'zfs_destroy_010_pos', 'zfs_destroy_011_pos', 'zfs_destroy_012_pos',
'zfs_destroy_013_neg', 'zfs_destroy_014_pos', 'zfs_destroy_015_pos', 'zfs_destroy_013_neg', 'zfs_destroy_014_pos', 'zfs_destroy_015_pos',
'zfs_destroy_016_pos', 'zfs_destroy_clone_livelist', 'zfs_destroy_016_pos', 'zfs_destroy_017_pos',
'zfs_destroy_dev_removal', 'zfs_destroy_dev_removal_condense'] 'zfs_destroy_clone_livelist', 'zfs_destroy_dev_removal',
'zfs_destroy_dev_removal_condense']
tags = ['functional', 'cli_root', 'zfs_destroy'] tags = ['functional', 'cli_root', 'zfs_destroy']
[tests/functional/cli_root/zfs_diff] [tests/functional/cli_root/zfs_diff]

View File

@ -0,0 +1,77 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source. A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.
#
# CDDL HEADER END
#
#
# Copyright (c) 2024 by Chris Simons <chris@simons.network>
#
. $STF_SUITE/include/libtest.shlib
. $STF_SUITE/tests/functional/cli_root/zfs_destroy/zfs_destroy.cfg
#
# DESCRIPTION:
# Verify that 'zfs destroy' with a type filter only destroys the datasets
# of the specified type.
#
# STRATEGY:
# 1. Create various types
# 2. Attempt to destroy with correct and incorrect types
# 3. Verify only expected items are destroyed
# 4. Ensure recursive destruction still occurs
#
function cleanup
{
for ds in $TESTPOOL/$TESTFS1 $TESTPOOL/$TESTVOL; do
datasetexists $ds && log_must zfs destroy -r $ds
done
}
log_assert "Verify 'zfs destroy' with a type filter only affects specified type"
log_onexit cleanup
# Create a filesystem, dataset and a volume
log_must zfs create $TESTPOOL/$TESTFS1
log_must zfs create $TESTPOOL/$TESTFS1/testdataset
log_must zfs create -V $VOLSIZE $TESTPOOL/$TESTVOL
# Take a snapshots
log_must zfs snapshot $TESTPOOL/$TESTFS1@snap
log_must zfs snapshot $TESTPOOL/$TESTVOL@snap
log_must zfs snapshot $TESTPOOL/$TESTFS1@snap_del
log_must zfs snapshot $TESTPOOL/$TESTVOL@snap_del
log_must zfs snapshot -r $TESTPOOL/$TESTFS1@snap_recursive
# Destroy only snapshots with the type filter
log_mustnot zfs destroy -t snapshot $TESTPOOL/$TESTFS1
log_mustnot zfs destroy -t snapshot -r $TESTPOOL/$TESTFS1
log_mustnot zfs destroy -t snapshot $TESTPOOL/$TESTVOL
log_mustnot zfs destroy -t snapshot -r $TESTPOOL/$TESTVOL
log_must zfs destroy -t snapshot $TESTPOOL/$TESTFS1@snap_del
log_must zfs destroy -t snapshot $TESTPOOL/$TESTVOL@snap_del
log_must zfs destroy -t snapshot -r $TESTPOOL/$TESTFS1@snap_recursive
# Verify the filesystem snapshot is destroyed and the volume snapshot remains
log_must datasetexists $TESTPOOL/$TESTFS1
log_must datasetexists $TESTPOOL/$TESTVOL
log_mustnot datasetexists $TESTPOOL/$TESTFS1@snap_del
log_mustnot datasetexists $TESTPOOL/$TESTVOL@snap_del
log_mustnot datasetexists $TESTPOOL/$TESTVOL@snap_recursive
log_mustnot datasetexists $TESTPOOL/$TESTVOL/testdataset@snap_recursive
log_pass "zfs destroy with a snapshot filter only affects specified type"