From 84c41b731b019f1e5a97c21ac1a4a4c63b6be38f Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 26 Jul 2026 11:01:42 +0900 Subject: [PATCH] ksmbd: stabilize allocation size after buffered writes Ordinary opens initialize their allocation size from stat.blocks. Buffered writes can leave delayed allocation pending, so separate handles can cache different block counts for the same file. This makes generic/568 fail when a zero write used for fallocate emulation is followed by an overwrite of the same range. The first query can report the pre-writeback block count, while the second query reports the block count after delayed allocation is completed. Complete writeback and refresh the cached block count before returning allocation information for ordinary opens. Track client-specified allocation sizes separately so CREATE allocation contexts and FILE_ALLOCATION_INFORMATION continue to return the requested value. Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 38 ++++++++++++++++++++++++++++++-------- fs/smb/server/vfs_cache.h | 1 + 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 1ab0e37ec99c..ed6fb63d4a6d 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -4544,6 +4544,7 @@ int smb2_open(struct ksmbd_work *work) goto err_out1; } alloc_size = le64_to_cpu(az_req->AllocationSize); + fp->allocation_size_set = true; ksmbd_debug(SMB, "request smb2 create allocate size : %llu\n", alloc_size); @@ -6172,6 +6173,30 @@ static int get_file_basic_info(struct smb2_query_info_rsp *rsp, return 0; } +static int get_file_allocation_stat(struct ksmbd_file *fp, struct kstat *stat) +{ + int ret; + + /* + * Buffered writes can leave delayed allocation in a state where two + * consecutive queries report different block counts even when the + * second write only overwrites the first one. Complete writeback before + * reporting the filesystem allocation for an ordinary open. + */ + if (!fp->allocation_size_set) { + ret = file_write_and_wait(fp->filp); + if (ret) + return ret; + } + + ret = vfs_getattr(&fp->filp->f_path, stat, STATX_BASIC_STATS, + AT_STATX_SYNC_AS_STAT); + if (!ret && !fp->allocation_size_set) + fp->allocation_size = S_ISDIR(stat->mode) ? 0 : stat->blocks << 9; + + return ret; +} + static int get_file_standard_info(struct smb2_query_info_rsp *rsp, struct ksmbd_file *fp, void *rsp_org) { @@ -6180,8 +6205,7 @@ static int get_file_standard_info(struct smb2_query_info_rsp *rsp, struct kstat stat; int ret; - ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS, - AT_STATX_SYNC_AS_STAT); + ret = get_file_allocation_stat(fp, &stat); if (ret) return ret; @@ -6250,8 +6274,7 @@ static int get_file_all_info(struct ksmbd_work *work, return -EINVAL; } - ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS, - AT_STATX_SYNC_AS_STAT); + ret = get_file_allocation_stat(fp, &stat); if (ret) { kfree(filename); return ret; @@ -6549,8 +6572,7 @@ static int get_file_network_open_info(struct smb2_query_info_rsp *rsp, return -EACCES; } - ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS, - AT_STATX_SYNC_AS_STAT); + ret = get_file_allocation_stat(fp, &stat); if (ret) return ret; @@ -6683,8 +6705,7 @@ static int find_file_posix_info(struct smb2_query_info_rsp *rsp, return -EACCES; } - ret = vfs_getattr(&fp->filp->f_path, &stat, STATX_BASIC_STATS, - AT_STATX_SYNC_AS_STAT); + ret = get_file_allocation_stat(fp, &stat); if (ret) return ret; @@ -7817,6 +7838,7 @@ static int set_file_allocation_info(struct ksmbd_work *work, } fp->allocation_size = le64_to_cpu(file_alloc_info->AllocationSize); + fp->allocation_size_set = true; return 0; } diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h index 5fac4b0b419d..d80f379d4e12 100644 --- a/fs/smb/server/vfs_cache.h +++ b/fs/smb/server/vfs_cache.h @@ -117,6 +117,7 @@ struct ksmbd_file { bool is_nt_open; bool attrib_only; + bool allocation_size_set; char client_guid[16]; char create_guid[16];