zfs_main: create, clone, rename: accept -pp for non-mountable parents

Teach `zfs {create,clone,rename}` to accept a doubled `-p` flag (`-pp`)
to create non-existing ancestor datasets with `canmount=off`.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Rob Norris <robn@despairlabs.com>
Signed-off-by: Ivan Shapovalov <intelfx@intelfx.name>
Closes #17000
This commit is contained in:
Ivan Shapovalov 2025-01-28 12:11:18 +04:00 committed by Brian Behlendorf
parent 2f3f1ab1ba
commit 8531621aba
11 changed files with 373 additions and 23 deletions

View File

@ -292,12 +292,13 @@ get_usage(zfs_help_t idx)
{ {
switch (idx) { switch (idx) {
case HELP_CLONE: case HELP_CLONE:
return (gettext("\tclone [-pu] [-o property=value] ... " return (gettext("\tclone [-p[p]u] [-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 [-Pnp[p]uv] [-o property=value] ... "
"<filesystem>\n" "<filesystem>\n"
"\tcreate [-Pnpsv] [-b blocksize] [-o property=value] ... " "\tcreate [-Pnp[p]sv] [-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] <filesystem|volume>\n"
@ -338,7 +339,8 @@ get_usage(zfs_help_t idx)
case HELP_RENAME: case HELP_RENAME:
return (gettext("\trename [-f] <filesystem|volume|snapshot> " return (gettext("\trename [-f] <filesystem|volume|snapshot> "
"<filesystem|volume|snapshot>\n" "<filesystem|volume|snapshot>\n"
"\trename -p [-f] <filesystem|volume> <filesystem|volume>\n" "\trename -p[p] [-f] <filesystem|volume> "
"<filesystem|volume>\n"
"\trename -u [-f] <filesystem> <filesystem>\n" "\trename -u [-f] <filesystem> <filesystem>\n"
"\trename -r <snapshot> <snapshot>\n")); "\trename -r <snapshot> <snapshot>\n"));
case HELP_ROLLBACK: case HELP_ROLLBACK:
@ -766,6 +768,26 @@ finish_progress(const char *done)
pt_header = NULL; pt_header = NULL;
} }
static void
makeprops_parents(nvlist_t **ptr, boolean_t parents_nomount)
{
nvlist_t *props = NULL;
if (parents_nomount) {
if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0)
nomem();
if (nvlist_add_string(props,
zfs_prop_to_name(ZFS_PROP_CANMOUNT),
"off") != 0) {
nvlist_free(props);
nomem();
}
}
*ptr = props;
}
static int static int
zfs_mount_and_share(libzfs_handle_t *hdl, const char *dataset, zfs_type_t type) zfs_mount_and_share(libzfs_handle_t *hdl, const char *dataset, zfs_type_t type)
{ {
@ -826,6 +848,7 @@ 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.
* If repeated twice, the ancestors are created with `canmount=off`.
* *
* The '-u' flag prevents the newly created file system from being mounted. * The '-u' flag prevents the newly created file system from being mounted.
*/ */
@ -834,8 +857,10 @@ 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 parents_nomount = B_FALSE;
boolean_t nomount = B_FALSE; boolean_t nomount = B_FALSE;
nvlist_t *props; nvlist_t *props;
nvlist_t *props_parents = NULL;
int ret = 1; int ret = 1;
int c; int c;
@ -852,7 +877,10 @@ zfs_do_clone(int argc, char **argv)
} }
break; break;
case 'p': case 'p':
parents = B_TRUE; if (!parents)
parents = B_TRUE;
else
parents_nomount = B_TRUE;
break; break;
case 'u': case 'u':
nomount = B_TRUE; nomount = B_TRUE;
@ -900,9 +928,11 @@ zfs_do_clone(int argc, char **argv)
ret = 0; ret = 0;
goto error; goto error;
} }
if (zfs_create_ancestors(g_zfs, argv[1]) != 0) {
makeprops_parents(&props_parents, parents_nomount);
if (zfs_create_ancestors_props(g_zfs, argv[1],
props_parents) != 0)
goto error; goto error;
}
} }
/* pass to libzfs */ /* pass to libzfs */
@ -930,11 +960,13 @@ error:
zfs_close(zhp); zfs_close(zhp);
error_open: error_open:
nvlist_free(props); nvlist_free(props);
nvlist_free(props_parents);
return (!!ret); return (!!ret);
usage: usage:
ASSERT0P(zhp); ASSERT0P(zhp);
nvlist_free(props); nvlist_free(props);
nvlist_free(props_parents);
usage(B_FALSE); usage(B_FALSE);
return (-1); return (-1);
} }
@ -1068,6 +1100,7 @@ default_volblocksize(zpool_handle_t *zhp, nvlist_t *props)
* SPA_VERSION_REFRESERVATION, we set a refreservation instead. * SPA_VERSION_REFRESERVATION, we set a refreservation instead.
* *
* 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.
* If repeated twice, the ancestors are created with `canmount=off`.
* *
* The '-n' flag is no-op (dry run) mode. This will perform a user-space sanity * The '-n' flag is no-op (dry run) mode. This will perform a user-space sanity
* check of arguments and properties, but does not check for permissions, * check of arguments and properties, but does not check for permissions,
@ -1090,12 +1123,14 @@ zfs_do_create(int argc, char **argv)
boolean_t noreserve = B_FALSE; boolean_t noreserve = B_FALSE;
boolean_t bflag = B_FALSE; boolean_t bflag = B_FALSE;
boolean_t parents = B_FALSE; boolean_t parents = B_FALSE;
boolean_t parents_nomount = B_FALSE;
boolean_t dryrun = B_FALSE; boolean_t dryrun = B_FALSE;
boolean_t nomount = B_FALSE; boolean_t nomount = B_FALSE;
boolean_t verbose = B_FALSE; boolean_t verbose = B_FALSE;
boolean_t parseable = B_FALSE; boolean_t parseable = B_FALSE;
int ret = 1; int ret = 1;
nvlist_t *props; nvlist_t *props;
nvlist_t *props_parents = NULL;
uint64_t intval; uint64_t intval;
const char *strval; const char *strval;
@ -1124,7 +1159,10 @@ zfs_do_create(int argc, char **argv)
parseable = B_TRUE; parseable = B_TRUE;
break; break;
case 'p': case 'p':
parents = B_TRUE; if (!parents)
parents = B_TRUE;
else
parents_nomount = B_TRUE;
break; break;
case 'b': case 'b':
bflag = B_TRUE; bflag = B_TRUE;
@ -1274,6 +1312,8 @@ zfs_do_create(int argc, char **argv)
} }
if (parents && zfs_name_valid(argv[0], type)) { if (parents && zfs_name_valid(argv[0], type)) {
makeprops_parents(&props_parents, parents_nomount);
/* /*
* Now create the ancestors of target dataset. If the target * Now create the ancestors of target dataset. If the target
* already exists and '-p' option was used we should not * already exists and '-p' option was used we should not
@ -1289,7 +1329,8 @@ zfs_do_create(int argc, char **argv)
"create ancestors of %s\n", argv[0]); "create ancestors of %s\n", argv[0]);
} }
if (!dryrun) { if (!dryrun) {
if (zfs_create_ancestors(g_zfs, argv[0]) != 0) { if (zfs_create_ancestors_props(g_zfs, argv[0],
props_parents) != 0) {
goto error; goto error;
} }
} }
@ -1348,9 +1389,11 @@ zfs_do_create(int argc, char **argv)
(void) zfs_mount_and_share(g_zfs, argv[0], ZFS_TYPE_DATASET); (void) zfs_mount_and_share(g_zfs, argv[0], ZFS_TYPE_DATASET);
error: error:
nvlist_free(props); nvlist_free(props);
nvlist_free(props_parents);
return (ret); return (ret);
badusage: badusage:
nvlist_free(props); nvlist_free(props);
nvlist_free(props_parents);
usage(B_FALSE); usage(B_FALSE);
return (2); return (2);
} }
@ -4039,6 +4082,8 @@ found3:;
* Renames the given dataset to another of the same type. * Renames the given dataset to another of the same type.
* *
* 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.
* If repeated twice, the ancestors are created with `canmount=off`.
*
* The '-u' flag prevents file systems from being remounted during rename. * The '-u' flag prevents file systems from being remounted during rename.
*/ */
static int static int
@ -4050,12 +4095,17 @@ zfs_do_rename(int argc, char **argv)
int ret = 1; int ret = 1;
int types; int types;
boolean_t parents = B_FALSE; boolean_t parents = B_FALSE;
boolean_t parents_nomount = B_FALSE;
nvlist_t *props_parents = NULL;
/* check options */ /* check options */
while ((c = getopt(argc, argv, "pruf")) != -1) { while ((c = getopt(argc, argv, "pruf")) != -1) {
switch (c) { switch (c) {
case 'p': case 'p':
parents = B_TRUE; if (parents)
parents_nomount = B_TRUE;
else
parents = B_TRUE;
break; break;
case 'r': case 'r':
flags.recursive = B_TRUE; flags.recursive = B_TRUE;
@ -4122,9 +4172,13 @@ zfs_do_rename(int argc, char **argv)
goto error_open; 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) {
goto error; makeprops_parents(&props_parents, parents_nomount);
if (zfs_create_ancestors_props(g_zfs, argv[1],
props_parents) != 0) {
goto error;
}
} }
ret = (zfs_rename(zhp, argv[1], flags) != 0); ret = (zfs_rename(zhp, argv[1], flags) != 0);
@ -4132,6 +4186,7 @@ zfs_do_rename(int argc, char **argv)
error: error:
zfs_close(zhp); zfs_close(zhp);
error_open: error_open:
nvlist_free(props_parents);
return (ret); return (ret);
} }

View File

@ -3657,7 +3657,7 @@ ancestorerr:
int int
zfs_create_ancestors(libzfs_handle_t *hdl, const char *path) zfs_create_ancestors(libzfs_handle_t *hdl, const char *path)
{ {
return zfs_create_ancestors_props(hdl, path, NULL); return (zfs_create_ancestors_props(hdl, path, NULL));
} }
/* /*

View File

@ -62,6 +62,10 @@ Creates all the non-existing parent datasets.
Datasets created in this manner are automatically mounted according to the Datasets created in this manner are automatically mounted according to the
.Sy mountpoint .Sy mountpoint
property inherited from their parent. property inherited from their parent.
If the
.Fl p
option is specified multiple times, parent datasets are created with
.Sy canmount Ns = Ns Ar off .
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 .It Fl u

View File

@ -85,6 +85,10 @@ property inherited from their parent.
Any property specified on the command line using the Any property specified on the command line using the
.Fl o .Fl o
option is ignored. option is ignored.
If the
.Fl p
option is specified multiple times, parent datasets are created with
.Sy canmount Ns = Ns Ar off .
If the target filesystem already exists, the operation completes successfully. If the target filesystem already exists, the operation completes successfully.
.It Fl n .It Fl n
Do a dry-run Do a dry-run
@ -177,6 +181,10 @@ property inherited from their parent.
Any property specified on the command line using the Any property specified on the command line using the
.Fl o .Fl o
option is ignored. option is ignored.
If the
.Fl p
option is specified multiple times, parent datasets are created with
.Sy canmount Ns = Ns Ar off .
If the target filesystem already exists, the operation completes successfully. If the target filesystem already exists, the operation completes successfully.
.It Fl s .It Fl s
Creates a sparse volume with no reservation. Creates a sparse volume with no reservation.

View File

@ -103,6 +103,10 @@ Creates all the non-existing parent datasets.
Datasets created in this manner are automatically mounted according to the Datasets created in this manner are automatically mounted according to the
.Sy mountpoint .Sy mountpoint
property inherited from their parent. property inherited from their parent.
Alernatively, if the
.Fl p
option is specified multiple times, parent datasets are created with
.Sy canmount Ns = Ns Ar off .
.It Fl u .It Fl u
Do not remount file systems during rename. Do not remount file systems during rename.
If a file system's If a file system's

View File

@ -196,8 +196,8 @@ tags = ['functional', 'cli_root', 'zfs_change-key']
tests = ['zfs_clone_001_neg', 'zfs_clone_002_pos', 'zfs_clone_003_pos', 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_011_pos', 'zfs_clone_encrypted',
'zfs_clone_rm_nested', 'zfs_clone_nomount'] 'zfs_clone_deeply_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]
@ -210,9 +210,9 @@ tests = ['zfs_create_001_pos', 'zfs_create_002_pos', 'zfs_create_003_pos',
'zfs_create_004_pos', 'zfs_create_005_pos', 'zfs_create_006_pos', 'zfs_create_004_pos', 'zfs_create_005_pos', 'zfs_create_006_pos',
'zfs_create_007_pos', 'zfs_create_008_neg', 'zfs_create_009_neg', 'zfs_create_007_pos', 'zfs_create_008_neg', 'zfs_create_009_neg',
'zfs_create_010_neg', 'zfs_create_011_pos', 'zfs_create_012_pos', 'zfs_create_010_neg', 'zfs_create_011_pos', 'zfs_create_012_pos',
'zfs_create_013_pos', 'zfs_create_014_pos', 'zfs_create_encrypted', 'zfs_create_013_pos', 'zfs_create_014_pos', 'zfs_create_015_pos',
'zfs_create_crypt_combos', 'zfs_create_dryrun', 'zfs_create_nomount', 'zfs_create_encrypted', 'zfs_create_crypt_combos', 'zfs_create_dryrun',
'zfs_create_verbose'] 'zfs_create_nomount', 'zfs_create_verbose']
tags = ['functional', 'cli_root', 'zfs_create'] tags = ['functional', 'cli_root', 'zfs_create']
[tests/functional/cli_root/zpool_prefetch] [tests/functional/cli_root/zpool_prefetch]
@ -300,8 +300,9 @@ tests = ['zfs_rename_001_pos', 'zfs_rename_002_pos', 'zfs_rename_003_pos',
'zfs_rename_004_neg', 'zfs_rename_005_neg', 'zfs_rename_006_pos', 'zfs_rename_004_neg', 'zfs_rename_005_neg', 'zfs_rename_006_pos',
'zfs_rename_007_pos', 'zfs_rename_008_pos', 'zfs_rename_009_neg', 'zfs_rename_007_pos', 'zfs_rename_008_pos', 'zfs_rename_009_neg',
'zfs_rename_010_neg', 'zfs_rename_011_pos', 'zfs_rename_012_neg', 'zfs_rename_010_neg', 'zfs_rename_011_pos', 'zfs_rename_012_neg',
'zfs_rename_013_pos', 'zfs_rename_014_neg', 'zfs_rename_encrypted_child', 'zfs_rename_013_pos', 'zfs_rename_014_neg', 'zfs_rename_015_pos',
'zfs_rename_to_encrypted', 'zfs_rename_mountpoint', 'zfs_rename_nounmount'] 'zfs_rename_encrypted_child', 'zfs_rename_to_encrypted', 'zfs_rename_mountpoint',
'zfs_rename_nounmount']
tags = ['functional', 'cli_root', 'zfs_rename'] tags = ['functional', 'cli_root', 'zfs_rename']
[tests/functional/cli_root/zfs_reservation] [tests/functional/cli_root/zfs_reservation]

View File

@ -1171,6 +1171,22 @@ function datasetnonexists
return 0 return 0
} }
# Check if the specified dataset property has the expected value or fail
function dataset_has_prop # property expected_value dataset
{
typeset prop=$1
typeset expected=$2
typeset dataset=$3
typeset value=""
value="$(get_prop "$prop" "$dataset")"
[[ "$value" == "$expected" ]] || {
log_note "dataset $dataset: property $prop == $value (!= $expected)"
return 1
}
}
# FreeBSD breaks exports(5) at whitespace and doesn't process escapes # FreeBSD breaks exports(5) at whitespace and doesn't process escapes
# Solaris just breaks # Solaris just breaks
# #
@ -2629,11 +2645,16 @@ function verify_opt_p_ops
typeset datatype=$2 typeset datatype=$2
typeset dataset=$3 typeset dataset=$3
typeset newdataset=$4 typeset newdataset=$4
typeset popt=$5
if [[ $datatype != "fs" && $datatype != "vol" ]]; then if [[ $datatype != "fs" && $datatype != "vol" ]]; then
log_fail "$datatype is not supported." log_fail "$datatype is not supported."
fi fi
if [[ -z "$popt" ]]; then
popt=-p
fi
# check parameters accordingly # check parameters accordingly
case $ops in case $ops in
create) create)
@ -2671,7 +2692,7 @@ function verify_opt_p_ops
log_mustnot datasetexists $newdataset ${newdataset%/*} log_mustnot datasetexists $newdataset ${newdataset%/*}
# with -p option, operation should succeed # with -p option, operation should succeed
log_must zfs $ops -p $dataset $newdataset log_must zfs $ops $popt $dataset $newdataset
block_device_wait block_device_wait
if ! datasetexists $newdataset ; then if ! datasetexists $newdataset ; then
@ -2680,7 +2701,7 @@ function verify_opt_p_ops
# when $ops is create or clone, redo the operation still return zero # when $ops is create or clone, redo the operation still return zero
if [[ $ops != "rename" ]]; then if [[ $ops != "rename" ]]; then
log_must zfs $ops -p $dataset $newdataset log_must zfs $ops $popt $dataset $newdataset
fi fi
return 0 return 0

View File

@ -677,6 +677,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zfs_clone/zfs_clone_008_neg.ksh \ functional/cli_root/zfs_clone/zfs_clone_008_neg.ksh \
functional/cli_root/zfs_clone/zfs_clone_009_neg.ksh \ functional/cli_root/zfs_clone/zfs_clone_009_neg.ksh \
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_011_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_nomount.ksh \
@ -705,6 +706,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zfs_create/zfs_create_012_pos.ksh \ functional/cli_root/zfs_create/zfs_create_012_pos.ksh \
functional/cli_root/zfs_create/zfs_create_013_pos.ksh \ functional/cli_root/zfs_create/zfs_create_013_pos.ksh \
functional/cli_root/zfs_create/zfs_create_014_pos.ksh \ functional/cli_root/zfs_create/zfs_create_014_pos.ksh \
functional/cli_root/zfs_create/zfs_create_015_pos.ksh \
functional/cli_root/zfs_create/zfs_create_crypt_combos.ksh \ functional/cli_root/zfs_create/zfs_create_crypt_combos.ksh \
functional/cli_root/zfs_create/zfs_create_dryrun.ksh \ functional/cli_root/zfs_create/zfs_create_dryrun.ksh \
functional/cli_root/zfs_create/zfs_create_encrypted.ksh \ functional/cli_root/zfs_create/zfs_create_encrypted.ksh \
@ -864,6 +866,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/cli_root/zfs_rename/zfs_rename_012_neg.ksh \ functional/cli_root/zfs_rename/zfs_rename_012_neg.ksh \
functional/cli_root/zfs_rename/zfs_rename_013_pos.ksh \ functional/cli_root/zfs_rename/zfs_rename_013_pos.ksh \
functional/cli_root/zfs_rename/zfs_rename_014_neg.ksh \ functional/cli_root/zfs_rename/zfs_rename_014_neg.ksh \
functional/cli_root/zfs_rename/zfs_rename_015_pos.ksh \
functional/cli_root/zfs_rename/zfs_rename_encrypted_child.ksh \ functional/cli_root/zfs_rename/zfs_rename_encrypted_child.ksh \
functional/cli_root/zfs_rename/zfs_rename_mountpoint.ksh \ functional/cli_root/zfs_rename/zfs_rename_mountpoint.ksh \
functional/cli_root/zfs_rename/zfs_rename_nounmount.ksh \ functional/cli_root/zfs_rename/zfs_rename_nounmount.ksh \

View File

@ -0,0 +1,94 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2026 Ivan Shapovalov <intelfx@intelfx.name>
#
. $STF_SUITE/include/libtest.shlib
#
# DESCRIPTION:
# `zfs clone -pp` should create the parent of the new filesystem with `canmount=off`.
#
# STRATEGY:
# 1. Prepare snapshots
# 2. Make sure the parent of the clone target does not exist
# 3. Make sure that `zfs clone -pp` works the same as `-p`
# 4. Make sure that the newly created parent has `canmount=off`
#
verify_runnable "both"
function setup
{
log_note "Create snapshots and mount them..."
for snap in $SNAPFS $SNAPFS1 ; do
if ! snapexists "$snap" ; then
log_must zfs snapshot "$snap"
fi
done
return 0
}
function cleanup
{
datasetexists "$TESTPOOL/notexist" && destroy_dataset "$TESTPOOL/notexist" -rRf
for snap in $SNAPFS $SNAPFS1 ; do
snapexists "$snap" && destroy_dataset "$snap" -Rf
done
return 0
}
log_onexit cleanup
log_assert "'zfs clone -pp' should work as expected."
setup
log_mustnot datasetexists "$TESTPOOL/notexist"
log_mustnot datasetexists "$TESTPOOL/notexist/new"
log_mustnot datasetexists "$TESTPOOL/notexist/new2"
log_must verify_opt_p_ops "clone" "fs" "$SNAPFS" \
"$TESTPOOL/notexist/new/clonefs$$" "-pp"
log_must dataset_has_prop canmount off "$TESTPOOL/notexist"
log_must dataset_has_prop canmount off "$TESTPOOL/notexist/new"
log_mustnot ismounted "$TESTPOOL/notexist"
log_mustnot ismounted "$TESTPOOL/notexist/new"
if is_global_zone ; then
log_must verify_opt_p_ops "clone" "vol" "$SNAPFS1" \
"$TESTPOOL/notexist/new2/clonevol$$" "-pp"
log_must dataset_has_prop canmount off "$TESTPOOL/notexist/new2"
log_mustnot ismounted "$TESTPOOL/notexist/new2"
fi
log_pass "'zfs clone -pp' works as expected."

View File

@ -0,0 +1,74 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2026 Ivan Shapovalov <intelfx@intelfx.name>
#
. $STF_SUITE/include/libtest.shlib
#
# DESCRIPTION:
# `zfs create -pp` should create the parent of the new filesystem with `canmount=off`.
#
# STRATEGY:
# 1. Make sure the parent of the dataset to be created does not exist
# 2. Make sure that `zfs create -pp` works the same as `-p`
# 3. Make sure that the newly created parent has `canmount=off`
verify_runnable "both"
function cleanup
{
datasetexists "$TESTPOOL/notexist" && \
destroy_dataset "$TESTPOOL/notexist" -rRf
return 0
}
log_onexit cleanup
log_assert "'zfs create -p' should work as expected."
log_mustnot datasetexists "$TESTPOOL/notexist"
log_mustnot datasetexists "$TESTPOOL/notexist/new"
log_mustnot datasetexists "$TESTPOOL/notexist/new2"
log_must verify_opt_p_ops "create" "fs" \
"$TESTPOOL/notexist/new/create$$" "" "-pp"
log_must dataset_has_prop canmount off "$TESTPOOL/notexist"
log_must dataset_has_prop canmount off "$TESTPOOL/notexist/new"
log_mustnot ismounted "$TESTPOOL/notexist"
log_mustnot ismounted "$TESTPOOL/notexist/new"
# verify volume creation
if is_global_zone; then
log_must verify_opt_p_ops "create" "vol" \
"$TESTPOOL/notexist/new2/volume$$" "" "-pp"
log_must dataset_has_prop canmount off "$TESTPOOL/notexist/new2"
log_mustnot ismounted "$TESTPOOL/notexist/new2"
fi
log_pass "'zfs create -p' works as expected."

View File

@ -0,0 +1,86 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or https://opensource.org/licenses/CDDL-1.0.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2026 Ivan Shapovalov <intelfx@intelfx.name>
#
. $STF_SUITE/include/libtest.shlib
#
# DESCRIPTION
# `zfs rename -pp` should create the parent of the new filesystem with `canmount=off`.
#
# STRATEGY:
# 1. Make sure the parent of the rename target does not exist
# 2. Make sure that `zfs rename -pp` works the same as `-p`
# 3. Make sure that the newly created parent has `canmount=off`
#
verify_runnable "both"
function cleanup
{
datasetexists "$TESTPOOL/notexist" && \
destroy_dataset "$TESTPOOL/notexist" -Rf
datasetexists "$TESTPOOL/$TESTFS" && \
destroy_dataset "$TESTPOOL/$TESTFS" -Rf
log_must zfs create "$TESTPOOL/$TESTFS"
if is_global_zone ; then
datasetexists "$TESTPOOL/$TESTVOL" && \
destroy_dataset "$TESTPOOL/$TESTVOL" -Rf
log_must zfs create -V "$VOLSIZE" "$TESTPOOL/$TESTVOL"
fi
return 0
}
log_onexit cleanup
log_assert "'zfs rename -pp' should work as expected."
log_must_not datasetexists "$TESTPOOL/notexist"
log_must_not datasetexists "$TESTPOOL/notexist/new"
log_must_not datasetexists "$TESTPOOL/notexist/new2"
log_must verify_opt_p_ops "rename" "fs" "$TESTPOOL/$TESTFS" \
"$TESTPOOL/notexist/new/$TESTFS1" "-pp"
log_must dataset_has_prop canmount off "$TESTPOOL/notexist"
log_must dataset_has_prop canmount off "$TESTPOOL/notexist/new"
log_mustnot ismounted "$TESTPOOL/notexist"
log_mustnot ismounted "$TESTPOOL/notexist/new"
if is_global_zone; then
log_must verify_opt_p_ops "rename" "vol" "$TESTPOOL/$TESTVOL" \
"$TESTPOOL/notexist/new2/$TESTVOL1" "-pp"
log_must dataset_has_prop canmount off "$TESTPOOL/notexist/new2"
log_mustnot ismounted "$TESTPOOL/notexist/new2"
fi
log_pass "'zfs rename -pp' works as expected."