From ab74edaeb1ae7c7194e79007e6afdfe788111a3f Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 20 Aug 2026 12:00:23 +0200 Subject: [PATCH 1/6] erofs: Fix EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS default logic When NR_CPUS is less than 16, or when SMP is disabled, the default value of 16 is invalid. While actual configuration picks up a sensible and valid default (NR_CPUS or 1), "make savedefconfig" will still write a line like CONFIG_EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS=1 to the defconfig file, even if that matches the sensible default. Avoid needlessly enlarging the defconfig files, and reduce churn for updating them, by specifying valid defaults depending on SMP and NR_CPUS. While at it, make the prompt depend on SMP, as there is no point in asking the user about the maximum number of decompression streams if there is only one valid answer. Fixes: c9b47e6b23114e93 ("erofs: cap LZMA stream pool size") Signed-off-by: Geert Uytterhoeven Reviewed-by: Guenter Roeck Reviewed-by: Chao Yu Signed-off-by: Gao Xiang --- fs/erofs/Kconfig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/erofs/Kconfig b/fs/erofs/Kconfig index 2dfc313588d2..37495e95a78d 100644 --- a/fs/erofs/Kconfig +++ b/fs/erofs/Kconfig @@ -132,11 +132,13 @@ config EROFS_FS_ZIP_LZMA Say N if you want to disable LZMA compression support. config EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS - int "EROFS LZMA default maximum decompression streams" + int "EROFS LZMA default maximum decompression streams" if SMP depends on EROFS_FS_ZIP_LZMA range 1 NR_CPUS if SMP range 1 1 if !SMP - default 16 + default 16 if SMP && NR_CPUS >= 16 + default NR_CPUS if SMP + default 1 help By default EROFS allocates one LZMA decompression stream per CPU. Each stream can hold a dictionary of up to 8 MiB taken from the @@ -144,7 +146,7 @@ config EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS of memory. This caps the default; the lzma_streams module parameter still overrides it. - If unsure, keep the default of 16. + If unsure, keep the suggested default (16 or lower). config EROFS_FS_ZIP_DEFLATE bool "EROFS DEFLATE compressed data support" From 8ebf9589788575f8868c236d077dbe612395e084 Mon Sep 17 00:00:00 2001 From: Jingbo Xu Date: Thu, 20 Aug 2026 21:40:06 +0800 Subject: [PATCH 2/6] erofs: support SEEK_HOLE/SEEK_DATA in inode_share mode When inode_share is enabled, erofs_ishare_fops.llseek falls back to generic_file_llseek, which treats the whole file as data and always returns i_size for SEEK_HOLE, hiding real holes in sparse files. Switch it to erofs_file_llseek instead. For user files f_mapping->host is always the real erofs inode, so SEEK_HOLE/SEEK_DATA resolve the per-file on-disk layout via iomap_seek_hole()/iomap_seek_data(). Reviewed-by: Gao Xiang Signed-off-by: Jingbo Xu Reviewed-by: Chao Yu Signed-off-by: Gao Xiang --- fs/erofs/data.c | 2 +- fs/erofs/internal.h | 1 + fs/erofs/ishare.c | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/erofs/data.c b/fs/erofs/data.c index 9aa48c8d67d1..7b58f49b23a0 100644 --- a/fs/erofs/data.c +++ b/fs/erofs/data.c @@ -507,7 +507,7 @@ static int erofs_file_mmap_prepare(struct vm_area_desc *desc) #define erofs_file_mmap_prepare generic_file_readonly_mmap_prepare #endif -static loff_t erofs_file_llseek(struct file *file, loff_t offset, int whence) +loff_t erofs_file_llseek(struct file *file, loff_t offset, int whence) { struct inode *inode = file->f_mapping->host; const struct iomap_ops *ops = &erofs_iomap_ops; diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h index 57bd21859c65..0cd434d939ba 100644 --- a/fs/erofs/internal.h +++ b/fs/erofs/internal.h @@ -411,6 +411,7 @@ void *erofs_read_metabuf(struct erofs_buf *buf, struct super_block *sb, int erofs_map_dev(struct super_block *sb, struct erofs_map_dev *dev); int erofs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, u64 start, u64 len); +loff_t erofs_file_llseek(struct file *file, loff_t offset, int whence); int erofs_map_blocks(struct inode *inode, struct erofs_map_blocks *map); void erofs_onlinefolio_init(struct folio *folio); void erofs_onlinefolio_split(struct folio *folio); diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c index fa7d4112dec5..4c7be60565cb 100644 --- a/fs/erofs/ishare.c +++ b/fs/erofs/ishare.c @@ -156,7 +156,7 @@ static int erofs_ishare_fadvise(struct file *file, loff_t offset, const struct file_operations erofs_ishare_fops = { .open = erofs_ishare_file_open, - .llseek = generic_file_llseek, + .llseek = erofs_file_llseek, .read_iter = erofs_ishare_file_read_iter, .mmap = erofs_ishare_mmap, .release = erofs_ishare_file_release, From e8325399d6e18d2acc670999632a461d424611b9 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Fri, 21 Aug 2026 09:56:41 +0800 Subject: [PATCH 3/6] erofs: support splice() in inode_share mode erofs_ishare_fops routes everything that touches the page cache to the backing file in ->private_data: read_iter clones the iocb onto it, mmap does vma_set_file(), fadvise calls vfs_fadvise() on it. splice_read was left as filemap_splice_read(), which works on the user file's own mapping: it does init_sync_kiocb(&iocb, in), and filemap_get_pages() then takes iocb->ki_filp->f_mapping. So splice() and sendfile() fill the per-inode page cache rather than the shared one. The content is the same either way, since erofs_fill_inode() sets a_ops on that mapping too, which is why this went unnoticed. Two identical 8 MiB files under inode_share, reading one of them with splice(2) alone, in pages: before own 2048 shared 0 after own 0 shared 2048 "own" is cachestat(fd), which reports the file's own mapping; "shared" is mmap()+mincore(), which erofs_ishare_mmap() redirects to the backing file. Read through the backing file, as read_iter already does. Link: https://lore.kernel.org/all/b7dc7192-d586-45a2-bc4a-b41dc681c9bb@linux.alibaba.com/ Signed-off-by: Zhan Xusheng Reviewed-by: Jingbo Xu Reviewed-by: Chao Yu Signed-off-by: Gao Xiang --- fs/erofs/ishare.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c index 4c7be60565cb..372e9db8fd8c 100644 --- a/fs/erofs/ishare.c +++ b/fs/erofs/ishare.c @@ -148,6 +148,13 @@ static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma) return generic_file_readonly_mmap(file, vma); } +static ssize_t erofs_ishare_splice_read(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, + size_t len, unsigned int flags) +{ + return filemap_splice_read(in->private_data, ppos, pipe, len, flags); +} + static int erofs_ishare_fadvise(struct file *file, loff_t offset, loff_t len, int advice) { @@ -161,7 +168,7 @@ const struct file_operations erofs_ishare_fops = { .mmap = erofs_ishare_mmap, .release = erofs_ishare_file_release, .get_unmapped_area = thp_get_unmapped_area, - .splice_read = filemap_splice_read, + .splice_read = erofs_ishare_splice_read, .fadvise = erofs_ishare_fadvise, }; From c77516e1e6fe914eb74d5f7a8ba9851f4ce31867 Mon Sep 17 00:00:00 2001 From: Jingbo Xu Date: Fri, 21 Aug 2026 19:25:40 +0800 Subject: [PATCH 4/6] erofs: support large folios in inode_share mode erofs_fill_inode() calls mapping_set_large_folios() on each erofs file's own mapping, but in inode_share mode reads are routed to the shared inode's page cache instead, whose mapping never had large folio support enabled. Set up large folios for the shared inode's mapping as well. Signed-off-by: Jingbo Xu Reviewed-by: Gao Xiang Reviewed-by: Chao Yu Signed-off-by: Gao Xiang --- fs/erofs/ishare.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c index 372e9db8fd8c..a1cf2aff8d32 100644 --- a/fs/erofs/ishare.c +++ b/fs/erofs/ishare.c @@ -54,6 +54,7 @@ bool erofs_ishare_fill_inode(struct inode *inode) si->i_mapping->a_ops = aops; si->i_mode = 0444 | S_IFREG; si->i_size = inode->i_size; + mapping_set_large_folios(si->i_mapping); unlock_new_inode(si); } else { kfree(fp.opaque); From a7d097cf01301c5da37927c8f26123d006f0fd8a Mon Sep 17 00:00:00 2001 From: Nikhil Gurudasani Date: Sat, 22 Aug 2026 19:30:17 +0530 Subject: [PATCH 5/6] erofs: skip sufficiently large global buffers when resizing z_erofs_gbuf_nrpages is advanced only after every global buffer has been grown. If a resize fails after some buffers were enlarged, a retry revisits those enlarged buffers. Retrying the same size then returns -ENOMEM because alloc_pages_bulk() has no pages to add and the unchanged return value is treated as a failure. Retrying an intermediate size allocates a temporary pointer array smaller than gbuf->nrpages and copies more existing pointers than the array can hold. Skip buffers that already satisfy the request. Once all remaining buffers have caught up, advancing z_erofs_gbuf_nrpages again describes the guaranteed minimum size across the pool. Fixes: d6db47e571dc ("erofs: do not use pagepool in z_erofs_gbuf_growsize()") Cc: stable@vger.kernel.org # 6.10+ Signed-off-by: Nikhil Gurudasani Reviewed-by: Gao Xiang Reviewed-by: Chao Yu Signed-off-by: Gao Xiang --- fs/erofs/zutil.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/erofs/zutil.c b/fs/erofs/zutil.c index a0ce9226bc02..6b9bfa2ef3b6 100644 --- a/fs/erofs/zutil.c +++ b/fs/erofs/zutil.c @@ -79,6 +79,8 @@ int z_erofs_gbuf_growsize(unsigned int nrpages) for (i = 0; i < z_erofs_gbuf_count; ++i) { gbuf = &z_erofs_gbufpool[i]; + if (gbuf->nrpages >= nrpages) + continue; tmp_pages = kzalloc_objs(*tmp_pages, nrpages); if (!tmp_pages) goto out; From a7d28aa0e9b2c983b915d091f9596f0e3b253355 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Sun, 23 Aug 2026 22:23:05 +0800 Subject: [PATCH 6/6] erofs: simplify z_erofs_gbuf_growsize() - Use guard(mutex)() to manage gbuf_resize_mutex; - Nullify tmp_pages after successful allocation, so that the error path becomes clearer. Reviewed-by: Chao Yu Signed-off-by: Gao Xiang --- fs/erofs/zutil.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/fs/erofs/zutil.c b/fs/erofs/zutil.c index 6b9bfa2ef3b6..960cfe86eeb6 100644 --- a/fs/erofs/zutil.c +++ b/fs/erofs/zutil.c @@ -70,12 +70,10 @@ int z_erofs_gbuf_growsize(unsigned int nrpages) void *ptr, *old_ptr; int last, i, j; - mutex_lock(&gbuf_resize_mutex); + guard(mutex)(&gbuf_resize_mutex); /* avoid shrinking gbufs, since no idea how many fses rely on */ - if (nrpages <= z_erofs_gbuf_nrpages) { - mutex_unlock(&gbuf_resize_mutex); + if (nrpages <= z_erofs_gbuf_nrpages) return 0; - } for (i = 0; i < z_erofs_gbuf_count; ++i) { gbuf = &z_erofs_gbufpool[i]; @@ -89,8 +87,7 @@ int z_erofs_gbuf_growsize(unsigned int nrpages) tmp_pages[j] = gbuf->pages[j]; do { last = j; - j = alloc_pages_bulk(GFP_KERNEL, nrpages, - tmp_pages); + j = alloc_pages_bulk(GFP_KERNEL, nrpages, tmp_pages); if (last == j) goto out; } while (j != nrpages); @@ -101,24 +98,23 @@ int z_erofs_gbuf_growsize(unsigned int nrpages) spin_lock(&gbuf->lock); kfree(gbuf->pages); - gbuf->pages = tmp_pages; old_ptr = gbuf->ptr; + gbuf->pages = tmp_pages; gbuf->ptr = ptr; gbuf->nrpages = nrpages; spin_unlock(&gbuf->lock); - if (old_ptr) - vunmap(old_ptr); + vunmap(old_ptr); + tmp_pages = NULL; } z_erofs_gbuf_nrpages = nrpages; out: - if (i < z_erofs_gbuf_count && tmp_pages) { + if (unlikely(tmp_pages)) { for (j = 0; j < nrpages; ++j) if (tmp_pages[j] && (j >= gbuf->nrpages || tmp_pages[j] != gbuf->pages[j])) __free_page(tmp_pages[j]); kfree(tmp_pages); } - mutex_unlock(&gbuf_resize_mutex); return i < z_erofs_gbuf_count ? -ENOMEM : 0; }