]> git.hungrycats.org Git - linux/commitdiff
btrfs: preserve the compression property when other inode flags change
authorSam Ho <samho@synology.com>
Fri, 14 Aug 2026 13:01:11 +0000 (13:01 +0000)
committerDavid Sterba <dsterba@suse.com>
Tue, 1 Sep 2026 14:49:49 +0000 (16:49 +0200)
Setting the compression property on an inode also sets BTRFS_INODE_COMPRESS
on it, and btrfs_inode_flags_to_fsflags() reports that back as FS_COMPR_FL
to FS_IOC_GETFLAGS. chattr(1), like any other FS_IOC_SETFLAGS caller, reads
the current flags, flips only the bit the user asked for and writes the
whole set back, so a request as unrelated as "chattr +i" reaches
btrfs_fileattr_set() with FS_COMPR_FL set.

btrfs_fileattr_set() takes that as a request to enable compression and
overwrites the compression property with the algorithm from the mount
options, falling back to zlib when the filesystem was not mounted with
-o compress. The algorithm the user selected is silently replaced:

  # btrfs property set /mnt/foo compression zstd
  # btrfs property get /mnt/foo compression
  compression=zstd
  # chattr +i /mnt/foo
  # btrfs property get /mnt/foo compression
  compression=zlib

Every chattr operation triggers this, not just +i, and directories are
affected as well, so files created afterwards inherit the wrong algorithm
too. On a filesystem mounted with -o compress=lzo the property is replaced
with lzo instead. Recovering needs a chattr -i first, because the immutable
flag rejects the setxattr that "btrfs property set" issues.

Prefer the algorithm recorded in the compression property and only fall
back to the mount default when there is no property, so that unrelated
flag changes no longer overwrite the user's choice. Inodes that have the
compress flag set but no property still get the default, so they behave
as before.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Sam Ho <samho@synology.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/ioctl.c

index 72bc9d4f77087b50caad99d7f7689f4ffa66dbe4..e4b2da31a0d5de33ae6d0e00006ac37838f494d1 100644 (file)
@@ -384,6 +384,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap,
                inode_flags &= ~BTRFS_INODE_COMPRESS;
                inode_flags |= BTRFS_INODE_NOCOMPRESS;
        } else if (fsflags & FS_COMPR_FL) {
+               enum btrfs_compression_type comp_type;
 
                if (IS_SWAPFILE(&inode->vfs_inode))
                        return -ETXTBSY;
@@ -391,9 +392,23 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap,
                inode_flags |= BTRFS_INODE_COMPRESS;
                inode_flags &= ~BTRFS_INODE_NOCOMPRESS;
 
-               comp = btrfs_compress_type2str(fs_info->compress_type);
-               if (!comp || comp[0] == 0)
-                       comp = btrfs_compress_type2str(BTRFS_COMPRESS_ZLIB);
+               /*
+                * Keep the algorithm recorded in the compression property,
+                * otherwise changing an unrelated attribute would reset it to
+                * the mount default, since FS_IOC_SETFLAGS callers write back
+                * the whole flag set they got from FS_IOC_GETFLAGS and that
+                * includes FS_COMPR_FL for any inode carrying the property.
+                *
+                * Inodes with the compress flag set but no property keep using
+                * the mount default, so they behave as before.
+                */
+               if (inode->prop_compress)
+                       comp_type = inode->prop_compress;
+               else if (fs_info->compress_type)
+                       comp_type = fs_info->compress_type;
+               else
+                       comp_type = BTRFS_COMPRESS_ZLIB;
+               comp = btrfs_compress_type2str(comp_type);
        } else {
                inode_flags &= ~(BTRFS_INODE_COMPRESS | BTRFS_INODE_NOCOMPRESS);
        }