diff options
| -rw-r--r-- | .mailmap | 1 | ||||
| -rw-r--r-- | Documentation/filesystems/erofs.rst | 7 | ||||
| -rw-r--r-- | MAINTAINERS | 4 | ||||
| -rw-r--r-- | fs/erofs/data.c | 34 | ||||
| -rw-r--r-- | fs/erofs/decompressor.c | 2 | ||||
| -rw-r--r-- | fs/erofs/fileio.c | 157 | ||||
| -rw-r--r-- | fs/erofs/internal.h | 12 | ||||
| -rw-r--r-- | fs/erofs/ishare.c | 4 | ||||
| -rw-r--r-- | fs/erofs/super.c | 56 |
9 files changed, 142 insertions, 135 deletions
@@ -321,6 +321,7 @@ Gao Xiang <xiang@kernel.org> <gaoxiang25@huawei.com> Gao Xiang <xiang@kernel.org> <hsiangkao@aol.com> Gao Xiang <xiang@kernel.org> <hsiangkao@linux.alibaba.com> Gao Xiang <xiang@kernel.org> <hsiangkao@redhat.com> +Gao Xiang <xiang@kernel.org> <xiang.gao@docker.com> Geliang Tang <geliang@kernel.org> <geliang.tang@linux.dev> Geliang Tang <geliang@kernel.org> <geliang.tang@suse.com> Geliang Tang <geliang@kernel.org> <geliangtang@xiaomi.com> diff --git a/Documentation/filesystems/erofs.rst b/Documentation/filesystems/erofs.rst index d301d9ac946a..02caf3b061fe 100644 --- a/Documentation/filesystems/erofs.rst +++ b/Documentation/filesystems/erofs.rst @@ -139,6 +139,9 @@ inode_share Enable inode page sharing for this filesystem. Inodes wi page cache. source=%s (For file-backed mounts) Specify the backing image as a path or as an already-opened file descriptor. +superblock_share (For file-backed mounts) Share the superblock when the same + backing file is mounted more than once with compatible + options. Remount is not allowed on shared superblocks. =================== ========================================================= File-backed mounts @@ -156,6 +159,10 @@ Only regular files are accepted as backing files; to mount an image that resides on a block device, use the traditional block device mount path instead. +When superblock_share is specified, multiple mounts of the same backing +file at the same fsoffset with compatible mount options can share a single +superblock. Remount is not allowed on shared superblocks. + Sysfs Entries ============= diff --git a/MAINTAINERS b/MAINTAINERS index 7a85e966d094..7b5cab72d851 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9715,14 +9715,14 @@ EROFS FILE SYSTEM M: Gao Xiang <xiang@kernel.org> M: Chao Yu <chao@kernel.org> R: Yue Hu <zbestahu@gmail.com> -R: Jeffle Xu <jefflexu@linux.alibaba.com> +R: Jingbo Xu <jefflexu@163.com> R: Sandeep Dhavale <dhavale@google.com> R: Hongbo Li <hongbohbli@tencent.com> R: Chunhai Guo <guochunhai@vivo.com> L: linux-erofs@lists.ozlabs.org S: Maintained W: https://erofs.docs.kernel.org -T: git git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git dev F: Documentation/ABI/testing/sysfs-fs-erofs F: Documentation/filesystems/erofs.rst F: fs/erofs/ diff --git a/fs/erofs/data.c b/fs/erofs/data.c index be63b89f0862..f09c152d5516 100644 --- a/fs/erofs/data.c +++ b/fs/erofs/data.c @@ -33,7 +33,10 @@ void *erofs_bread(struct erofs_buf *buf, erofs_off_t offset, bool need_kmap) if (buf->page) { folio = page_folio(buf->page); - if (folio_file_page(folio, index) != buf->page) + if (folio->mapping != buf->mapping) + /* the cached folio belongs to another address_space */ + folio = NULL; + else if (folio_file_page(folio, index) != buf->page) erofs_unmap_metabuf(buf); } if (!folio || !folio_contains(folio, index)) { @@ -278,12 +281,6 @@ void erofs_onlinefolio_end(struct folio *folio, int err, bool dirty) folio_end_read(folio, !(v & BIT(EROFS_ONLINEFOLIO_EIO))); } -struct erofs_iomap_iter_ctx { - struct page *page; - void *base; - struct inode *realinode; -}; - static int erofs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, unsigned int flags, struct iomap *iomap, struct iomap *srcmap) { @@ -305,6 +302,7 @@ static int erofs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, iomap->length = map.m_llen; iomap->flags = 0; iomap->addr = IOMAP_NULL_ADDR; + iomap->private = NULL; if (!(map.m_flags & EROFS_MAP_MAPPED)) { iomap->type = IOMAP_HOLE; return 0; @@ -319,6 +317,7 @@ static int erofs_iomap_begin(struct inode *inode, loff_t offset, loff_t length, if (ret) return ret; + iomap->private = mdev.m_dif; if (flags & IOMAP_DAX) iomap->dax_dev = mdev.m_dif->dax_dev; else @@ -393,14 +392,16 @@ int erofs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, */ static int erofs_read_folio(struct file *file, struct folio *folio) { - struct iomap_read_folio_ctx read_ctx = { - .ops = &iomap_bio_read_ops, - .cur_folio = folio, - }; bool need_iput; struct erofs_iomap_iter_ctx iter_ctx = { .realinode = erofs_real_inode(folio_inode(folio), &need_iput), }; + struct erofs_sb_info *sbi = EROFS_I_SB(iter_ctx.realinode); + struct iomap_read_folio_ctx read_ctx = { + .ops = erofs_is_fileio_mode(sbi) ? + &erofs_fileio_iomap_read_ops : &iomap_bio_read_ops, + .cur_folio = folio, + }; trace_erofs_read_folio(iter_ctx.realinode, folio, true); iomap_read_folio(&erofs_iomap_ops, &read_ctx, &iter_ctx); @@ -411,14 +412,16 @@ static int erofs_read_folio(struct file *file, struct folio *folio) static void erofs_readahead(struct readahead_control *rac) { - struct iomap_read_folio_ctx read_ctx = { - .ops = &iomap_bio_read_ops, - .rac = rac, - }; bool need_iput; struct erofs_iomap_iter_ctx iter_ctx = { .realinode = erofs_real_inode(rac->mapping->host, &need_iput), }; + struct erofs_sb_info *sbi = EROFS_I_SB(iter_ctx.realinode); + struct iomap_read_folio_ctx read_ctx = { + .ops = erofs_is_fileio_mode(sbi) ? + &erofs_fileio_iomap_read_ops : &iomap_bio_read_ops, + .rac = rac, + }; trace_erofs_readahead(iter_ctx.realinode, readahead_index(rac), readahead_count(rac), true); @@ -454,7 +457,6 @@ static ssize_t erofs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) return filemap_read(iocb, to, 0); } -/* for uncompressed (aligned) files and raw access for other files */ const struct address_space_operations erofs_aops = { .read_folio = erofs_read_folio, .readahead = erofs_readahead, diff --git a/fs/erofs/decompressor.c b/fs/erofs/decompressor.c index d387b27c4ee2..771d862afc2e 100644 --- a/fs/erofs/decompressor.c +++ b/fs/erofs/decompressor.c @@ -23,7 +23,7 @@ static int z_erofs_load_lz4_config(struct super_block *sb, if (!sbi->lz4.max_pclusterblks) { sbi->lz4.max_pclusterblks = 1; /* reserved case */ } else if (sbi->lz4.max_pclusterblks > - erofs_blknr(sb, Z_EROFS_PCLUSTER_MAX_SIZE)) { + Z_EROFS_PCLUSTER_MAX_SIZE >> sbi->blkszbits) { erofs_err(sb, "too large lz4 pclusterblks %u", sbi->lz4.max_pclusterblks); return -EINVAL; diff --git a/fs/erofs/fileio.c b/fs/erofs/fileio.c index ebb81a7ffd4c..c36a90a1a4cb 100644 --- a/fs/erofs/fileio.c +++ b/fs/erofs/fileio.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2024, Alibaba Cloud + * Copyright 2026 Docker, Inc. */ #include "internal.h" #include <trace/events/erofs.h> @@ -30,7 +31,8 @@ static void erofs_fileio_ki_complete(struct kiocb *iocb, long ret) if (!rq->bio.bi_end_io) { bio_for_each_folio_all(fi, &rq->bio) { DBG_BUGON(folio_test_uptodate(fi.folio)); - erofs_onlinefolio_end(fi.folio, ret < 0, false); + iomap_finish_folio_read(fi.folio, fi.offset, fi.length, + ret < 0 ? ret : 0); } } else if (ret < 0 && !rq->bio.bi_status) { rq->bio.bi_status = errno_to_blk_status(ret); @@ -64,137 +66,76 @@ static void erofs_fileio_rq_submit(struct erofs_fileio_rq *rq) kfree(rq); } -static struct erofs_fileio_rq *erofs_fileio_rq_alloc(struct erofs_map_dev *mdev) +static struct erofs_fileio_rq *erofs_fileio_rq_alloc(struct super_block *sb, + struct file *file) { struct erofs_fileio_rq *rq = kzalloc_obj(*rq, GFP_KERNEL | __GFP_NOFAIL); bio_init(&rq->bio, NULL, rq->bvecs, ARRAY_SIZE(rq->bvecs), REQ_OP_READ); - rq->iocb.ki_filp = mdev->m_dif->file; - rq->sb = mdev->m_sb; + rq->iocb.ki_filp = file; + rq->sb = sb; refcount_set(&rq->ref, 2); return rq; } struct bio *erofs_fileio_bio_alloc(struct erofs_map_dev *mdev) { - return &erofs_fileio_rq_alloc(mdev)->bio; + return &erofs_fileio_rq_alloc(mdev->m_sb, mdev->m_dif->file)->bio; } void erofs_fileio_submit_bio(struct bio *bio) { - return erofs_fileio_rq_submit(container_of(bio, struct erofs_fileio_rq, - bio)); + erofs_fileio_rq_submit(container_of(bio, struct erofs_fileio_rq, bio)); } -static int erofs_fileio_scan_folio(struct erofs_fileio *io, - struct inode *inode, struct folio *folio) +static void erofs_fileio_iomap_submit_read(const struct iomap_iter *iter, + struct iomap_read_folio_ctx *ctx) { - struct erofs_map_blocks *map = &io->map; - unsigned int cur = 0, end = folio_size(folio), len, attached = 0; - loff_t pos = folio_pos(folio), ofs; - int err = 0; - - erofs_onlinefolio_init(folio); - while (cur < end) { - if (!in_range(pos + cur, map->m_la, map->m_llen)) { - map->m_la = pos + cur; - map->m_llen = end - cur; - err = erofs_map_blocks(inode, map); - if (err) - break; - } - - ofs = folio_pos(folio) + cur - map->m_la; - len = min_t(loff_t, map->m_llen - ofs, end - cur); - if (map->m_flags & EROFS_MAP_META) { - struct erofs_buf buf = __EROFS_BUF_INITIALIZER; - void *src; - - src = erofs_read_metabuf(&buf, inode->i_sb, - map->m_pa + ofs, erofs_inode_in_metabox(inode)); - if (IS_ERR(src)) { - err = PTR_ERR(src); - break; - } - memcpy_to_folio(folio, cur, src, len); - erofs_put_metabuf(&buf); - } else if (!(map->m_flags & EROFS_MAP_MAPPED)) { - folio_zero_segment(folio, cur, cur + len); - attached = 0; - } else { - if (io->rq && (map->m_pa + ofs != io->dev.m_pa || - map->m_deviceid != io->dev.m_deviceid)) { -io_retry: - erofs_fileio_rq_submit(io->rq); - io->rq = NULL; - } - - if (!io->rq) { - io->dev = (struct erofs_map_dev) { - .m_pa = io->map.m_pa + ofs, - .m_deviceid = io->map.m_deviceid, - }; - err = erofs_map_dev(inode->i_sb, &io->dev); - if (err) - break; - io->rq = erofs_fileio_rq_alloc(&io->dev); - io->rq->bio.bi_iter.bi_sector = - (io->dev.m_dif->fsoff + io->dev.m_pa) >> 9; - attached = 0; - } - if (!bio_add_folio(&io->rq->bio, folio, len, cur)) - goto io_retry; - if (!attached++) - erofs_onlinefolio_split(folio); - io->dev.m_pa += len; - } - cur += len; - } - erofs_onlinefolio_end(folio, err, false); - return err; + erofs_fileio_rq_submit((struct erofs_fileio_rq *)ctx->read_ctx); + ctx->read_ctx = NULL; } -static int erofs_fileio_read_folio(struct file *file, struct folio *folio) +static int erofs_fileio_iomap_read_folio_range(const struct iomap_iter *iter, + struct iomap_read_folio_ctx *ctx, size_t plen) { - bool need_iput; - struct inode *realinode = erofs_real_inode(folio_inode(folio), &need_iput); - struct erofs_fileio io = {}; - int err; - - trace_erofs_read_folio(realinode, folio, true); - err = erofs_fileio_scan_folio(&io, realinode, folio); - erofs_fileio_rq_submit(io.rq); - if (need_iput) - iput(realinode); - return err; + struct erofs_iomap_iter_ctx *iter_ctx = iter->private; + struct folio *folio = ctx->cur_folio; + struct erofs_fileio_rq *rq = ctx->read_ctx; + sector_t sector = iomap_sector(&iter->iomap, iter->pos); + + if (!rq || bio_end_sector(&rq->bio) != sector || + !bio_add_folio(&rq->bio, folio, plen, + offset_in_folio(folio, iter->pos))) { + struct super_block *sb = iter_ctx->realinode->i_sb; + struct erofs_device_info *dif = iter->iomap.private; + + /* submit the existing range if there was one. */ + if (ctx->read_ctx) + erofs_fileio_iomap_submit_read(iter, ctx); + rq = erofs_fileio_rq_alloc(sb, dif->file); + rq->bio.bi_iter.bi_sector = sector; + bio_add_folio_nofail(&rq->bio, folio, plen, + offset_in_folio(folio, iter->pos)); + ctx->read_ctx = rq; + ctx->read_ctx_file_offset = iter->pos; + } + return 0; } -static void erofs_fileio_readahead(struct readahead_control *rac) +const struct iomap_read_ops erofs_fileio_iomap_read_ops = { + .read_folio_range = erofs_fileio_iomap_read_folio_range, + .submit_read = erofs_fileio_iomap_submit_read, +}; + +static void erofs_fileio_meta_endio(struct bio *bio) { - bool need_iput; - struct inode *realinode = erofs_real_inode(rac->mapping->host, &need_iput); - struct erofs_fileio io = {}; - struct folio *folio; - int err; + bool uptodate = !bio->bi_status; + struct folio_iter fi; - trace_erofs_readahead(realinode, readahead_index(rac), - readahead_count(rac), true); - while ((folio = readahead_folio(rac))) { - err = erofs_fileio_scan_folio(&io, realinode, folio); - if (err && err != -EINTR) - erofs_err(realinode->i_sb, "readahead error at folio %lu @ nid %llu", - folio->index, EROFS_I(realinode)->nid); - } - erofs_fileio_rq_submit(io.rq); - if (need_iput) - iput(realinode); + bio_for_each_folio_all(fi, bio) + folio_end_read(fi.folio, uptodate); } -const struct address_space_operations erofs_fileio_aops = { - .read_folio = erofs_fileio_read_folio, - .readahead = erofs_fileio_readahead, -}; - int erofs_read_meta_folio(struct file *file, struct folio *folio) { struct erofs_fileio io = { @@ -207,10 +148,10 @@ int erofs_read_meta_folio(struct file *file, struct folio *folio) if (err) return err; - io.rq = erofs_fileio_rq_alloc(&io.dev); + io.rq = erofs_fileio_rq_alloc(inode->i_sb, io.dev.m_dif->file); + io.rq->bio.bi_end_io = erofs_fileio_meta_endio; io.rq->bio.bi_iter.bi_sector = (io.dev.m_dif->fsoff + io.dev.m_pa) >> 9; - erofs_onlinefolio_init(folio); bio_add_folio_nofail(&io.rq->bio, folio, folio_size(folio), 0); erofs_fileio_rq_submit(io.rq); return 0; diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h index ab817091bd29..8363c0c051f6 100644 --- a/fs/erofs/internal.h +++ b/fs/erofs/internal.h @@ -150,6 +150,7 @@ struct erofs_sb_info { #define EROFS_MOUNT_DAX_NEVER 0x00000080 #define EROFS_MOUNT_DIRECT_IO 0x00000100 #define EROFS_MOUNT_INODE_SHARE 0x00000200 +#define EROFS_MOUNT_SUPERBLOCK_SHARE 0x00000400 #define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option) #define set_opt(opt, option) ((opt)->mount_opt |= EROFS_MOUNT_##option) @@ -379,7 +380,6 @@ struct erofs_map_dev { extern const struct super_operations erofs_sops; extern const struct address_space_operations erofs_aops; -extern const struct address_space_operations erofs_fileio_aops; extern const struct address_space_operations z_erofs_aops; extern const struct inode_operations erofs_generic_iops; @@ -391,7 +391,14 @@ extern const struct file_operations erofs_file_fops; extern const struct file_operations erofs_dir_fops; extern const struct file_operations erofs_ishare_fops; +struct erofs_iomap_iter_ctx { + struct page *page; + void *base; + struct inode *realinode; +}; + extern const struct iomap_ops z_erofs_iomap_report_ops; +extern const struct iomap_read_ops erofs_fileio_iomap_read_ops; int erofs_setup_managed_cache(struct super_block *sb); #ifdef CONFIG_EROFS_FS_BACKED_BY_FILE @@ -449,9 +456,6 @@ erofs_get_aops(struct inode *realinode) "EXPERIMENTAL EROFS subpage compressed block support in use. Use at your own risk!"); return &z_erofs_aops; } - if (IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE) && - erofs_is_fileio_mode(EROFS_SB(realinode->i_sb))) - return &erofs_fileio_aops; return &erofs_aops; } diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c index a1cf2aff8d32..db32d16517a5 100644 --- a/fs/erofs/ishare.c +++ b/fs/erofs/ishare.c @@ -54,11 +54,13 @@ 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; + si->i_blkbits = inode->i_blkbits; mapping_set_large_folios(si->i_mapping); unlock_new_inode(si); } else { kfree(fp.opaque); - if (!si || aops != si->i_mapping->a_ops) { + if (!si || aops != si->i_mapping->a_ops || + si->i_blkbits != inode->i_blkbits) { iput(si); return false; } diff --git a/fs/erofs/super.c b/fs/erofs/super.c index 0967aace9f7f..16fa3b853f1b 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -386,7 +386,7 @@ static void erofs_default_options(struct erofs_sb_info *sbi) enum { Opt_user_xattr, Opt_acl, Opt_cache_strategy, Opt_dax, Opt_dax_enum, Opt_device, Opt_domain_id, Opt_directio, Opt_fsoffset, Opt_inode_share, - Opt_source, + Opt_source, Opt_superblock_share, }; static const struct constant_table erofs_param_cache_strategy[] = { @@ -415,6 +415,7 @@ static const struct fs_parameter_spec erofs_fs_parameters[] = { fsparam_u64("fsoffset", Opt_fsoffset), fsparam_flag("inode_share", Opt_inode_share), fsparam_file_or_string("source", Opt_source), + fsparam_flag("superblock_share", Opt_superblock_share), {} }; @@ -560,6 +561,12 @@ static int erofs_fc_parse_param(struct fs_context *fc, else set_opt(&sbi->opt, INODE_SHARE); break; + case Opt_superblock_share: + if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE)) + errorfc(fc, "%s option not supported", erofs_fs_parameters[opt].name); + else + set_opt(&sbi->opt, SUPERBLOCK_SHARE); + break; case Opt_source: return erofs_fc_parse_source(fc, param); } @@ -667,6 +674,16 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) errorfc(fc, "FSDAX is not allowed when inode_share is on"); return -EINVAL; } + if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) && + test_opt(&sbi->opt, INODE_SHARE)) { + errorfc(fc, "superblock_share is not allowed when inode_share is on"); + return -EINVAL; + } + if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) && + sbi->devs->extra_devices) { + errorfc(fc, "superblock_share does not support extra devices"); + return -EINVAL; + } sbi->blkszbits = PAGE_SHIFT; if (erofs_is_fileio_mode(sbi)) { @@ -794,6 +811,35 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) return 0; } +/* + * The function is used only with a sbi that has SUPERBLOCK_SHARE set, + * so that the memcmp later ensures new_sbi is also shareable. + */ +static int erofs_sb_share_test_super(struct super_block *sb, + struct fs_context *fc) +{ + struct erofs_sb_info *sbi = EROFS_SB(sb); + struct erofs_sb_info *new_sbi = fc->s_fs_info; + + if (sb->s_iflags & SB_I_RETIRED) + return 0; + if (!sbi->dif0.file || !new_sbi->dif0.file) + return 0; + return file_inode(sbi->dif0.file) == file_inode(new_sbi->dif0.file) && + sbi->dif0.fsoff == new_sbi->dif0.fsoff && + !memcmp(&sbi->opt, &new_sbi->opt, sizeof(sbi->opt)); +} + +static int erofs_file_get_tree(struct fs_context *fc) +{ + struct erofs_sb_info *sbi = fc->s_fs_info; + + if (!test_opt(&sbi->opt, SUPERBLOCK_SHARE)) + return get_tree_nodev(fc, erofs_fc_fill_super); + + return get_tree_super(fc, erofs_sb_share_test_super, erofs_fc_fill_super); +} + static int erofs_fc_get_tree(struct fs_context *fc) { struct erofs_sb_info *sbi = fc->s_fs_info; @@ -809,7 +855,7 @@ static int erofs_fc_get_tree(struct fs_context *fc) errorfc(fc, "source is unsupported"); return -EINVAL; } - return get_tree_nodev(fc, erofs_fc_fill_super); + return erofs_file_get_tree(fc); } ret = get_tree_bdev_flags(fc, erofs_fc_fill_super, @@ -827,7 +873,7 @@ static int erofs_fc_get_tree(struct fs_context *fc) if (S_ISREG(file_inode(sbi->dif0.file)->i_mode) && sbi->dif0.file->f_mapping->a_ops->read_folio) - return get_tree_nodev(fc, erofs_fc_fill_super); + return erofs_file_get_tree(fc); } return ret; } @@ -840,6 +886,10 @@ static int erofs_fc_reconfigure(struct fs_context *fc) DBG_BUGON(!sb_rdonly(sb)); + /* Shared superblocks must not be reconfigured. */ + if (test_opt(&sbi->opt, SUPERBLOCK_SHARE)) + return -EBUSY; + if (new_sbi->domain_id) erofs_info(sb, "ignoring reconfiguration for domain_id."); |
