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] ... "
"-V <size> <volume>\n"));
case HELP_DESTROY:
return (gettext("\tdestroy [-fnpRrv] <filesystem|volume>\n"
return (gettext("\tdestroy [-fnpRrv] [-t type] "
"<filesystem|volume>\n"
"\tdestroy [-dnpRrv] "
"<filesystem|volume>@<snap>[%<snap>][,...]\n"
"\tdestroy <filesystem|volume>#<bookmark>\n"));
@ -1642,9 +1643,10 @@ zfs_do_destroy(int argc, char **argv)
zfs_handle_t *zhp = NULL;
char *at, *pound;
zfs_type_t type = ZFS_TYPE_DATASET;
char *type_filter = NULL;
/* check options */
while ((c = getopt(argc, argv, "vpndfrR")) != -1) {
while ((c = getopt(argc, argv, "vpndfrRt:")) != -1) {
switch (c) {
case 'v':
cb.cb_verbose = B_TRUE;
@ -1670,6 +1672,9 @@ zfs_do_destroy(int argc, char **argv)
cb.cb_recurse = B_TRUE;
cb.cb_doclones = B_TRUE;
break;
case 't':
type_filter = optarg;
break;
case '?':
default:
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
@ -1691,6 +1696,54 @@ zfs_do_destroy(int argc, char **argv)
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], '@');
pound = strchr(argv[0], '#');
if (at != NULL) {

View File

@ -40,10 +40,12 @@
.Nm zfs
.Cm destroy
.Op Fl Rfnprv
.Oo Fl t Ar type Ns Oc
.Ar filesystem Ns | Ns Ar volume
.Nm zfs
.Cm destroy
.Op Fl Rdnprv
.Oo Fl t Ar type Ns Oc
.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
.Nm zfs
@ -56,6 +58,7 @@
.Nm zfs
.Cm destroy
.Op Fl Rfnprv
.Oo Fl t Ar type Ns Oc
.Ar filesystem Ns | Ns Ar volume
.Xc
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.
.It Fl r
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
Print verbose information about the deleted data.
.El
@ -98,6 +123,7 @@ behavior for mounted file systems in use.
.Nm zfs
.Cm destroy
.Op Fl Rdnprv
.Oo Fl t Ar type Ns Oc
.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
.Xc
@ -155,6 +181,28 @@ Print machine-parsable verbose information about the deleted data.
Destroy
.Pq or mark for deferred deletion
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
Print verbose information about the deleted data.
.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_010_pos', 'zfs_destroy_011_pos', 'zfs_destroy_012_pos',
'zfs_destroy_013_neg', 'zfs_destroy_014_pos', 'zfs_destroy_015_pos',
'zfs_destroy_016_pos', 'zfs_destroy_clone_livelist',
'zfs_destroy_dev_removal', 'zfs_destroy_dev_removal_condense']
'zfs_destroy_016_pos', 'zfs_destroy_017_pos',
'zfs_destroy_clone_livelist', 'zfs_destroy_dev_removal',
'zfs_destroy_dev_removal_condense']
tags = ['functional', 'cli_root', 'zfs_destroy']
[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"