From f4c3a38b845f5525542c7c7c7dee815f6dde2674 Mon Sep 17 00:00:00 2001 From: Pawel Jakub Dawidek Date: Sat, 1 Apr 2023 20:01:26 -0700 Subject: [PATCH] Allow block cloning across encrypted datasets. Block cloning is now possible between two encrypted datasets that share the same encryption root. Signed-off-by: Pawel Jakub Dawidek --- include/sys/dmu.h | 1 + include/sys/dmu_objset.h | 1 + include/sys/dsl_crypt.h | 1 + man/man7/zpool-features.7 | 6 +++--- module/zfs/brt.c | 6 ++---- module/zfs/dmu_objset.c | 12 ++++++++++++ module/zfs/dsl_crypt.c | 2 +- module/zfs/zfs_vnops.c | 25 ++++++++++--------------- 8 files changed, 31 insertions(+), 23 deletions(-) diff --git a/include/sys/dmu.h b/include/sys/dmu.h index 1b82ff620f27..1ee32db2a224 100644 --- a/include/sys/dmu.h +++ b/include/sys/dmu.h @@ -64,6 +64,7 @@ struct zio; struct blkptr; struct zap_cursor; struct dsl_dataset; +struct dsl_dir; struct dsl_pool; struct dnode; struct drr_begin; diff --git a/include/sys/dmu_objset.h b/include/sys/dmu_objset.h index d22c682875d8..9062ddce72c2 100644 --- a/include/sys/dmu_objset.h +++ b/include/sys/dmu_objset.h @@ -246,6 +246,7 @@ boolean_t dmu_objset_userspace_present(objset_t *os); boolean_t dmu_objset_userobjused_enabled(objset_t *os); boolean_t dmu_objset_userobjspace_upgradable(objset_t *os); boolean_t dmu_objset_userobjspace_present(objset_t *os); +uint64_t dmu_objset_encryption_root(objset_t *os); boolean_t dmu_objset_incompatible_encryption_version(objset_t *os); boolean_t dmu_objset_projectquota_enabled(objset_t *os); boolean_t dmu_objset_projectquota_present(objset_t *os); diff --git a/include/sys/dsl_crypt.h b/include/sys/dsl_crypt.h index 72716e296c9e..db7339ff1588 100644 --- a/include/sys/dsl_crypt.h +++ b/include/sys/dsl_crypt.h @@ -190,6 +190,7 @@ void key_mapping_rele(spa_t *spa, dsl_key_mapping_t *km, const void *tag); int spa_keystore_lookup_key(spa_t *spa, uint64_t dsobj, const void *tag, dsl_crypto_key_t **dck_out); +int dsl_dir_get_encryption_root_ddobj(dsl_dir_t *dd, uint64_t *rddobj); int dsl_crypto_populate_key_nvlist(struct objset *os, uint64_t from_ivset_guid, nvlist_t **nvl_out); int dsl_crypto_recv_raw_key_check(struct dsl_dataset *ds, diff --git a/man/man7/zpool-features.7 b/man/man7/zpool-features.7 index a4d595cd3cd9..5584002a34fd 100644 --- a/man/man7/zpool-features.7 +++ b/man/man7/zpool-features.7 @@ -353,9 +353,9 @@ When this feature is enabled ZFS will use block cloning for operations like Block cloning allows to create multiple references to a single block. It is much faster than copying the data (as the actual data is neither read nor written) and takes no additional space. -Blocks can be cloned across datasets under some conditions (like disabled -encryption and equal -.Nm recordsize ) . +Blocks can be cloned across datasets under some conditions (like equal +.Nm recordsize , +the same encryption root, etc.). .Pp This feature becomes .Sy active diff --git a/module/zfs/brt.c b/module/zfs/brt.c index 99bd472d6fb4..39d5a6c6182f 100644 --- a/module/zfs/brt.c +++ b/module/zfs/brt.c @@ -156,10 +156,8 @@ * (copying the file content to the new dataset and removing the source file). * In that case Block Cloning will only be used briefly, because the BRT entries * will be removed when the source is removed. - * Note: currently it is not possible to clone blocks between encrypted - * datasets, even if those datasets use the same encryption key (this includes - * snapshots of encrypted datasets). Cloning blocks between datasets that use - * the same keys should be possible and should be implemented in the future. + * Block Cloning across encrypted datasets is supported as long as both datasets + * share the same encryption root. * * Block Cloning flow through ZFS layers. * diff --git a/module/zfs/dmu_objset.c b/module/zfs/dmu_objset.c index c19ebf424953..09c84b2846ee 100644 --- a/module/zfs/dmu_objset.c +++ b/module/zfs/dmu_objset.c @@ -2973,6 +2973,18 @@ dmu_objset_find(const char *name, int func(const char *, void *), void *arg, return (error); } +uint64_t +dmu_objset_encryption_root(objset_t *os) +{ + dsl_dir_t *dd = os->os_dsl_dataset->ds_dir; + uint64_t encroot = 0; + + if (dsl_dir_get_encryption_root_ddobj(dd, &encroot) != 0) { + return (0); + } + return (encroot); +} + boolean_t dmu_objset_incompatible_encryption_version(objset_t *os) { diff --git a/module/zfs/dsl_crypt.c b/module/zfs/dsl_crypt.c index 5e6e4e3d6c39..066bc2cdda62 100644 --- a/module/zfs/dsl_crypt.c +++ b/module/zfs/dsl_crypt.c @@ -328,7 +328,7 @@ spa_keystore_fini(spa_keystore_t *sk) rw_destroy(&sk->sk_dk_lock); } -static int +int dsl_dir_get_encryption_root_ddobj(dsl_dir_t *dd, uint64_t *rddobj) { if (dd->dd_crypto_obj == 0) diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c index db80be783899..5ed3dc026c5a 100644 --- a/module/zfs/zfs_vnops.c +++ b/module/zfs/zfs_vnops.c @@ -1107,6 +1107,16 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, return (SET_ERROR(EXDEV)); } + /* + * Cloning across encrypted dataset is possible only if they share + * the same encryption root. + */ + if (inos != outos && dmu_objset_encryption_root(inos) != + dmu_objset_encryption_root(outos)) { + zfs_exit_two(inzfsvfs, outzfsvfs, FTAG); + return (SET_ERROR(EXDEV)); + } + /* * We don't copy source file's flags that's why we don't allow to clone * files that are in quarantine. @@ -1266,21 +1276,6 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, } break; } - /* - * Encrypted data is fine as long as it comes from the same - * dataset. - * TODO: We want to extend it in the future to allow cloning to - * datasets with the same keys, like clones or to be able to - * clone a file from a snapshot of an encrypted dataset into the - * dataset itself. - */ - if (BP_IS_PROTECTED(&bps[0])) { - if (inzfsvfs != outzfsvfs) { - dmu_tx_abort(tx); - error = SET_ERROR(EXDEV); - break; - } - } dmu_tx_hold_sa(tx, outzp->z_sa_hdl, B_FALSE); db = (dmu_buf_impl_t *)sa_get_db(outzp->z_sa_hdl);