* [PATCH 1/3] smb/client: update i_blocks after contiguous writes
2026-06-04 15:03 [PATCH 0/3] smb: client: fix i_blocks accounting for swapfile xfstests Huiwen He
@ 2026-06-04 15:03 ` Huiwen He
2026-06-04 15:03 ` [PATCH 2/3] smb/client: do not account EOF extension as allocation Huiwen He
2026-06-04 15:03 ` [PATCH 3/3] smb/client: refresh allocation size after fallocate Huiwen He
2 siblings, 0 replies; 4+ messages in thread
From: Huiwen He @ 2026-06-04 15:03 UTC (permalink / raw)
To: smfrench, linkinjeon, pc, ronniesahlberg, sprasad, tom, bharathsm,
senozhatsky, dhowells, metze, chenxiaosong
Cc: linux-cifs
From: Huiwen He <hehuiwen@kylinos.cn>
When a lease allows CIFS to use cached inode attributes, getattr may
return the locally cached attributes instead of revalidating them from
the server. After local writes extend a file, the write path updates the
file size, but i_blocks can remain based on the old allocation size.
For example, while the file is still open after two contiguous writes,
the local block count can remain smaller than the written range:
after first write: st_size = 4096, st_blocks = 7
after second write: st_size = 12288, st_blocks = 21
after close: st_size = 12288, st_blocks = 24
This can make a fully written file look sparse:
i_blocks * 512 < i_size
and can cause swap activation to reject a valid write-created swapfile
as having holes. This results in xfstests skipping swap-related tests
on CIFS mounts:
generic/472 [not run] swapfiles are not supported
generic/494 [not run] swapfiles are not supported
generic/497 [not run] swapfiles are not supported
generic/569 [not run] swapfiles are not supported
generic/636 [not run] swapfiles are not supported
generic/643 [not run] swapfiles are not supported
Update the local i_blocks estimate after successful writes, but only
when the write starts at or before the currently known allocated range.
This lets sequential writes grow i_blocks while avoiding treating
write-past-EOF holes as allocated.
Skip the local estimate for files that are already marked sparse, since
their allocation needs to come from the server rather than from a
contiguous-write estimate.
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/cifsglob.h | 9 ++++++---
fs/smb/client/file.c | 31 +++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+), 3 deletions(-)
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 82e0adc1dabd..943b7cd2c096 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -2387,9 +2387,12 @@ static inline int cifs_open_create_options(unsigned int oflags, int opts)
}
/*
- * The number of blocks is not related to (i_size / i_blksize), but instead
- * 512 byte (2**9) size is required for calculating num blocks.
+ * inode->i_blocks is counted in 512-byte units, independent of
+ * inode->i_blksize.
*/
-#define CIFS_INO_BLOCKS(size) DIV_ROUND_UP_ULL((u64)(size), 512)
+#define CIFS_INO_BLOCK_SIZE 512ULL
+#define CIFS_INO_BLOCKS(size) \
+ DIV_ROUND_UP_ULL((u64)(size), CIFS_INO_BLOCK_SIZE)
+#define CIFS_INO_BYTES(blocks) ((u64)(blocks) * CIFS_INO_BLOCK_SIZE)
#endif /* _CIFS_GLOB_H */
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index b60344125f27..7da96ea3a59e 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -2514,6 +2514,28 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *flock)
return rc;
}
+static void cifs_update_i_blocks_after_write(struct inode *inode, loff_t start,
+ loff_t end)
+{
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ u64 allocated_end = CIFS_INO_BYTES(inode->i_blocks);
+ u64 blocks;
+
+ if (cinode->cifsAttrs & FILE_ATTRIBUTE_SPARSE_FILE)
+ return;
+
+ /*
+ * Grow the local estimate only across the currently known allocated
+ * prefix. A write beyond that may leave a hole.
+ */
+ if ((u64)start > allocated_end)
+ return;
+
+ blocks = CIFS_INO_BLOCKS(end);
+ if ((u64)inode->i_blocks < blocks)
+ inode->i_blocks = blocks;
+}
+
void cifs_write_subrequest_terminated(struct cifs_io_subrequest *wdata, ssize_t result)
{
struct netfs_io_request *wreq = wdata->rreq;
@@ -2532,6 +2554,8 @@ void cifs_write_subrequest_terminated(struct cifs_io_subrequest *wdata, ssize_t
netfs_write_zero_point(inode, wrend);
if (wrend > ictx->_remote_i_size)
netfs_resize_file(ictx, wrend, true);
+ cifs_update_i_blocks_after_write(inode, wdata->subreq.start,
+ wrend);
spin_unlock(&inode->i_lock);
}
@@ -2895,6 +2919,7 @@ cifs_writev(struct kiocb *iocb, struct iov_iter *from)
struct cifsInodeInfo *cinode = CIFS_I(inode);
struct TCP_Server_Info *server = tlink_tcon(cfile->tlink)->ses->server;
struct cifs_sb_info *cifs_sb = CIFS_SB(inode);
+ loff_t start;
ssize_t rc;
rc = netfs_start_io_write(inode);
@@ -2919,7 +2944,13 @@ cifs_writev(struct kiocb *iocb, struct iov_iter *from)
goto out;
}
+ start = iocb->ki_pos;
rc = netfs_buffered_write_iter_locked(iocb, from, NULL);
+ if (rc > 0) {
+ spin_lock(&inode->i_lock);
+ cifs_update_i_blocks_after_write(inode, start, start + rc);
+ spin_unlock(&inode->i_lock);
+ }
out:
up_read(&cinode->lock_sem);
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH 2/3] smb/client: do not account EOF extension as allocation
2026-06-04 15:03 [PATCH 0/3] smb: client: fix i_blocks accounting for swapfile xfstests Huiwen He
2026-06-04 15:03 ` [PATCH 1/3] smb/client: update i_blocks after contiguous writes Huiwen He
@ 2026-06-04 15:03 ` Huiwen He
2026-06-04 15:03 ` [PATCH 3/3] smb/client: refresh allocation size after fallocate Huiwen He
2 siblings, 0 replies; 4+ messages in thread
From: Huiwen He @ 2026-06-04 15:03 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(nocache): 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 correctly rejects the sparse swapfile.
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 826d36ed13ec..e0aa71b87d27 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] 4+ messages in thread* [PATCH 3/3] smb/client: refresh allocation size after fallocate
2026-06-04 15:03 [PATCH 0/3] smb: client: fix i_blocks accounting for swapfile xfstests Huiwen He
2026-06-04 15:03 ` [PATCH 1/3] smb/client: update i_blocks after contiguous writes Huiwen He
2026-06-04 15:03 ` [PATCH 2/3] smb/client: do not account EOF extension as allocation Huiwen He
@ 2026-06-04 15:03 ` Huiwen He
2 siblings, 0 replies; 4+ messages in thread
From: Huiwen He @ 2026-06-04 15:03 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 fallocate extends EOF using FILE_END_OF_FILE_INFORMATION, but the
server may also update the file's AllocationSize. If the client keeps
the old cached i_blocks value after fallocate, the swapfile hole check
can still see:
i_blocks * 512 < i_size
and reject the file as sparse.
This shows up in xfstests generic/496 as:
generic/496 [not run] fallocated swap not supported here
After a successful EOF-extending fallocate, query FILE_ALL_INFORMATION on
the open handle and update i_blocks from the returned AllocationSize. If
the query fails, leave the fallocate result unchanged and force a later
attribute revalidation by setting cifsi->time to zero.
With this client-side refresh, and with a server that really allocates the
fallocated range, for example Samba configured with:
[scratch_share]
strict allocate = yes
generic/496 can pass the swapfile hole check.
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/client/smb2ops.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index d4875f9532b4..89230141b5dd 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -3698,8 +3698,23 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid,
cfile->fid.volatile_fid, cfile->pid, new_eof);
if (rc == 0) {
+ struct smb2_file_all_info file_inf;
+ u64 asize;
+ int qrc;
+
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;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread