Files
erofs-freebsd-out-tree/zmap.c
T
2026-10-19 08:35:59 +08:00

950 lines
28 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2018-2019 HUAWEI, Inc.
* https://www.huawei.com/
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/endian.h>
#include <sys/libkern.h>
#include "internal.h"
struct z_erofs_maprecorder {
struct erofs_sb_info *sbi;
struct erofs_inode *vi;
struct erofs_map_blocks *map;
uint64_t lcn;
uint8_t type, headtype;
unsigned int clusterofs;
uint16_t delta[2];
erofs_blk_t pblk, compressedblks;
erofs_off_t nextpackoff;
bool partialref;
};
_Static_assert((EROFS_MAP_MAPPED | EROFS_MAP_META |
EROFS_MAP_PARTIAL_MAPPED | EROFS_MAP_PARTIAL_REF |
EROFS_MAP_FRAGMENT) == 0x1f, "map adapter must preserve every map flag");
/*
* The arithmetic in this file is plain on purpose. Every value is bounded
* before it gets here, and a position that did wrap would still be range
* checked where it is read (erofs_bread_device(), erofs_read_data_impl()):
*
* - blkszbits is in [9, PAGE_SHIFT] (super.c) and z_lclusterbits is
* blkszbits + 0..15, rejected from 31 up (z_erofs_fill_inode()), so a
* cluster is at most 1 GiB and a shift by z_lclusterbits is safe.
* - vi->size is at most OFF_MAX (erofs_read_inode()) and every request has
* m_la < vi->size (z_erofs_map_blocks()), so lcn << z_lclusterbits stays
* below 2^63 + 2^46 even after adding a 16-bit delta.
* - vi->inode_off lies inside the image (erofs_nid_is_valid()), inode_isize
* is 32 or 64 and xattr_isize is below 2^19.
* - An extent table has at most 2^48 records of at most 32 bytes each.
* - erofs_blk_t is 64 bits wide (internal.h).
*
* The list is repeated in docs/validation-policy.md.
*/
/* The map header follows the inode and its xattrs, 8-byte aligned. */
static inline erofs_off_t
z_erofs_header_pos(const struct erofs_inode *vi)
{
return (roundup2(vi->inode_off + vi->inode_isize + vi->xattr_isize, 8));
}
static int
z_erofs_read_index(struct z_erofs_maprecorder *m, erofs_off_t pos, size_t len,
struct erofs_buf *buf)
{
return (erofs_read_metadata_range(m->sbi, m->vi->nid, pos, len, buf));
}
static int
z_erofs_load_full_lcluster(struct z_erofs_maprecorder *m, uint64_t lcn)
{
struct erofs_buf buf = EROFS_BUF_INITIALIZER;
struct erofs_inode *vi;
struct z_erofs_lcluster_index *di;
erofs_off_t base, pos;
unsigned int advise;
int error;
vi = m->vi;
base = z_erofs_header_pos(vi) + sizeof(struct z_erofs_map_header) + 8;
pos = base + lcn * sizeof(*di);
m->nextpackoff = pos + sizeof(*di);
error = z_erofs_read_index(m, pos, sizeof(*di), &buf);
if (error != 0)
return (error);
di = buf.data;
m->lcn = lcn;
advise = le16toh(di->di_advise);
m->type = advise & Z_EROFS_LI_LCLUSTER_TYPE_MASK;
if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) {
m->clusterofs = 1U << vi->z_lclusterbits;
m->delta[0] = le16toh(di->di_u.delta[0]);
if ((m->delta[0] & Z_EROFS_LI_D0_CBLKCNT) != 0) {
if ((vi->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 |
Z_EROFS_ADVISE_BIG_PCLUSTER_2)) == 0) {
erofs_put_metabuf(&buf);
return (erofs_corrupt(m->sbi, vi->nid,
"lcn %ju has a compressed block count but "
"the inode has no big pclusters",
(uintmax_t)lcn));
}
m->compressedblks =
m->delta[0] & ~Z_EROFS_LI_D0_CBLKCNT;
m->delta[0] = 1;
}
m->delta[1] = le16toh(di->di_u.delta[1]);
} else {
m->partialref = (advise & Z_EROFS_LI_PARTIAL_REF) != 0;
m->clusterofs = le16toh(di->di_clusterofs);
m->pblk = le32toh(di->di_u.blkaddr);
}
erofs_put_metabuf(&buf);
return (0);
}
static unsigned int
decode_compactedbits(unsigned int lobits, const uint8_t *in,
unsigned int pos, uint8_t *type)
{
uint32_t v;
unsigned int lo;
v = le32dec(in + pos / 8) >> (pos & 7);
lo = v & ((1U << lobits) - 1);
*type = (v >> lobits) & 3;
return (lo);
}
static unsigned int
get_compacted_la_distance(unsigned int lobits, unsigned int encodebits,
unsigned int vcnt, const uint8_t *in, int i)
{
unsigned int lo, d1;
uint8_t type;
d1 = 0;
do {
lo = decode_compactedbits(lobits, in, encodebits * i, &type);
if (type != Z_EROFS_LCLUSTER_TYPE_NONHEAD)
return (d1);
++d1;
} while (++i < (int)vcnt);
/* vcnt - 1 (Z_EROFS_LCLUSTER_TYPE_NONHEAD) item */
if ((lo & Z_EROFS_LI_D0_CBLKCNT) == 0)
d1 += lo - 1;
return (d1);
}
static int
z_erofs_load_compact_lcluster(struct z_erofs_maprecorder *m, uint64_t lcn,
bool lookahead)
{
struct erofs_buf buf = EROFS_BUF_INITIALIZER;
struct erofs_inode *vi;
erofs_off_t ebase, pos;
uint64_t compacted_2b, totalidx, original_lcn;
unsigned int compacted_4b_initial, amortizedshift;
unsigned int vcnt, lo, lobits, encodebits, nblk, bytes, packsize;
bool big_pcluster;
uint8_t *in, type;
int error, i;
vi = m->vi;
/*
* A compact index entry is 16 bits (4-byte units) or 14 bits (2-byte
* units): a 2-bit type and the offset in the lcluster. Linux has the
* same limit.
*/
if (vi->z_lclusterbits > 14)
return (erofs_corrupt(m->sbi, vi->nid,
"invalid lcluster bits %u", vi->z_lclusterbits));
ebase = z_erofs_header_pos(vi) + sizeof(struct z_erofs_map_header);
totalidx = (vi->size + (1ULL << vi->z_lclusterbits) - 1) >>
vi->z_lclusterbits;
if (lcn >= totalidx)
return (EINVAL);
original_lcn = lcn;
m->lcn = lcn;
compacted_4b_initial = ((32 - ebase % 32) / 4) & 7;
compacted_2b = 0;
if ((vi->z_advise & Z_EROFS_ADVISE_COMPACTED_2B) != 0 &&
compacted_4b_initial < totalidx)
compacted_2b = rounddown2(totalidx - compacted_4b_initial, 16);
pos = ebase;
amortizedshift = 2;
if (lcn >= compacted_4b_initial) {
pos += compacted_4b_initial * 4;
lcn -= compacted_4b_initial;
if (lcn < compacted_2b) {
amortizedshift = 1;
} else {
pos += compacted_2b * 2;
lcn -= compacted_2b;
}
}
pos += lcn << amortizedshift;
if (amortizedshift == 2 && vi->z_lclusterbits <= 14)
vcnt = 2;
else if (amortizedshift == 1 && vi->z_lclusterbits <= 12)
vcnt = 16;
else {
erofs_err(m->sbi, "unsupported lcluster bits %u @ nid %ju",
vi->z_lclusterbits, (uintmax_t)vi->nid);
return (EOPNOTSUPP);
}
packsize = vcnt << amortizedshift;
bytes = pos & (packsize - 1);
pos -= bytes;
m->nextpackoff = pos + packsize;
error = z_erofs_read_index(m, pos, packsize, &buf);
if (error != 0)
return (error);
in = buf.data;
lobits = MAX(vi->z_lclusterbits, fls(Z_EROFS_LI_D0_CBLKCNT));
encodebits = (packsize - sizeof(uint32_t)) * 8 / vcnt;
i = bytes >> amortizedshift;
lo = decode_compactedbits(lobits, in, encodebits * i, &type);
m->type = type;
if (type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) {
m->clusterofs = 1U << vi->z_lclusterbits;
if (lookahead) {
m->delta[1] = get_compacted_la_distance(lobits,
encodebits, vcnt, in, i);
}
big_pcluster =
(vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0;
if ((lo & Z_EROFS_LI_D0_CBLKCNT) != 0) {
if (!big_pcluster) {
erofs_put_metabuf(&buf);
return (erofs_corrupt(m->sbi, vi->nid,
"lcn %ju has a compressed block count but "
"the inode has no big pclusters",
(uintmax_t)original_lcn));
}
m->compressedblks = lo & ~Z_EROFS_LI_D0_CBLKCNT;
m->delta[0] = 1;
} else if (i + 1 != (int)vcnt) {
m->delta[0] = lo;
} else {
EROFS_DEBUG_ASSERT(i > 0,
("erofs: pack of %u slots", vcnt));
lo = decode_compactedbits(lobits, in,
encodebits * (i - 1), &type);
if (type != Z_EROFS_LCLUSTER_TYPE_NONHEAD)
lo = 0;
else if ((lo & Z_EROFS_LI_D0_CBLKCNT) != 0)
lo = 1;
m->delta[0] = lo + 1;
}
erofs_put_metabuf(&buf);
return (0);
}
m->clusterofs = lo;
m->delta[0] = 0;
big_pcluster =
(vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0;
if (!big_pcluster) {
nblk = 1;
while (i > 0) {
--i;
lo = decode_compactedbits(lobits, in,
encodebits * i, &type);
if (type == Z_EROFS_LCLUSTER_TYPE_NONHEAD)
i -= lo;
if (i >= 0)
++nblk;
}
} else {
nblk = 0;
while (i > 0) {
--i;
lo = decode_compactedbits(lobits, in,
encodebits * i, &type);
if (type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) {
if ((lo & Z_EROFS_LI_D0_CBLKCNT) != 0) {
--i;
nblk += lo & ~Z_EROFS_LI_D0_CBLKCNT;
continue;
}
if (lo <= 1) {
erofs_put_metabuf(&buf);
return (erofs_corrupt(m->sbi, vi->nid,
"invalid delta %u before lcn %ju",
lo, (uintmax_t)original_lcn));
}
i -= lo - 2;
continue;
}
++nblk;
}
}
m->pblk = (erofs_blk_t)le32dec(in + packsize - sizeof(uint32_t)) + nblk;
erofs_put_metabuf(&buf);
m->lcn = original_lcn;
return (0);
}
static int
z_erofs_load_lcluster_from_disk(struct z_erofs_maprecorder *m, uint64_t lcn,
bool lookahead)
{
int err;
EROFS_DEBUG_ASSERT(m->vi->datalayout ==
EROFS_INODE_COMPRESSED_COMPACT ||
m->vi->datalayout == EROFS_INODE_COMPRESSED_FULL,
("erofs: data layout %u is not compressed", m->vi->datalayout));
if (m->vi->datalayout == EROFS_INODE_COMPRESSED_COMPACT)
err = z_erofs_load_compact_lcluster(m, lcn, lookahead);
else
err = z_erofs_load_full_lcluster(m, lcn);
if (err != 0)
return (err);
/* The type is a 2-bit field, so it is always below TYPE_MAX. */
if (m->type != Z_EROFS_LCLUSTER_TYPE_NONHEAD &&
m->clusterofs >= (1U << m->vi->z_lclusterbits))
return (erofs_corrupt(m->sbi, m->vi->nid,
"clusterofs %u of lcn %ju is out of range", m->clusterofs,
(uintmax_t)lcn));
return (0);
}
static int
z_erofs_extent_lookback(struct z_erofs_maprecorder *m,
unsigned int lookback_distance)
{
uint64_t lcn;
int err;
while (lookback_distance != 0 && m->lcn >= lookback_distance) {
lcn = m->lcn - lookback_distance;
err = z_erofs_load_lcluster_from_disk(m, lcn, false);
if (err != 0)
return (err);
if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) {
lookback_distance = m->delta[0];
continue;
}
m->headtype = m->type;
m->map->m_la = (lcn << m->vi->z_lclusterbits) + m->clusterofs;
return (0);
}
return (erofs_corrupt(m->sbi, m->vi->nid,
"no head lcluster found looking back from lcn %ju",
(uintmax_t)m->lcn));
}
static int
z_erofs_get_extent_compressedlen(struct z_erofs_maprecorder *m)
{
struct erofs_inode *vi;
bool bigpcl1, bigpcl2;
uint64_t lcn, lcnpos;
int err;
vi = m->vi;
bigpcl1 = (vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0;
bigpcl2 = (vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_2) != 0;
lcn = m->lcn + 1;
lcnpos = lcn << vi->z_lclusterbits;
if ((m->headtype == Z_EROFS_LCLUSTER_TYPE_HEAD1 && !bigpcl1) ||
((m->headtype == Z_EROFS_LCLUSTER_TYPE_PLAIN ||
m->headtype == Z_EROFS_LCLUSTER_TYPE_HEAD2) && !bigpcl2) ||
lcnpos >= vi->size)
m->compressedblks = 1;
if (m->compressedblks == 0) {
err = z_erofs_load_lcluster_from_disk(m, lcn, false);
if (err != 0)
return (err);
if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD &&
m->delta[0] != 1)
return (erofs_corrupt(m->sbi, vi->nid,
"lcn %ju: delta %u should be 1 for the compressed "
"block count", (uintmax_t)lcn, m->delta[0]));
if (m->type != Z_EROFS_LCLUSTER_TYPE_NONHEAD ||
m->compressedblks == 0)
m->compressedblks = 1;
}
m->map->m_plen = m->compressedblks * m->sbi->block_size;
return (0);
}
static int
z_erofs_get_extent_decompressedlen(struct z_erofs_maprecorder *m)
{
struct erofs_inode *vi;
struct erofs_map_blocks *map;
uint64_t lcn, headlcn, lend, lcnpos;
int err;
vi = m->vi;
map = m->map;
lcn = m->lcn;
headlcn = map->m_la >> vi->z_lclusterbits;
for (;;) {
lcnpos = lcn << vi->z_lclusterbits;
if (lcnpos >= vi->size) {
EROFS_DEBUG_ASSERT(map->m_la <= vi->size,
("erofs: extent at %ju beyond the size %ju",
(uintmax_t)map->m_la, (uintmax_t)vi->size));
map->m_llen = vi->size - map->m_la;
return (0);
}
err = z_erofs_load_lcluster_from_disk(m, lcn, true);
if (err != 0)
return (err);
if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) {
if (m->delta[1] == 0)
m->delta[1] = 1;
} else {
if (lcn != headlcn)
break;
m->delta[1] = 1;
}
lcn += m->delta[1];
}
lend = (lcn << vi->z_lclusterbits) + m->clusterofs;
EROFS_DEBUG_ASSERT(lend >= map->m_la,
("erofs: extent ends at %ju, before its start %ju", (uintmax_t)lend,
(uintmax_t)map->m_la));
map->m_llen = lend - map->m_la;
return (0);
}
static inline erofs_off_t
z_erofs_extent_table_pos(const struct erofs_inode *vi, unsigned int recsz)
{
return (roundup2(z_erofs_header_pos(vi) +
sizeof(struct z_erofs_map_header), recsz));
}
static inline erofs_off_t
z_erofs_extent_record_pos(erofs_off_t table_pos, unsigned int recsz,
uint64_t index)
{
return (table_pos + index * recsz);
}
static int
z_erofs_read_extent(struct erofs_sb_info *sbi, struct erofs_inode *vi,
erofs_off_t pos, unsigned int recsz, struct z_erofs_extent *ext)
{
struct erofs_buf buf = EROFS_BUF_INITIALIZER;
int error;
bzero(ext, sizeof(*ext));
error = erofs_read_metadata_range(sbi, vi->nid, pos, recsz, &buf);
if (error != 0)
return (error);
memcpy(ext, buf.data, recsz);
erofs_put_metabuf(&buf);
return (0);
}
static uint64_t
z_erofs_extent_lstart(const struct z_erofs_extent *ext, unsigned int recsz)
{
uint64_t lstart;
lstart = le32toh(ext->lstart_lo);
if (recsz > offsetof(struct z_erofs_extent, lstart_hi))
lstart |= (uint64_t)le32toh(ext->lstart_hi) << 32;
return (lstart);
}
static int
z_erofs_map_blocks_fo(struct erofs_sb_info *sbi, struct erofs_inode *vi,
struct erofs_map_blocks *map, int flags)
{
bool fragment, ztailpacking;
struct z_erofs_maprecorder m;
uint64_t initial_lcn, ofs, end;
unsigned int endoff;
int err;
fragment = (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0;
ztailpacking = vi->z_idata_size != 0;
bzero(&m, sizeof(m));
m.sbi = sbi;
m.vi = vi;
m.map = map;
if (vi->size == 0) {
map->m_la = 0;
map->m_llen = 0;
map->m_flags = 0;
return (0);
}
ofs = (flags & EROFS_GET_BLOCKS_FINDTAIL) != 0 ?
vi->size - 1 : map->m_la;
if (fragment && (flags & EROFS_GET_BLOCKS_FINDTAIL) == 0 &&
vi->z_tailextent_headlcn == 0) {
map->m_la = 0;
map->m_llen = vi->size;
map->m_flags = EROFS_MAP_FRAGMENT;
return (0);
}
initial_lcn = ofs >> vi->z_lclusterbits;
endoff = ofs & ((1U << vi->z_lclusterbits) - 1);
err = z_erofs_load_lcluster_from_disk(&m, initial_lcn, false);
if (err != 0)
return (err);
if ((flags & EROFS_GET_BLOCKS_FINDTAIL) != 0 && ztailpacking)
vi->z_fragmentoff = m.nextpackoff;
map->m_flags = EROFS_MAP_MAPPED | EROFS_MAP_PARTIAL_MAPPED;
end = (m.lcn + 1) << vi->z_lclusterbits;
if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD &&
endoff >= m.clusterofs) {
m.headtype = m.type;
map->m_la = (m.lcn << vi->z_lclusterbits) + m.clusterofs;
if (ztailpacking && end > vi->size)
end = vi->size;
} else {
if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD) {
end = (m.lcn << vi->z_lclusterbits) + m.clusterofs;
map->m_flags &= ~EROFS_MAP_PARTIAL_MAPPED;
m.delta[0] = 1;
}
err = z_erofs_extent_lookback(&m, m.delta[0]);
if (err != 0)
return (err);
}
if (m.partialref)
map->m_flags |= EROFS_MAP_PARTIAL_REF;
EROFS_DEBUG_ASSERT(end >= map->m_la,
("erofs: extent ends at %ju, before its start %ju", (uintmax_t)end,
(uintmax_t)map->m_la));
map->m_llen = end - map->m_la;
if ((flags & EROFS_GET_BLOCKS_FINDTAIL) != 0) {
vi->z_tailextent_headlcn = m.lcn;
if (fragment &&
vi->datalayout == EROFS_INODE_COMPRESSED_FULL) {
/* Both are 32-bit fields of the on-disk index. */
vi->z_fragmentoff |= m.pblk << 32;
}
}
if (ztailpacking && m.lcn == vi->z_tailextent_headlcn) {
map->m_flags |= EROFS_MAP_META;
map->m_pa = vi->z_fragmentoff;
map->m_plen = vi->z_idata_size;
/* Same rule as Linux: inline data does not cross a block. */
if ((map->m_pa & (sbi->block_size - 1)) + map->m_plen >
sbi->block_size)
return (erofs_corrupt(sbi, vi->nid,
"inline pcluster of %ju bytes crosses its block",
(uintmax_t)map->m_plen));
} else if (fragment && m.lcn == vi->z_tailextent_headlcn) {
map->m_flags = EROFS_MAP_FRAGMENT;
} else {
map->m_pa = m.pblk * sbi->block_size;
err = z_erofs_get_extent_compressedlen(&m);
if (err != 0)
return (err);
}
if (m.headtype == Z_EROFS_LCLUSTER_TYPE_PLAIN) {
/*
* Interlaced pclusters are block-aligned on disk. An inline
* (ztailpacking) pcluster is not, so it can only be shifted,
* whatever the inode advises.
*/
map->m_algorithmformat =
(vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) != 0 &&
(map->m_flags & EROFS_MAP_META) == 0 ?
Z_EROFS_COMPRESSION_INTERLACED :
Z_EROFS_COMPRESSION_SHIFTED;
} else if (m.headtype == Z_EROFS_LCLUSTER_TYPE_HEAD2) {
map->m_algorithmformat = vi->z_algorithmtype[1];
} else {
map->m_algorithmformat = vi->z_algorithmtype[0];
}
if ((flags & EROFS_GET_BLOCKS_FIEMAP) != 0) {
err = z_erofs_get_extent_decompressedlen(&m);
if (err == 0)
map->m_flags &= ~EROFS_MAP_PARTIAL_MAPPED;
return (err);
}
return (0);
}
/*
* The extent table is not checked when the inode is loaded: that its
* records are sorted and inside the file is fsck.erofs's job, and scanning
* it would read a table that grows with the file on every load. Each
* record the search visits is range checked by the read, and a table that
* is out of order only makes it return a hole or the wrong extent.
*/
static int
z_erofs_map_blocks_ext(struct erofs_sb_info *sbi, struct erofs_inode *vi,
struct erofs_map_blocks *map)
{
struct erofs_buf buf = EROFS_BUF_INITIALIZER;
struct z_erofs_extent ext;
unsigned int recsz, bmask;
uint8_t fmt;
uint64_t cluster_size, extent_idx;
erofs_off_t extent_pos, pos, table_pos;
uint64_t lend, l, r, mid, pa, la, lstart;
bool interlaced, last;
int error;
interlaced =
(vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) != 0;
recsz = z_erofs_extent_recsize(vi->z_advise);
table_pos = z_erofs_extent_table_pos(vi, recsz);
pos = table_pos;
bmask = sbi->block_size - 1;
lend = vi->size;
cluster_size = 1ULL << vi->z_lclusterbits;
map->m_flags = 0;
if (recsz <= offsetof(struct z_erofs_extent, pstart_hi)) {
if (recsz <= offsetof(struct z_erofs_extent, pstart_lo)) {
error = erofs_read_metadata_range(sbi, vi->nid, pos,
sizeof(uint64_t), &buf);
if (error != 0)
return (error);
pa = le64dec(buf.data);
erofs_put_metabuf(&buf);
pos += sizeof(uint64_t);
lstart = 0;
extent_idx = 0;
} else {
lstart = rounddown2(map->m_la, cluster_size);
extent_idx = lstart >> vi->z_lclusterbits;
pa = EROFS_NULL_ADDR;
}
for (;;) {
EROFS_DEBUG_ASSERT(extent_idx < vi->z_extents,
("erofs: extent %ju of %ju", (uintmax_t)extent_idx,
(uintmax_t)vi->z_extents));
extent_pos = z_erofs_extent_record_pos(pos, recsz,
extent_idx);
error = z_erofs_read_extent(sbi, vi, extent_pos, recsz,
&ext);
if (error != 0)
return (error);
map->m_plen = le32toh(ext.plen);
if (pa != EROFS_NULL_ADDR) {
map->m_pa = pa;
pa += map->m_plen & Z_EROFS_EXTENT_PLEN_MASK;
} else {
map->m_pa = le32toh(ext.pstart_lo);
}
extent_idx++;
lstart += cluster_size;
if (lstart > map->m_la)
break;
}
last = lstart >= roundup2(lend, cluster_size);
lend = MIN(lstart, lend);
lstart -= cluster_size;
} else {
lstart = lend;
for (l = 0, r = vi->z_extents; l < r;) {
mid = l + (r - l) / 2;
extent_pos = z_erofs_extent_record_pos(table_pos, recsz,
mid);
error = z_erofs_read_extent(sbi, vi, extent_pos,
recsz, &ext);
if (error != 0)
return (error);
la = z_erofs_extent_lstart(&ext, recsz);
pa = le32toh(ext.pstart_lo) |
((uint64_t)le32toh(ext.pstart_hi) << 32);
if (la > map->m_la) {
r = mid;
/*
* The records are not compared with each
* other, but this one must not start past the
* next one or the size: m_llen stays within the
* file.
*/
if (la > lend)
return (erofs_corrupt(sbi, vi->nid,
"extent %ju starts at %ju, past "
"the following extent or the size "
"%ju",
(uintmax_t)mid, (uintmax_t)la,
(uintmax_t)lend));
lend = la;
} else {
l = mid + 1;
if (map->m_la == la)
r = MIN(l + 1, r);
lstart = la;
map->m_plen = le32toh(ext.plen);
map->m_pa = pa;
}
}
last = l >= vi->z_extents;
}
if (lstart < lend) {
map->m_la = lstart;
if (last &&
(vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) {
map->m_flags = EROFS_MAP_FRAGMENT;
if (recsz > offsetof(struct z_erofs_extent,
pstart_lo)) {
vi->z_fragmentoff = map->m_plen |
(map->m_pa << 32);
} else {
vi->z_fragmentoff = map->m_plen;
}
} else if ((map->m_plen & Z_EROFS_EXTENT_PLEN_MASK) != 0) {
map->m_flags = EROFS_MAP_MAPPED;
fmt = (uint8_t)(map->m_plen >>
Z_EROFS_EXTENT_PLEN_FMT_BIT);
if ((map->m_plen & Z_EROFS_EXTENT_PLEN_PARTIAL) != 0)
map->m_flags |= EROFS_MAP_PARTIAL_REF;
map->m_plen &= Z_EROFS_EXTENT_PLEN_MASK;
if (fmt != 0) {
if (fmt - 1 >= Z_EROFS_COMPRESSION_MAX) {
erofs_err(sbi,
"unsupported compression "
"algorithm %u @ nid %ju", fmt - 1,
(uintmax_t)vi->nid);
return (EOPNOTSUPP);
}
map->m_algorithmformat = (uint8_t)(fmt - 1);
} else if (interlaced &&
((map->m_pa | map->m_plen) & bmask) == 0)
map->m_algorithmformat =
Z_EROFS_COMPRESSION_INTERLACED;
else
map->m_algorithmformat =
Z_EROFS_COMPRESSION_SHIFTED;
}
}
map->m_llen = lend - map->m_la;
return (0);
}
int
z_erofs_fill_inode(struct erofs_sb_info *sbi, struct erofs_inode *vi)
{
struct erofs_buf buf = EROFS_BUF_INITIALIZER;
struct z_erofs_map_header *h;
struct erofs_map_blocks map;
uint64_t cluster_size, raw;
erofs_off_t pos;
unsigned int recsz;
int err;
if (vi->z_initialized)
return (0);
pos = z_erofs_header_pos(vi);
err = erofs_read_metadata_range(sbi, vi->nid, pos, sizeof(*h), &buf);
if (err != 0)
return (err);
h = buf.data;
if ((h->h_clusterbits & (1U << Z_EROFS_FRAGMENT_INODE_BIT)) != 0) {
if (!erofs_sb_has_fragments(sbi) || sbi->packed_nid == 0) {
erofs_put_metabuf(&buf);
return (erofs_corrupt(sbi, vi->nid,
"fragment inode, but the image has no packed "
"inode"));
}
raw = le64dec(h);
vi->z_advise = Z_EROFS_ADVISE_FRAGMENT_PCLUSTER;
vi->z_fragmentoff = raw ^ (1ULL << 63);
vi->z_tailextent_headlcn = 0;
erofs_put_metabuf(&buf);
vi->z_initialized = true;
return (0);
}
vi->z_advise = le16toh(h->h_advise);
vi->z_lclusterbits = sbi->blkszbits + (h->h_clusterbits & 15);
/* The lcluster size is used as a 32-bit shift count and size below. */
if (vi->z_lclusterbits >= 31) {
erofs_put_metabuf(&buf);
return (erofs_corrupt(sbi, vi->nid,
"lcluster bits %u are too large", vi->z_lclusterbits));
}
if (vi->datalayout == EROFS_INODE_COMPRESSED_FULL &&
(vi->z_advise & Z_EROFS_ADVISE_EXTENTS) != 0) {
recsz = z_erofs_extent_recsize(vi->z_advise);
if (recsz <= offsetof(struct z_erofs_extent, pstart_hi)) {
cluster_size = 1ULL << vi->z_lclusterbits;
vi->z_extents = roundup2(vi->size, cluster_size) >>
vi->z_lclusterbits;
} else {
vi->z_extents = le32toh(h->h_extents_lo) |
((uint64_t)le16toh(h->h_extents_hi) << 32);
}
erofs_put_metabuf(&buf);
if ((vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0 &&
(!erofs_sb_has_fragments(sbi) || sbi->packed_nid == 0))
return (erofs_corrupt(sbi, vi->nid,
"fragment inode, but the image has no packed "
"inode"));
vi->z_initialized = true;
return (0);
}
vi->z_algorithmtype[0] = (uint8_t)(h->h_algorithmtype & 15);
vi->z_algorithmtype[1] = (uint8_t)(h->h_algorithmtype >> 4);
/*
* Both HEAD formats are on-disk algorithm IDs. IDs at or above
* Z_EROFS_COMPRESSION_MAX would alias the runtime-only SHIFTED and
* INTERLACED formats, so refuse the inode instead of decoding it as
* uncompressed data.
*/
if (vi->z_algorithmtype[0] >= Z_EROFS_COMPRESSION_MAX ||
vi->z_algorithmtype[1] >= Z_EROFS_COMPRESSION_MAX) {
erofs_put_metabuf(&buf);
erofs_err(sbi, "unsupported compression algorithm %u/%u "
"@ nid %ju", vi->z_algorithmtype[0], vi->z_algorithmtype[1],
(uintmax_t)vi->nid);
return (EOPNOTSUPP);
}
if ((vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0)
vi->z_fragmentoff = le32toh(h->h_fragmentoff);
else if ((vi->z_advise & Z_EROFS_ADVISE_INLINE_PCLUSTER) != 0)
vi->z_idata_size = le16toh(h->h_idata_size);
erofs_put_metabuf(&buf);
/* Same rule as Linux: big pcluster advise needs the feature flag. */
if (!erofs_sb_has_big_pcluster(sbi) &&
(vi->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 |
Z_EROFS_ADVISE_BIG_PCLUSTER_2)) != 0)
return (erofs_corrupt(sbi, vi->nid,
"big pcluster advise %#x, but the image has no big "
"pclusters",
vi->z_advise));
/*
* Same rule as Linux: compact indexes set both big-pcluster bits or
* neither.
*/
if (vi->datalayout == EROFS_INODE_COMPRESSED_COMPACT &&
(((vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0) !=
((vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_2) != 0)))
return (erofs_corrupt(sbi, vi->nid,
"compact inode with inconsistent big pcluster advise %#x",
vi->z_advise));
if (vi->z_idata_size != 0 ||
(vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) {
if ((vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0 &&
(!erofs_sb_has_fragments(sbi) || sbi->packed_nid == 0))
return (erofs_corrupt(sbi, vi->nid,
"fragment inode, but the image has no packed "
"inode"));
bzero(&map, sizeof(map));
err = z_erofs_map_blocks_fo(sbi, vi, &map,
EROFS_GET_BLOCKS_FINDTAIL);
if (err != 0)
return (err);
}
vi->z_initialized = true;
return (0);
}
static int
z_erofs_map_sanity_check(struct erofs_sb_info *sbi, struct erofs_inode *vi,
struct erofs_map_blocks *map)
{
if ((map->m_flags & EROFS_MAP_FRAGMENT) != 0) {
/*
* The fragment is read from the packed inode, and that read
* checks the packed inode and the range (z_erofs_do_read(),
* erofs_read_data()).
*/
EROFS_DEBUG_ASSERT((map->m_flags &
(EROFS_MAP_MAPPED | EROFS_MAP_META)) == 0,
("erofs: fragment map with flags %#x",
(unsigned int)map->m_flags));
return (0);
}
if ((map->m_flags & EROFS_MAP_MAPPED) == 0)
return (0);
EROFS_DEBUG_ASSERT(map->m_la < vi->size,
("erofs: extent at %ju, beyond the size %ju",
(uintmax_t)map->m_la, (uintmax_t)vi->size));
/*
* On-disk algorithm IDs are validated where they are read, so only
* the runtime formats can reach this point.
*/
EROFS_DEBUG_ASSERT(map->m_algorithmformat <
Z_EROFS_COMPRESSION_RUNTIME_MAX,
("erofs: unvalidated algorithm format %u", map->m_algorithmformat));
if (map->m_algorithmformat < Z_EROFS_COMPRESSION_MAX) {
if ((sbi->available_compr_algs &
(1U << map->m_algorithmformat)) == 0)
return (erofs_corrupt(sbi, vi->nid,
"algorithm %u is not in the image's algorithm list",
map->m_algorithmformat));
/*
* A fully mapped compressed extent does not decode to fewer
* bytes than it occupies, unless the end of the file cuts it
* short.
*/
if (EROFS_MAP_FULL(map->m_flags) && map->m_llen < map->m_plen &&
map->m_llen < vi->size - map->m_la)
return (erofs_corrupt(sbi, vi->nid,
"extent at %ju decompresses to %ju bytes from %ju "
"compressed", (uintmax_t)map->m_la,
(uintmax_t)map->m_llen, (uintmax_t)map->m_plen));
} else if (map->m_llen > map->m_plen) {
/* Stored data is not longer than the pcluster holding it. */
return (erofs_corrupt(sbi, vi->nid,
"uncompressed extent at %ju has length %ju > stored %ju",
(uintmax_t)map->m_la, (uintmax_t)map->m_llen,
(uintmax_t)map->m_plen));
}
if (map->m_plen > Z_EROFS_PCLUSTER_MAX_SIZE ||
map->m_llen > Z_EROFS_PCLUSTER_MAX_DSIZE) {
erofs_err(sbi, "extent at %ju is too large (%ju bytes encoded, "
"%ju decoded) @ nid %ju", (uintmax_t)map->m_la,
(uintmax_t)map->m_plen, (uintmax_t)map->m_llen,
(uintmax_t)vi->nid);
return (EOPNOTSUPP);
}
return (0);
}
int
z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,
struct erofs_map_blocks *map)
{
int err;
if (map->m_la >= vi->size) {
map->m_llen = map->m_la - vi->size + 1;
map->m_la = vi->size;
map->m_flags = 0;
return (0);
}
err = z_erofs_fill_inode(sbi, vi);
if (err == 0) {
if (vi->datalayout == EROFS_INODE_COMPRESSED_FULL &&
(vi->z_advise & Z_EROFS_ADVISE_EXTENTS) != 0)
err = z_erofs_map_blocks_ext(sbi, vi, map);
else
err = z_erofs_map_blocks_fo(sbi, vi, map,
EROFS_GET_BLOCKS_FIEMAP);
}
if (err == 0)
err = z_erofs_map_sanity_check(sbi, vi, map);
if (err != 0)
map->m_llen = 0;
return (err);
}