From 35b896ee0fcbbc50dddb0a2ea85f337df6711da7 Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Thu, 5 Jun 2025 16:23:22 +1000 Subject: [PATCH 1/4] dsl_dataset: rename dmu_objset_clone* to dsl_dataset_clone* And make its check and sync functions visible, so I can hook them up to zcp_synctask. Rename not strictly necessary, but it definitely looks more like a dsl_dataset thing than a dmu_objset thing, to the extent that those things even have a meaningful distinction. Signed-off-by: Rob Norris Sponsored-by: https://despairlabs.com/sponsor/ Signed-off-by: Rob Norris Sponsored-by: https://despairlabs.com/sponsor/ --- cmd/ztest.c | 10 ++-- include/sys/dmu.h | 1 - include/sys/dsl_dataset.h | 9 ++++ module/zfs/dmu_objset.c | 107 -------------------------------------- module/zfs/dsl_dataset.c | 100 +++++++++++++++++++++++++++++++++++ module/zfs/zfs_ioctl.c | 2 +- 6 files changed, 115 insertions(+), 114 deletions(-) diff --git a/cmd/ztest.c b/cmd/ztest.c index 9191c9d70e8a..3b63ad43fdf8 100644 --- a/cmd/ztest.c +++ b/cmd/ztest.c @@ -4916,7 +4916,7 @@ ztest_dsl_dataset_promote_busy(ztest_ds_t *zd, uint64_t id) fatal(B_FALSE, "dmu_take_snapshot(%s) = %d", snap1name, error); } - error = dmu_objset_clone(clone1name, snap1name); + error = dsl_dataset_clone(clone1name, snap1name); if (error) { if (error == ENOSPC) { ztest_record_enospc(FTAG); @@ -4943,7 +4943,7 @@ ztest_dsl_dataset_promote_busy(ztest_ds_t *zd, uint64_t id) fatal(B_FALSE, "dmu_open_snapshot(%s) = %d", snap3name, error); } - error = dmu_objset_clone(clone2name, snap3name); + error = dsl_dataset_clone(clone2name, snap3name); if (error) { if (error == ENOSPC) { ztest_record_enospc(FTAG); @@ -6334,13 +6334,13 @@ ztest_dmu_snapshot_hold(ztest_ds_t *zd, uint64_t id) fatal(B_FALSE, "dmu_objset_snapshot(%s) = %d", fullname, error); } - error = dmu_objset_clone(clonename, fullname); + error = dsl_dataset_clone(clonename, fullname); if (error) { if (error == ENOSPC) { - ztest_record_enospc("dmu_objset_clone"); + ztest_record_enospc("dsl_dataset_clone"); goto out; } - fatal(B_FALSE, "dmu_objset_clone(%s) = %d", clonename, error); + fatal(B_FALSE, "dsl_dataset_clone(%s) = %d", clonename, error); } error = dsl_destroy_snapshot(fullname, B_TRUE); diff --git a/include/sys/dmu.h b/include/sys/dmu.h index bc2aea71e6a0..fec7714fd62d 100644 --- a/include/sys/dmu.h +++ b/include/sys/dmu.h @@ -360,7 +360,6 @@ void dmu_objset_evict_dbufs(objset_t *os); int dmu_objset_create(const char *name, dmu_objset_type_t type, uint64_t flags, struct dsl_crypto_params *dcp, dmu_objset_create_sync_func_t func, void *arg); -int dmu_objset_clone(const char *name, const char *origin); int dsl_destroy_snapshots_nvl(struct nvlist *snaps, boolean_t defer, struct nvlist *errlist); int dmu_objset_snapshot_one(const char *fsname, const char *snapname); diff --git a/include/sys/dsl_dataset.h b/include/sys/dsl_dataset.h index 681294593001..2e1f9847f34c 100644 --- a/include/sys/dsl_dataset.h +++ b/include/sys/dsl_dataset.h @@ -276,6 +276,12 @@ dsl_dataset_phys(dsl_dataset_t *ds) return ((dsl_dataset_phys_t *)ds->ds_dbuf->db_data); } +typedef struct dsl_dataset_clone_arg_t { + const char *ddca_clone; + const char *ddca_origin; + cred_t *ddca_cred; +} dsl_dataset_clone_arg_t; + typedef struct dsl_dataset_promote_arg { const char *ddpa_clonename; dsl_dataset_t *ddpa_clone; @@ -364,6 +370,9 @@ uint64_t dsl_dataset_create_sync_dd(dsl_dir_t *dd, dsl_dataset_t *origin, void dsl_dataset_snapshot_sync(void *arg, dmu_tx_t *tx); int dsl_dataset_snapshot_check(void *arg, dmu_tx_t *tx); int dsl_dataset_snapshot(nvlist_t *snaps, nvlist_t *props, nvlist_t *errors); +void dsl_dataset_clone_sync(void *arg, dmu_tx_t *tx); +int dsl_dataset_clone_check(void *arg, dmu_tx_t *tx); +int dsl_dataset_clone(const char *clone, const char *origin); void dsl_dataset_promote_sync(void *arg, dmu_tx_t *tx); int dsl_dataset_promote_check(void *arg, dmu_tx_t *tx); int dsl_dataset_promote(const char *name, char *conflsnap); diff --git a/module/zfs/dmu_objset.c b/module/zfs/dmu_objset.c index d08252e3c9d9..b3f792e4ae6b 100644 --- a/module/zfs/dmu_objset.c +++ b/module/zfs/dmu_objset.c @@ -1383,112 +1383,6 @@ dmu_objset_create(const char *name, dmu_objset_type_t type, uint64_t flags, return (rv); } -typedef struct dmu_objset_clone_arg { - const char *doca_clone; - const char *doca_origin; - cred_t *doca_cred; -} dmu_objset_clone_arg_t; - -static int -dmu_objset_clone_check(void *arg, dmu_tx_t *tx) -{ - dmu_objset_clone_arg_t *doca = arg; - dsl_dir_t *pdd; - const char *tail; - int error; - dsl_dataset_t *origin; - dsl_pool_t *dp = dmu_tx_pool(tx); - - if (strchr(doca->doca_clone, '@') != NULL) - return (SET_ERROR(EINVAL)); - - if (strlen(doca->doca_clone) >= ZFS_MAX_DATASET_NAME_LEN) - return (SET_ERROR(ENAMETOOLONG)); - - error = dsl_dir_hold(dp, doca->doca_clone, FTAG, &pdd, &tail); - if (error != 0) - return (error); - if (tail == NULL) { - dsl_dir_rele(pdd, FTAG); - return (SET_ERROR(EEXIST)); - } - - error = dsl_fs_ss_limit_check(pdd, 1, ZFS_PROP_FILESYSTEM_LIMIT, NULL, - doca->doca_cred); - if (error != 0) { - dsl_dir_rele(pdd, FTAG); - return (SET_ERROR(EDQUOT)); - } - - error = dsl_dataset_hold(dp, doca->doca_origin, FTAG, &origin); - if (error != 0) { - dsl_dir_rele(pdd, FTAG); - return (error); - } - - /* You can only clone snapshots, not the head datasets. */ - if (!origin->ds_is_snapshot) { - dsl_dataset_rele(origin, FTAG); - dsl_dir_rele(pdd, FTAG); - return (SET_ERROR(EINVAL)); - } - - dsl_dataset_rele(origin, FTAG); - dsl_dir_rele(pdd, FTAG); - - return (0); -} - -static void -dmu_objset_clone_sync(void *arg, dmu_tx_t *tx) -{ - dmu_objset_clone_arg_t *doca = arg; - dsl_pool_t *dp = dmu_tx_pool(tx); - dsl_dir_t *pdd; - const char *tail; - dsl_dataset_t *origin, *ds; - uint64_t obj; - char namebuf[ZFS_MAX_DATASET_NAME_LEN]; - - VERIFY0(dsl_dir_hold(dp, doca->doca_clone, FTAG, &pdd, &tail)); - VERIFY0(dsl_dataset_hold(dp, doca->doca_origin, FTAG, &origin)); - - obj = dsl_dataset_create_sync(pdd, tail, origin, 0, - doca->doca_cred, NULL, tx); - - VERIFY0(dsl_dataset_hold_obj(pdd->dd_pool, obj, FTAG, &ds)); - dsl_dataset_name(origin, namebuf); - spa_history_log_internal_ds(ds, "clone", tx, - "origin=%s (%llu)", namebuf, (u_longlong_t)origin->ds_object); - dsl_dataset_rele(ds, FTAG); - dsl_dataset_rele(origin, FTAG); - dsl_dir_rele(pdd, FTAG); -} - -int -dmu_objset_clone(const char *clone, const char *origin) -{ - dmu_objset_clone_arg_t doca; - - cred_t *cr = CRED(); - crhold(cr); - - doca.doca_clone = clone; - doca.doca_origin = origin; - doca.doca_cred = cr; - - int rv = dsl_sync_task(clone, - dmu_objset_clone_check, dmu_objset_clone_sync, &doca, - 6, ZFS_SPACE_CHECK_NORMAL); - - if (rv == 0) - zvol_create_minor(clone); - - crfree(cr); - - return (rv); -} - int dmu_objset_snapshot_one(const char *fsname, const char *snapname) { @@ -3165,7 +3059,6 @@ EXPORT_SYMBOL(dmu_objset_rele_flags); EXPORT_SYMBOL(dmu_objset_disown); EXPORT_SYMBOL(dmu_objset_from_ds); EXPORT_SYMBOL(dmu_objset_create); -EXPORT_SYMBOL(dmu_objset_clone); EXPORT_SYMBOL(dmu_objset_stats); EXPORT_SYMBOL(dmu_objset_fast_stat); EXPORT_SYMBOL(dmu_objset_spa); diff --git a/module/zfs/dsl_dataset.c b/module/zfs/dsl_dataset.c index ae55cf8b8ae2..c0a7872c40ad 100644 --- a/module/zfs/dsl_dataset.c +++ b/module/zfs/dsl_dataset.c @@ -3320,6 +3320,106 @@ dsl_dataset_rollback(const char *fsname, const char *tosnap, void *owner, 1, ZFS_SPACE_CHECK_RESERVED)); } +int +dsl_dataset_clone_check(void *arg, dmu_tx_t *tx) +{ + dsl_dataset_clone_arg_t *ddca = arg; + dsl_dir_t *pdd; + const char *tail; + int error; + dsl_dataset_t *origin; + dsl_pool_t *dp = dmu_tx_pool(tx); + + if (strchr(ddca->ddca_clone, '@') != NULL) + return (SET_ERROR(EINVAL)); + + if (strlen(ddca->ddca_clone) >= ZFS_MAX_DATASET_NAME_LEN) + return (SET_ERROR(ENAMETOOLONG)); + + error = dsl_dir_hold(dp, ddca->ddca_clone, FTAG, &pdd, &tail); + if (error != 0) + return (error); + if (tail == NULL) { + dsl_dir_rele(pdd, FTAG); + return (SET_ERROR(EEXIST)); + } + + error = dsl_fs_ss_limit_check(pdd, 1, ZFS_PROP_FILESYSTEM_LIMIT, NULL, + ddca->ddca_cred); + if (error != 0) { + dsl_dir_rele(pdd, FTAG); + return (SET_ERROR(EDQUOT)); + } + + error = dsl_dataset_hold(dp, ddca->ddca_origin, FTAG, &origin); + if (error != 0) { + dsl_dir_rele(pdd, FTAG); + return (error); + } + + /* You can only clone snapshots, not the head datasets. */ + if (!origin->ds_is_snapshot) { + dsl_dataset_rele(origin, FTAG); + dsl_dir_rele(pdd, FTAG); + return (SET_ERROR(EINVAL)); + } + + dsl_dataset_rele(origin, FTAG); + dsl_dir_rele(pdd, FTAG); + + return (0); +} + +void +dsl_dataset_clone_sync(void *arg, dmu_tx_t *tx) +{ + dsl_dataset_clone_arg_t *ddca = arg; + dsl_pool_t *dp = dmu_tx_pool(tx); + dsl_dir_t *pdd; + const char *tail; + dsl_dataset_t *origin, *ds; + uint64_t obj; + char namebuf[ZFS_MAX_DATASET_NAME_LEN]; + + VERIFY0(dsl_dir_hold(dp, ddca->ddca_clone, FTAG, &pdd, &tail)); + VERIFY0(dsl_dataset_hold(dp, ddca->ddca_origin, FTAG, &origin)); + + obj = dsl_dataset_create_sync(pdd, tail, origin, 0, + ddca->ddca_cred, NULL, tx); + + VERIFY0(dsl_dataset_hold_obj(pdd->dd_pool, obj, FTAG, &ds)); + dsl_dataset_name(origin, namebuf); + spa_history_log_internal_ds(ds, "clone", tx, + "origin=%s (%llu)", namebuf, (u_longlong_t)origin->ds_object); + dsl_dataset_rele(ds, FTAG); + dsl_dataset_rele(origin, FTAG); + dsl_dir_rele(pdd, FTAG); +} + +int +dsl_dataset_clone(const char *clone, const char *origin) +{ + dsl_dataset_clone_arg_t ddca; + + cred_t *cr = CRED(); + crhold(cr); + + ddca.ddca_clone = clone; + ddca.ddca_origin = origin; + ddca.ddca_cred = cr; + + int rv = dsl_sync_task(clone, + dsl_dataset_clone_check, dsl_dataset_clone_sync, &ddca, + 6, ZFS_SPACE_CHECK_NORMAL); + + if (rv == 0) + zvol_create_minor(clone); + + crfree(cr); + + return (rv); +} + struct promotenode { list_node_t link; dsl_dataset_t *ds; diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index c37d8a1c07e1..ebb1cfd07125 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -3730,7 +3730,7 @@ zfs_ioc_clone(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl) if (dataset_namecheck(origin_name, NULL, NULL) != 0) return (SET_ERROR(EINVAL)); - error = dmu_objset_clone(fsname, origin_name); + error = dsl_dataset_clone(fsname, origin_name); /* * It would be nice to do this atomically. From 00c8e18f51550117dc5d3fe540d20951df2da6da Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Thu, 5 Jun 2025 16:07:36 +1000 Subject: [PATCH 2/4] zcp_synctask: add zfs.sync.clone() Signed-off-by: Rob Norris Sponsored-by: https://despairlabs.com/sponsor/ --- module/zfs/zcp_synctask.c | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/module/zfs/zcp_synctask.c b/module/zfs/zcp_synctask.c index 28f734abfea7..ce3a4e8a9538 100644 --- a/module/zfs/zcp_synctask.c +++ b/module/zfs/zcp_synctask.c @@ -18,6 +18,7 @@ * Copyright (c) 2016, 2017 by Delphix. All rights reserved. * Copyright (c) 2019, 2020 by Christian Schwarz. All rights reserved. * Copyright 2020 Joyent, Inc. + * Copyright (c) 2025, Rob Norris */ #include @@ -113,6 +114,48 @@ zcp_sync_task(lua_State *state, dsl_checkfunc_t *checkfunc, return (err); } +static int zcp_synctask_clone(lua_State *, boolean_t, nvlist_t *); +static const zcp_synctask_info_t zcp_synctask_clone_info = { + .name = "clone", + .func = zcp_synctask_clone, + .pargs = { + {.za_name = "snapshot", .za_lua_type = LUA_TSTRING }, + {.za_name = "newdataset", .za_lua_type = LUA_TSTRING }, + {NULL, 0} + }, + .kwargs = { + {NULL, 0} + }, + .space_check = ZFS_SPACE_CHECK_NORMAL, + .blocks_modified = 3 +}; +static int +zcp_synctask_clone(lua_State *state, boolean_t sync, nvlist_t *err_details) +{ + (void) err_details; + int err; + + zcp_run_info_t *ri = zcp_run_info(state); + dsl_dataset_clone_arg_t ddca = { + .ddca_origin = lua_tostring(state, 1), + .ddca_clone = lua_tostring(state, 2), + .ddca_cred = ri->zri_cred, + }; + + err = zcp_sync_task(state, dsl_dataset_clone_check, + dsl_dataset_clone_sync, &ddca, sync, ddca.ddca_origin); + + if (err == 0) + /* + * If the new dataset is a zvol, it will need a device + * node. Put it on the list to be considered in open context. + * We don't have to check the type here; if it's not a zvol, + * or has volmode=none, it will be ignored. + */ + fnvlist_add_boolean(ri->zri_new_zvols, ddca.ddca_clone); + + return (err); +} static int zcp_synctask_destroy(lua_State *, boolean_t, nvlist_t *); static const zcp_synctask_info_t zcp_synctask_destroy_info = { @@ -561,6 +604,7 @@ zcp_load_synctask_lib(lua_State *state, boolean_t sync) { const zcp_synctask_info_t *zcp_synctask_funcs[] = { &zcp_synctask_destroy_info, + &zcp_synctask_clone_info, &zcp_synctask_promote_info, &zcp_synctask_rollback_info, &zcp_synctask_snapshot_info, From 4f09801bbbd9fb8891396b597554ea38fe5780f2 Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Thu, 5 Jun 2025 17:04:52 +1000 Subject: [PATCH 3/4] ZTS: test zfs.sync.clone() for filesystems and volumes Signed-off-by: Rob Norris Sponsored-by: https://despairlabs.com/sponsor/ --- tests/runfiles/common.run | 2 +- tests/runfiles/sanity.run | 2 +- tests/zfs-tests/tests/Makefile.am | 2 + .../synctask_core/tst.clone.ksh | 55 +++++++++++++++++++ .../synctask_core/tst.clone.zcp | 28 ++++++++++ 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100755 tests/zfs-tests/tests/functional/channel_program/synctask_core/tst.clone.ksh create mode 100644 tests/zfs-tests/tests/functional/channel_program/synctask_core/tst.clone.zcp diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 41e285a03eb5..144b228bcaba 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -140,7 +140,7 @@ tests = ['tst.destroy_fs', 'tst.destroy_snap', 'tst.get_count_and_limit', 'tst.promote_multiple', 'tst.promote_simple', 'tst.rollback_mult', 'tst.rollback_one', 'tst.set_props', 'tst.snapshot_destroy', 'tst.snapshot_neg', 'tst.snapshot_recursive', 'tst.snapshot_rename', 'tst.snapshot_simple', - 'tst.bookmark.create', 'tst.bookmark.copy', + 'tst.bookmark.create', 'tst.bookmark.copy', 'tst.clone', 'tst.terminate_by_signal' ] tags = ['functional', 'channel_program', 'synctask_core'] diff --git a/tests/runfiles/sanity.run b/tests/runfiles/sanity.run index 1dbdc4d4ea43..732f252b52d2 100644 --- a/tests/runfiles/sanity.run +++ b/tests/runfiles/sanity.run @@ -84,7 +84,7 @@ tests = ['tst.destroy_fs', 'tst.destroy_snap', 'tst.get_count_and_limit', 'tst.promote_multiple', 'tst.promote_simple', 'tst.rollback_mult', 'tst.rollback_one', 'tst.set_props', 'tst.snapshot_destroy', 'tst.snapshot_neg', 'tst.snapshot_recursive', 'tst.snapshot_simple', - 'tst.bookmark.create', 'tst.bookmark.copy'] + 'tst.bookmark.create', 'tst.bookmark.copy', 'tst.clone'] tags = ['functional', 'channel_program', 'synctask_core'] [tests/functional/cli_root/zdb] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 25247b9feec3..02b595be15e4 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -124,6 +124,7 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \ functional/channel_program/lua_core/tst.timeout.zcp \ functional/channel_program/synctask_core/tst.bookmark.copy.zcp \ functional/channel_program/synctask_core/tst.bookmark.create.zcp \ + functional/channel_program/synctask_core/tst.clone.zcp \ functional/channel_program/synctask_core/tst.get_index_props.out \ functional/channel_program/synctask_core/tst.get_index_props.zcp \ functional/channel_program/synctask_core/tst.get_number_props.out \ @@ -569,6 +570,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/channel_program/synctask_core/setup.ksh \ functional/channel_program/synctask_core/tst.bookmark.copy.ksh \ functional/channel_program/synctask_core/tst.bookmark.create.ksh \ + functional/channel_program/synctask_core/tst.clone.ksh \ functional/channel_program/synctask_core/tst.destroy_fs.ksh \ functional/channel_program/synctask_core/tst.destroy_snap.ksh \ functional/channel_program/synctask_core/tst.get_count_and_limit.ksh \ diff --git a/tests/zfs-tests/tests/functional/channel_program/synctask_core/tst.clone.ksh b/tests/zfs-tests/tests/functional/channel_program/synctask_core/tst.clone.ksh new file mode 100755 index 000000000000..d05a8253a288 --- /dev/null +++ b/tests/zfs-tests/tests/functional/channel_program/synctask_core/tst.clone.ksh @@ -0,0 +1,55 @@ +#!/bin/ksh -p +# 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 (c) 2016, 2017 by Delphix. All rights reserved. +# Copyright (c) 2025, Rob Norris +# + +. $STF_SUITE/tests/functional/channel_program/channel_common.kshlib + +# +# DESCRIPTION: Make sure basic cloning functionality works in channel programs +# + +verify_runnable "global" + +base=$TESTPOOL/$TESTFS/base + +function cleanup +{ + destroy_dataset $base "-R" +} + +log_onexit cleanup + +log_must zfs create $base + +# test filesystem cloning +log_must zfs create $base/fs +log_must zfs snapshot $base/fs@snap + +log_must_program_sync $TESTPOOL \ + $ZCP_ROOT/synctask_core/tst.clone.zcp $base/fs@snap $base/newfs + +# test zvol cloning +log_must zfs create -s -V 100G $base/vol +log_must zfs snapshot $base/vol@snap + +log_must_program_sync $TESTPOOL \ + $ZCP_ROOT/synctask_core/tst.clone.zcp $base/vol@snap $base/newvol + +# make sure the dev node was created +block_device_wait $ZVOL_DEVDIR/$base/newvol + +log_pass "Cloning works" diff --git a/tests/zfs-tests/tests/functional/channel_program/synctask_core/tst.clone.zcp b/tests/zfs-tests/tests/functional/channel_program/synctask_core/tst.clone.zcp new file mode 100644 index 000000000000..f6f63b09c150 --- /dev/null +++ b/tests/zfs-tests/tests/functional/channel_program/synctask_core/tst.clone.zcp @@ -0,0 +1,28 @@ +-- 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 (c) 2016, 2017 by Delphix. All rights reserved. +-- Copyright (c) 2025, Rob Norris +-- + +-- This program should be invoked as "zfs program " + +args = ... +argv = args["argv"] +assert(zfs.sync.clone(argv[1], argv[2]) == 0) +clones = {} +for c in zfs.list.clones(argv[1]) do + table.insert(clones, c) +end +assert(#clones == 1) +assert(clones[1] == argv[2]) From b7560361a95b1869aed08cc20edb016b1b04b9d6 Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Thu, 5 Jun 2025 15:50:42 +1000 Subject: [PATCH 4/4] zfs-program(8): document zfs.sync.clone() Signed-off-by: Rob Norris Sponsored-by: https://despairlabs.com/sponsor/ --- man/man8/zfs-program.8 | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/man/man8/zfs-program.8 b/man/man8/zfs-program.8 index 710142d1e76e..d87042c4c0c7 100644 --- a/man/man8/zfs-program.8 +++ b/man/man8/zfs-program.8 @@ -12,8 +12,9 @@ .\" Copyright (c) 2016, 2019 by Delphix. All Rights Reserved. .\" Copyright (c) 2019, 2020 by Christian Schwarz. All Rights Reserved. .\" Copyright 2020 Joyent, Inc. +.\" Copyright (c) 2025, Rob Norris .\" -.Dd May 27, 2021 +.Dd June 5, 2025 .Dt ZFS-PROGRAM 8 .Os . @@ -347,6 +348,32 @@ They are executed in "syncing context". .Pp The available sync submodule functions are as follows: .Bl -tag -width "xx" +.It Fn zfs.sync.clone snapshot newdataset +Create a new filesystem from a snapshot. +Returns 0 if the filesystem was successfully created, +and a nonzero error code otherwise. +.Pp +Note: Due to general limitations in channel programs, a filesystem created +this way will not be mounted, regardless of the value of the +.Sy mountpoint +and +.Sy canmount +properties. +This limitation may be removed in the future, +so it is recommended that you set +.Sy mountpoint Ns = Ns Sy none +or +.Sy canmount Ns = Ns Sy off +or +.Sy noauto +to avoid surprises. +.Pp +.Bl -tag -compact -width "newbookmark (string)" +.It Ar snapshot Pq string +Name of the source snapshot to clone. +.It Ar newdataset Pq string +Name of the target dataset to create. +.El .It Sy zfs.sync.destroy Ns Pq Ar dataset , Op Ar defer Ns = Ns Sy true Ns | Ns Sy false Destroy the given dataset. Returns 0 on successful destroy, or a nonzero error code if the dataset could @@ -474,6 +501,7 @@ The available .Sy zfs.check functions are: .Bl -tag -compact -width "xx" +.It Fn zfs.check.clone snapshot newdataset .It Sy zfs.check.destroy Ns Pq Ar dataset , Op Ar defer Ns = Ns Sy true Ns | Ns Sy false .It Fn zfs.check.promote dataset .It Fn zfs.check.rollback filesystem