summaryrefslogtreecommitdiffstats
path: root/fs/erofs/data.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/erofs/data.c')
-rw-r--r--fs/erofs/data.c534
1 files changed, 534 insertions, 0 deletions
diff --git a/fs/erofs/data.c b/fs/erofs/data.c
new file mode 100644
index 000000000..0885b1f2f
--- /dev/null
+++ b/fs/erofs/data.c
@@ -0,0 +1,534 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2017-2018 HUAWEI, Inc.
+ * https://www.huawei.com/
+ * Copyright (C) 2021, Alibaba Cloud
+ */
+#include "internal.h"
+#include <linux/filelock.h>
+#include <linux/sched/mm.h>
+#include <trace/events/erofs.h>
+
+void erofs_unmap_metabuf(struct erofs_buf *buf)
+{
+ if (!buf->base)
+ return;
+ kunmap_local(buf->base);
+ buf->base = NULL;
+}
+
+void erofs_put_metabuf(struct erofs_buf *buf)
+{
+ if (!buf->page)
+ return;
+ erofs_unmap_metabuf(buf);
+ folio_put(page_folio(buf->page));
+ buf->page = NULL;
+}
+
+void *erofs_bread(struct erofs_buf *buf, erofs_off_t offset, bool need_kmap)
+{
+ pgoff_t index = (buf->off + offset) >> PAGE_SHIFT;
+ struct folio *folio = NULL;
+
+ if (buf->page) {
+ folio = page_folio(buf->page);
+ if (folio_file_page(folio, index) != buf->page)
+ erofs_unmap_metabuf(buf);
+ }
+ if (!folio || !folio_contains(folio, index)) {
+ erofs_put_metabuf(buf);
+ folio = read_cache_folio(buf->mapping, index,
+ buf->mc ? erofs_read_meta_folio : NULL, NULL);
+ if (IS_ERR(folio))
+ return folio;
+ }
+ buf->page = folio_file_page(folio, index);
+ if (!need_kmap)
+ return NULL;
+ if (!buf->base)
+ buf->base = kmap_local_page(buf->page);
+ return buf->base + (offset & ~PAGE_MASK);
+}
+
+int erofs_init_metabuf(struct erofs_buf *buf, struct super_block *sb,
+ bool in_metabox)
+{
+ struct erofs_sb_info *sbi = EROFS_SB(sb);
+
+ buf->mc = false;
+ if (in_metabox) {
+ if (unlikely(!sbi->metabox_inode))
+ return -EFSCORRUPTED;
+ buf->mapping = sbi->metabox_inode->i_mapping;
+ return 0;
+ }
+ if (erofs_is_fileio_mode(sbi)) {
+ buf->mapping = sbi->managed_cache->i_mapping;
+ buf->mc = true;
+ } else {
+ buf->off = sbi->dif0.fsoff;
+ buf->mapping = sb->s_bdev->bd_mapping;
+ }
+ return 0;
+}
+
+void *erofs_read_metabuf(struct erofs_buf *buf, struct super_block *sb,
+ erofs_off_t offset, bool in_metabox)
+{
+ int err;
+
+ err = erofs_init_metabuf(buf, sb, in_metabox);
+ if (err)
+ return ERR_PTR(err);
+ return erofs_bread(buf, offset, true);
+}
+
+static int erofs_map_chunks(struct inode *inode, struct erofs_map_blocks *map)
+{
+ struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
+ struct super_block *sb = inode->i_sb;
+ struct erofs_inode *vi = EROFS_I(inode);
+ struct erofs_inode_chunk_index *idx;
+ unsigned int unit = vi->chunkformat & EROFS_CHUNK_FORMAT_INDEXES ?
+ sizeof(*idx) : EROFS_BLOCK_MAP_ENTRY_SIZE;
+ erofs_blk_t addrmask = (vi->chunkformat & EROFS_CHUNK_FORMAT_48BIT) ?
+ BIT_ULL(48) - 1 : BIT_ULL(32) - 1;
+ u64 nr = map->m_la >> vi->chunkbits, chunksize = 1ULL << vi->chunkbits;
+ erofs_off_t pos = ALIGN(erofs_iloc(inode) + vi->inode_isize +
+ vi->xattr_isize, unit) + unit * nr;
+ /* m_llen will be clamped to EOF in the end */
+ erofs_off_t endpos = round_up(pos + 1, sb->s_blocksize);
+ u64 last, addr;
+
+ idx = erofs_read_metabuf(&buf, sb, pos, erofs_inode_in_metabox(inode));
+ if (IS_ERR(idx))
+ return PTR_ERR(idx);
+
+ map->m_la = nr << vi->chunkbits;
+ map->m_llen = 0;
+ nr = 0;
+ do {
+ if (unit == EROFS_BLOCK_MAP_ENTRY_SIZE) {
+ addr = le32_to_cpu(((__le32 *)idx)[nr]);
+ if (addr == (u32)EROFS_NULL_ADDR)
+ addr = EROFS_NULL_ADDR;
+ } else {
+ addr = (((u64)le16_to_cpu(idx[nr].startblk_hi) << 32) |
+ le32_to_cpu(idx[nr].startblk_lo)) & addrmask;
+ if (addr ^ (EROFS_NULL_ADDR & addrmask))
+ addr |= (u64)(le16_to_cpu(idx[nr].device_id) &
+ EROFS_SB(sb)->device_id_mask) << 48;
+ else
+ addr = EROFS_NULL_ADDR;
+ }
+ if (!nr) {
+ last = addr;
+ continue;
+ }
+ /* expand and account the prior chunk here */
+ map->m_llen += chunksize;
+ if (last != EROFS_NULL_ADDR)
+ last += erofs_blknr(sb, chunksize);
+ } while (addr == last && pos + (++nr) * unit < endpos);
+
+ if (last != EROFS_NULL_ADDR) {
+ map->m_pa = erofs_pos(sb, last & addrmask) - map->m_llen;
+ map->m_deviceid = last >> 48;
+ map->m_flags = EROFS_MAP_MAPPED;
+ }
+ if (addr == last)
+ map->m_llen += chunksize;
+ map->m_llen = min_t(erofs_off_t, map->m_llen,
+ round_up(inode->i_size - map->m_la, sb->s_blocksize));
+ erofs_put_metabuf(&buf);
+ return 0;
+}
+
+int erofs_map_blocks(struct inode *inode, struct erofs_map_blocks *map)
+{
+ struct super_block *sb = inode->i_sb;
+ struct erofs_inode *vi = EROFS_I(inode);
+ bool tailinline = (vi->datalayout == EROFS_INODE_FLAT_INLINE);
+ erofs_off_t pos;
+ int err = 0;
+
+ trace_erofs_map_blocks_enter(inode, map, 0);
+ map->m_deviceid = 0;
+ map->m_flags = 0;
+ if (map->m_la >= inode->i_size)
+ goto out;
+ if (vi->datalayout == EROFS_INODE_CHUNK_BASED) {
+ err = erofs_map_chunks(inode, map);
+ } else if (tailinline || vi->startblk != EROFS_NULL_ADDR) {
+ pos = erofs_pos(sb, erofs_iblks(inode) - tailinline);
+ map->m_flags = EROFS_MAP_MAPPED;
+ if (map->m_la < pos) {
+ map->m_pa = erofs_pos(sb, vi->startblk) + map->m_la;
+ map->m_llen = pos - map->m_la;
+ } else {
+ map->m_pa = erofs_iloc(inode) + vi->inode_isize +
+ vi->xattr_isize + erofs_blkoff(sb, map->m_la);
+ map->m_llen = inode->i_size - map->m_la;
+ map->m_flags |= EROFS_MAP_META;
+ if (erofs_blkoff(sb, map->m_pa) + map->m_llen >
+ sb->s_blocksize) {
+ erofs_err(sb, "inline data across blocks @ nid %llu", vi->nid);
+ return -EFSCORRUPTED;
+ }
+ }
+ }
+out:
+ map->m_plen = err ? 0 : map->m_llen;
+ trace_erofs_map_blocks_exit(inode, map, 0, err);
+ return err;
+}
+
+static void erofs_fill_from_devinfo(struct erofs_map_dev *map,
+ struct super_block *sb, struct erofs_device_info *dif)
+{
+ map->m_sb = sb;
+ map->m_dif = dif;
+ map->m_bdev = NULL;
+ if (dif->file && S_ISBLK(file_inode(dif->file)->i_mode))
+ map->m_bdev = file_bdev(dif->file);
+}
+
+int erofs_map_dev(struct super_block *sb, struct erofs_map_dev *map)
+{
+ struct erofs_dev_context *devs = EROFS_SB(sb)->devs;
+ struct erofs_device_info *dif;
+ erofs_off_t startoff;
+ int id;
+
+ erofs_fill_from_devinfo(map, sb, &EROFS_SB(sb)->dif0);
+ map->m_bdev = sb->s_bdev; /* use s_bdev for the primary device */
+ if (map->m_deviceid) {
+ down_read(&devs->rwsem);
+ dif = idr_find(&devs->tree, map->m_deviceid - 1);
+ if (!dif) {
+ up_read(&devs->rwsem);
+ return -ENODEV;
+ }
+ if (devs->flatdev) {
+ map->m_pa += erofs_pos(sb, dif->uniaddr);
+ up_read(&devs->rwsem);
+ return 0;
+ }
+ erofs_fill_from_devinfo(map, sb, dif);
+ up_read(&devs->rwsem);
+ } else if (devs->extra_devices && !devs->flatdev) {
+ down_read(&devs->rwsem);
+ idr_for_each_entry(&devs->tree, dif, id) {
+ if (!dif->uniaddr)
+ continue;
+
+ startoff = erofs_pos(sb, dif->uniaddr);
+ if (map->m_pa >= startoff &&
+ map->m_pa < startoff + erofs_pos(sb, dif->blocks)) {
+ map->m_pa -= startoff;
+ erofs_fill_from_devinfo(map, sb, dif);
+ break;
+ }
+ }
+ up_read(&devs->rwsem);
+ }
+ return 0;
+}
+
+/*
+ * bit 30: I/O error occurred on this folio
+ * bit 29: CPU has dirty data in D-cache (needs aliasing handling);
+ * bit 0 - 29: remaining parts to complete this folio
+ */
+#define EROFS_ONLINEFOLIO_EIO 30
+#define EROFS_ONLINEFOLIO_DIRTY 29
+
+void erofs_onlinefolio_init(struct folio *folio)
+{
+ union {
+ atomic_t o;
+ void *v;
+ } u = { .o = ATOMIC_INIT(1) };
+
+ folio->private = u.v; /* valid only if file-backed folio is locked */
+}
+
+void erofs_onlinefolio_split(struct folio *folio)
+{
+ atomic_inc((atomic_t *)&folio->private);
+}
+
+void erofs_onlinefolio_end(struct folio *folio, int err, bool dirty)
+{
+ int orig, v;
+
+ do {
+ orig = atomic_read((atomic_t *)&folio->private);
+ DBG_BUGON(orig <= 0);
+ v = dirty << EROFS_ONLINEFOLIO_DIRTY;
+ v |= (orig - 1) | (!!err << EROFS_ONLINEFOLIO_EIO);
+ } while (atomic_cmpxchg((atomic_t *)&folio->private, orig, v) != orig);
+
+ if (v & (BIT(EROFS_ONLINEFOLIO_DIRTY) - 1))
+ return;
+ folio->private = 0;
+ if (v & BIT(EROFS_ONLINEFOLIO_DIRTY))
+ flush_dcache_folio(folio);
+ 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)
+{
+ struct iomap_iter *iter = container_of(iomap, struct iomap_iter, iomap);
+ struct erofs_iomap_iter_ctx *ctx = iter->private;
+ struct inode *realinode = ctx ? ctx->realinode : inode;
+ struct super_block *sb = realinode->i_sb;
+ struct erofs_map_blocks map;
+ struct erofs_map_dev mdev;
+ int ret;
+
+ map.m_la = offset;
+ map.m_llen = length;
+ ret = erofs_map_blocks(realinode, &map);
+ if (ret < 0)
+ return ret;
+
+ iomap->offset = map.m_la;
+ iomap->length = map.m_llen;
+ iomap->flags = 0;
+ iomap->addr = IOMAP_NULL_ADDR;
+ if (!(map.m_flags & EROFS_MAP_MAPPED)) {
+ iomap->type = IOMAP_HOLE;
+ return 0;
+ }
+
+ if (!(map.m_flags & EROFS_MAP_META) || !erofs_inode_in_metabox(realinode)) {
+ mdev = (struct erofs_map_dev) {
+ .m_deviceid = map.m_deviceid,
+ .m_pa = map.m_pa,
+ };
+ ret = erofs_map_dev(sb, &mdev);
+ if (ret)
+ return ret;
+
+ if (flags & IOMAP_DAX)
+ iomap->dax_dev = mdev.m_dif->dax_dev;
+ else
+ iomap->bdev = mdev.m_bdev;
+ iomap->addr = mdev.m_dif->fsoff + mdev.m_pa;
+ if (flags & IOMAP_DAX)
+ iomap->addr += mdev.m_dif->dax_part_off;
+ }
+
+ if (map.m_flags & EROFS_MAP_META) {
+ iomap->type = IOMAP_INLINE;
+ /* read context should read the inlined data */
+ if (ctx) {
+ struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
+ void *ptr;
+
+ ptr = erofs_read_metabuf(&buf, sb, map.m_pa,
+ erofs_inode_in_metabox(realinode));
+ if (IS_ERR(ptr))
+ return PTR_ERR(ptr);
+ iomap->inline_data = ptr;
+ ctx->page = buf.page;
+ ctx->base = buf.base;
+ }
+ } else {
+ iomap->type = IOMAP_MAPPED;
+ }
+ return 0;
+}
+
+static int erofs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
+ ssize_t written, unsigned int flags, struct iomap *iomap)
+{
+ struct iomap_iter *iter = container_of(iomap, struct iomap_iter, iomap);
+ struct erofs_iomap_iter_ctx *ctx = iter->private;
+
+ if (ctx && ctx->base) {
+ struct erofs_buf buf = {
+ .page = ctx->page,
+ .base = ctx->base,
+ };
+
+ DBG_BUGON(iomap->type != IOMAP_INLINE);
+ erofs_put_metabuf(&buf);
+ ctx->base = NULL;
+ }
+ return written;
+}
+
+static DEFINE_IOMAP_ITER_NEXT_END(erofs_iomap_next, erofs_iomap_begin,
+ erofs_iomap_end);
+
+static const struct iomap_ops erofs_iomap_ops = {
+ .iomap_next = erofs_iomap_next,
+};
+
+int erofs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
+ u64 start, u64 len)
+{
+ if (erofs_inode_is_data_compressed(EROFS_I(inode)->datalayout)) {
+ if (!IS_ENABLED(CONFIG_EROFS_FS_ZIP))
+ return -EOPNOTSUPP;
+ return iomap_fiemap(inode, fieinfo, start, len,
+ &z_erofs_iomap_report_ops);
+ }
+ return iomap_fiemap(inode, fieinfo, start, len, &erofs_iomap_ops);
+}
+
+/*
+ * since we dont have write or truncate flows, so no inode
+ * locking needs to be held at the moment.
+ */
+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),
+ };
+
+ trace_erofs_read_folio(iter_ctx.realinode, folio, true);
+ iomap_read_folio(&erofs_iomap_ops, &read_ctx, &iter_ctx);
+ if (need_iput)
+ iput(iter_ctx.realinode);
+ return 0;
+}
+
+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),
+ };
+
+ trace_erofs_readahead(iter_ctx.realinode, readahead_index(rac),
+ readahead_count(rac), true);
+ iomap_readahead(&erofs_iomap_ops, &read_ctx, &iter_ctx);
+ if (need_iput)
+ iput(iter_ctx.realinode);
+}
+
+static sector_t erofs_bmap(struct address_space *mapping, sector_t block)
+{
+ return iomap_bmap(mapping, block, &erofs_iomap_ops);
+}
+
+static ssize_t erofs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
+{
+ struct inode *inode = file_inode(iocb->ki_filp);
+
+ /* no need taking (shared) inode lock since it's a ro filesystem */
+ if (!iov_iter_count(to))
+ return 0;
+
+ if (IS_ENABLED(CONFIG_FS_DAX) && IS_DAX(inode))
+ return dax_iomap_rw(iocb, to, &erofs_iomap_ops);
+
+ if ((iocb->ki_flags & IOCB_DIRECT) && inode->i_sb->s_bdev) {
+ struct erofs_iomap_iter_ctx iter_ctx = {
+ .realinode = inode,
+ };
+
+ return iomap_dio_rw(iocb, to, &erofs_iomap_ops,
+ NULL, 0, &iter_ctx, 0);
+ }
+ 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,
+ .bmap = erofs_bmap,
+ .direct_IO = noop_direct_IO,
+ .release_folio = iomap_release_folio,
+ .invalidate_folio = iomap_invalidate_folio,
+};
+
+#ifdef CONFIG_FS_DAX
+static vm_fault_t erofs_dax_huge_fault(struct vm_fault *vmf,
+ unsigned int order)
+{
+ return dax_iomap_fault(vmf, order, NULL, NULL, &erofs_iomap_ops);
+}
+
+static vm_fault_t erofs_dax_fault(struct vm_fault *vmf)
+{
+ return erofs_dax_huge_fault(vmf, 0);
+}
+
+static const struct vm_operations_struct erofs_dax_vm_ops = {
+ .fault = erofs_dax_fault,
+ .huge_fault = erofs_dax_huge_fault,
+};
+
+static int erofs_file_mmap_prepare(struct vm_area_desc *desc)
+{
+ if (!IS_DAX(file_inode(desc->file)))
+ return generic_file_readonly_mmap_prepare(desc);
+
+ if (vma_desc_test_all(desc, VMA_SHARED_BIT, VMA_MAYWRITE_BIT))
+ return -EINVAL;
+
+ desc->vm_ops = &erofs_dax_vm_ops;
+ vma_desc_set_flags(desc, VMA_HUGEPAGE_BIT);
+ return 0;
+}
+#else
+#define erofs_file_mmap_prepare generic_file_readonly_mmap_prepare
+#endif
+
+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;
+
+ if (erofs_inode_is_data_compressed(EROFS_I(inode)->datalayout)) {
+ if (!IS_ENABLED(CONFIG_EROFS_FS_ZIP))
+ return generic_file_llseek(file, offset, whence);
+ ops = &z_erofs_iomap_report_ops;
+ }
+
+ if (whence == SEEK_HOLE)
+ offset = iomap_seek_hole(inode, offset, ops);
+ else if (whence == SEEK_DATA)
+ offset = iomap_seek_data(inode, offset, ops);
+ else
+ return generic_file_llseek(file, offset, whence);
+
+ if (offset < 0)
+ return offset;
+ return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
+}
+
+const struct file_operations erofs_file_fops = {
+ .llseek = erofs_file_llseek,
+ .read_iter = erofs_file_read_iter,
+ .unlocked_ioctl = erofs_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = erofs_compat_ioctl,
+#endif
+ .mmap_prepare = erofs_file_mmap_prepare,
+ .get_unmapped_area = thp_get_unmapped_area,
+ .splice_read = filemap_splice_read,
+ .setlease = generic_setlease,
+};