cmd/zfs: clone: accept -u to not mount newly created datasets

Signed-off-by: Ivan Shapovalov <intelfx@intelfx.name>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Tony Hutter <hutter2@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Closes #18080
This commit is contained in:
Ivan Shapovalov 2026-01-05 18:21:56 +01:00 committed by GitHub
parent b9b84445ea
commit dbb3f247ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 108 additions and 31 deletions

View File

@ -292,7 +292,7 @@ get_usage(zfs_help_t idx)
{ {
switch (idx) { switch (idx) {
case HELP_CLONE: case HELP_CLONE:
return (gettext("\tclone [-p] [-o property=value] ... " return (gettext("\tclone [-pu] [-o property=value] ... "
"<snapshot> <filesystem|volume>\n")); "<snapshot> <filesystem|volume>\n"));
case HELP_CREATE: case HELP_CREATE:
return (gettext("\tcreate [-Pnpuv] [-o property=value] ... " 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] ... <snap> <fs | vol> * zfs clone [-pu] [-o prop=value] ... <snap> <fs | vol>
* *
* Given an existing dataset, create a writable copy whose initial contents * Given an existing dataset, create a writable copy whose initial contents
* are the same as the source. The newly created dataset maintains a * 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 clone exists.
* *
* The '-p' flag creates all the non-existing ancestors of the target first. * 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 static int
zfs_do_clone(int argc, char **argv) zfs_do_clone(int argc, char **argv)
{ {
zfs_handle_t *zhp = NULL; zfs_handle_t *zhp = NULL;
boolean_t parents = B_FALSE; boolean_t parents = B_FALSE;
boolean_t nomount = B_FALSE;
nvlist_t *props; nvlist_t *props;
int ret = 0; int ret = 1;
int c; int c;
if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0)
nomem(); nomem();
/* check options */ /* check options */
while ((c = getopt(argc, argv, "o:p")) != -1) { while ((c = getopt(argc, argv, "o:pu")) != -1) {
switch (c) { switch (c) {
case 'o': case 'o':
if (!parseprop(props, optarg)) { if (!parseprop(props, optarg)) {
@ -851,6 +854,9 @@ zfs_do_clone(int argc, char **argv)
case 'p': case 'p':
parents = B_TRUE; parents = B_TRUE;
break; break;
case 'u':
nomount = B_TRUE;
break;
case '?': case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"), (void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt); optopt);
@ -879,8 +885,7 @@ zfs_do_clone(int argc, char **argv)
/* open the source dataset */ /* open the source dataset */
if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_SNAPSHOT)) == NULL) { if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_SNAPSHOT)) == NULL) {
nvlist_free(props); goto error_open;
return (1);
} }
if (parents && zfs_name_valid(argv[1], ZFS_TYPE_FILESYSTEM | 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 | if (zfs_dataset_exists(g_zfs, argv[1], ZFS_TYPE_FILESYSTEM |
ZFS_TYPE_VOLUME)) { ZFS_TYPE_VOLUME)) {
zfs_close(zhp); ret = 0;
nvlist_free(props); goto error;
return (0);
} }
if (zfs_create_ancestors(g_zfs, argv[1]) != 0) { if (zfs_create_ancestors(g_zfs, argv[1]) != 0) {
zfs_close(zhp); goto error;
nvlist_free(props);
return (1);
} }
} }
/* pass to libzfs */ /* pass to libzfs */
ret = zfs_clone(zhp, argv[1], props); ret = zfs_clone(zhp, argv[1], props);
/* create the mountpoint if necessary */ if (ret != 0)
if (ret == 0) { goto error;
if (log_history) {
(void) zpool_log_history(g_zfs, history_str);
log_history = B_FALSE;
}
/* /* create the mountpoint if necessary */
* Dataset cloned successfully, mount/share failures are if (log_history) {
* non-fatal. (void) zpool_log_history(g_zfs, history_str);
*/ log_history = B_FALSE;
(void) zfs_mount_and_share(g_zfs, argv[1], ZFS_TYPE_DATASET);
} }
zfs_close(zhp); if (nomount)
nvlist_free(props); 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); return (!!ret);
usage: usage:
@ -4046,7 +4053,7 @@ zfs_do_rename(int argc, char **argv)
zfs_handle_t *zhp; zfs_handle_t *zhp;
renameflags_t flags = { 0 }; renameflags_t flags = { 0 };
int c; int c;
int ret = 0; int ret = 1;
int types; int types;
boolean_t parents = B_FALSE; boolean_t parents = B_FALSE;
@ -4118,18 +4125,19 @@ zfs_do_rename(int argc, char **argv)
types = ZFS_TYPE_DATASET; types = ZFS_TYPE_DATASET;
if ((zhp = zfs_open(g_zfs, argv[0], types)) == NULL) 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 we were asked and the name looks good, try to create ancestors. */
if (parents && zfs_name_valid(argv[1], zfs_get_type(zhp)) && if (parents && zfs_name_valid(argv[1], zfs_get_type(zhp)) &&
zfs_create_ancestors(g_zfs, argv[1]) != 0) { zfs_create_ancestors(g_zfs, argv[1]) != 0) {
zfs_close(zhp); goto error;
return (1);
} }
ret = (zfs_rename(zhp, argv[1], flags) != 0); ret = (zfs_rename(zhp, argv[1], flags) != 0);
error:
zfs_close(zhp); zfs_close(zhp);
error_open:
return (ret); return (ret);
} }

View File

@ -40,7 +40,7 @@
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm zfs .Nm zfs
.Cm clone .Cm clone
.Op Fl p .Op Fl pu
.Oo Fl o Ar property Ns = Ns Ar value Oc Ns .Oo Fl o Ar property Ns = Ns Ar value Oc Ns
.Ar snapshot Ar filesystem Ns | Ns Ar volume .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. property inherited from their parent.
If the target filesystem or volume already exists, the operation completes If the target filesystem or volume already exists, the operation completes
successfully. successfully.
.It Fl u
Do not mount the newly created file system.
.El .El
. .
.Sh EXAMPLES .Sh EXAMPLES

View File

@ -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_004_pos', 'zfs_clone_005_pos', 'zfs_clone_006_pos',
'zfs_clone_007_pos', 'zfs_clone_008_neg', 'zfs_clone_009_neg', '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_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'] tags = ['functional', 'cli_root', 'zfs_clone']
[tests/functional/cli_root/zfs_copies] [tests/functional/cli_root/zfs_copies]

View File

@ -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_010_pos.ksh \
functional/cli_root/zfs_clone/zfs_clone_deeply_nested.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_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_clone/zfs_clone_rm_nested.ksh \
functional/cli_root/zfs_copies/cleanup.ksh \ functional/cli_root/zfs_copies/cleanup.ksh \
functional/cli_root/zfs_copies/setup.ksh \ functional/cli_root/zfs_copies/setup.ksh \

View File

@ -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 <intelfx@intelfx.name>
#
. "$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"