diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 35f48334a..4152bbf8d 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -292,7 +292,7 @@ get_usage(zfs_help_t idx) { switch (idx) { case HELP_CLONE: - return (gettext("\tclone [-p] [-o property=value] ... " + return (gettext("\tclone [-pu] [-o property=value] ... " " \n")); case HELP_CREATE: return (gettext("\tcreate [-Pnpuv] [-o property=value] ... " @@ -818,7 +818,7 @@ zfs_mount_and_share(libzfs_handle_t *hdl, const char *dataset, zfs_type_t type) } /* - * zfs clone [-p] [-o prop=value] ... + * zfs clone [-pu] [-o prop=value] ... * * Given an existing dataset, create a writable copy whose initial contents * are the same as the source. The newly created dataset maintains a @@ -826,21 +826,24 @@ zfs_mount_and_share(libzfs_handle_t *hdl, const char *dataset, zfs_type_t type) * the clone exists. * * The '-p' flag creates all the non-existing ancestors of the target first. + * + * The '-u' flag prevents the newly created file system from being mounted. */ static int zfs_do_clone(int argc, char **argv) { zfs_handle_t *zhp = NULL; boolean_t parents = B_FALSE; + boolean_t nomount = B_FALSE; nvlist_t *props; - int ret = 0; + int ret = 1; int c; if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) nomem(); /* check options */ - while ((c = getopt(argc, argv, "o:p")) != -1) { + while ((c = getopt(argc, argv, "o:pu")) != -1) { switch (c) { case 'o': if (!parseprop(props, optarg)) { @@ -851,6 +854,9 @@ zfs_do_clone(int argc, char **argv) case 'p': parents = B_TRUE; break; + case 'u': + nomount = B_TRUE; + break; case '?': (void) fprintf(stderr, gettext("invalid option '%c'\n"), optopt); @@ -879,8 +885,7 @@ zfs_do_clone(int argc, char **argv) /* open the source dataset */ if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_SNAPSHOT)) == NULL) { - nvlist_free(props); - return (1); + goto error_open; } if (parents && zfs_name_valid(argv[1], ZFS_TYPE_FILESYSTEM | @@ -892,37 +897,39 @@ zfs_do_clone(int argc, char **argv) */ if (zfs_dataset_exists(g_zfs, argv[1], ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) { - zfs_close(zhp); - nvlist_free(props); - return (0); + ret = 0; + goto error; } if (zfs_create_ancestors(g_zfs, argv[1]) != 0) { - zfs_close(zhp); - nvlist_free(props); - return (1); + goto error; } } /* pass to libzfs */ ret = zfs_clone(zhp, argv[1], props); - /* create the mountpoint if necessary */ - if (ret == 0) { - if (log_history) { - (void) zpool_log_history(g_zfs, history_str); - log_history = B_FALSE; - } + if (ret != 0) + goto error; - /* - * Dataset cloned successfully, mount/share failures are - * non-fatal. - */ - (void) zfs_mount_and_share(g_zfs, argv[1], ZFS_TYPE_DATASET); + /* create the mountpoint if necessary */ + if (log_history) { + (void) zpool_log_history(g_zfs, history_str); + log_history = B_FALSE; } - zfs_close(zhp); - nvlist_free(props); + if (nomount) + goto error; + /* + * Dataset cloned successfully, mount/share failures are + * non-fatal. + */ + (void) zfs_mount_and_share(g_zfs, argv[1], ZFS_TYPE_DATASET); + +error: + zfs_close(zhp); +error_open: + nvlist_free(props); return (!!ret); usage: @@ -4046,7 +4053,7 @@ zfs_do_rename(int argc, char **argv) zfs_handle_t *zhp; renameflags_t flags = { 0 }; int c; - int ret = 0; + int ret = 1; int types; boolean_t parents = B_FALSE; @@ -4118,18 +4125,19 @@ zfs_do_rename(int argc, char **argv) types = ZFS_TYPE_DATASET; if ((zhp = zfs_open(g_zfs, argv[0], types)) == NULL) - return (1); + goto error_open; /* If we were asked and the name looks good, try to create ancestors. */ if (parents && zfs_name_valid(argv[1], zfs_get_type(zhp)) && zfs_create_ancestors(g_zfs, argv[1]) != 0) { - zfs_close(zhp); - return (1); + goto error; } ret = (zfs_rename(zhp, argv[1], flags) != 0); +error: zfs_close(zhp); +error_open: return (ret); } diff --git a/man/man8/zfs-clone.8 b/man/man8/zfs-clone.8 index 9609cf2ce..786b17521 100644 --- a/man/man8/zfs-clone.8 +++ b/man/man8/zfs-clone.8 @@ -40,7 +40,7 @@ .Sh SYNOPSIS .Nm zfs .Cm clone -.Op Fl p +.Op Fl pu .Oo Fl o Ar property Ns = Ns Ar value Oc Ns … .Ar snapshot Ar filesystem Ns | Ns Ar volume . @@ -64,6 +64,8 @@ Datasets created in this manner are automatically mounted according to the property inherited from their parent. If the target filesystem or volume already exists, the operation completes successfully. +.It Fl u +Do not mount the newly created file system. .El . .Sh EXAMPLES diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 9b4c4154c..d0b35933c 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -196,7 +196,7 @@ tests = ['zfs_clone_001_neg', 'zfs_clone_002_pos', 'zfs_clone_003_pos', 'zfs_clone_004_pos', 'zfs_clone_005_pos', 'zfs_clone_006_pos', 'zfs_clone_007_pos', 'zfs_clone_008_neg', 'zfs_clone_009_neg', 'zfs_clone_010_pos', 'zfs_clone_encrypted', 'zfs_clone_deeply_nested', - 'zfs_clone_rm_nested'] + 'zfs_clone_rm_nested', 'zfs_clone_nomount'] tags = ['functional', 'cli_root', 'zfs_clone'] [tests/functional/cli_root/zfs_copies] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index a4b3e7376..5c486754d 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -677,6 +677,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/cli_root/zfs_clone/zfs_clone_010_pos.ksh \ functional/cli_root/zfs_clone/zfs_clone_deeply_nested.ksh \ functional/cli_root/zfs_clone/zfs_clone_encrypted.ksh \ + functional/cli_root/zfs_clone/zfs_clone_nomount.ksh \ functional/cli_root/zfs_clone/zfs_clone_rm_nested.ksh \ functional/cli_root/zfs_copies/cleanup.ksh \ functional/cli_root/zfs_copies/setup.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_clone/zfs_clone_nomount.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_clone/zfs_clone_nomount.ksh new file mode 100755 index 000000000..b549f4571 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_clone/zfs_clone_nomount.ksh @@ -0,0 +1,66 @@ +#!/bin/ksh -p +# shellcheck disable=SC2066 +# SPDX-License-Identifier: CDDL-1.0 +# +# 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. +# + +# +# Copyright 2025 Ivan Shapovalov +# + +. "$STF_SUITE/include/libtest.shlib" + +# +# DESCRIPTION: +# `zfs clone -u` should leave the new file system unmounted. +# +# STRATEGY: +# 1. Prepare snapshots +# 2. Clone a snapshot using `-u` and make sure the clone is not mounted. +# + +verify_runnable "both" + +function setup_all +{ + log_note "Creating snapshots..." + + for snap in "$SNAPFS" ; do + if ! snapexists "$snap" ; then + log_must zfs snapshot "$snap" + fi + done + + return 0 +} + +function cleanup_all +{ + datasetexists "$fs" && destroy_dataset "$fs" + + for snap in "$SNAPFS" ; do + snapexists "$snap" && destroy_dataset "$snap" -Rf + done + + return 0 +} + +log_onexit cleanup_all +log_must setup_all + +log_assert "zfs clone -u should leave the new file system unmounted" + +typeset fs="$TESTPOOL/clonefs$$" + +log_must zfs clone -u "$SNAPFS" "$fs" +log_mustnot ismounted "$fs" + +log_pass "zfs clone -u leaves the new file system unmounted"