From a72692c5bcabb67d2320e1630192f78874a7c524 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 12 Jul 2026 00:11:39 +0900 Subject: [PATCH] ksmbd: support file level trim Advertise trim support through FS_SECTOR_SIZE_INFORMATION and handle FSCTL_FILE_LEVEL_TRIM requests. Process each trim range by punching a hole while keeping the file size unchanged, and report the number of ranges completed in the ioctl response. The trim operation uses the same byte-range lock handling as zero data for the affected part of the file. Signed-off-by: Namjae Jeon --- fs/smb/common/fscc.h | 15 ++++++++ fs/smb/server/smb2pdu.c | 82 ++++++++++++++++++++++++++++++++++++++++- fs/smb/server/vfs.c | 27 ++++++++++++++ fs/smb/server/vfs.h | 2 + 4 files changed, 125 insertions(+), 1 deletion(-) diff --git a/fs/smb/common/fscc.h b/fs/smb/common/fscc.h index 941db5a95564..e46d3379b779 100644 --- a/fs/smb/common/fscc.h +++ b/fs/smb/common/fscc.h @@ -202,6 +202,21 @@ struct file_zero_data_information { __le64 BeyondFinalZero; } __packed; +struct file_level_trim_range { + __le64 Offset; + __le64 Length; +} __packed; + +struct file_level_trim { + __le32 Key; + __le32 NumRanges; + struct file_level_trim_range Ranges[]; +} __packed; + +struct file_level_trim_output { + __le32 NumRangesProcessed; +} __packed; + /* * This level 18, although with struct with same name is different from cifs * level 0x107. Level 0x107 has an extra u64 between AccessFlags and diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index f57ba8baa63b..6c7f6e48b92b 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -6634,7 +6634,8 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, info->FSEffPhysicalBytesPerSectorForAtomicity = cpu_to_le32(sector_size); info->Flags = cpu_to_le32(SSINFO_FLAGS_ALIGNED_DEVICE | - SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE); + SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE | + SSINFO_FLAGS_TRIM_ENABLED); info->ByteOffsetForSectorAlignment = 0; info->ByteOffsetForPartitionAlignment = 0; rsp->OutputBufferLength = cpu_to_le32(28); @@ -9791,6 +9792,85 @@ int smb2_ioctl(struct ksmbd_work *work) } break; } + case FSCTL_FILE_LEVEL_TRIM: + { + struct file_level_trim *trim_req; + struct file_level_trim_output *trim_rsp; + struct ksmbd_file *fp; + u32 i, num_ranges; + + if (!test_tree_conn_flag(work->tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) { + ksmbd_debug(SMB, + "User does not have write permission\n"); + ret = -EACCES; + goto out; + } + + if (in_buf_len < offsetof(struct file_level_trim, Ranges)) { + ret = -EINVAL; + goto out; + } + + if (out_buf_len < sizeof(struct file_level_trim_output)) { + ret = -EINVAL; + goto out; + } + + trim_req = (struct file_level_trim *)buffer; + num_ranges = le32_to_cpu(trim_req->NumRanges); + if (num_ranges > + (in_buf_len - offsetof(struct file_level_trim, Ranges)) / + sizeof(struct file_level_trim_range)) { + ret = -EINVAL; + goto out; + } + + fp = ksmbd_lookup_fd_fast(work, id); + if (!fp) { + ret = -ENOENT; + goto out; + } + + if (!(fp->daccess & FILE_WRITE_DATA_LE)) { + ksmbd_fd_put(work, fp); + ret = -EACCES; + goto out; + } + + trim_rsp = (struct file_level_trim_output *)&rsp->Buffer[0]; + trim_rsp->NumRangesProcessed = 0; + for (i = 0; i < num_ranges; i++) { + loff_t off = le64_to_cpu(trim_req->Ranges[i].Offset); + loff_t len = le64_to_cpu(trim_req->Ranges[i].Length); + + if (off < 0 || len < 0) { + ret = -EINVAL; + break; + } + + if (!len) { + trim_rsp->NumRangesProcessed = + cpu_to_le32(i + 1); + continue; + } + + ret = ksmbd_vfs_trim_data(work, fp, off, len); + if (ret) + break; + trim_rsp->NumRangesProcessed = cpu_to_le32(i + 1); + } + ksmbd_fd_put(work, fp); + if (ret == -EAGAIN) { + rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT; + ret = 0; + goto out; + } else if (ret < 0) { + goto out; + } + + nbytes = sizeof(struct file_level_trim_output); + break; + } case FSCTL_QUERY_ALLOCATED_RANGES: if (in_buf_len < sizeof(struct file_allocated_range_buffer)) { ret = -EINVAL; diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index 12ab15b3bff9..33d47f9f1d69 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -953,6 +953,33 @@ int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp, return err; } +int ksmbd_vfs_trim_data(struct ksmbd_work *work, struct ksmbd_file *fp, + loff_t off, loff_t len) +{ + const struct cred *saved_cred; + int err; + + smb_break_all_levII_oplock(work, fp, 1); + if (!work->tcon->posix_extensions) { + loff_t size = i_size_read(file_inode(fp->filp)); + + if (off < size) { + err = check_lock_range(fp->filp, off, + min(off + len, size) - 1, + WRITE); + if (err) + return -EAGAIN; + } + } + + saved_cred = override_creds(fp->filp->f_cred); + err = vfs_fallocate(fp->filp, + FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, + off, len); + revert_creds(saved_cred); + return err; +} + int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length, struct file_allocated_range_buffer *ranges, unsigned int in_count, unsigned int *out_count) diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h index 2e8f9f2d95b0..022b78268a7e 100644 --- a/fs/smb/server/vfs.h +++ b/fs/smb/server/vfs.h @@ -133,6 +133,8 @@ int ksmbd_vfs_empty_dir(struct ksmbd_file *fp); void ksmbd_vfs_set_fadvise(struct file *filp, __le32 option); int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp, loff_t off, loff_t len); +int ksmbd_vfs_trim_data(struct ksmbd_work *work, struct ksmbd_file *fp, + loff_t off, loff_t len); struct file_allocated_range_buffer; int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length, struct file_allocated_range_buffer *ranges,