]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
smb/client: use writable handle for FS_IOC_SETFLAGS compression
authorHuiwen He <hehuiwen@kylinos.cn>
Mon, 8 Jun 2026 15:57:30 +0000 (23:57 +0800)
committerSteve French <stfrench@microsoft.com>
Sun, 14 Jun 2026 20:12:23 +0000 (15:12 -0500)
Setting the compressed flag on a CIFS mount can fail with -EACCES:

[compress_share]
vfs objects = btrfs

        $ touch test.bin
        $ chattr +c test.bin
        chattr: Permission denied while setting flags on test.bin

This can be reproduced against a Samba share backed by a filesystem that
supports compression, such as btrfs.

FS_IOC_SETFLAGS is issued on the file handle opened by userspace.  chattr
opens the target read-only before setting FS_COMPR_FL, so the SMB client
currently sends FSCTL_SET_COMPRESSION on a handle that may not have
FILE_WRITE_DATA access.  Samba requires FILE_WRITE_DATA for
FSCTL_SET_COMPRESSION and rejects the request.

Use the current handle only if it already has FILE_WRITE_DATA.  Otherwise
try an existing writable handle for the inode.  If none is available, open
a temporary FILE_WRITE_DATA handle for the compression request.

After FSCTL_SET_COMPRESSION succeeds, update the cached compressed
attribute immediately, matching how smb2_set_sparse() updates
FILE_ATTRIBUTE_SPARSE_FILE after a successful FSCTL_SET_SPARSE.

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/ioctl.c

index 746d70091f3d1d22223b90776e0cfb6d9ce32006..886e6893a552d325a34a1905c0e7d7a98c0743ea 100644 (file)
@@ -67,6 +67,110 @@ static long cifs_ioctl_query_info(unsigned int xid, struct file *filep,
        return rc;
 }
 
+static int cifs_set_compression_by_path(unsigned int xid, struct file *filep,
+                                       struct cifs_tcon *tcon)
+{
+       struct inode *inode = file_inode(filep);
+       struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
+       struct TCP_Server_Info *server = tcon->ses->server;
+       struct cifs_open_parms oparms;
+       struct cifs_open_info_data data = {};
+       struct cifsFileInfo *tmp_cfile = NULL;
+       struct cifs_fid fid = {};
+       const char *full_path;
+       __u32 oplock = 0;
+       u64 uniqueid;
+       void *page;
+       int rc;
+
+       if (!server->ops->open || !server->ops->close ||
+           !server->ops->query_file_info)
+               return -EOPNOTSUPP;
+
+       if (!(cifs_sb_flags(cifs_sb) & CIFS_MOUNT_SERVER_INUM) ||
+           cifs_sb->mnt_cifs_serverino_autodisabled)
+               return -EOPNOTSUPP;
+
+       if (d_unhashed(filep->f_path.dentry))
+               return -ESTALE;
+
+       page = alloc_dentry_path();
+       full_path = build_path_from_dentry(filep->f_path.dentry, page);
+       if (IS_ERR(full_path)) {
+               free_dentry_path(page);
+               return PTR_ERR(full_path);
+       }
+
+       oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_WRITE_DATA |
+                            FILE_READ_ATTRIBUTES,
+                            FILE_OPEN, 0, ACL_NO_MODE);
+       oparms.fid = &fid;
+
+       rc = server->ops->open(xid, &oparms, &oplock, NULL);
+       if (rc)
+               goto out;
+
+       tmp_cfile = kzalloc_obj(*tmp_cfile);
+       if (!tmp_cfile) {
+               rc = -ENOMEM;
+               goto close;
+       }
+
+       tmp_cfile->fid = fid;
+       rc = server->ops->query_file_info(xid, tcon, tmp_cfile, &data);
+       if (rc)
+               goto close;
+
+       uniqueid = le64_to_cpu(data.fi.IndexNumber);
+       if (uniqueid != CIFS_I(inode)->uniqueid) {
+               rc = -ESTALE;
+               goto close;
+       }
+
+       rc = server->ops->set_compression(xid, tcon, tmp_cfile);
+
+close:
+       server->ops->close(xid, tcon, &fid);
+       if (tmp_cfile)
+               kfree(tmp_cfile);
+       cifs_free_open_info(&data);
+out:
+       free_dentry_path(page);
+       return rc;
+}
+
+static int cifs_ioctl_set_compression(unsigned int xid, struct file *filep,
+                                     struct cifs_tcon *tcon,
+                                     struct cifsFileInfo *cfile)
+{
+       struct cifsFileInfo *wfile;
+       struct cifs_tcon *wtcon;
+       struct inode *inode = file_inode(filep);
+       int rc;
+
+       if (!tcon->ses->server->ops->set_compression)
+               return -EOPNOTSUPP;
+
+       if (cfile && (cfile->fid.access & FILE_WRITE_DATA)) {
+               rc = tcon->ses->server->ops->set_compression(xid, tcon, cfile);
+               if (rc != -EACCES)
+                       return rc;
+       }
+
+       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);
+               cifsFileInfo_put(wfile);
+               if (rc != -EACCES)
+                       return rc;
+       } else if (rc != -EBADF) {
+               return rc;
+       }
+
+       return cifs_set_compression_by_path(xid, filep, tcon);
+}
+
 static long cifs_ioctl_copychunk(unsigned int xid, struct file *dst_file,
                        unsigned long srcfd)
 {
@@ -424,11 +528,15 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
                                break;
 
                        /* Try to set compress flag */
-                       if (tcon->ses->server->ops->set_compression) {
-                               rc = tcon->ses->server->ops->set_compression(
-                                                       xid, tcon, pSMBFile);
-                               cifs_dbg(FYI, "set compress flag rc %d\n", rc);
+                       rc = cifs_ioctl_set_compression(xid, filep, tcon,
+                                                       pSMBFile);
+                       if (rc == 0) {
+                               spin_lock(&inode->i_lock);
+                               CIFS_I(inode)->cifsAttrs |=
+                                       FILE_ATTRIBUTE_COMPRESSED;
+                               spin_unlock(&inode->i_lock);
                        }
+                       cifs_dbg(FYI, "set compress flag rc %d\n", rc);
                        break;
                case CIFS_IOC_COPYCHUNK_FILE:
                        rc = cifs_ioctl_copychunk(xid, filep, arg);