From bd8c6bd66f9dde7534ae2f52237a1b208721cbf7 Mon Sep 17 00:00:00 2001 From: Pawel Jakub Dawidek Date: Tue, 2 May 2023 14:24:43 -0700 Subject: [PATCH] Deny block cloning is dbuf size doesn't match BP size. I don't know an easy way to shrink down dbuf size, so just deny block cloning into dbufs that don't match our BP's size. This fixes the following situation: 1. Create a small file, eg. 1kB of random bytes. Its dbuf will be 1kB. 2. Create a larger file, eg. 2kB of random bytes. Its dbuf will be 2kB. 3. Truncate the large file to 0. Its dbuf will remain 2kB. 4. Clone the small file into the large file. Small file's BP lsize is 1kB, but the large file's dbuf is 2kB. Reviewed-by: Brian Behlendorf Signed-off-by: Pawel Jakub Dawidek Closes #14825 --- include/sys/dmu.h | 2 +- module/zfs/dmu.c | 29 +++++++++++++++++++++++++---- module/zfs/zfs_vnops.c | 8 ++++++-- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/include/sys/dmu.h b/include/sys/dmu.h index a5a5c3782..6a5fb5530 100644 --- a/include/sys/dmu.h +++ b/include/sys/dmu.h @@ -1066,7 +1066,7 @@ int dmu_offset_next(objset_t *os, uint64_t object, boolean_t hole, int dmu_read_l0_bps(objset_t *os, uint64_t object, uint64_t offset, uint64_t length, dmu_tx_t *tx, struct blkptr *bps, size_t *nbpsp); -void dmu_brt_clone(objset_t *os, uint64_t object, uint64_t offset, +int dmu_brt_clone(objset_t *os, uint64_t object, uint64_t offset, uint64_t length, dmu_tx_t *tx, const struct blkptr *bps, size_t nbps, boolean_t replay); diff --git a/module/zfs/dmu.c b/module/zfs/dmu.c index f8accafd6..c1f9d02f0 100644 --- a/module/zfs/dmu.c +++ b/module/zfs/dmu.c @@ -2257,7 +2257,7 @@ out: return (error); } -void +int dmu_brt_clone(objset_t *os, uint64_t object, uint64_t offset, uint64_t length, dmu_tx_t *tx, const blkptr_t *bps, size_t nbps, boolean_t replay) { @@ -2267,7 +2267,7 @@ dmu_brt_clone(objset_t *os, uint64_t object, uint64_t offset, uint64_t length, struct dirty_leaf *dl; dbuf_dirty_record_t *dr; const blkptr_t *bp; - int numbufs; + int error = 0, i, numbufs; spa = os->os_spa; @@ -2275,7 +2275,26 @@ dmu_brt_clone(objset_t *os, uint64_t object, uint64_t offset, uint64_t length, &numbufs, &dbp)); ASSERT3U(nbps, ==, numbufs); - for (int i = 0; i < numbufs; i++) { + /* + * Before we start cloning make sure that the dbufs sizes much new BPs + * sizes. If they don't, that's a no-go, as we are not able to shrink + * dbufs. + */ + for (i = 0; i < numbufs; i++) { + dbuf = dbp[i]; + db = (dmu_buf_impl_t *)dbuf; + bp = &bps[i]; + + ASSERT0(db->db_level); + ASSERT(db->db_blkid != DMU_BONUS_BLKID); + + if (!BP_IS_HOLE(bp) && BP_GET_LSIZE(bp) != dbuf->db_size) { + error = SET_ERROR(EXDEV); + goto out; + } + } + + for (i = 0; i < numbufs; i++) { dbuf = dbp[i]; db = (dmu_buf_impl_t *)dbuf; bp = &bps[i]; @@ -2319,8 +2338,10 @@ dmu_brt_clone(objset_t *os, uint64_t object, uint64_t offset, uint64_t length, brt_pending_add(spa, bp, tx); } } - +out: dmu_buf_rele_array(dbp, numbufs, FTAG); + + return (error); } void diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c index a6a27222b..71955f90d 100644 --- a/module/zfs/zfs_vnops.c +++ b/module/zfs/zfs_vnops.c @@ -1309,8 +1309,12 @@ zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, ((len - 1) / inblksz + 1) * inblksz); } - dmu_brt_clone(outos, outzp->z_id, outoff, size, tx, bps, nbps, - B_FALSE); + error = dmu_brt_clone(outos, outzp->z_id, outoff, size, tx, + bps, nbps, B_FALSE); + if (error != 0) { + dmu_tx_commit(tx); + break; + } zfs_clear_setid_bits_if_necessary(outzfsvfs, outzp, cr, &clear_setid_bits_txg, tx);