Skip to content

Conversation

@Mic92
Copy link

@Mic92 Mic92 commented Jul 6, 2025

ZFS now supports file cloning/reflinks via cp --reflink in OpenZFS 2.2+. This commit adds proper support for ZFS in rmlint by distinguishing between filesystems that support the FIDEDUPERANGE ioctl (used by 'rmlint --dedupe' and sh:handler=clone) and those that only support cp --reflink.

Changes:

  • Split filesystem detection into fs_supports_dedupe() and fs_supports_reflinks()
  • Add separate dedupefs_table to track filesystems supporting FIDEDUPERANGE
  • Update clone handler to check dedupe support specifically
  • Add rm_mounts_can_dedupe() function for checking dedupe capability
  • Document that ZFS supports reflinks but not the dedupe ioctl

This ensures that:

  • sh:handler=reflink works correctly on ZFS (uses cp --reflink)
  • sh:handler=clone is skipped on ZFS (no FIDEDUPERANGE support)
  • The 'link' shortcut now works properly on ZFS by falling back to reflink

Documentation has been updated to clarify the behavior on ZFS.

Fixes: #745

@jdehaan
Copy link

jdehaan commented Sep 6, 2025

I'm not very familiar with the codebase here, but formally there are tests and maybe something needs to be adapted here as well to provide/enable testing?

_REFLINK_CAPABLE_FILESYSTEMS = {'btrfs', 'xfs', 'ocfs2'}

@speed47
Copy link
Contributor

speed47 commented Sep 23, 2025

Damn, I just re-implemented it on my side before seeing that this PR was open... I should really have checked first!

I didn't go as far as differentiating between FIDEDUPERANGE and FICLONERANGE, but clearly that's a good thing to do.
However I implemented a check, as done with xfs, to ensure that block cloning is actually enabled/available on the given zpool. This is the code, taken from my would-have-been PR that you might want to add on top of yours if you want to add this check too (my local compiled version is actually your branch with this snippet on top):

diff --git a/lib/utilities.c b/lib/utilities.c
index ef52d347..a3c50c94 100644
--- a/lib/utilities.c
+++ b/lib/utilities.c
@@ -597,7 +597,7 @@ static bool fs_supports_dedupe(char *fstype, char *mountpoint) {
     return false;
 }

-static bool fs_supports_reflinks(char *fstype, char *mountpoint) {
+static bool fs_supports_reflinks(char *fstype, char *mountpoint, char *fsname) {
     /* Check if filesystem supports any form of reflinks/clones */
     if(fs_supports_dedupe(fstype, mountpoint)) {
         /* If it supports dedupe ioctl, it supports reflinks */
@@ -609,7 +609,20 @@ static bool fs_supports_reflinks(char *fstype, char *mountpoint) {
          * 'rmlint --dedupe', but does support 'cp --reflink=always'.
          * For sh:handler=reflink, the generated script will use cp --reflink.
          * For sh:handler=clone, the dedupe operation will fail gracefully. */
-        return true;
+
+        /* However we need to check that the zpool has the feature enabled,
+         * so get the pool name from the fsname (this is the part before the
+         * first '/', if any). It can either be disabled, or not supported
+         * because the pool has not been upgraded yet, or because ZFS is too old. */
+        char *fs = g_strdup(fsname);
+        g_strdelimit(fs, "/", '\0'); /* modifies 'fs' inplace */
+        char *pool = g_shell_quote(fs);
+        char *cmd = g_strdup_printf("zpool get -H -o value feature@block_cloning %s 2>/dev/null | grep -q -e active -e enabled", pool);
+        int res = system(cmd);
+        g_free(cmd);
+        g_free(pool);
+        g_free(fs);
+        return(res==0);
     }
     return false;
 }
@@ -693,7 +706,7 @@ static RmMountEntries *rm_mount_list_open(RmMountTable *table) {
                   evilfs_found->name, wrap_entry->dir, (unsigned)dir_stat.st_dev);
         }

-        if(fs_supports_reflinks(wrap_entry->type, wrap_entry->dir)) {
+        if(fs_supports_reflinks(wrap_entry->type, wrap_entry->dir, wrap_entry->fsname)) {
             RmStat dir_stat;
             if(rm_sys_stat(wrap_entry->dir, &dir_stat) == 0) {
                 g_hash_table_insert(table->reflinkfs_table,

I'm not extremely fond of using the shell version of system(), but I didn't want to introduce a different way of doing it w.r.t the xfs check.

@CodingWithAnxiety CodingWithAnxiety self-requested a review November 20, 2025 19:55
@CodingWithAnxiety
Copy link
Collaborator

Hey, Finally back from my hiatus.

I'll give this a readover, though as @jdehaan says, unit tests should probably be added for this. I may commit to this in the future with this.

ZFS now supports file cloning/reflinks via cp --reflink in OpenZFS 2.2+.
This commit adds proper support for ZFS in rmlint by distinguishing between
filesystems that support the FIDEDUPERANGE ioctl (used by 'rmlint --dedupe'
and sh:handler=clone) and those that only support cp --reflink.

Changes:
- Split filesystem detection into fs_supports_dedupe() and fs_supports_reflinks()
- Add separate dedupefs_table to track filesystems supporting FIDEDUPERANGE
- Update clone handler to check dedupe support specifically
- Add rm_mounts_can_dedupe() function for checking dedupe capability
- Document that ZFS supports reflinks but not the dedupe ioctl

This ensures that:
- sh:handler=reflink works correctly on ZFS (uses cp --reflink)
- sh:handler=clone is skipped on ZFS (no FIDEDUPERANGE support)
- The 'link' shortcut now works properly on ZFS by falling back to reflink

Documentation has been updated to clarify the behavior on ZFS.

Fixes: sahib#745
@EchterAgo
Copy link

You might want to use --reflink=auto instead of --reflink=always to make this work across different datasets. See openzfs/zfs#15345

gint exit_status = 0;
bool supports_cloning = false;

if(g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative would be not to check reflink-capability at all here and let the coreutils commands used in the script to emit error if the ioctl calls fail.

cp seems to try for FICLONE by default and use copy_file_range on failure.

Our fs_supports_dedupe() uses system() for XFS so what we do here is not so bad, at least we don't invoke the shell.

@gmelikov
Copy link

@vassilit saw your commit with zfs tests, iirc ubuntu 24 has zfs 2.2, it has disabled block cloning by default. It may be enabled with echo 1 > /sys/module/zfs/parameters/zfs_bclone_enabled. Some tests may not work because in zfs 2.2.2 block cloning may be not 100% working, in this case feel free to ping me, I can add fresh zfs build to CI if you want it (basically it's described here https://openzfs.github.io/openzfs-docs/Developer%20Resources/Custom%20Packages.html#debian-and-ubuntu ).

@vassilit
Copy link
Collaborator

vassilit commented Dec 24, 2025

@gmelikov Спасибо! But I saw your message too late (just now).
I am running ZFS extensively on FreeBSD so I naively tried sysctl :) then modprobe zfs zfs_bclone_enabled=1 (strangely enough, seems to be the default in man 4 zfs on ZFS 2.2.2 on Ubuntu 24.04.3 (the one used on runners), but is not de facto enabled) and then settled for using the sys filesystem as postinst of zfsutils-linux do insmod zfs anyway.

I plan to add FreeBSD tests on 14.4 (ZFS 2.2) and 15.0 (2.4) soon that will cover more recent versions if 2.2.2 on Linux is failing.

@gmelikov
Copy link

@vassilit usually get FreeBSD in github actions is not so straightforward, but yeah, it's too should work :) Although better to have both Linux and FreeBSD. JFYI openzfs in the end just started qemu in CI for non-Ubuntu OSes https://github.com/openzfs/zfs/blob/master/.github/workflows/zfs-qemu.yml

@vassilit
Copy link
Collaborator

@gmelikov it's a bit off-topic here but for FreeBSD runners on Github I used Cirrus-CI in the past, then switched to vmactions, but it is very slow. The quite new Firecracker seems promising but I have not tested it yet.

@vassilit
Copy link
Collaborator

Some tests may not work [...], in this case feel free to ping me, I can add fresh zfs build to CI if you want it

@gmelikov, ping. Your help is appreciated, could you open a separate PR (I don't know how to give you rights on Mac92's branch) and we will rebase this one on yours ?

@gmelikov
Copy link

@vassilit got it, I'll do that in several days.

@gmelikov
Copy link

zfs 2.4 didn't help, and looks like problem is not with zfs 2.2 from Ubuntu, here you can see run with 2.4:

I've built this PR, read test_second_extent_differs test and tried by hand on zfs 2.2 (/tmp is ZFS dataset):

ubuntu@ubuntu:~/rmlint$ sudo zfs -V
zfs-2.2.2-0ubuntu9.2
zfs-kmod-2.2.2-0ubuntu9.1
ubuntu@ubuntu:~/rmlint$ echo 1 | sudo tee /sys/module/zfs/parameters/zfs_bclone_enabled
1

# Create original file
ubuntu@ubuntu:~/rmlint$ dd if=/dev/urandom of=/tmp/a bs=4K count=1

# Before: block table empty
ubuntu@ubuntu:~/rmlint$ sudo zdb -T rpool
BRT: empty

# Run test
ubuntu@ubuntu:~/rmlint$ cp --reflink=auto /tmp/a /tmp/b

# After, ZFS sees duplicates, reflink was a success
ubuntu@ubuntu:~/rmlint$ sudo zdb -T rpool
BRT: used 4K; saved 4K; ratio 2.00x

ubuntu@ubuntu:~/rmlint$ ./rmlint --is-reflink /tmp/a /tmp/b
ubuntu@ubuntu:~/rmlint$ echo $?
5

from docs exit code 5 is "fiemaps can't be read". Ah, I thought ZFS supported it but it's not openzfs/zfs#9554

So, reflink works apparently, BUT rmlint can't easily test it with FIOMAP.

@vassilit
Copy link
Collaborator

2.4 helped not getting EAGAIN: Resource temporarily unavailable. At least, we can test now.

Thank you !

Signed-off-by: George Melikov <mail@gmelikov.ru>
@gmelikov
Copy link

@vassilit ok, here is clean commit to use zfs 2.4 gmelikov@86ed254

@vassilit vassilit linked an issue Dec 27, 2025 that may be closed by this pull request
@vassilit
Copy link
Collaborator

vassilit commented Jan 7, 2026

I guess we could add needs_fiemap to some tests and modify the code so that reflink operations are best-efforts on ZFS (no easy way to check, I don't think we should call zdb or import headers from OpenZFS and use their non-stable API).

It is kind of already supported, as we can make the generated script to use --reflink=always on Linux and on FreeBSD it's not clear for me how to call copy_file_range(COPY_FILE_RANGE_CLONE), clone_file_range, dedupe_file_range and remap_file_range from 15.0-RELEASE base userland utilities. That may change with future versions.
No idea on other platforms that uses ZFS and if they ever support block cloning (Illumos-based, Solaris-based, etc)

What are your thoughts on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sh:handler=reflink does not work on zfs, even though zfs now supports cp --reflink Only original_cmd in shell script generated for reflink

7 participants