From c3a968c2f05f645866e5ec6f71fd40c0fec11d34 Mon Sep 17 00:00:00 2001 From: Chris Simons Date: Sun, 3 Mar 2024 18:02:24 -0800 Subject: [PATCH] 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 --- cmd/zfs/zfs_main.c | 57 +++++++++++++- man/man8/zfs-destroy.8 | 48 ++++++++++++ tests/runfiles/common.run | 5 +- .../zfs_destroy/zfs_destroy_017_pos.ksh | 77 +++++++++++++++++++ 4 files changed, 183 insertions(+), 4 deletions(-) create mode 100755 tests/zfs-tests/tests/functional/cli_root/zfs_destroy/zfs_destroy_017_pos.ksh diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index c2147c8f4a..56a26d2d42 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -287,7 +287,8 @@ get_usage(zfs_help_t idx) "\tcreate [-Pnpsv] [-b blocksize] [-o property=value] ... " "-V \n")); case HELP_DESTROY: - return (gettext("\tdestroy [-fnpRrv] \n" + return (gettext("\tdestroy [-fnpRrv] [-t type] " + "\n" "\tdestroy [-dnpRrv] " "@[%][,...]\n" "\tdestroy #\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) { diff --git a/man/man8/zfs-destroy.8 b/man/man8/zfs-destroy.8 index fe4f19d18e..bc9a1a71a6 100644 --- a/man/man8/zfs-destroy.8 +++ b/man/man8/zfs-destroy.8 @@ -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 diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 502b4de2ba..9b5dc2ff84 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -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] diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_destroy/zfs_destroy_017_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_destroy/zfs_destroy_017_pos.ksh new file mode 100755 index 0000000000..2251803712 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_destroy/zfs_destroy_017_pos.ksh @@ -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 +# + +. $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" +