Block Cloning in ZFS

Overview

Block cloning lets ZFS create a file — or extend an existing one — that references another file's existing data blocks instead of copying them. Nothing is read from or written to the underlying vdevs; only block pointers and a reference count change. Writing to a cloned region later behaves exactly like any other copy-on-write: ZFS allocates a fresh block for the changed data and leaves every other reference to the original block untouched. This is ZFS's version of what XFS, Btrfs, and Windows ReFS call a reflink.

Note

"Clone" already meant something in ZFS. zfs clone creates a new dataset or zvol from a snapshot, sharing blocks with that snapshot until they diverge — it's dataset-level and has existed since early ZFS. Block cloning is unrelated: it operates on individual files inside a dataset, needs no snapshot, and is invoked through ordinary copy syscalls rather than the zfs command. The rest of this page is about the latter.

Block cloning landed in OpenZFS 2.2.0 (October 2023), authored by Pawel Dawidek as PR #13392, after being proposed at the 2020 OpenZFS Developer Summit.

Block Cloning vs. Related Mechanisms

ZFS has four different ways to make one block do the work of many. Only one of them is block cloning.

MechanismGranularityTriggered byData I/OShared until
Regular copyWhole filecp, rsync, any copyFull read + full writeNever — fully duplicated
zfs cloneDataset / zvolzfs clone from a snapshotNoneEither side writes past the snapshot
DeduplicationBlock, at write timeAny write, when dedup=onFull read + write, plus a DDT hash lookup per writeMatching block is freed
Block cloningBlock, on requestcopy_file_range, FICLONE(RANGE), reflink cpNoneEither side writes to that block

The Block Reference Table

Every cloned block gets an entry in the Block Reference Table (BRT): a vdev, an offset, and a reference count. It's a much simpler structure than the dedup table (DDT), and the two behave very differently in practice:

One asymmetry runs the other way. Block pointers carry a spare bit (the "D" bit) that flags whether a block is in the DDT, so freeing a non-deduped block skips the DDT lookup entirely. There's no equivalent bit for block cloning, so every block free has to check the BRT regardless of whether that particular block was ever cloned. Cloning is free on the read and write side; it isn't quite free on the free path.

Modifying a cloned block is ordinary copy-on-write: ZFS allocates a new block for the changed data, decrements the BRT reference count on the original, and every other file still pointing at that original block is unaffected.

Triggering a Clone

Block cloning is opt-in per operation — nothing clones automatically just because two files happen to contain the same bytes. A program has to explicitly ask, through one of:

$ cp --reflink=always /tank/vm/base.qcow2 /tank/vm/clone.qcow2
$ cp /tank/movies/.zfs/snapshot/nightly/movie.mkv /tank/movies/

copy_file_range(2)

ZFS clones through this syscall whenever the source range, destination range, and both files' properties satisfy the requirements below — otherwise the kernel transparently falls back to a read/write copy loop.

ssize_t copy_file_range(int fd_in, off_t *off_in,
                         int fd_out, off_t *off_out,
                         size_t len, unsigned int flags);

FICLONERANGE takes an explicit source/destination range via a struct passed to ioctl(2):

struct file_clone_range {
    __s64 src_fd;
    __u64 src_offset;
    __u64 src_length;
    __u64 dest_offset;
};

Requirements and Limitations

A clone request falls back to a real copy — sometimes silently — unless all of the following hold:

Tip

If a clone is silently doing a full copy, check recordsize mismatch and cross-pool destinations first — they account for most cases that aren't simply zfs_bclone_enabled=0.

Feature Flag and Tunables

NameTypeDefaultEffect
feature@block_cloning Pool feature Read/write-compatible. Becomes active the first time any block on the pool is cloned; drops back to enabled once the last clone reference pool-wide is freed.
feature@block_cloning_endian Pool feature Corrective feature (com.truenas:block_cloning_endian), landed for OpenZFS 2.4. Original BRT ZAP entries were stored as eight separate one-byte fields instead of one eight-byte field, which isn't portable between big- and little-endian systems. New pools get the corrected layout automatically; pools created under the original implementation keep it until rebuilt.
zfs_bclone_enabled Module parameter 1 on current releases Gates whether copy_file_range / FICLONE / FICLONERANGE are allowed to clone at all. Linux: /sys/module/zfs/parameters/zfs_bclone_enabled. FreeBSD: sysctl vfs.zfs.bclone_enabled.
zfs_bclone_wait_dirty Module parameter 1 Behavior when the requested range has dirty data. 1: wait for the next txg sync, then clone. 0: fail the clone immediately instead of waiting.
# cat /sys/module/zfs/parameters/zfs_bclone_enabled
1
# echo "options zfs zfs_bclone_enabled=1" > /etc/modprobe.d/zfs.conf

Stability History

Danger

OpenZFS 2.2.0 (October 2023) shipped block cloning enabled by default. Within weeks, Gentoo users reported files coming back with chunks silently replaced by zeroes — real data corruption, tracked as issue #15526. OpenZFS 2.2.1 responded immediately by switching block cloning off by default via the new zfs_bclone_enabled parameter, rather than shipping a root-cause fix. That fix — for an incorrect dirty-dnode check, only exposed in practice by behavioral changes in coreutils 9.x's cp — landed in 2.2.2 (#15571). Even after the fix, zfs_bclone_enabled stayed off by default through the rest of the 2.2.x series as a deliberate stabilization period.

Current OpenZFS defaults zfs_bclone_enabled back to 1 — but confirm the actual value on your system before relying on it; distributions can and do override module defaults. Any host still running a 2.2.x release should be treated as off-by-default and opt-in only, regardless of what upstream ships elsewhere.

Observability

Pool-level properties expose clone savings without walking the filesystem for BRT references:

PropertyMeaning
bcloneusedTotal logical size of all blocks currently referenced by a clone.
bclonesavedSpace avoided by cloning instead of copying — the gap between logical and physical footprint of cloned data.
bcloneratiobclonesaved expressed as a ratio; 2.00x means clones currently occupy half the space a full copy would have.
$ zpool get bcloneused,bclonesaved,bcloneratio tank
NAME  PROPERTY      VALUE   SOURCE
tank  bcloneused    100M    -
tank  bclonesaved   100M    -
tank  bcloneratio   2.00x   -

zfs send / zfs receive

Warn

The BRT is local to a pool and doesn't travel in a send stream. Every clone gets fully materialized as independent data on the receiving side — there's no "send only the reference." A dataset that looks compact on disk because it's built from clones (ten near-identical VM images sharing most of their blocks, for example) can produce a send stream, and consume space on the receiving pool, far larger than its bcloneratio suggests. Size a replication target off zfs list USED and an observed incremental stream size, not off how little space the source appears to use.

Practical Use Cases

Warn

Production issue #17485 reported reflink failures during high-throughput Veeam synthetic-full runs specifically when zfs_bclone_wait_dirty was forced to 0 — clones failed outright under sustained load instead of degrading gracefully. Leave it at its default of 1 unless a specific low-latency requirement says otherwise.