Skip to content

Commit a7ba93c

Browse files
committed
Block cloning conditionally destroy ARC buffer
dmu_buf_will_clone() calls arc_buf_destroy() if there is an assosciated ARC buffer with the dbuf. However, this can only be done conditionally. If the preivous dirty record's dr_data is pointed at db_dbf then destroying it can lead to NULL pointer deference when syncing out the previous dirty record. This updates dmu_buf_fill_clone() to only call arc_buf_destroy() if the previous dirty records dr_data is not pointing to db_buf. The block clone wil still set the dbuf's db_buf and db_data to NULL, but this will not cause any issues as any previous dirty record dr_data will still be pointing at the ARC buffer. A test case block_cloning_overwrite was added that was provided by Ameer Hamza that would previously fail without this patch. Signed-off-by: Brian Atkinson <[email protected]>
1 parent 326040b commit a7ba93c

File tree

5 files changed

+97
-3
lines changed

5 files changed

+97
-3
lines changed

module/zfs/dbuf.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2705,6 +2705,9 @@ void
27052705
dmu_buf_will_clone(dmu_buf_t *db_fake, dmu_tx_t *tx)
27062706
{
27072707
dmu_buf_impl_t *db = (dmu_buf_impl_t *)db_fake;
2708+
ASSERT0(db->db_level);
2709+
ASSERT(db->db_blkid != DMU_BONUS_BLKID);
2710+
ASSERT(db->db.db_object != DMU_META_DNODE_OBJECT);
27082711

27092712
/*
27102713
* Block cloning: We are going to clone into this block, so undirty
@@ -2716,11 +2719,22 @@ dmu_buf_will_clone(dmu_buf_t *db_fake, dmu_tx_t *tx)
27162719
VERIFY(!dbuf_undirty(db, tx));
27172720
ASSERT0P(dbuf_find_dirty_eq(db, tx->tx_txg));
27182721
if (db->db_buf != NULL) {
2719-
arc_buf_destroy(db->db_buf, db);
2722+
/*
2723+
* If there is an associated ARC buffer with this dbuf we can
2724+
* only destroy it if the previous dirty record does not
2725+
* reference it.
2726+
*/
2727+
dbuf_dirty_record_t *dr = list_head(&db->db_dirty_records);
2728+
if (dr == NULL || dr->dt.dl.dr_data != db->db_buf)
2729+
arc_buf_destroy(db->db_buf, db);
2730+
27202731
db->db_buf = NULL;
27212732
dbuf_clear_data(db);
27222733
}
27232734

2735+
ASSERT3P(db->db_buf, ==, NULL);
2736+
ASSERT3P(db->db.db_data, ==, NULL);
2737+
27242738
db->db_state = DB_NOFILL;
27252739
DTRACE_SET_STATE(db, "allocating NOFILL buffer for clone");
27262740

tests/runfiles/common.run

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ tests = ['block_cloning_clone_mmap_cached',
8080
'block_cloning_copyfilerange_cross_dataset',
8181
'block_cloning_cross_enc_dataset',
8282
'block_cloning_copyfilerange_fallback_same_txg',
83-
'block_cloning_replay', 'block_cloning_replay_encrypted',
84-
'block_cloning_lwb_buffer_overflow', 'block_cloning_clone_mmap_write']
83+
'block_cloning_overwrites', 'block_cloning_replay',
84+
'block_cloning_replay_encrypted', 'block_cloning_lwb_buffer_overflow',
85+
'block_cloning_clone_mmap_write']
8586
tags = ['functional', 'block_cloning']
8687

8788
[tests/functional/bootfs]

tests/test-runner/bin/zts-report.py.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,8 @@ elif sys.platform.startswith('linux'):
324324
['SKIP', cfr_cross_reason],
325325
'block_cloning/block_cloning_disabled_copyfilerange':
326326
['SKIP', cfr_reason],
327+
'block_cloning/block_cloning_overwrite':
328+
['SKIP', cfr_reason],
327329
'block_cloning/block_cloning_lwb_buffer_overflow':
328330
['SKIP', cfr_reason],
329331
'block_cloning/block_cloning_replay':

tests/zfs-tests/tests/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
475475
functional/block_cloning/block_cloning_ficlonerange.ksh \
476476
functional/block_cloning/block_cloning_ficlonerange_partial.ksh \
477477
functional/block_cloning/block_cloning_cross_enc_dataset.ksh \
478+
functional/block_cloning/block_cloning_overwrites.ksh \
478479
functional/block_cloning/block_cloning_replay.ksh \
479480
functional/block_cloning/block_cloning_replay_encrypted.ksh \
480481
functional/block_cloning/block_cloning_lwb_buffer_overflow.ksh \
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/bin/ksh -p
2+
#
3+
# CDDL HEADER START
4+
#
5+
# The contents of this file are subject to the terms of the
6+
# Common Development and Distribution License (the "License").
7+
# You may not use this file except in compliance with the License.
8+
#
9+
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10+
# or https://opensource.org/licenses/CDDL-1.0.
11+
# See the License for the specific language governing permissions
12+
# and limitations under the License.
13+
#
14+
# When distributing Covered Code, include this CDDL HEADER in each
15+
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16+
# If applicable, add the following below this CDDL HEADER, with the
17+
# fields enclosed by brackets "[]" replaced with your own identifying
18+
# information: Portions Copyright [yyyy] [name of copyright owner]
19+
#
20+
# CDDL HEADER END
21+
#
22+
23+
#
24+
# Copyright (c) 2024 by Triad National Security, LLC.
25+
#
26+
27+
#
28+
# DESCRIPTION:
29+
# Verify that overwriting a file that is being block cloned works
30+
#
31+
# STRATEGY:
32+
# 1. Create a file in a dataset
33+
# 2. Continously block clone the file to a second file in a different
34+
# dataset and sync the zpool after cloning
35+
# 3. Continously overwrite the second file
36+
#
37+
38+
. $STF_SUITE/include/libtest.shlib
39+
. $STF_SUITE/tests/functional/block_cloning/block_cloning.kshlib
40+
41+
verify_runnable "global"
42+
43+
if is_linux && [[ $(linux_version) -lt $(linux_version "4.5") ]]; then
44+
log_unsupported "copy_file_range not available before Linux 4.5"
45+
fi
46+
47+
claim="Overwriting a block cloned file does not cause issues."
48+
49+
log_assert $claim
50+
51+
function cleanup
52+
{
53+
datasetexists $TESTPOOL && destroy_pool $TESTPOOL
54+
}
55+
56+
log_onexit cleanup
57+
58+
log_must zpool create -o feature@block_cloning=enabled $TESTPOOL $DISKS
59+
log_must zfs create -o sync=always $TESTPOOL/$TESTFS1
60+
log_must zfs create -o sync=always $TESTPOOL/$TESTFS2
61+
62+
log_must dd if=/dev/urandom of=/$TESTPOOL/$TESTFS1/file1 bs=1M count=128
63+
log_must sync_pool $TESTPOOL
64+
runtime=$(($(date +%s) + 180)) # 3 mins
65+
66+
while [[ $(date +%s) -lt $runtime ]]; do
67+
log_must clonefile -f /$TESTPOOL/$TESTFS1/file1 \
68+
/$TESTPOOL/$TESTFS2/file2 0 0 $((128 * 1024 * 1024))
69+
log_must sync_pool $TESTPOOL
70+
done &
71+
while [[ $(date +%s) -lt $runtime ]]; do
72+
log_must dd if=/dev/urandom of=/$TESTPOOL/$TESTFS2/file2 bs=1M count=128
73+
done &
74+
wait
75+
76+
log_pass $claim

0 commit comments

Comments
 (0)