Re: What is needed for ZFS block cloning to work?
"Rob Norris" <[email protected]> Sun, 12 Jul 2026 14:13:21 +1000
| Newsgroups | gmane.os.freebsd.devel.file-systems |
|---|---|
| Message-ID | <[email protected]> |
--7557a180a360f72e777ea78d7d703ecc681a3c09
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
Yeah, the decision making is tricky. It's implementation-defined on purpose, because we sometimes change things (mostly trying to remove restrictions).
Almost always it's something to do with the block alignment, but I'm not sure off top of my head from this one
On Sun, 12 Jul 2026, at 7:34 AM, John F Carr wrote:
> I enabled block cloning on 15.1-STABLE but copy_file_range
> between two files in the same directory does not cause
> bcloneused to change. I have vfs.zfs.bclone_enabled=1
> and feature@block_cloning=enabled. Is anything more
> needed?
Your settings from the output you showed suggests everything was enabled.
Incidentally, the feature goes from enabled->active when the first block is actually cloned, and from active->enabled with the last cloned block on the pool is destroyed (that's normal ZFS feature flag management).
> I looked at zfs_clone_range and zfs_freebsd_copy_file_range
> and saw nothing obvious that would make the copy fail.
Confirming those are the correct functions to look at. However, some of the fine details are tricky to see, like the storage tier migration stuff, or the block sizes.
> $ grep copy_file_range /tmp/t
> copy_file_range(0x3,0x0,0x4,0x0,0x7fffffffffffffff,0x0) = 33289458 (0x1fbf4f2)
> copy_file_range(0x3,0x0,0x4,0x0,0x7fffffffffffffff,0x0) = 0 (0x0)
NULL as the offsets (args 2 and 4) mean to use the current file offset. So unless it did a seek or something to start the source at a weird offset, that should be about the best possible case (and it wouldn't make any sense to do that, just saying).
So without looking at the dataset config itself, and the nature of the file question, its hard to tell why not.
The usual way I try to see what's happening is to trace the function, or the SET_ERROR calls, something like:
# dtrace -n 'fbt::zfs_clone_range:return { print(arg0) }'
# dtrace -n 'sdt:::set-error /arg0 != 0/ { stack(1); print(arg0) }
I am bad at dtrace, so there's probably much better ways. The output though can help you get back to which condition it hit.
The other way that is sometimes helpful is to use zdb to dump the L0 block structure on the original and the copy (the one that didn't clone):
# zdb tank -vv -dddddd -bbbbbb -O 20240930_195916.jpg | grep ' L0 '
0 L0 DVA[0]=<0:72200:20000> [L0 ZFS plain file] fletcher4 uncompressed unencrypted LE contiguous unique single size=20000L/20000P birth=58L/58P fill=1 cksum=000034f7743c1b68:0d33d8a49e7c498f:bdabce29daf9c36e:5a94839df86ca5b9
20000 L0 DVA[0]=<0:12200:20000> [L0 ZFS plain file] fletcher4 uncompressed unencrypted LE contiguous unique single size=20000L/20000P birth=58L/58P fill=1 cksum=00003d5c8b629f10:0f4220ab15d3d245:c7109b9f43780acb:a25a0e574b4c23c4
40000 L0 DVA[0]=<0:32200:20000> [L0 ZFS plain file] fletcher4 uncompressed unencrypted LE contiguous unique single size=20000L/20000P birth=58L/58P fill=1 cksum=00003d82eb6af316:0f5669851b30428c:e031566c1c107d50:9855785ebcc8bc0e
...
Sometimes comparing the block sizes and options can tell you what happened. The clone setup failed because it wanted to produce something like the destination but couldn't turn the source into that.
Yeah, complicated.
Rob.
--7557a180a360f72e777ea78d7d703ecc681a3c09
Content-Type: text/html
Content-Transfer-Encoding: quoted-printable
<!DOCTYPE html><html><head><title></title></head><body><div>Yeah, the de=
cision making is tricky. It's implementation-defined on purpose, because=
we sometimes change things (mostly trying to remove restrictions).</div=
><div><br></div><div>Almost always it's something to do with the block a=
lignment, but I'm not sure off top of my head from this one</div><div><b=
r></div><div>On Sun, 12 Jul 2026, at 7:34 AM, John F Carr wrote:</div><b=
lockquote type=3D"cite" id=3D"qt" style=3D""><div>I enabled block clonin=
g on 15.1-STABLE but copy_file_range</div><div>between two files in the =
same directory does not cause</div><div>bcloneused to change. I ha=
ve vfs.zfs.bclone_enabled=3D1</div><div>and feature@block_cloning=3Denab=
led. Is anything more</div><div>needed?</div></blockquote><div><br=
></div><div>Your settings from the output you showed suggests everything=
was enabled.</div><div><br></div><div>Incidentally, the feature goes fr=
om enabled->active when the first block is actually cloned, and from =
active->enabled with the last cloned block on the pool is destroyed (=
that's normal ZFS feature flag management).</div><div><br></div><blockqu=
ote type=3D"cite" id=3D"qt" style=3D""><div>I looked at zfs_clone_range =
and zfs_freebsd_copy_file_range</div><div>and saw nothing obvious that w=
ould make the copy fail.</div></blockquote><div><br></div><div>Confirmin=
g those are the correct functions to look at. However, some of the fine =
details are tricky to see, like the storage tier migration stuff, or the=
block sizes.</div><div><br></div><blockquote type=3D"cite" id=3D"qt" st=
yle=3D""><div>$ grep copy_file_range /tmp/t</div><div>copy_file_range(0x=
3,0x0,0x4,0x0,0x7fffffffffffffff,0x0) =3D 33289458 (0x1fbf4f2)</div><div=
>copy_file_range(0x3,0x0,0x4,0x0,0x7fffffffffffffff,0x0) =3D 0 (0x0)</di=
v></blockquote><div><br></div><div>NULL as the offsets (args 2 and 4) me=
an to use the current file offset. So unless it did a seek or something =
to start the source at a weird offset, that should be about the best pos=
sible case (and it wouldn't make any sense to do that, just saying).</di=
v><div><br></div><div>So without looking at the dataset config itself, a=
nd the nature of the file question, its hard to tell why not.</div><div>=
<br></div><div>The usual way I try to see what's happening is to trace t=
he function, or the SET_ERROR calls, something like:</div><div><br></div=
><div> # dtrace -n 'fbt::zfs_clone_range:return { print(arg=
0) }'</div><div> # dtrace -n 'sdt:::set-error /arg0 !=3D 0/=
{ stack(1); print(arg0) }</div><div><br></div><div>I am bad at dtrace, =
so there's probably much better ways. The output though can help you get=
back to which condition it hit.</div><div><br></div><div>The other way =
that is sometimes helpful is to use zdb to dump the L0 block structure o=
n the original and the copy (the one that didn't clone):</div><div><br><=
/div><div> # zdb tank -vv -dddddd -bbbbbb -O 20240930_19591=
6.jpg | grep ' L0 '</div><div> =
0 L0 DVA[0]=3D<0:72200:20000> [L0=
ZFS plain file] fletcher4 uncompressed unencrypted LE contiguous unique=
single size=3D20000L/20000P birth=3D58L/58P fill=3D1 cksum=3D000034f774=
3c1b68:0d33d8a49e7c498f:bdabce29daf9c36e:5a94839df86ca5b9</div><div>&nbs=
p; 20000 L0 DVA[0]=
=3D<0:12200:20000> [L0 ZFS plain file] fletcher4 uncompressed unen=
crypted LE contiguous unique single size=3D20000L/20000P birth=3D58L/58P=
fill=3D1 cksum=3D00003d5c8b629f10:0f4220ab15d3d245:c7109b9f43780acb:a25=
a0e574b4c23c4</div><div> =
40000 L0 DVA[0]=3D<0:32200:20000> [L0 ZFS plain file]=
fletcher4 uncompressed unencrypted LE contiguous unique single size=3D2=
0000L/20000P birth=3D58L/58P fill=3D1 cksum=3D00003d82eb6af316:0f5669851=
b30428c:e031566c1c107d50:9855785ebcc8bc0e</div><div> ...</d=
iv><div><br></div><div>Sometimes comparing the block sizes and options c=
an tell you what happened. The clone setup failed because it wanted to p=
roduce something like the destination but couldn't turn the source into =
that.</div><div><br></div><div>Yeah, complicated.</div><div><br></div><d=
iv>Rob.</div></body></html>
--7557a180a360f72e777ea78d7d703ecc681a3c09--