// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2018-2019 HUAWEI, Inc.
 *             https://www.huawei.com/
 */

#include <sys/param.h>
#include <sys/endian.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/systm.h>

#include "internal.h"

#define Z_EROFS_EXTENT_VALIDATE_CHUNK_SIZE (64 * 1024)

struct z_erofs_maprecorder {
	struct erofs_sb_info *sbi;
	struct erofs_inode *vi;
	struct erofs_map_blocks *map;
	uint64_t lcn;
	uint8_t type;
	uint8_t headtype;
	unsigned int clusterofs;
	uint16_t delta[2];
	erofs_blk_t pblk;
	erofs_blk_t 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");

static int
z_erofs_index_base(const struct erofs_inode *vi, erofs_off_t *base)
{
	erofs_off_t end;

	if (__builtin_add_overflow(vi->inode_off,
	    (erofs_off_t)vi->inode_isize, &end) ||
	    __builtin_add_overflow(end, (erofs_off_t)vi->xattr_isize, &end) ||
	    __builtin_add_overflow(end, (erofs_off_t)7, &end))
		return (EINTEGRITY);
	end = rounddown2(end, 8);
	if (__builtin_add_overflow(end,
	    (erofs_off_t)sizeof(struct z_erofs_map_header), base))
		return (EINTEGRITY);
	return (0);
}

static int
z_erofs_index_advance(erofs_off_t *pos, uint64_t count, uint64_t unit)
{
	erofs_off_t delta, next;

	if (__builtin_mul_overflow(count, unit, &delta) ||
	    __builtin_add_overflow(*pos, delta, &next))
		return (EINTEGRITY);
	*pos = next;
	return (0);
}

static int
z_erofs_lcluster_count(uint64_t size, unsigned int lclusterbits,
    uint64_t *count)
{
	uint64_t mask;

	if (lclusterbits >= 64)
		return (EINTEGRITY);
	mask = (1ULL << lclusterbits) - 1;
	*count = size >> lclusterbits;
	if ((size & mask) != 0 && __builtin_add_overflow(*count, 1, count))
		return (EINTEGRITY);
	return (0);
}

static int
z_erofs_lcluster_pos(uint64_t lcn, unsigned int lclusterbits,
    uint64_t clusterofs, uint64_t *pos)
{
	uint64_t base, clustersize;

	if (lclusterbits >= 64)
		return (EINTEGRITY);
	clustersize = 1ULL << lclusterbits;
	if (__builtin_mul_overflow(lcn, clustersize, &base) ||
	    __builtin_add_overflow(base, clusterofs, pos))
		return (EINTEGRITY);
	return (0);
}

static int
z_erofs_lcn_advance(uint64_t *lcn, uint64_t delta)
{
	uint64_t next;

	if (__builtin_add_overflow(*lcn, delta, &next))
		return (EINTEGRITY);
	*lcn = next;
	return (0);
}

static int
z_erofs_compact_pblk(uint32_t base, unsigned int nblk, erofs_blk_t *pblk)
{
	if (__builtin_add_overflow((erofs_blk_t)base, (erofs_blk_t)nblk,
	    pblk))
		return (EINTEGRITY);
	return (0);
}

static int
z_erofs_fragment_offset(uint64_t low, erofs_blk_t high, erofs_off_t *offset)
{
	if (low > UINT32_MAX || high > UINT32_MAX)
		return (EINTEGRITY);
	*offset = low | (high << 32);
	return (0);
}

static int
z_erofs_post_eof_len(uint64_t la, uint64_t size, uint64_t *len)
{
	if (la < size)
		return (EINTEGRITY);
	*len = la - size;
	if (__builtin_add_overflow(*len, 1, len))
		*len = UINT64_MAX;
	return (0);
}

static int
z_erofs_physical_end(uint64_t pa, uint64_t plen, uint64_t block_size)
{
	uint64_t limit, pend;

	if (__builtin_add_overflow(pa, plen, &pend))
		return (EINTEGRITY);
	if (__builtin_mul_overflow(1ULL << 48, block_size, &limit))
		return (0);
	if (pend > limit)
		return (EINTEGRITY);
	return (0);
}

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(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;
	if (z_erofs_index_base(vi, &base) != 0 ||
	    z_erofs_index_advance(&base, 1, 8) != 0)
		return (EINTEGRITY);
	pos = base;
	if (z_erofs_index_advance(&pos, lcn, sizeof(*di)) != 0)
		return (EINTEGRITY);
	m->nextpackoff = pos;
	if (z_erofs_index_advance(&m->nextpackoff, 1, sizeof(*di)) != 0)
		return (EINTEGRITY);
	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 (EINTEGRITY);
			}
			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 value;
	unsigned int lo;

	value = le32dec(in + pos / 8) >> (pos & 7);
	lo = value & ((1U << lobits) - 1);
	*type = (value >> lobits) & 3;
	return (lo);
}

static int
get_compacted_la_distance(unsigned int lobits, unsigned int encodebits,
    unsigned int vcnt, const uint8_t *in, int i)
{
	unsigned int lo, distance;
	uint8_t type;

	distance = 0;
	do {
		lo = decode_compactedbits(lobits, in, encodebits * i, &type);
		if (type != Z_EROFS_LCLUSTER_TYPE_NONHEAD)
			return (distance);
		++distance;
	} while (++i < (int)vcnt);

	if ((lo & Z_EROFS_LI_D0_CBLKCNT) == 0) {
		if (lo == 0)
			return (-1);
		distance += lo - 1;
	}
	return ((int)distance);
}

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 distance, error, i;

	vi = m->vi;
	if (vi->z_lclusterbits > 14 ||
	    z_erofs_index_base(vi, &ebase) != 0 ||
	    z_erofs_lcluster_count(vi->size, vi->z_lclusterbits,
	    &totalidx) != 0)
		return (EINTEGRITY);
	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) {
		if (z_erofs_index_advance(&pos, compacted_4b_initial, 4) != 0)
			return (EINTEGRITY);
		lcn -= compacted_4b_initial;
		if (lcn < compacted_2b) {
			amortizedshift = 1;
		} else {
			if (z_erofs_index_advance(&pos, compacted_2b, 2) != 0)
				return (EINTEGRITY);
			lcn -= compacted_2b;
		}
	}
	if (z_erofs_index_advance(&pos, lcn, 1U << amortizedshift) != 0)
		return (EINTEGRITY);

	if (amortizedshift == 2 && vi->z_lclusterbits <= 14)
		vcnt = 2;
	else if (amortizedshift == 1 && vi->z_lclusterbits <= 12)
		vcnt = 16;
	else
		return (EOPNOTSUPP);

	packsize = vcnt << amortizedshift;
	bytes = pos & (packsize - 1);
	pos -= bytes;
	m->nextpackoff = pos;
	if (z_erofs_index_advance(&m->nextpackoff, 1, packsize) != 0)
		return (EINTEGRITY);
	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) {
			distance = get_compacted_la_distance(lobits, encodebits,
			    vcnt, in, i);
			if (distance < 0) {
				erofs_put_metabuf(&buf);
				return (EINTEGRITY);
			}
			m->delta[1] = distance;
		}
		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 (EINTEGRITY);
			}
			m->compressedblks = lo & ~Z_EROFS_LI_D0_CBLKCNT;
			m->delta[0] = 1;
		} else if (i + 1 != (int)vcnt) {
			m->delta[0] = lo;
		} else {
			if (i == 0) {
				erofs_put_metabuf(&buf);
				return (EINTEGRITY);
			}
			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 (m->delta[0] == 0 ? EINTEGRITY : 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 (EINTEGRITY);
				}
				i -= lo - 2;
				continue;
			}
			++nblk;
		}
	}
	error = z_erofs_compact_pblk(
	    le32dec(in + packsize - sizeof(uint32_t)), nblk, &m->pblk);
	erofs_put_metabuf(&buf);
	if (error != 0)
		return (error);
	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 error;

	if (m->vi->datalayout == EROFS_INODE_COMPRESSED_COMPACT)
		error = z_erofs_load_compact_lcluster(m, lcn, lookahead);
	else if (m->vi->datalayout == EROFS_INODE_COMPRESSED_FULL)
		error = z_erofs_load_full_lcluster(m, lcn);
	else
		return (EINTEGRITY);
	if (error != 0)
		return (error);
	if (m->type >= Z_EROFS_LCLUSTER_TYPE_MAX)
		return (EOPNOTSUPP);
	if (m->type != Z_EROFS_LCLUSTER_TYPE_NONHEAD &&
	    m->clusterofs >= (1U << m->vi->z_lclusterbits))
		return (EINTEGRITY);
	return (0);
}

static int
z_erofs_extent_lookback(struct z_erofs_maprecorder *m,
    unsigned int lookback_distance)
{
	uint64_t lcn;
	int error;

	while (lookback_distance != 0 && m->lcn >= lookback_distance) {
		lcn = m->lcn - lookback_distance;
		error = z_erofs_load_lcluster_from_disk(m, lcn, false);
		if (error != 0)
			return (error);
		if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) {
			lookback_distance = m->delta[0];
			continue;
		}
		m->headtype = m->type;
		error = z_erofs_lcluster_pos(lcn, m->vi->z_lclusterbits,
		    m->clusterofs, &m->map->m_la);
		if (error != 0)
			return (error);
		return (0);
	}
	return (EINTEGRITY);
}

static int
z_erofs_get_extent_compressedlen(struct z_erofs_maprecorder *m,
    uint64_t initial_lcn)
{
	struct erofs_inode *vi;
	bool bigpcl1, bigpcl2;
	uint64_t lcn, lcnpos;
	int error;

	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;
	if (z_erofs_lcn_advance(&lcn, 1) != 0 ||
	    z_erofs_lcluster_pos(lcn, vi->z_lclusterbits, 0, &lcnpos) != 0)
		return (EINTEGRITY);
	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) {
		error = z_erofs_load_lcluster_from_disk(m, lcn, false);
		if (error != 0)
			return (error);
		if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD &&
		    m->delta[0] != 1)
			return (EINTEGRITY);
		if (m->type != Z_EROFS_LCLUSTER_TYPE_NONHEAD ||
		    m->compressedblks == 0)
			m->compressedblks = 1;
	}
	if (__builtin_mul_overflow(m->compressedblks,
	    (uint64_t)m->sbi->block_size, &m->map->m_plen))
		return (EINTEGRITY);
	(void)initial_lcn;
	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 error;

	vi = m->vi;
	map = m->map;
	lcn = m->lcn;
	headlcn = map->m_la >> vi->z_lclusterbits;
	for (;;) {
		if (z_erofs_lcluster_pos(lcn, vi->z_lclusterbits, 0,
		    &lcnpos) != 0)
			return (EINTEGRITY);
		if (lcnpos >= vi->size) {
			if (map->m_la > vi->size)
				return (EINTEGRITY);
			map->m_llen = vi->size - map->m_la;
			return (0);
		}
		error = z_erofs_load_lcluster_from_disk(m, lcn, true);
		if (error != 0)
			return (error);
		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;
		}
		if (z_erofs_lcn_advance(&lcn, m->delta[1]) != 0)
			return (EINTEGRITY);
	}
	if (z_erofs_lcluster_pos(lcn, vi->z_lclusterbits, m->clusterofs,
	    &lend) != 0 || lend < map->m_la)
		return (EINTEGRITY);
	map->m_llen = lend - map->m_la;
	return (0);
}

static int
z_erofs_extent_add(uint64_t left, uint64_t right, uint64_t *result)
{
	if (__builtin_add_overflow(left, right, result))
		return (EINTEGRITY);
	return (0);
}

static int
z_erofs_extent_roundup(uint64_t value, unsigned int alignment,
    uint64_t *result)
{
	uint64_t rounded;

	if (z_erofs_extent_add(value, alignment - 1, &rounded) != 0)
		return (EINTEGRITY);
	*result = rounddown2(rounded, alignment);
	return (0);
}

static int
z_erofs_extent_table_pos(const struct erofs_inode *vi, unsigned int recsz,
    erofs_off_t *result)
{
	erofs_off_t pos;

	if (z_erofs_extent_add(vi->inode_off, vi->inode_isize, &pos) != 0 ||
	    z_erofs_extent_add(pos, vi->xattr_isize, &pos) != 0 ||
	    z_erofs_extent_roundup(pos, 8, &pos) != 0 ||
	    z_erofs_extent_add(pos, sizeof(struct z_erofs_map_header), &pos) != 0 ||
	    z_erofs_extent_roundup(pos, recsz, result) != 0)
		return (EINTEGRITY);
	return (0);
}

static int
z_erofs_extent_record_pos(const struct erofs_inode *vi, erofs_off_t table_pos,
    unsigned int recsz, uint64_t index, erofs_off_t *result)
{
	uint64_t offset;

	if (index >= vi->z_extents ||
	    __builtin_mul_overflow(index, recsz, &offset) ||
	    z_erofs_extent_add(table_pos, offset, result) != 0)
		return (EINTEGRITY);
	return (0);
}

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(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_validate_extent_table(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    unsigned int recsz)
{
	struct erofs_buf buf = EROFS_BUF_INITIALIZER;
	struct z_erofs_extent ext;
	erofs_off_t extent_pos, record_pos, scan_pos, table_end;
	uint64_t index, lstart, previous;
	size_t chunk_len, offset;
	int error;

	if (vi->z_extents == 0)
		return (vi->size == 0 ? 0 : EINTEGRITY);
	error = z_erofs_extent_table_pos(vi, recsz, &extent_pos);
	if (error != 0)
		return (error);
	if (recsz <= offsetof(struct z_erofs_extent, pstart_lo) &&
	    z_erofs_extent_add(extent_pos, sizeof(uint64_t), &extent_pos) != 0)
		return (EINTEGRITY);
	error = z_erofs_extent_record_pos(vi, extent_pos, recsz,
	    vi->z_extents - 1, &record_pos);
	if (error != 0 ||
	    z_erofs_extent_add(record_pos, recsz, &table_end) != 0)
		return (EINTEGRITY);
	if (recsz <= offsetof(struct z_erofs_extent, pstart_hi))
		return (z_erofs_read_extent(sbi, vi, record_pos, recsz, &ext));
	if (vi->size == 0)
		return (EINTEGRITY);

	scan_pos = extent_pos;
	index = 0;
	previous = 0;
	while (scan_pos < table_end) {
		chunk_len = (size_t)MIN(table_end - scan_pos,
		    (uint64_t)Z_EROFS_EXTENT_VALIDATE_CHUNK_SIZE);
		error = erofs_read_metadata(sbi, vi->nid, scan_pos, chunk_len, &buf);
		if (error != 0)
			return (error);
		for (offset = 0; offset < chunk_len; offset += recsz, index++) {
			bzero(&ext, sizeof(ext));
			memcpy(&ext, (const char *)buf.data + offset, recsz);
			lstart = z_erofs_extent_lstart(&ext, recsz);
			if (lstart >= vi->size ||
			    (index != 0 && lstart <= previous)) {
				error = EINTEGRITY;
				goto out;
			}
			previous = lstart;
		}
		erofs_put_metabuf(&buf);
		scan_pos += chunk_len;
	}
	return (index == vi->z_extents ? 0 : EINTEGRITY);
out:
	erofs_put_metabuf(&buf);
	return (error);
}

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, end_lcn;
	erofs_off_t fragmentoff;
	unsigned int endoff;
	int error;

	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);
	error = z_erofs_load_lcluster_from_disk(&m, initial_lcn, false);
	if (error != 0)
		return (error);
	if ((flags & EROFS_GET_BLOCKS_FINDTAIL) != 0 && ztailpacking)
		vi->z_fragmentoff = m.nextpackoff;

	map->m_flags = EROFS_MAP_MAPPED | EROFS_MAP_PARTIAL_MAPPED;
	end_lcn = m.lcn;
	if (z_erofs_lcn_advance(&end_lcn, 1) != 0 ||
	    z_erofs_lcluster_pos(end_lcn, vi->z_lclusterbits, 0, &end) != 0)
		return (EINTEGRITY);
	if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD &&
	    endoff >= m.clusterofs) {
		m.headtype = m.type;
		if (z_erofs_lcluster_pos(m.lcn, vi->z_lclusterbits,
		    m.clusterofs, &map->m_la) != 0)
			return (EINTEGRITY);
		if (ztailpacking && end > vi->size)
			end = vi->size;
	} else {
		if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD) {
			if (z_erofs_lcluster_pos(m.lcn, vi->z_lclusterbits,
			    m.clusterofs, &end) != 0)
				return (EINTEGRITY);
			map->m_flags &= ~EROFS_MAP_PARTIAL_MAPPED;
			m.delta[0] = 1;
		}
		error = z_erofs_extent_lookback(&m, m.delta[0]);
		if (error != 0)
			return (error);
	}
	if (m.partialref)
		map->m_flags |= EROFS_MAP_PARTIAL_REF;
	if (end < map->m_la)
		return (EINTEGRITY);
	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) {
			error = z_erofs_fragment_offset(vi->z_fragmentoff, m.pblk,
			    &fragmentoff);
			if (error != 0)
				return (error);
			vi->z_fragmentoff = fragmentoff;
		}
	}
	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;
		if ((map->m_pa & (sbi->block_size - 1)) + map->m_plen >
		    sbi->block_size)
			return (EINTEGRITY);
	} else if (fragment && m.lcn == vi->z_tailextent_headlcn) {
		map->m_flags = EROFS_MAP_FRAGMENT;
	} else {
		if (__builtin_mul_overflow(m.pblk, (erofs_blk_t)sbi->block_size,
		    &map->m_pa))
			return (EINTEGRITY);
		error = z_erofs_get_extent_compressedlen(&m, initial_lcn);
		if (error != 0)
			return (error);
	}

	if (m.headtype == Z_EROFS_LCLUSTER_TYPE_PLAIN) {
		map->m_algorithmformat =
		    (vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) != 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 ||
	    ((flags & EROFS_GET_BLOCKS_READMORE) != 0 &&
	    (map->m_algorithmformat == Z_EROFS_COMPRESSION_LZMA ||
	    map->m_algorithmformat == Z_EROFS_COMPRESSION_DEFLATE ||
	    map->m_algorithmformat == Z_EROFS_COMPRESSION_ZSTD) &&
	    map->m_llen >= sbi->block_size)) {
		error = z_erofs_get_extent_decompressedlen(&m);
		if (error == 0)
			map->m_flags &= ~EROFS_MAP_PARTIAL_MAPPED;
		return (error);
	}
	return (0);
}

static int
z_erofs_map_blocks_ext(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    struct erofs_map_blocks *map, int flags)
{
	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, next, rounded_lend;
	erofs_off_t extent_pos, pos, table_pos;
	uint64_t fragmentoff, lend, l, r, mid, pa, la, lstart;
	bool interlaced, last;
	int error;

	(void)flags;
	interlaced =
	    (vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) != 0;
	recsz = z_erofs_extent_recsize(vi->z_advise);
	error = z_erofs_extent_table_pos(vi, recsz, &table_pos);
	if (error != 0)
		return (error);
	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(sbi, vi->nid, pos,
			    sizeof(uint64_t), &buf);
			if (error != 0)
				return (error);
			pa = le64dec(buf.data);
			erofs_put_metabuf(&buf);
			if (z_erofs_extent_add(pos, sizeof(uint64_t), &pos) != 0)
				return (EINTEGRITY);
			lstart = 0;
			extent_idx = 0;
		} else {
			lstart = rounddown2(map->m_la, cluster_size);
			extent_idx = lstart >> vi->z_lclusterbits;
			pa = EROFS_NULL_ADDR;
		}
		for (;;) {
			error = z_erofs_extent_record_pos(vi, pos, recsz,
			    extent_idx, &extent_pos);
			if (error != 0)
				return (error);
			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;
				if (z_erofs_extent_add(pa,
				    map->m_plen & Z_EROFS_EXTENT_PLEN_MASK,
				    &next) != 0)
					return (EINTEGRITY);
				pa = next;
			} else {
				map->m_pa = le32toh(ext.pstart_lo);
			}
			if (extent_idx == UINT64_MAX)
				return (EINTEGRITY);
			extent_idx++;
			if (z_erofs_extent_add(lstart, cluster_size, &next) != 0)
				return (EINTEGRITY);
			lstart = next;
			if (lstart > map->m_la)
				break;
		}
		if (z_erofs_extent_roundup(lend, cluster_size, &rounded_lend) != 0)
			return (EINTEGRITY);
		last = lstart >= rounded_lend;
		lend = MIN(lstart, lend);
		lstart -= cluster_size;
	} else {
		lstart = lend;
		for (l = 0, r = vi->z_extents; l < r;) {
			mid = l + (r - l) / 2;
			error = z_erofs_extent_record_pos(vi, table_pos, recsz, mid,
			    &extent_pos);
			if (error != 0)
				return (error);
			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;
				if (la > lend)
					return (EINTEGRITY);
				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)) {
				error = z_erofs_fragment_offset(map->m_plen, map->m_pa,
				    &fragmentoff);
				if (error != 0)
					return (error);
				vi->z_fragmentoff = fragmentoff;
			} else {
				vi->z_fragmentoff = map->m_plen;
			}
		} else if ((map->m_plen & Z_EROFS_EXTENT_PLEN_MASK) != 0) {
			map->m_flags = EROFS_MAP_MAPPED;
			if ((map->m_plen >> Z_EROFS_EXTENT_PLEN_FMT_BIT) >
			    UINT8_MAX)
				return (EINTEGRITY);
			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)
				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, rounded_size;
	erofs_off_t pos;
	unsigned int recsz;
	int error;

	if (vi->z_initialized)
		return (0);
	if (z_erofs_extent_add(vi->inode_off, vi->inode_isize, &pos) != 0 ||
	    z_erofs_extent_add(pos, vi->xattr_isize, &pos) != 0 ||
	    z_erofs_extent_roundup(pos, 8, &pos) != 0)
		return (EINTEGRITY);
	error = erofs_read_metadata(sbi, vi->nid, pos, sizeof(*h), &buf);
	if (error != 0)
		return (error);
	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 (EINTEGRITY);
		}
		raw = le64dec(h);
		vi->z_advise = Z_EROFS_ADVISE_FRAGMENT_PCLUSTER;
		vi->z_fragmentoff = raw ^ (1ULL << 63);
		vi->z_tailextent_headlcn = 0;
		vi->fragment = true;
		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);
	if (vi->z_lclusterbits >= 31) {
		erofs_put_metabuf(&buf);
		return (EINTEGRITY);
	}
	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;
			if (z_erofs_extent_roundup(vi->size, cluster_size,
			    &rounded_size) != 0) {
				erofs_put_metabuf(&buf);
				return (EINTEGRITY);
			}
			vi->z_extents = rounded_size >> vi->z_lclusterbits;
		} else {
			vi->z_extents = le32toh(h->h_extents_lo) |
			    ((uint64_t)le16toh(h->h_extents_hi) << 32);
		}
		vi->fragment =
		    (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0;
		erofs_put_metabuf(&buf);
		if (vi->fragment &&
		    (!erofs_sb_has_fragments(sbi) || sbi->packed_nid == 0))
			return (EINTEGRITY);
		if (recsz > offsetof(struct z_erofs_extent, pstart_hi) &&
		    vi->z_extents == 0 && vi->size != 0)
			return (EINTEGRITY);
		error = z_erofs_validate_extent_table(sbi, vi, recsz);
		if (error != 0)
			return (error);
		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);
	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);

	if (!erofs_sb_has_big_pcluster(sbi) &&
	    (vi->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 |
	    Z_EROFS_ADVISE_BIG_PCLUSTER_2)) != 0)
		return (EINTEGRITY);
	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 (EINTEGRITY);
	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 (EINTEGRITY);
		bzero(&map, sizeof(map));
		error = z_erofs_map_blocks_fo(sbi, vi, &map,
		    EROFS_GET_BLOCKS_FINDTAIL);
		if (error != 0)
			return (error);
	}
	vi->fragment =
	    (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0;
	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) {
		if ((map->m_flags & (EROFS_MAP_MAPPED | EROFS_MAP_META)) != 0 ||
		    sbi->packed_inode == NULL || sbi->packed_inode->nid == vi->nid ||
		    vi->z_fragmentoff > sbi->packed_inode->size ||
		    map->m_llen > sbi->packed_inode->size - vi->z_fragmentoff)
			return (EINTEGRITY);
		return (0);
	}
	if ((map->m_flags & EROFS_MAP_MAPPED) == 0)
		return (0);
	if (map->m_algorithmformat >= Z_EROFS_COMPRESSION_RUNTIME_MAX)
		return (EOPNOTSUPP);
	if (map->m_algorithmformat < Z_EROFS_COMPRESSION_MAX) {
		if ((sbi->available_compr_algs &
		    (1U << map->m_algorithmformat)) == 0)
			return (EINTEGRITY);
		if (EROFS_MAP_FULL(map->m_flags) && map->m_llen < map->m_plen)
			return (EINTEGRITY);
	} else if (map->m_llen > map->m_plen) {
		return (EINTEGRITY);
	}
	if (map->m_plen > Z_EROFS_PCLUSTER_MAX_SIZE ||
	    map->m_llen > Z_EROFS_PCLUSTER_MAX_DSIZE)
		return (EOPNOTSUPP);
	if ((map->m_flags & EROFS_MAP_META) != 0)
		return (0);
	if (z_erofs_physical_end(map->m_pa, map->m_plen,
	    sbi->block_size) != 0)
		return (EINTEGRITY);
	(void)vi;
	return (0);
}

int
z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,
    struct erofs_map_blocks *map)
{
	int error;

	if (map->m_la >= vi->size) {
		error = z_erofs_post_eof_len(map->m_la, vi->size,
		    &map->m_llen);
		if (error != 0)
			return (error);
		map->m_la = vi->size;
		map->m_flags = 0;
		return (0);
	}
	error = z_erofs_fill_inode(sbi, vi);
	if (error == 0) {
		if (vi->datalayout == EROFS_INODE_COMPRESSED_FULL &&
		    (vi->z_advise & Z_EROFS_ADVISE_EXTENTS) != 0)
			error = z_erofs_map_blocks_ext(sbi, vi, map,
			    EROFS_GET_BLOCKS_FIEMAP);
		else
			error = z_erofs_map_blocks_fo(sbi, vi, map,
			    EROFS_GET_BLOCKS_FIEMAP);
	}
	if (error == 0)
		error = z_erofs_map_sanity_check(sbi, vi, map);
	if (error != 0)
		map->m_llen = 0;
	return (error);
}
