* [PATCH v1 2/3] smb/client: use writable handle for FS_IOC_SETFLAGS compression
2026-06-06 14:43 [PATCH v1 0/3] smb: client: fix file compression flag ioctls Huiwen He
2026-06-06 14:43 ` [PATCH v1 1/3] smb/client: always return a value for FS_IOC_GETFLAGS Huiwen He
@ 2026-06-06 14:43 ` Huiwen He
2026-06-06 14:43 ` [PATCH v1 3/3] smb/client: allow FS_IOC_SETFLAGS to clear compression Huiwen He
2 siblings, 0 replies; 4+ messages in thread
From: Huiwen He @ 2026-06-06 14:43 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
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>
---
fs/smb/client/ioctl.c | 115 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 113 insertions(+), 2 deletions(-)
diff --git a/fs/smb/client/ioctl.c b/fs/smb/client/ioctl.c
index 746d70091f3d..bcf56f264ff4 100644
--- a/fs/smb/client/ioctl.c
+++ b/fs/smb/client/ioctl.c
@@ -67,6 +67,111 @@ static long cifs_ioctl_query_info(unsigned int xid, struct file *filep,
return rc;
}
+static int cifs_set_compression_handle(unsigned int xid,
+ struct cifs_tcon *tcon,
+ struct cifsFileInfo *cfile)
+{
+ struct TCP_Server_Info *server = tcon->ses->server;
+
+ if (!server->ops->set_compression)
+ return -EOPNOTSUPP;
+
+ return server->ops->set_compression(xid, tcon, cfile);
+}
+
+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 = {};
+ 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_OPEN, 0, ACL_NO_MODE);
+ oparms.fid = &fid;
+
+ rc = server->ops->open(xid, &oparms, &oplock, NULL);
+ if (rc)
+ goto out;
+
+ 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 = cifs_set_compression_handle(xid, tcon, &tmp_cfile);
+
+close:
+ server->ops->close(xid, tcon, &fid);
+ 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 = cifs_set_compression_handle(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 = cifs_set_compression_handle(xid, wtcon, wfile);
+ cifsFileInfo_put(wfile);
+ if (rc != -EACCES)
+ 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)
{
@@ -425,8 +530,14 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
/* Try to set compress flag */
if (tcon->ses->server->ops->set_compression) {
- rc = tcon->ses->server->ops->set_compression(
- xid, tcon, pSMBFile);
+ 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;
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH v1 3/3] smb/client: allow FS_IOC_SETFLAGS to clear compression
2026-06-06 14:43 [PATCH v1 0/3] smb: client: fix file compression flag ioctls Huiwen He
2026-06-06 14:43 ` [PATCH v1 1/3] smb/client: always return a value for FS_IOC_GETFLAGS Huiwen He
2026-06-06 14:43 ` [PATCH v1 2/3] smb/client: use writable handle for FS_IOC_SETFLAGS compression Huiwen He
@ 2026-06-06 14:43 ` Huiwen He
2 siblings, 0 replies; 4+ messages in thread
From: Huiwen He @ 2026-06-06 14:43 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
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>
---
fs/smb/client/cifsglob.h | 2 +-
fs/smb/client/cifssmb.c | 4 ++--
fs/smb/client/ioctl.c | 46 +++++++++++++++++++++++++++------------
fs/smb/client/smb1ops.c | 5 +++--
fs/smb/client/smb1proto.h | 2 +-
fs/smb/client/smb2ops.c | 4 ++--
fs/smb/client/smb2pdu.c | 6 ++---
fs/smb/client/smb2proto.h | 3 ++-
8 files changed, 46 insertions(+), 26 deletions(-)
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 943b7cd2c096..a462c1590a9e 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -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 */
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index 9e27bfa7376b..d39175cdf1b1 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -3207,7 +3207,7 @@ struct inode *cifs_create_reparse_inode(struct cifs_open_info_data *data,
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);
diff --git a/fs/smb/client/ioctl.c b/fs/smb/client/ioctl.c
index bcf56f264ff4..5387105722b0 100644
--- a/fs/smb/client/ioctl.c
+++ b/fs/smb/client/ioctl.c
@@ -69,18 +69,21 @@ static long cifs_ioctl_query_info(unsigned int xid, struct file *filep,
static int cifs_set_compression_handle(unsigned int xid,
struct cifs_tcon *tcon,
- struct cifsFileInfo *cfile)
+ struct cifsFileInfo *cfile,
+ __u16 compression_state)
{
struct TCP_Server_Info *server = tcon->ses->server;
if (!server->ops->set_compression)
return -EOPNOTSUPP;
- return server->ops->set_compression(xid, tcon, cfile);
+ return server->ops->set_compression(xid, tcon, cfile,
+ compression_state);
}
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);
@@ -132,7 +135,8 @@ static int cifs_set_compression_by_path(unsigned int xid, struct file *filep,
goto close;
}
- rc = cifs_set_compression_handle(xid, tcon, &tmp_cfile);
+ rc = cifs_set_compression_handle(xid, tcon, &tmp_cfile,
+ compression_state);
close:
server->ops->close(xid, tcon, &fid);
@@ -144,7 +148,8 @@ static int cifs_set_compression_by_path(unsigned int xid, struct file *filep,
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;
@@ -155,7 +160,8 @@ static int cifs_ioctl_set_compression(unsigned int xid, struct file *filep,
return -EOPNOTSUPP;
if (cfile && (cfile->fid.access & FILE_WRITE_DATA)) {
- rc = cifs_set_compression_handle(xid, tcon, cfile);
+ rc = cifs_set_compression_handle(xid, tcon, cfile,
+ compression_state);
if (rc != -EACCES)
return rc;
}
@@ -163,13 +169,15 @@ 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 = cifs_set_compression_handle(xid, wtcon, wfile);
+ rc = cifs_set_compression_handle(xid, wtcon, wfile,
+ compression_state);
cifsFileInfo_put(wfile);
if (rc != -EACCES)
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,
@@ -524,18 +532,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)
break;
- /* Try to set compress flag */
if (tcon->ses->server->ops->set_compression) {
+ __u16 compression_state;
+
+ compression_state = (ExtAttrBits & FS_COMPR_FL) ?
+ COMPRESSION_FORMAT_DEFAULT :
+ COMPRESSION_FORMAT_NONE;
+
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 (ExtAttrBits & FS_COMPR_FL)
+ 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);
diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
index e198e3dda917..d34b3d99f6ed 100644
--- a/fs/smb/client/smb1ops.c
+++ b/fs/smb/client/smb1ops.c
@@ -1110,9 +1110,10 @@ smb_set_file_info(struct inode *inode, const char *full_path,
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
diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h
index 5f522d359952..80eaeb3dd2ec 100644
--- a/fs/smb/client/smb1proto.h
+++ b/fs/smb/client/smb1proto.h
@@ -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,
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 89230141b5dd..23807023c027 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -2246,10 +2246,10 @@ smb2_duplicate_extents(const unsigned int xid,
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
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index fbeb2156ddb6..6a185b805c1e 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -3626,14 +3626,14 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
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,
diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
index 1ceb95b907e6..78a4e1c340f9 100644
--- a/fs/smb/client/smb2proto.h
+++ b/fs/smb/client/smb2proto.h
@@ -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);
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread