From: Andrea Cervesato exfat's fallocate allocates clusters without updating valid_size, leaving them invisible to bmap(). Extend valid_size to i_size so that generic_swapfile_activate() can map all blocks. This bug has been found during a Linux Test Project regression test using swapon/swapoff testing suite. Signed-off-by: Andrea Cervesato Fixes: bf1797960c20 ("exfat: add fallocate FALLOC_FL_ALLOCATE_RANGE support") Link: https://patchwork.ozlabs.org/project/ltp/patch/20260608155241.270875-1-japo@linux.ibm.com/ --- bf1797960c20 - ("exfat: add fallocate FALLOC_FL_ALLOCATE_RANGE support") introduces fallocate() support in exfat, but omits the swap activate implementation, causing EINVAL inside Linux Test Project swapon/swapoff testing suites which are testing these syscalls with many filesystems, including exfat. This turned out to be a bug inside the kernel. The patch implements swap activate in order to ensure that exfat valid_size is correctly updated and the generic_swapfile_activate() doesn't fail with EINVAL due to valid_size=0, which is passed to bmap(). --- fs/exfat/exfat_fs.h | 1 + fs/exfat/file.c | 2 +- fs/exfat/inode.c | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index aff4dcd4e75a55296d536c19306813a566d6f0bb..18382c661d4358f8b80e6f7b5032a356a5905614 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -489,6 +489,7 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range); /* file.c */ extern const struct file_operations exfat_file_operations; +int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size); int __exfat_truncate(struct inode *inode); void exfat_truncate(struct inode *inode); int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 91e5511945d11b12658908dc6287fabd572d1d6a..12ed28c3ff896fb545bad58b47cccf808b48618d 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -642,7 +642,7 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) return blkdev_issue_flush(inode->i_sb->s_bdev); } -static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size) +int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size) { int err; loff_t pos; diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index 1ea4c740fef9ef6932a75e122e1b0130c85533b2..5a395851062e4a14b456fa79546541227d8427e6 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -534,6 +535,27 @@ int exfat_block_truncate_page(struct inode *inode, loff_t from) return block_truncate_page(inode->i_mapping, from, exfat_get_block); } +static int exfat_swap_activate(struct swap_info_struct *sis, + struct file *file, sector_t *span) +{ + struct inode *inode = file_inode(file); + struct exfat_inode_info *ei = EXFAT_I(inode); + int ret; + + /* + * exfat's fallocate allocates clusters without updating valid_size, + * leaving them invisible to bmap(). Extend valid_size to i_size so + * that generic_swapfile_activate() can map all blocks. + */ + if (ei->valid_size < i_size_read(inode)) { + ret = exfat_extend_valid_size(inode, i_size_read(inode)); + if (ret) + return ret; + } + + return generic_swapfile_activate(sis, file, span); +} + static const struct address_space_operations exfat_aops = { .dirty_folio = block_dirty_folio, .invalidate_folio = block_invalidate_folio, @@ -545,6 +567,7 @@ static const struct address_space_operations exfat_aops = { .direct_IO = exfat_direct_IO, .bmap = exfat_aop_bmap, .migrate_folio = buffer_migrate_folio, + .swap_activate = exfat_swap_activate, }; static inline unsigned long exfat_hash(loff_t i_pos) --- base-commit: e771677c937da5808f7b6c1f0e4a97ec1a84f8a8 change-id: 20260618-exfat_swap_activate-86e4f1a475ef Best regards, -- Andrea Cervesato