// SPDX-License-Identifier: GPL-2.0+ #include "internal.h" #include "decompress.h" static int erofs_map_blocks_flatmode(struct erofs_inode *inode, struct erofs_map_blocks *map, int flags) { int err = 0; erofs_blk_t nblocks, lastblk; u64 offset = map->m_la; struct erofs_inode *vi = inode; bool tailendpacking = (vi->datalayout == EROFS_INODE_FLAT_INLINE); nblocks = BLK_ROUND_UP(inode->i_size); lastblk = nblocks - tailendpacking; /* there is no hole in flatmode */ map->m_flags = EROFS_MAP_MAPPED; if (offset < erofs_pos(lastblk)) { map->m_pa = erofs_pos(vi->u.i_blkaddr) + map->m_la; map->m_plen = erofs_pos(lastblk) - offset; } else if (tailendpacking) { /* 2 - inode inline B: inode, [xattrs], inline last blk... */ map->m_pa = iloc(vi->nid) + vi->inode_isize + vi->xattr_isize + erofs_blkoff(map->m_la); map->m_plen = inode->i_size - offset; /* inline data should be located in the same meta block */ if (erofs_blkoff(map->m_pa) + map->m_plen > erofs_blksiz()) { erofs_err("inline data cross block boundary @ nid %" PRIu64, vi->nid); DBG_BUGON(1); err = -EFSCORRUPTED; goto err_out; } map->m_flags |= EROFS_MAP_META; } else { erofs_err("internal error @ nid: %" PRIu64 " (size %llu), m_la 0x%" PRIx64, vi->nid, (unsigned long long)inode->i_size, map->m_la); DBG_BUGON(1); err = -EIO; goto err_out; } map->m_llen = map->m_plen; err_out: return err; } int erofs_map_blocks(struct erofs_inode *inode, struct erofs_map_blocks *map, int flags) { struct erofs_inode *vi = inode; struct erofs_inode_chunk_index *idx; u8 buf[EROFS_MAX_BLOCK_SIZE]; u64 chunknr; unsigned int unit; erofs_off_t pos; int err = 0; map->m_deviceid = 0; if (map->m_la >= inode->i_size) { /* leave out-of-bound access unmapped */ map->m_flags = 0; map->m_plen = 0; goto out; } if (vi->datalayout != EROFS_INODE_CHUNK_BASED) return erofs_map_blocks_flatmode(inode, map, flags); if (vi->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES) unit = sizeof(*idx); /* chunk index */ else unit = EROFS_BLOCK_MAP_ENTRY_SIZE; /* block map */ chunknr = map->m_la >> vi->u.chunkbits; pos = roundup(iloc(vi->nid) + vi->inode_isize + vi->xattr_isize, unit) + unit * chunknr; err = erofs_blk_read(buf, erofs_blknr(pos), 1); if (err < 0) return -EIO; map->m_la = chunknr << vi->u.chunkbits; map->m_plen = min_t(erofs_off_t, 1UL << vi->u.chunkbits, roundup(inode->i_size - map->m_la, erofs_blksiz())); /* handle block map */ if (!(vi->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)) { __le32 *blkaddr = (void *)buf + erofs_blkoff(pos); if (le32_to_cpu(*blkaddr) == EROFS_NULL_ADDR) { map->m_flags = 0; } else { map->m_pa = erofs_pos(le32_to_cpu(*blkaddr)); map->m_flags = EROFS_MAP_MAPPED; } goto out; } /* parse chunk indexes */ idx = (void *)buf + erofs_blkoff(pos); switch (le32_to_cpu(idx->blkaddr)) { case EROFS_NULL_ADDR: map->m_flags = 0; break; default: map->m_deviceid = le16_to_cpu(idx->device_id) & sbi.device_id_mask; map->m_pa = erofs_pos(le32_to_cpu(idx->blkaddr)); map->m_flags = EROFS_MAP_MAPPED; break; } out: map->m_llen = map->m_plen; return err; } int erofs_map_dev(struct erofs_map_dev *map) { struct erofs_device_info *dif; int id; if (map->m_deviceid) { if (sbi.extra_devices < map->m_deviceid) return -ENODEV; } else if (sbi.extra_devices) { for (id = 0; id < sbi.extra_devices; ++id) { erofs_off_t startoff, length; dif = sbi.devs + id; if (!dif->mapped_blkaddr) continue; startoff = erofs_pos(dif->mapped_blkaddr); length = erofs_pos(dif->blocks); if (map->m_pa >= startoff && map->m_pa < startoff + length) { map->m_pa -= startoff; break; } } } return 0; } int erofs_read_one_data(struct erofs_map_blocks *map, char *buffer, u64 offset, size_t len) { struct erofs_map_dev mdev; int ret; mdev = (struct erofs_map_dev) { .m_deviceid = map->m_deviceid, .m_pa = map->m_pa, }; ret = erofs_map_dev(&mdev); if (ret) return ret; ret = erofs_dev_read(mdev.m_deviceid, buffer, mdev.m_pa + offset, len); if (ret < 0) return -EIO; return 0; } static int erofs_read_raw_data(struct erofs_inode *inode, char *buffer, erofs_off_t size, erofs_off_t offset) { struct erofs_map_blocks map = { .index = UINT_MAX, }; int ret; erofs_off_t ptr = offset; while (ptr < offset + size) { char *const estart = buffer + ptr - offset; erofs_off_t eend, moff = 0; map.m_la = ptr; ret = erofs_map_blocks(inode, &map, 0); if (ret) return ret; DBG_BUGON(map.m_plen != map.m_llen); /* trim extent */ eend = min(offset + size, map.m_la + map.m_llen); DBG_BUGON(ptr < map.m_la); if (!(map.m_flags & EROFS_MAP_MAPPED)) { if (!map.m_llen) { /* reached EOF */ memset(estart, 0, offset + size - ptr); ptr = offset + size; continue; } memset(estart, 0, eend - ptr); ptr = eend; continue; } if (ptr > map.m_la) { moff = ptr - map.m_la; map.m_la = ptr; } ret = erofs_read_one_data(&map, estart, moff, eend - map.m_la); if (ret) return ret; ptr = eend; } return 0; } int z_erofs_read_one_data(struct erofs_inode *inode, struct erofs_map_blocks *map, char *raw, char *buffer, erofs_off_t skip, erofs_off_t length, bool trimmed) { struct erofs_map_dev mdev; int ret = 0; if (map->m_flags & EROFS_MAP_FRAGMENT) { struct erofs_inode packed_inode = { .nid = sbi.packed_nid, }; ret = erofs_read_inode_from_disk(&packed_inode); if (ret) { erofs_err("failed to read packed inode from disk"); return ret; } return erofs_pread(&packed_inode, buffer, length - skip, inode->fragmentoff + skip); } /* no device id here, thus it will always succeed */ mdev = (struct erofs_map_dev) { .m_pa = map->m_pa, }; ret = erofs_map_dev(&mdev); if (ret) { DBG_BUGON(1); return ret; } ret = erofs_dev_read(mdev.m_deviceid, raw, mdev.m_pa, map->m_plen); if (ret < 0) return ret; ret = z_erofs_decompress(&(struct z_erofs_decompress_req) { .in = raw, .out = buffer, .decodedskip = skip, .interlaced_offset = map->m_algorithmformat == Z_EROFS_COMPRESSION_INTERLACED ? erofs_blkoff(map->m_la) : 0, .inputsize = map->m_plen, .decodedlength = length, .alg = map->m_algorithmformat, .partial_decoding = trimmed ? true : !(map->m_flags & EROFS_MAP_FULL_MAPPED) || (map->m_flags & EROFS_MAP_PARTIAL_REF), }); if (ret < 0) return ret; return 0; } static int z_erofs_read_data(struct erofs_inode *inode, char *buffer, erofs_off_t size, erofs_off_t offset) { erofs_off_t end, length, skip; struct erofs_map_blocks map = { .index = UINT_MAX, }; bool trimmed; unsigned int bufsize = 0; char *raw = NULL; int ret = 0; end = offset + size; while (end > offset) { map.m_la = end - 1; ret = z_erofs_map_blocks_iter(inode, &map, 0); if (ret) break; /* * trim to the needed size if the returned extent is quite * larger than requested, and set up partial flag as well. */ if (end < map.m_la + map.m_llen) { length = end - map.m_la; trimmed = true; } else { DBG_BUGON(end != map.m_la + map.m_llen); length = map.m_llen; trimmed = false; } if (map.m_la < offset) { skip = offset - map.m_la; end = offset; } else { skip = 0; end = map.m_la; } if (!(map.m_flags & EROFS_MAP_MAPPED)) { memset(buffer + end - offset, 0, length); end = map.m_la; continue; } if (map.m_plen > bufsize) { bufsize = map.m_plen; raw = realloc(raw, bufsize); if (!raw) { ret = -ENOMEM; break; } } ret = z_erofs_read_one_data(inode, &map, raw, buffer + end - offset, skip, length, trimmed); if (ret < 0) break; } if (raw) free(raw); return ret < 0 ? ret : 0; } int erofs_pread(struct erofs_inode *inode, char *buf, erofs_off_t count, erofs_off_t offset) { switch (inode->datalayout) { case EROFS_INODE_FLAT_PLAIN: case EROFS_INODE_FLAT_INLINE: case EROFS_INODE_CHUNK_BASED: return erofs_read_raw_data(inode, buf, count, offset); case EROFS_INODE_COMPRESSED_FULL: case EROFS_INODE_COMPRESSED_COMPACT: return z_erofs_read_data(inode, buf, count, offset); default: break; } return -EINVAL; }