* [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling
@ 2026-06-24 2:15 Huiwen He
2026-06-24 2:15 ` [PATCH v2 1/9] smb/client: name the default fallocate mode Huiwen He
` (8 more replies)
0 siblings, 9 replies; 12+ messages in thread
From: Huiwen He @ 2026-06-24 2:15 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
Changes since v1:
- Add patch 2 to preserve FSCTL_SET_SPARSE errors and only cache
EOPNOTSUPP as missing sparse support.
- Update patch 6 to allow verified EOF-adjacent fallocate on non-sparse
files.
- Add patch 7 to handle allocated ranges that overlap the requested
fallocate range.
- Update patch 9 to limit zero-write emulation to small EOF-adjacent
ranges on sparse files.
Link to v1:
https://lore.kernel.org/linux-cifs/20260623024619.1360127-1-huiwen.he@linux.dev
Huiwen He (9):
smb/client: name the default fallocate mode
smb/client: preserve errors from smb2_set_sparse()
smb/client: handle smb2_set_sparse() failure in EOF-extending
fallocate
smb/client: handle smb2_set_sparse() failure in non-extending
fallocate
smb/client: do not account EOF extension as allocation
smb/client: verify allocation after EOF-extending fallocate
smb/client: handle overlapping allocated ranges in fallocate
smb/client: reduce fallocate zero buffer allocation
smb/client: emulate small sparse fallocate ranges at EOF
fs/smb/client/inode.c | 13 ++-
fs/smb/client/smb2ops.c | 181 ++++++++++++++++++++++++++++++--------
fs/smb/client/smb2pdu.c | 19 ++++
fs/smb/client/smb2proto.h | 3 +
fs/smb/common/fscc.h | 5 ++
5 files changed, 182 insertions(+), 39 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 1/9] smb/client: name the default fallocate mode
2026-06-24 2:15 [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling Huiwen He
@ 2026-06-24 2:15 ` Huiwen He
2026-06-24 2:15 ` [PATCH v2 2/9] smb/client: preserve errors from smb2_set_sparse() Huiwen He
` (7 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Huiwen He @ 2026-06-24 2:15 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
FALLOC_FL_ALLOCATE_RANGE identifies the default fallocate
allocation mode and is defined as zero.
Use the symbolic name instead of a literal zero in
smb3_fallocate() to make the mode dispatch clearer. This
does not change behavior.
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/smb2ops.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index a8f8feeeccb5..2964f461fc84 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -4071,7 +4071,7 @@ static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int mode,
return smb3_collapse_range(file, tcon, off, len);
else if (mode == FALLOC_FL_INSERT_RANGE)
return smb3_insert_range(file, tcon, off, len);
- else if (mode == 0)
+ else if (mode == FALLOC_FL_ALLOCATE_RANGE)
return smb3_simple_falloc(file, tcon, off, len, false);
return -EOPNOTSUPP;
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 2/9] smb/client: preserve errors from smb2_set_sparse()
2026-06-24 2:15 [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling Huiwen He
2026-06-24 2:15 ` [PATCH v2 1/9] smb/client: name the default fallocate mode Huiwen He
@ 2026-06-24 2:15 ` Huiwen He
2026-06-24 2:15 ` [PATCH v2 3/9] smb/client: handle smb2_set_sparse() failure in EOF-extending fallocate Huiwen He
` (6 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Huiwen He @ 2026-06-24 2:15 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
smb2_set_sparse() converts every FSCTL_SET_SPARSE failure to false and
marks sparse support as broken for the share. Callers therefore report
EOPNOTSUPP even for errors such as ENOSPC or EACCES, and later sparse
operations remain disabled until the share is unmounted.
Return the SMB2 ioctl error directly. Set broken_sparse_sup only for
EOPNOTSUPP, which indicates that the server does not support the request.
Update smb3_punch_hole() to propagate the error returned by
smb2_set_sparse().
Fixes: 3d1a3745d8ca ("Add sparse file support to SMB2/SMB3 mounts")
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/smb2ops.c | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 2964f461fc84..965a4d21dd43 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -2117,8 +2117,9 @@ smb2_sync_write(const unsigned int xid, struct cifs_fid *pfid,
}
/* Set or clear the SPARSE_FILE attribute based on value passed in setsparse */
-static bool smb2_set_sparse(const unsigned int xid, struct cifs_tcon *tcon,
- struct cifsFileInfo *cfile, struct inode *inode, __u8 setsparse)
+static int smb2_set_sparse(const unsigned int xid, struct cifs_tcon *tcon,
+ struct cifsFileInfo *cfile, struct inode *inode,
+ __u8 setsparse)
{
struct cifsInodeInfo *cifsi;
int rc;
@@ -2127,31 +2128,31 @@ static bool smb2_set_sparse(const unsigned int xid, struct cifs_tcon *tcon,
/* if file already sparse don't bother setting sparse again */
if ((cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) && setsparse)
- return true; /* already sparse */
+ return 0; /* already sparse */
if (!(cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) && !setsparse)
- return true; /* already not sparse */
+ return 0; /* already not sparse */
/*
* Can't check for sparse support on share the usual way via the
* FS attribute info (FILE_SUPPORTS_SPARSE_FILES) on the share
* since Samba server doesn't set the flag on the share, yet
* supports the set sparse FSCTL and returns sparse correctly
- * in the file attributes. If we fail setting sparse though we
- * mark that server does not support sparse files for this share
- * to avoid repeatedly sending the unsupported fsctl to server
- * if the file is repeatedly extended.
+ * in the file attributes. If the server returns EOPNOTSUPP, mark
+ * that sparse files are not supported on this share to avoid
+ * repeatedly sending the unsupported FSCTL.
*/
if (tcon->broken_sparse_sup)
- return false;
+ return -EOPNOTSUPP;
rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid, FSCTL_SET_SPARSE,
&setsparse, 1, CIFSMaxBufSize, NULL, NULL);
if (rc) {
- tcon->broken_sparse_sup = true;
+ if (rc == -EOPNOTSUPP)
+ tcon->broken_sparse_sup = true;
cifs_dbg(FYI, "set sparse rc = %d\n", rc);
- return false;
+ return rc;
}
if (setsparse)
@@ -2159,7 +2160,7 @@ static bool smb2_set_sparse(const unsigned int xid, struct cifs_tcon *tcon,
else
cifsi->cifsAttrs &= (~FILE_ATTRIBUTE_SPARSE_FILE);
- return true;
+ return 0;
}
static int
@@ -3483,10 +3484,9 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon,
/* Need to make file sparse, if not already, before freeing range. */
/* Consider adding equivalent for compressed since it could also work */
- if (!smb2_set_sparse(xid, tcon, cfile, inode, set_sparse)) {
- rc = -EOPNOTSUPP;
+ rc = smb2_set_sparse(xid, tcon, cfile, inode, set_sparse);
+ if (rc)
goto out;
- }
filemap_invalidate_lock(inode->i_mapping);
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 3/9] smb/client: handle smb2_set_sparse() failure in EOF-extending fallocate
2026-06-24 2:15 [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling Huiwen He
2026-06-24 2:15 ` [PATCH v2 1/9] smb/client: name the default fallocate mode Huiwen He
2026-06-24 2:15 ` [PATCH v2 2/9] smb/client: preserve errors from smb2_set_sparse() Huiwen He
@ 2026-06-24 2:15 ` Huiwen He
2026-06-24 2:48 ` Steve French
2026-06-24 2:15 ` [PATCH v2 4/9] smb/client: handle smb2_set_sparse() failure in non-extending fallocate Huiwen He
` (5 subsequent siblings)
8 siblings, 1 reply; 12+ messages in thread
From: Huiwen He @ 2026-06-24 2:15 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
An EOF-extending fallocate(mode=0) can return success while the newly
extended range remains sparse. A later write into that range may
therefore still fail with ENOSPC despite fallocate having succeeded.
CIFS clears the sparse attribute before extending EOF so that the
requested range is allocated. However, it ignores failure from
smb2_set_sparse() and continues to SetEOF.
Return the smb2_set_sparse() error without extending EOF. This prevents
CIFS from reporting successful preallocation when the server rejected
FSCTL_SET_SPARSE.
Fixes: 8bd0d701445e ("cifs: add support for fallocate mode 0 for non-sparse files")
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/smb2ops.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 965a4d21dd43..f68c0df1b66e 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -3691,8 +3691,11 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
if (rc)
goto out;
- if (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)
- smb2_set_sparse(xid, tcon, cfile, inode, false);
+ if (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) {
+ rc = smb2_set_sparse(xid, tcon, cfile, inode, false);
+ if (rc)
+ goto out;
+ }
new_eof = off + len;
rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 4/9] smb/client: handle smb2_set_sparse() failure in non-extending fallocate
2026-06-24 2:15 [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling Huiwen He
` (2 preceding siblings ...)
2026-06-24 2:15 ` [PATCH v2 3/9] smb/client: handle smb2_set_sparse() failure in EOF-extending fallocate Huiwen He
@ 2026-06-24 2:15 ` Huiwen He
2026-06-24 2:15 ` [PATCH v2 5/9] smb/client: do not account EOF extension as allocation Huiwen He
` (4 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Huiwen He @ 2026-06-24 2:15 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
A non-extending fallocate on a sparse file can return success while the
requested range still contains holes. This affects FALLOC_FL_ALLOCATE_RANGE
within EOF and FALLOC_FL_KEEP_SIZE when CIFS clears the sparse attribute.
Later writes into the range may still fail.
CIFS emulates these requests by clearing the sparse attribute for the
whole file. However, it ignores failure from smb2_set_sparse() and returns
success unconditionally.
Return the smb2_set_sparse() error. This prevents CIFS from reporting
successful preallocation when the server rejected FSCTL_SET_SPARSE.
Fixes: f16994797ea8 ("cifs: fix incorrect handling of smb2_set_sparse() return in smb3_simple_falloc")
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/smb2ops.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index f68c0df1b66e..b8474ed9af16 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -3770,8 +3770,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
}
}
- smb2_set_sparse(xid, tcon, cfile, inode, false);
- rc = 0;
+ rc = smb2_set_sparse(xid, tcon, cfile, inode, false);
out:
if (rc)
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 5/9] smb/client: do not account EOF extension as allocation
2026-06-24 2:15 [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling Huiwen He
` (3 preceding siblings ...)
2026-06-24 2:15 ` [PATCH v2 4/9] smb/client: handle smb2_set_sparse() failure in non-extending fallocate Huiwen He
@ 2026-06-24 2:15 ` Huiwen He
2026-06-24 2:15 ` [PATCH v2 6/9] smb/client: verify allocation after EOF-extending fallocate Huiwen He
` (3 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Huiwen He @ 2026-06-24 2:15 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
cifs_setsize() updates the local inode size after SetEOF succeeds. It also
used the new EOF as a local i_blocks estimate, but extending EOF does not
prove that the intervening range was allocated.
For example, after writing 1 MiB and then extending EOF to 10 MiB, the
client can report the file as fully allocated even though the server still
reports a much smaller AllocationSize:
$ dd if=/dev/zero of=test bs=1M count=1
$ truncate -s 10M test && stat -c 'size=%s blocks=%b' test
$ stat --cached=never -c 'size=%s blocks=%b' test
client stat: size=10485760 blocks=20480
server stat: size=10485760 blocks=2056
client stat after revalidation: size=10485760 blocks=2056
A later attribute revalidation may correct i_blocks, but callers such as
xfstests generic/495 invoke swapon immediately after truncate. The swapfile
hole check can therefore observe the inflated local i_blocks value and
accept a sparse file.
Do not grow i_blocks from cifs_setsize() on EOF extension. Only clamp it
on shrink; allocation growth must come from write completion or from
server-reported AllocationSize.
With this change, EOF extension no longer makes a sparse file appear
fully allocated before the next attribute revalidation, and xfstests
generic/495 no longer accepts it through the inflated local i_blocks value.
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/inode.c | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index 0af93e881608..84d95d2c3ed9 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -3038,13 +3038,20 @@ int cifs_fiemap(struct inode *inode, struct fiemap_extent_info *fei, u64 start,
void cifs_setsize(struct inode *inode, loff_t offset)
{
+ loff_t old_size;
+ u64 blocks = CIFS_INO_BLOCKS(offset);
+
spin_lock(&inode->i_lock);
+ old_size = i_size_read(inode);
i_size_write(inode, offset);
+
/*
- * Until we can query the server for actual allocation size,
- * this is best estimate we have for blocks allocated for a file.
+ * Extending EOF does not allocate the intervening range. Only clamp
+ * i_blocks on shrink; allocation growth comes from writes or from the
+ * server-reported AllocationSize.
*/
- inode->i_blocks = CIFS_INO_BLOCKS(offset);
+ if (offset < old_size && (u64)inode->i_blocks > blocks)
+ inode->i_blocks = blocks;
spin_unlock(&inode->i_lock);
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
truncate_pagecache(inode, offset);
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 6/9] smb/client: verify allocation after EOF-extending fallocate
2026-06-24 2:15 [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling Huiwen He
` (4 preceding siblings ...)
2026-06-24 2:15 ` [PATCH v2 5/9] smb/client: do not account EOF extension as allocation Huiwen He
@ 2026-06-24 2:15 ` Huiwen He
2026-06-24 2:15 ` [PATCH v2 7/9] smb/client: handle overlapping allocated ranges in fallocate Huiwen He
` (2 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Huiwen He @ 2026-06-24 2:15 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
EOF-extending fallocate(mode=0) currently returns success after SetEOF
even if the server has not allocated the requested space. This gives
userspace a false preallocation guarantee and can cause a later write
into that range to fail with ENOSPC.
This happens because SMB2 has no operation that directly matches Linux
fallocate for an arbitrary byte range. SetEOF changes only the logical
file size, while FILE_ALLOCATION_INFORMATION can request allocation only
from offset 0 up to a specified allocation size.
Use FILE_ALLOCATION_INFORMATION only when allocating [0, off + len)
safely covers the requested range [off, off + len). For example, both
ranges are identical when the request starts at offset zero:
xfs_io -f -c "falloc 0 4m" file
Allocating the additional range before off is also safe for an empty
file:
truncate -s 0 file
xfs_io -c "falloc 1m 4m" file
An EOF-adjacent request is safe for a non-sparse file because the range
before EOF is already allocated:
xfs_io -f -c "pwrite 0 1m" file
xfs_io -c "falloc 1m 4m" file
Request and verify the resulting AllocationSize before extending EOF, so
an allocation failure leaves the file size unchanged. Reject other
non-zero-offset requests because AllocationSize cannot identify which
byte ranges were allocated.
With Samba `strict allocate=yes`, this allows generic/496, generic/568 and
generic/701 to pass after verifying the server allocation. With
`strict allocate=no`, the requests fail instead of reporting false
preallocation success.
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/smb2ops.c | 69 +++++++++++++++++++++++++++++++++++----
fs/smb/client/smb2pdu.c | 19 +++++++++++
fs/smb/client/smb2proto.h | 3 ++
fs/smb/common/fscc.h | 5 +++
4 files changed, 90 insertions(+), 6 deletions(-)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index b8474ed9af16..20cc66f728a7 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -3665,12 +3665,16 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
struct cifsFileInfo *cfile = file->private_data;
long rc = -EOPNOTSUPP;
unsigned int xid;
- loff_t new_eof;
+ loff_t old_eof, new_eof;
+ struct smb2_file_all_info file_inf;
+ u64 asize = 0;
+ int qrc;
xid = get_xid();
inode = d_inode(cfile->dentry);
cifsi = CIFS_I(inode);
+ old_eof = i_size_read(inode);
trace_smb3_falloc_enter(xid, cfile->fid.persistent_fid, tcon->tid,
tcon->ses->Suid, off, len);
@@ -3686,11 +3690,24 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
/*
* Extending the file
*/
- if ((keep_size == false) && i_size_read(inode) < off + len) {
+ if (!keep_size && old_eof < off + len) {
rc = inode_newsize_ok(inode, off + len);
if (rc)
goto out;
+ /*
+ * FILE_ALLOCATION_INFORMATION sets the allocation size for the whole
+ * file and cannot allocate an arbitrary byte range. Use it only for
+ * requests starting at offset zero, requests on an empty file, or
+ * requests appending to a non-sparse file.
+ */
+ if (off != 0 && old_eof != 0 &&
+ (off != old_eof ||
+ (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE))) {
+ rc = -EOPNOTSUPP;
+ goto out;
+ }
+
if (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) {
rc = smb2_set_sparse(xid, tcon, cfile, inode, false);
if (rc)
@@ -3698,12 +3715,52 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
}
new_eof = off + len;
+
+ qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid,
+ cfile->fid.volatile_fid, &file_inf);
+ if (qrc == 0)
+ asize = le64_to_cpu(file_inf.AllocationSize);
+
+ if (qrc || asize < new_eof) {
+ rc = SMB2_set_allocation(xid, tcon,
+ cfile->fid.persistent_fid,
+ cfile->fid.volatile_fid,
+ cfile->pid, new_eof);
+ if (rc)
+ goto invalidate_attrs;
+
+ qrc = SMB2_query_info(xid, tcon,
+ cfile->fid.persistent_fid,
+ cfile->fid.volatile_fid, &file_inf);
+ if (qrc) {
+ rc = qrc;
+ goto invalidate_attrs;
+ }
+
+ asize = le64_to_cpu(file_inf.AllocationSize);
+ if (asize < new_eof) {
+ rc = -EOPNOTSUPP;
+ goto invalidate_attrs;
+ }
+ }
+
rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid, cfile->pid, new_eof);
- if (rc == 0) {
- netfs_resize_file(&cifsi->netfs, new_eof, true);
- cifs_setsize(inode, new_eof);
- }
+ if (rc)
+ goto invalidate_attrs;
+
+ netfs_resize_file(&cifsi->netfs, new_eof, true);
+ cifs_setsize(inode, new_eof);
+
+ spin_lock(&inode->i_lock);
+ inode->i_blocks = CIFS_INO_BLOCKS(asize);
+ spin_unlock(&inode->i_lock);
+ goto out;
+
+invalidate_attrs:
+ spin_lock(&inode->i_lock);
+ cifsi->time = 0;
+ spin_unlock(&inode->i_lock);
goto out;
}
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index 4972cfe249f6..1c6fadb7fbf3 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -5899,6 +5899,25 @@ SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
0, 1, &data, &size);
}
+int
+SMB2_set_allocation(const unsigned int xid, struct cifs_tcon *tcon,
+ u64 persistent_fid, u64 volatile_fid, u32 pid,
+ loff_t allocation_size)
+{
+ struct smb2_file_alloc_info info;
+ void *data;
+ unsigned int size;
+
+ info.AllocationSize = cpu_to_le64(allocation_size);
+
+ data = &info;
+ size = sizeof(struct smb2_file_alloc_info);
+
+ return send_set_info(xid, tcon, persistent_fid, volatile_fid,
+ pid, FILE_ALLOCATION_INFORMATION, SMB2_O_INFO_FILE,
+ 0, 1, &data, &size);
+}
+
int
SMB2_set_acl(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid,
diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
index 78a4e1c340f9..16a02c1eb0a1 100644
--- a/fs/smb/client/smb2proto.h
+++ b/fs/smb/client/smb2proto.h
@@ -204,6 +204,9 @@ void SMB2_query_directory_free(struct smb_rqst *rqst);
int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon,
u64 persistent_fid, u64 volatile_fid, u32 pid,
loff_t new_eof);
+int SMB2_set_allocation(const unsigned int xid, struct cifs_tcon *tcon,
+ u64 persistent_fid, u64 volatile_fid, u32 pid,
+ loff_t allocation_size);
int SMB2_set_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
struct smb_rqst *rqst, u64 persistent_fid,
u64 volatile_fid, u32 pid, u8 info_class, u8 info_type,
diff --git a/fs/smb/common/fscc.h b/fs/smb/common/fscc.h
index bc3012cc295d..c9d5aa94727f 100644
--- a/fs/smb/common/fscc.h
+++ b/fs/smb/common/fscc.h
@@ -265,6 +265,11 @@ struct smb2_file_eof_info { /* encoding of request for level 10 */
__le64 EndOfFile; /* new end of file value */
} __packed; /* level 20 Set */
+/* See MS-FSCC 2.4.4 */
+struct smb2_file_alloc_info { /* encoding of request for level 19 */
+ __le64 AllocationSize;
+} __packed;
+
/* See MS-FSCC 2.4.15 */
typedef struct {
__le32 NextEntryOffset;
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 7/9] smb/client: handle overlapping allocated ranges in fallocate
2026-06-24 2:15 [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling Huiwen He
` (5 preceding siblings ...)
2026-06-24 2:15 ` [PATCH v2 6/9] smb/client: verify allocation after EOF-extending fallocate Huiwen He
@ 2026-06-24 2:15 ` Huiwen He
2026-06-24 2:15 ` [PATCH v2 8/9] smb/client: reduce fallocate zero buffer allocation Huiwen He
2026-06-24 2:15 ` [PATCH v2 9/9] smb/client: emulate small sparse fallocate ranges at EOF Huiwen He
8 siblings, 0 replies; 12+ messages in thread
From: Huiwen He @ 2026-06-24 2:15 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
smb3_simple_fallocate_range() can skip holes when an allocated range
returned by the server starts before the current fallocate offset. The
skipped hole is not zero-filled, but fallocate still returns success. A
later write to that hole may therefore fail with ENOSPC.
The function queries allocated ranges so that it can preserve existing
contents and write zeroes only into holes. However, the server may return
a range that starts before the current fallocate offset.
For example, assume the fallocate request is [100, 400) and the only
allocated range returned by the server is [0, 200):
Request: [100, 400)
Server range: [ 0, 200) allocated
Correct:
[100, 200) allocated data, skip
[200, 400) hole, zero-fill
Current:
[100, 300) skipped
[300, 400) zero-filled afterwards
The current code adds the full server range length, 200, to the current
offset 100 and moves to 300. As a result, the hole in [200, 300) is
skipped without being zero-filled.
Calculate the end of each allocated range and advance from the current
offset to that end. Ignore ranges ending before the current offset and
reject ranges whose end offset overflows.
Fixes: 966a3cb7c7db ("cifs: improve fallocate emulation")
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/smb2ops.c | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 20cc66f728a7..3c0b47a41027 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -3581,6 +3581,7 @@ static int smb3_simple_fallocate_range(unsigned int xid,
struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data;
u32 out_data_len;
char *buf = NULL;
+ u64 range_start, range_len, range_end;
loff_t l;
int rc;
@@ -3617,13 +3618,21 @@ static int smb3_simple_fallocate_range(unsigned int xid,
goto out;
}
- if (off < le64_to_cpu(tmp_data->file_offset)) {
+ range_start = le64_to_cpu(tmp_data->file_offset);
+ range_len = le64_to_cpu(tmp_data->length);
+ if (check_add_overflow(range_start, range_len, &range_end) ||
+ range_end > S64_MAX) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (off < range_start) {
/*
* We are at a hole. Write until the end of the region
* or until the next allocated data,
* whichever comes next.
*/
- l = le64_to_cpu(tmp_data->file_offset) - off;
+ l = range_start - off;
if (len < l)
l = len;
rc = smb3_simple_fallocate_write_range(xid, tcon,
@@ -3640,11 +3649,13 @@ static int smb3_simple_fallocate_range(unsigned int xid,
* until the end of the data or the end of the region
* we are supposed to fallocate, whichever comes first.
*/
- l = le64_to_cpu(tmp_data->length);
- if (len < l)
- l = len;
- off += l;
- len -= l;
+ if (off < range_end) {
+ l = range_end - off;
+ if (len < l)
+ l = len;
+ off += l;
+ len -= l;
+ }
tmp_data = &tmp_data[1];
out_data_len -= sizeof(struct file_allocated_range_buffer);
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 8/9] smb/client: reduce fallocate zero buffer allocation
2026-06-24 2:15 [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling Huiwen He
` (6 preceding siblings ...)
2026-06-24 2:15 ` [PATCH v2 7/9] smb/client: handle overlapping allocated ranges in fallocate Huiwen He
@ 2026-06-24 2:15 ` Huiwen He
2026-06-24 2:15 ` [PATCH v2 9/9] smb/client: emulate small sparse fallocate ranges at EOF Huiwen He
8 siblings, 0 replies; 12+ messages in thread
From: Huiwen He @ 2026-06-24 2:15 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 fallocate emulation allocates a 1 MiB zero-filled buffer even though
each SMB2_write request is limited to SMB2_MAX_BUFFER_SIZE, which is
64 KiB. A high-order 1 MiB allocation is more likely to fail on a
fragmented system.
Allocate only the smaller of the requested range and
SMB2_MAX_BUFFER_SIZE, and reuse that zero-filled buffer for every write
request. Also reject a successful write that makes no progress to avoid
looping indefinitely.
This reduces the contiguous allocation required by fallocate emulation
without changing the written data or range semantics.
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/smb2ops.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 3c0b47a41027..e0815805caca 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -3543,7 +3543,7 @@ static int smb3_simple_fallocate_write_range(unsigned int xid,
char *buf)
{
struct cifs_io_parms io_parms = {0};
- int nbytes;
+ unsigned int nbytes;
int rc = 0;
struct kvec iov[2];
@@ -3564,9 +3564,10 @@ static int smb3_simple_fallocate_write_range(unsigned int xid,
rc = SMB2_write(xid, &io_parms, &nbytes, iov, 1);
if (rc)
break;
+ if (!nbytes)
+ return -EIO;
if (nbytes > len)
return -EINVAL;
- buf += nbytes;
off += nbytes;
len -= nbytes;
}
@@ -3596,7 +3597,7 @@ static int smb3_simple_fallocate_range(unsigned int xid,
if (rc)
goto out;
- buf = kzalloc(1024 * 1024, GFP_KERNEL);
+ buf = kzalloc(min_t(loff_t, len, SMB2_MAX_BUFFER_SIZE), GFP_KERNEL);
if (buf == NULL) {
rc = -ENOMEM;
goto out;
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v2 9/9] smb/client: emulate small sparse fallocate ranges at EOF
2026-06-24 2:15 [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling Huiwen He
` (7 preceding siblings ...)
2026-06-24 2:15 ` [PATCH v2 8/9] smb/client: reduce fallocate zero buffer allocation Huiwen He
@ 2026-06-24 2:15 ` Huiwen He
8 siblings, 0 replies; 12+ messages in thread
From: Huiwen He @ 2026-06-24 2:15 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
smb3_simple_falloc() returns -EOPNOTSUPP for mode 0 requests that start
at EOF of a non-empty sparse file because FILE_ALLOCATION_INFORMATION
cannot allocate an arbitrary byte range.
However, one small case can be handled safely: a range that starts exactly
at the current EOF. For example:
xfs_io -f -c "pwrite 0 1m" file
xfs_io -c "falloc 1m 4k" file
Writing zeroes to this range does not create an intervening hole or
overwrite existing data. It allocates the requested range and extends EOF
without changing other sparse regions. Limit this emulation to 1 MiB to
keep the network and server I/O cost bounded.
This allows small EOF-adjacent fallocate requests on sparse files to
succeed. After the write, update the local file size and refresh i_blocks
from the server-reported AllocationSize. Larger ranges remain
unsupported.
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/smb2ops.c | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index e0815805caca..b6d5771a7c54 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -3707,6 +3707,44 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
if (rc)
goto out;
+ /*
+ * A small range immediately after EOF can be allocated by
+ * writing zeroes without creating an intervening hole.
+ */
+ if (off == old_eof && old_eof != 0 &&
+ (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)) {
+ if (len > 1024 * 1024) {
+ rc = -EOPNOTSUPP;
+ goto out;
+ }
+
+ rc = smb3_simple_fallocate_range(xid, tcon, cfile,
+ off, len);
+ if (rc) {
+ spin_lock(&inode->i_lock);
+ cifsi->time = 0;
+ spin_unlock(&inode->i_lock);
+ goto out;
+ }
+
+ new_eof = off + len;
+ netfs_resize_file(&cifsi->netfs, new_eof, true);
+ cifs_setsize(inode, new_eof);
+
+ qrc = SMB2_query_info(xid, tcon,
+ cfile->fid.persistent_fid,
+ cfile->fid.volatile_fid, &file_inf);
+ spin_lock(&inode->i_lock);
+ if (qrc == 0) {
+ asize = le64_to_cpu(file_inf.AllocationSize);
+ inode->i_blocks = CIFS_INO_BLOCKS(asize);
+ } else {
+ cifsi->time = 0;
+ }
+ spin_unlock(&inode->i_lock);
+ goto out;
+ }
+
/*
* FILE_ALLOCATION_INFORMATION sets the allocation size for the whole
* file and cannot allocate an arbitrary byte range. Use it only for
--
2.43.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v2 3/9] smb/client: handle smb2_set_sparse() failure in EOF-extending fallocate
2026-06-24 2:15 ` [PATCH v2 3/9] smb/client: handle smb2_set_sparse() failure in EOF-extending fallocate Huiwen He
@ 2026-06-24 2:48 ` Steve French
2026-06-24 4:04 ` hehuiwen
0 siblings, 1 reply; 12+ messages in thread
From: Steve French @ 2026-06-24 2:48 UTC (permalink / raw)
To: Huiwen He
Cc: linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong, linux-cifs
Would it be possible in Linux to have a sparse file but still with
space reserved beyond end of file? In other words are there cases
where you could have a file with holes in it in Linux which still
fallocated beyond end of file. What happens to ext4, xfs, btrfs if
you try that? Does fallocate beyond of file remove all holes in a
(sparse) file?
On Tue, Jun 23, 2026 at 9:17 PM Huiwen He <huiwen.he@linux.dev> wrote:
>
> From: Huiwen He <hehuiwen@kylinos.cn>
>
> An EOF-extending fallocate(mode=0) can return success while the newly
> extended range remains sparse. A later write into that range may
> therefore still fail with ENOSPC despite fallocate having succeeded.
>
> CIFS clears the sparse attribute before extending EOF so that the
> requested range is allocated. However, it ignores failure from
> smb2_set_sparse() and continues to SetEOF.
>
> Return the smb2_set_sparse() error without extending EOF. This prevents
> CIFS from reporting successful preallocation when the server rejected
> FSCTL_SET_SPARSE.
>
> Fixes: 8bd0d701445e ("cifs: add support for fallocate mode 0 for non-sparse files")
> Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
> Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
> ---
> fs/smb/client/smb2ops.c | 7 +++++--
> 1 file changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
> index 965a4d21dd43..f68c0df1b66e 100644
> --- a/fs/smb/client/smb2ops.c
> +++ b/fs/smb/client/smb2ops.c
> @@ -3691,8 +3691,11 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
> if (rc)
> goto out;
>
> - if (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)
> - smb2_set_sparse(xid, tcon, cfile, inode, false);
> + if (cifsi->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE) {
> + rc = smb2_set_sparse(xid, tcon, cfile, inode, false);
> + if (rc)
> + goto out;
> + }
>
> new_eof = off + len;
> rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
> --
> 2.43.0
>
--
Thanks,
Steve
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 3/9] smb/client: handle smb2_set_sparse() failure in EOF-extending fallocate
2026-06-24 2:48 ` Steve French
@ 2026-06-24 4:04 ` hehuiwen
0 siblings, 0 replies; 12+ messages in thread
From: hehuiwen @ 2026-06-24 4:04 UTC (permalink / raw)
To: Steve French
Cc: linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong, linux-cifs
Hi Steve,
Yes, that is possible. I checked ext4, XFS and btrfs with examples like:
truncate -s 1M file
fallocate -n -o 2M -l 1M file
filefrag -v file
and:
truncate -s 1M file
fallocate -o 2M -l 1M file
filefrag -v file
With FALLOC_FL_KEEP_SIZE, the file size stays at 1M while the range
[2M, 3M) is preallocated. Without FALLOC_FL_KEEP_SIZE, the file size
grows to 3M, but the filesystems still only allocate the requested
range [2M, 3M). Holes before that range are not removed.
Patch 3 only changes the error handling around the existing
smb2_set_sparse(..., false) call. I agree that clearing the sparse
attribute is not a general byte-range allocation mechanism.
Patch 9 handles the sparse EOF-adjacent case differently: small ranges
are allocated by writing zeroes only to the requested range, and larger
ranges remain unsupported. That avoids clearing the sparse attribute
for this case.
Thanks,
Huiwen
在 2026/6/24 10:48, Steve French 写道:
> Would it be possible in Linux to have a sparse file but still with
> space reserved beyond end of file? In other words are there cases
> where you could have a file with holes in it in Linux which still
> fallocated beyond end of file. What happens to ext4, xfs, btrfs if
> you try that? Does fallocate beyond of file remove all holes in a
> (sparse) file?
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-06-24 4:05 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-24 2:15 [PATCH v2 0/9] smb/client: fix mode 0 fallocate handling Huiwen He
2026-06-24 2:15 ` [PATCH v2 1/9] smb/client: name the default fallocate mode Huiwen He
2026-06-24 2:15 ` [PATCH v2 2/9] smb/client: preserve errors from smb2_set_sparse() Huiwen He
2026-06-24 2:15 ` [PATCH v2 3/9] smb/client: handle smb2_set_sparse() failure in EOF-extending fallocate Huiwen He
2026-06-24 2:48 ` Steve French
2026-06-24 4:04 ` hehuiwen
2026-06-24 2:15 ` [PATCH v2 4/9] smb/client: handle smb2_set_sparse() failure in non-extending fallocate Huiwen He
2026-06-24 2:15 ` [PATCH v2 5/9] smb/client: do not account EOF extension as allocation Huiwen He
2026-06-24 2:15 ` [PATCH v2 6/9] smb/client: verify allocation after EOF-extending fallocate Huiwen He
2026-06-24 2:15 ` [PATCH v2 7/9] smb/client: handle overlapping allocated ranges in fallocate Huiwen He
2026-06-24 2:15 ` [PATCH v2 8/9] smb/client: reduce fallocate zero buffer allocation Huiwen He
2026-06-24 2:15 ` [PATCH v2 9/9] smb/client: emulate small sparse fallocate ranges at EOF Huiwen He
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.