rmlint icon indicating copy to clipboard operation
rmlint copied to clipboard

Add ZFS filesystem reflink support

Open Mic92 opened this issue 5 months ago • 3 comments

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

Mic92 avatar Jul 06 '25 19:07 Mic92

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?

https://github.com/sahib/rmlint/blob/a7e1df4b22a1be67597d46438193132464cf27d2/tests/utils.py#L49

jdehaan avatar Sep 06 '25 14:09 jdehaan

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.

speed47 avatar Sep 23 '25 17:09 speed47

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.

CodingWithAnxiety avatar Nov 20 '25 19:11 CodingWithAnxiety