]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
smb/client: allow FS_IOC_SETFLAGS to clear compression
authorHuiwen He <hehuiwen@kylinos.cn>
Mon, 8 Jun 2026 15:57:31 +0000 (23:57 +0800)
committerSteve French <stfrench@microsoft.com>
Sun, 14 Jun 2026 20:12:23 +0000 (15:12 -0500)
The CIFS FS_IOC_SETFLAGS path can set FS_COMPR_FL now, but it cannot
clear it again. This can be reproduced on a share backed by a filesystem
that supports compression, for example btrfs exported by Samba:

[compress_share]
vfs objects = btrfs

$ touch test.bin
$ chattr +c test.bin
$ lsattr test.bin
$ chattr -c test.bin

The final chattr -c fails with EOPNOTSUPP, and leaves the remote object
with the compressed attribute still set, because the client always sends
FSCTL_SET_COMPRESSION with COMPRESSION_FORMAT_DEFAULT. That is correct
for setting FS_COMPR_FL, but clearing FS_COMPR_FL requires sending
COMPRESSION_FORMAT_NONE.

Fix this by passing the requested compression state through the
set_compression operation.  The SMB1 and SMB2 helpers no longer hard-code
COMPRESSION_FORMAT_DEFAULT.

When FS_COMPR_FL is set, send COMPRESSION_FORMAT_DEFAULT.  When it is
cleared, send COMPRESSION_FORMAT_NONE.  If the server accepts the request,
update the cached FILE_ATTRIBUTE_COMPRESSED bit under i_lock so
FS_IOC_GETFLAGS reports the new state.

Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsglob.h
fs/smb/client/cifssmb.c
fs/smb/client/ioctl.c
fs/smb/client/smb1ops.c
fs/smb/client/smb1proto.h
fs/smb/client/smb2ops.c
fs/smb/client/smb2pdu.c
fs/smb/client/smb2proto.h

index 943b7cd2c096875837f9068f1d7c2844840356c4..a462c1590a9ec774a3f158fc0df2c0c57884b5f4 100644 (file)
@@ -425,7 +425,7 @@ struct smb_version_operations {
        int (*set_file_info)(struct inode *, const char *, FILE_BASIC_INFO *,
                             const unsigned int);
        int (*set_compression)(const unsigned int, struct cifs_tcon *,
-                              struct cifsFileInfo *);
+                              struct cifsFileInfo *, __u16);
        /* check if we can send an echo or nor */
        bool (*can_echo)(struct TCP_Server_Info *);
        /* send echo request */
index 9e27bfa7376b1c9b6048648bd2a839b66be7411f..d39175cdf1b18107457e747d3301ed5a26eb0a73 100644 (file)
@@ -3207,7 +3207,7 @@ out_close:
 
 int
 CIFSSMB_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
-                   __u16 fid)
+                   __u16 fid, __u16 compression_state)
 {
        int rc = 0;
        int bytes_returned;
@@ -3222,7 +3222,7 @@ CIFSSMB_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
                return rc;
        in_len = rc;
 
-       pSMB->compression_state = cpu_to_le16(COMPRESSION_FORMAT_DEFAULT);
+       pSMB->compression_state = cpu_to_le16(compression_state);
 
        pSMB->TotalParameterCount = 0;
        pSMB->TotalDataCount = cpu_to_le32(2);
index 886e6893a552d325a34a1905c0e7d7a98c0743ea..9fa743be3652afce09473929c331380103fe1386 100644 (file)
@@ -68,7 +68,8 @@ static long cifs_ioctl_query_info(unsigned int xid, struct file *filep,
 }
 
 static int cifs_set_compression_by_path(unsigned int xid, struct file *filep,
-                                       struct cifs_tcon *tcon)
+                                       struct cifs_tcon *tcon,
+                                       __u16 compression_state)
 {
        struct inode *inode = file_inode(filep);
        struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
@@ -127,7 +128,8 @@ static int cifs_set_compression_by_path(unsigned int xid, struct file *filep,
                goto close;
        }
 
-       rc = server->ops->set_compression(xid, tcon, tmp_cfile);
+       rc = server->ops->set_compression(xid, tcon, tmp_cfile,
+                                        compression_state);
 
 close:
        server->ops->close(xid, tcon, &fid);
@@ -141,7 +143,8 @@ out:
 
 static int cifs_ioctl_set_compression(unsigned int xid, struct file *filep,
                                      struct cifs_tcon *tcon,
-                                     struct cifsFileInfo *cfile)
+                                     struct cifsFileInfo *cfile,
+                                     __u16 compression_state)
 {
        struct cifsFileInfo *wfile;
        struct cifs_tcon *wtcon;
@@ -152,7 +155,8 @@ static int cifs_ioctl_set_compression(unsigned int xid, struct file *filep,
                return -EOPNOTSUPP;
 
        if (cfile && (cfile->fid.access & FILE_WRITE_DATA)) {
-               rc = tcon->ses->server->ops->set_compression(xid, tcon, cfile);
+               rc = tcon->ses->server->ops->set_compression(xid, tcon, cfile,
+                                                              compression_state);
                if (rc != -EACCES)
                        return rc;
        }
@@ -160,7 +164,8 @@ static int cifs_ioctl_set_compression(unsigned int xid, struct file *filep,
        rc = cifs_get_writable_file(CIFS_I(inode), FIND_FSUID_ONLY, &wfile);
        if (!rc) {
                wtcon = tlink_tcon(wfile->tlink);
-               rc = wtcon->ses->server->ops->set_compression(xid, wtcon, wfile);
+               rc = wtcon->ses->server->ops->set_compression(xid, wtcon, wfile,
+                                                              compression_state);
                cifsFileInfo_put(wfile);
                if (rc != -EACCES)
                        return rc;
@@ -168,7 +173,8 @@ static int cifs_ioctl_set_compression(unsigned int xid, struct file *filep,
                return rc;
        }
 
-       return cifs_set_compression_by_path(xid, filep, tcon);
+       return cifs_set_compression_by_path(xid, filep, tcon,
+                                           compression_state);
 }
 
 static long cifs_ioctl_copychunk(unsigned int xid, struct file *dst_file,
@@ -460,6 +466,8 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
        struct tcon_link *tlink;
        struct cifs_sb_info *cifs_sb;
        __u64   ExtAttrBits = 0;
+       bool enable_compression;
+       __u16 compression_state;
 #ifdef CONFIG_CIFS_POSIX
 #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
        __u64   caps;
@@ -523,17 +531,28 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
                         *      break;
                         */
 
-                       /* Currently only flag we can set is compressed flag */
-                       if ((ExtAttrBits & FS_COMPR_FL) == 0)
+                       /* Currently only flag we can set or clear is compressed. */
+                       if (ExtAttrBits & ~FS_COMPR_FL) {
+                               rc = -EOPNOTSUPP;
                                break;
+                       }
+
+                       enable_compression = ExtAttrBits & FS_COMPR_FL;
+                       compression_state = enable_compression ?
+                               COMPRESSION_FORMAT_DEFAULT :
+                               COMPRESSION_FORMAT_NONE;
 
-                       /* Try to set compress flag */
                        rc = cifs_ioctl_set_compression(xid, filep, tcon,
-                                                       pSMBFile);
+                                               pSMBFile,
+                                               compression_state);
                        if (rc == 0) {
                                spin_lock(&inode->i_lock);
-                               CIFS_I(inode)->cifsAttrs |=
-                                       FILE_ATTRIBUTE_COMPRESSED;
+                               if (enable_compression)
+                                       CIFS_I(inode)->cifsAttrs |=
+                                               FILE_ATTRIBUTE_COMPRESSED;
+                               else
+                                       CIFS_I(inode)->cifsAttrs &=
+                                               ~FILE_ATTRIBUTE_COMPRESSED;
                                spin_unlock(&inode->i_lock);
                        }
                        cifs_dbg(FYI, "set compress flag rc %d\n", rc);
index e198e3dda91772e52234b29931be3a69a1903e53..d34b3d99f6ed3a26bd3540ccb2ea614dd550ab7f 100644 (file)
@@ -1110,9 +1110,10 @@ out:
 
 static int
 cifs_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
-                  struct cifsFileInfo *cfile)
+                  struct cifsFileInfo *cfile, __u16 compression_state)
 {
-       return CIFSSMB_set_compression(xid, tcon, cfile->fid.netfid);
+       return CIFSSMB_set_compression(xid, tcon, cfile->fid.netfid,
+                                      compression_state);
 }
 
 static int
index 5f522d359952d461bc7c2fa1abfa763d74aafc5b..80eaeb3dd2ecb3628b7adf8738215d91deaba556 100644 (file)
@@ -117,7 +117,7 @@ struct inode *cifs_create_reparse_inode(struct cifs_open_info_data *data,
                                        struct kvec *reparse_iov,
                                        struct kvec *xattr_iov);
 int CIFSSMB_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
-                           __u16 fid);
+                           __u16 fid, __u16 compression_state);
 int cifs_do_get_acl(const unsigned int xid, struct cifs_tcon *tcon,
                    const unsigned char *searchName, struct posix_acl **acl,
                    const int acl_type, const struct nls_table *nls_codepage,
index d4875f9532b4dc9be4991e40fc948bab3a34d13b..a3257815e6617f5c3ea53fbe523619cb09d88400 100644 (file)
@@ -2246,10 +2246,10 @@ duplicate_extents_out:
 
 static int
 smb2_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
-                  struct cifsFileInfo *cfile)
+                  struct cifsFileInfo *cfile, __u16 compression_state)
 {
        return SMB2_set_compression(xid, tcon, cfile->fid.persistent_fid,
-                           cfile->fid.volatile_fid);
+                           cfile->fid.volatile_fid, compression_state);
 }
 
 static int
index fbeb2156ddb6b08f5cf4bc9720f7452a805700c9..6a185b805c1e882452477e659eee0034a4eece37 100644 (file)
@@ -3626,14 +3626,14 @@ ioctl_exit:
 
 int
 SMB2_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
-                    u64 persistent_fid, u64 volatile_fid)
+                    u64 persistent_fid, u64 volatile_fid,
+                    __u16 compression_state)
 {
        int rc;
        struct  compress_ioctl fsctl_input;
        char *ret_data = NULL;
 
-       fsctl_input.CompressionState =
-                       cpu_to_le16(COMPRESSION_FORMAT_DEFAULT);
+       fsctl_input.CompressionState = cpu_to_le16(compression_state);
 
        rc = SMB2_ioctl(xid, tcon, persistent_fid, volatile_fid,
                        FSCTL_SET_COMPRESSION,
index 1ceb95b907e6b2bf9163db8b6f8d372e259b3692..78a4e1c340f99536deb4fdd21d4b021bc731c122 100644 (file)
@@ -216,7 +216,8 @@ int SMB2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
                u64 persistent_fid, u64 volatile_fid,
                struct smb2_file_full_ea_info *buf, int len);
 int SMB2_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
-                        u64 persistent_fid, u64 volatile_fid);
+                        u64 persistent_fid, u64 volatile_fid,
+                        __u16 compression_state);
 int SMB2_oplock_break(const unsigned int xid, struct cifs_tcon *tcon,
                      const u64 persistent_fid, const u64 volatile_fid,
                      __u8 oplock_level);