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
+23 -2
View File
@@ -1171,6 +1171,22 @@ function datasetnonexists
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
# Solaris just breaks
#
@@ -2629,11 +2645,16 @@ function verify_opt_p_ops
typeset datatype=$2
typeset dataset=$3
typeset newdataset=$4
typeset popt=$5
if [[ $datatype != "fs" && $datatype != "vol" ]]; then
log_fail "$datatype is not supported."
fi
if [[ -z "$popt" ]]; then
popt=-p
fi
# check parameters accordingly
case $ops in
create)
@@ -2671,7 +2692,7 @@ function verify_opt_p_ops
log_mustnot datasetexists $newdataset ${newdataset%/*}
# with -p option, operation should succeed
log_must zfs $ops -p $dataset $newdataset
log_must zfs $ops $popt $dataset $newdataset
block_device_wait
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
if [[ $ops != "rename" ]]; then
log_must zfs $ops -p $dataset $newdataset
log_must zfs $ops $popt $dataset $newdataset
fi
return 0
+3
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_009_neg.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_encrypted.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_013_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_dryrun.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_013_pos.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_mountpoint.ksh \
functional/cli_root/zfs_rename/zfs_rename_nounmount.ksh \
@@ -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."
@@ -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."
@@ -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."