commit 0bbc2491557e560fadb339822a02356feca91ba5 Author: imrcpan Date: Fri Aug 14 12:01:38 2026 +0200 update diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3d436fa --- /dev/null +++ b/.clang-format @@ -0,0 +1,199 @@ +# Basic .clang-format +--- +BasedOnStyle: WebKit +AlignAfterOpenBracket: DontAlign +AlignConsecutiveMacros: AcrossEmptyLines +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: false +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: TopLevelDefinitions +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: WebKit +BreakBeforeTernaryOperators: false +# TODO: BreakStringLiterals can cause very strange formatting so turn it off? +BreakStringLiterals: false +# Prefer: +# some_var = function(arg1, +# arg2) +# over: +# some_var = +# function(arg1, arg2) +PenaltyBreakAssignment: 100 +# Prefer: +# some_long_function(arg1, arg2 +# arg3) +# over: +# some_long_function( +# arg1, arg2, arg3) +PenaltyBreakBeforeFirstCallParameter: 100 +CompactNamespaces: true +DerivePointerAlignment: false +DisableFormat: false +ForEachMacros: + - ARB_ARRFOREACH + - ARB_ARRFOREACH_REVWCOND + - ARB_ARRFOREACH_REVERSE + - ARB_FOREACH + - ARB_FOREACH_FROM + - ARB_FOREACH_SAFE + - ARB_FOREACH_REVERSE + - ARB_FOREACH_REVERSE_FROM + - ARB_FOREACH_REVERSE_SAFE + - BIT_FOREACH_ISCLR + - BIT_FOREACH_ISSET + - CPU_FOREACH + - CPU_FOREACH_ISCLR + - CPU_FOREACH_ISSET + - FOREACH_THREAD_IN_PROC + - FOREACH_PROC_IN_SYSTEM + - FOREACH_PRISON_CHILD + - FOREACH_PRISON_DESCENDANT + - FOREACH_PRISON_DESCENDANT_LOCKED + - FOREACH_PRISON_DESCENDANT_LOCKED_LEVEL + - MNT_VNODE_FOREACH_ALL + - MNT_VNODE_FOREACH_ACTIVE + - RB_FOREACH + - RB_FOREACH_FROM + - RB_FOREACH_SAFE + - RB_FOREACH_REVERSE + - RB_FOREACH_REVERSE_FROM + - RB_FOREACH_REVERSE_SAFE + - SLIST_FOREACH + - SLIST_FOREACH_FROM + - SLIST_FOREACH_FROM_SAFE + - SLIST_FOREACH_SAFE + - SLIST_FOREACH_PREVPTR + - SPLAY_FOREACH + - LIST_FOREACH + - LIST_FOREACH_FROM + - LIST_FOREACH_FROM_SAFE + - LIST_FOREACH_SAFE + - STAILQ_FOREACH + - STAILQ_FOREACH_FROM + - STAILQ_FOREACH_FROM_SAFE + - STAILQ_FOREACH_SAFE + - TAILQ_FOREACH + - TAILQ_FOREACH_FROM + - TAILQ_FOREACH_FROM_SAFE + - TAILQ_FOREACH_REVERSE + - TAILQ_FOREACH_REVERSE_FROM + - TAILQ_FOREACH_REVERSE_FROM_SAFE + - TAILQ_FOREACH_REVERSE_SAFE + - TAILQ_FOREACH_SAFE + - VM_MAP_ENTRY_FOREACH + - VM_PAGE_DUMP_FOREACH +SpaceBeforeParens: ControlStatementsExceptForEachMacros +IndentCaseLabels: false +IndentPPDirectives: None +Language: Cpp +NamespaceIndentation: None +PointerAlignment: Right +ContinuationIndentWidth: 4 +IndentWidth: 8 +TabWidth: 8 +ColumnLimit: 80 +UseTab: Always +SpaceAfterCStyleCast: false +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^\"opt_.*\.h\"' + Priority: 1 + SortPriority: 10 + - Regex: '^' + Priority: 2 + SortPriority: 20 + - Regex: '^' + Priority: 2 + SortPriority: 21 + - Regex: '^' + Priority: 2 + SortPriority: 22 + - Regex: '^' + Priority: 2 + SortPriority: 23 + - Regex: '^' + Priority: 3 + SortPriority: 30 + - Regex: '^ diff --git a/data.c b/data.c new file mode 100644 index 0000000..fdc138b --- /dev/null +++ b/data.c @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017-2018 HUAWEI, Inc. + * https://www.huawei.com/ + * Copyright (C) 2021, Alibaba Cloud + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +/* + * For flat inline files, compute the "inline tail" start offset. + * Linux EROFS semantics: the last logical block may be tailpacked + * into the inode metadata area. + */ +static uint64_t +erofs_inline_tail_start(const struct erofs_mount *em, + const struct erofs_node *en) +{ + if (en->size == 0) + return (0); + return (roundup2(en->size, (uint64_t)em->block_size) - em->block_size); +} + +static int +erofs_check_device_range(const struct erofs_mount *em, + const struct erofs_device_info *dif, uint64_t off, uint64_t len) +{ + uint64_t end, limit; + + if (dif->blocks > (UINT64_MAX >> em->block_bits) || + __builtin_add_overflow(off, len, &end)) + return (EINTEGRITY); + limit = dif->blocks << em->block_bits; + return (end > limit ? EINTEGRITY : 0); +} + +int +erofs_map_dev(struct erofs_mount *em, struct erofs_map_dev *map) +{ + struct erofs_device_info *dif; + uint64_t start; + unsigned int id; + int error; + + map->m_em = em; + map->m_dif = &em->dif0; + if (map->m_deviceid != 0) { + if (map->m_deviceid > em->extra_devices || em->devs == NULL) + return (ENODEV); + dif = &em->devs[map->m_deviceid - 1]; + error = erofs_check_device_range(em, dif, map->m_pa, + map->m_plen); + if (error != 0) + return (error); + if (em->flatdev) { + if (dif->uniaddr > (UINT64_MAX >> em->block_bits)) + return (EINTEGRITY); + start = dif->uniaddr << em->block_bits; + if (__builtin_add_overflow(map->m_pa, start, &map->m_pa)) + return (EINTEGRITY); + return (0); + } + if (dif->devvp == NULL || dif->cp == NULL) + return (ENODEV); + map->m_dif = dif; + return (0); + } + + if (em->extra_devices == 0) + return (0); + if (em->flatdev) { + error = erofs_check_device_range(em, &em->dif0, map->m_pa, + map->m_plen); + if (error == 0) + return (0); + for (id = 0; id < em->extra_devices; ++id) { + dif = &em->devs[id]; + if (dif->uniaddr == 0 || + dif->uniaddr > (UINT64_MAX >> em->block_bits)) + continue; + start = dif->uniaddr << em->block_bits; + if (map->m_pa < start) + continue; + error = erofs_check_device_range(em, dif, + map->m_pa - start, map->m_plen); + if (error == 0) + return (0); + if (map->m_pa - start < + (dif->blocks << em->block_bits)) + return (error); + } + return (EINTEGRITY); + } + for (id = 0; id < em->extra_devices; ++id) { + dif = &em->devs[id]; + if (dif->uniaddr == 0) + continue; + if (dif->uniaddr > (UINT64_MAX >> em->block_bits)) + return (EINTEGRITY); + start = dif->uniaddr << em->block_bits; + if (map->m_pa >= start && + map->m_pa - start < (dif->blocks << em->block_bits)) { + error = erofs_check_device_range(em, dif, + map->m_pa - start, map->m_plen); + if (error != 0) + return (error); + if (dif->devvp == NULL || dif->cp == NULL) + return (ENODEV); + map->m_pa -= start; + map->m_dif = dif; + break; + } + } + return (0); +} + +/* Map chunk-based file to physical extent */ +static int +erofs_map_blocks_chunk(struct erofs_mount *em, struct erofs_node *en, + uint64_t loff, uint64_t *phys_off, unsigned int *device_id, + size_t *run_len, bool *hole) +{ + struct erofs_inode_chunk_index *idx; + void *buf; + uint64_t chunk_idx, idx_off, chunk_size, entry_size, chunk_off; + uint64_t idx_base, image_size, addrmask; + uint64_t blkaddr; + uint16_t raw_device_id; + int error; + + chunk_size = 1ULL << en->chunkbits; + chunk_idx = loff >> en->chunkbits; + chunk_off = loff & (chunk_size - 1); + + if ((en->chunkformat & EROFS_CHUNK_FORMAT_INDEXES) != 0) + entry_size = sizeof(struct erofs_inode_chunk_index); + else + entry_size = EROFS_BLOCK_MAP_ENTRY_SIZE; + if (en->inode_off > UINT64_MAX - en->inode_isize || + en->inode_off + en->inode_isize > UINT64_MAX - en->xattr_isize) + return (EOVERFLOW); + idx_base = en->inode_off + en->inode_isize + en->xattr_isize; + if (idx_base > UINT64_MAX - (entry_size - 1)) + return (EOVERFLOW); + idx_base = roundup2(idx_base, entry_size); + if (chunk_idx > (UINT64_MAX - idx_base) / entry_size) + return (EOVERFLOW); + idx_off = idx_base + chunk_idx * entry_size; + if (erofs_nid_in_metabox(en->nid)) { + if (em->metabox_en == NULL || idx_off > em->metabox_en->size || + entry_size > em->metabox_en->size - idx_off) + return (EINTEGRITY); + } else { + if (em->blocks > (UINT64_MAX >> em->block_bits)) + return (EOVERFLOW); + image_size = em->blocks << em->block_bits; + if (idx_off > image_size || entry_size > image_size - idx_off) + return (EINTEGRITY); + } + + error = erofs_read_metadata(em, en->nid, idx_off, entry_size, &buf); + if (error != 0) + return (error); + + idx = buf; + if ((en->chunkformat & EROFS_CHUNK_FORMAT_INDEXES) != 0) { + blkaddr = le32toh(idx->startblk_lo); + if ((en->chunkformat & EROFS_CHUNK_FORMAT_48BIT) != 0) + blkaddr |= (uint64_t)le16toh(idx->startblk_hi) << 32; + raw_device_id = le16toh(idx->device_id); + addrmask = (en->chunkformat & EROFS_CHUNK_FORMAT_48BIT) != 0 ? + ((1ULL << 48) - 1) : UINT32_MAX; + } else { + blkaddr = le32toh(*(__le32 *)idx); + raw_device_id = 0; + addrmask = UINT32_MAX; + } + erofs_brelse(buf); + + if (!((blkaddr ^ EROFS_NULL_ADDR) & addrmask)) { + *hole = true; + *phys_off = 0; + *run_len = MIN(chunk_size - chunk_off, en->size - loff); + return (0); + } + + *run_len = MIN(chunk_size - chunk_off, en->size - loff); + *device_id = raw_device_id & em->device_id_mask; + if (blkaddr > (UINT64_MAX >> em->block_bits)) + return (EOVERFLOW); + *phys_off = blkaddr << em->block_bits; + if (chunk_off > UINT64_MAX - *phys_off) + return (EOVERFLOW); + *phys_off += chunk_off; + *hole = false; + return (0); +} + +static int +erofs_bread_device(struct erofs_mount *em, struct erofs_device_info *dif, + erofs_blk_t blocks, uint64_t off, size_t len, void **bufp) +{ + struct buf *bp; + uint64_t end, limit; + off_t blkoff, current; + size_t blklen, done, iosize; + char *out; + int error; + + if (bufp == NULL) + return (EINVAL); + *bufp = NULL; + if (len == 0) { + return (0); + } + if (dif == NULL || dif->devvp == NULL || dif->cp == NULL) + return (ENODEV); + if (__builtin_add_overflow(off, (uint64_t)len, &end)) + return (EINTEGRITY); + if (blocks != 0) { + if (blocks > (UINT64_MAX >> em->block_bits)) + return (EINTEGRITY); + limit = blocks << em->block_bits; + if (end > limit) + return (EINTEGRITY); + } + if (end > dif->mediasize) + return (ENXIO); + if (off > INT64_MAX || end > (uint64_t)INT64_MAX + 1) + return (EOVERFLOW); + + iosize = em->block_size != 0 ? em->block_size : dif->sectorsize; + if (iosize == 0 || (iosize & (iosize - 1)) != 0) + return (EINVAL); + out = malloc(len, M_EROFS, M_WAITOK); + done = 0; + while (done < len) { + current = (off_t)(off + done); + blkoff = rounddown2(current, (off_t)iosize); + blklen = MIN(iosize - (size_t)(current - blkoff), len - done); + error = bread(dif->devvp, btodb(blkoff), iosize, NOCRED, &bp); + if (error != 0) { + free(out, M_EROFS); + return (error); + } + if (bp->b_data == NULL) { + brelse(bp); + free(out, M_EROFS); + return (EIO); + } + memcpy(out + done, (char *)bp->b_data + (current - blkoff), + blklen); + brelse(bp); + done += blklen; + } + *bufp = out; + return (0); +} + +int +erofs_bread(struct erofs_mount *em, uint64_t off, size_t len, void **bufp) +{ + return (erofs_bread_device(em, &em->dif0, em->dif0.blocks, off, len, + bufp)); +} + +int +erofs_read_physical(struct erofs_mount *em, unsigned int device_id, + uint64_t off, size_t len, void **bufp) +{ + struct erofs_map_dev map; + erofs_blk_t blocks; + int error; + + map = (struct erofs_map_dev) { + .m_pa = off, + .m_plen = len, + .m_deviceid = device_id, + }; + error = erofs_map_dev(em, &map); + if (error != 0) + return (error); + blocks = map.m_dif->blocks; + if (map.m_dif == &em->dif0 && em->flatdev) + blocks = em->flatdev_blocks; + return (erofs_bread_device(em, map.m_dif, blocks, map.m_pa, len, bufp)); +} + +/* Release a contiguous buffer returned by erofs_bread(). */ +void +erofs_brelse(void *buf) +{ + free(buf, M_EROFS); +} + +/* Read inode metadata from either the primary image or the metabox file. */ +int +erofs_read_metadata(struct erofs_mount *em, erofs_nid_t nid, uint64_t off, + size_t len, void **bufp) +{ + if (!erofs_nid_in_metabox(nid)) { + if (off > INT64_MAX) + return (EOVERFLOW); + return (erofs_bread(em, (off_t)off, len, bufp)); + } + if (!erofs_sb_has_metabox(em) || em->metabox_en == NULL) + return (EINTEGRITY); + return (erofs_read_data(em, em->metabox_en, off, len, bufp)); +} + +/* + * Map a logical file offset to a physical position for an uncompressed + * plain/inline inode. + * + * Output: + * - phys_off: physical byte offset; + * - run_len: contiguous length readable from the current position; + * - hole: whether the current range maps to a zero-filled hole (NULL_ADDR). + */ +int +erofs_map_blocks(struct erofs_mount *em, struct erofs_node *en, uint64_t loff, + uint64_t *phys_off, unsigned int *device_id, size_t *run_len, bool *hole, + bool *metadata) +{ + uint64_t tail_start, remain, block_rem; + + *phys_off = 0; + *device_id = 0; + *run_len = 0; + *hole = false; + *metadata = false; + if (loff >= en->size) + return (0); + + remain = en->size - loff; + switch (en->datalayout) { + case EROFS_INODE_CHUNK_BASED: + return (erofs_map_blocks_chunk(em, en, loff, phys_off, device_id, + run_len, hole)); + case EROFS_INODE_FLAT_PLAIN: + block_rem = em->block_size - (loff & (em->block_size - 1)); + *run_len = MIN(remain, block_rem); + if (en->startblk == EROFS_NULL_ADDR) { + *hole = true; + return (0); + } + if (en->startblk > (UINT64_MAX >> em->block_bits) || + __builtin_add_overflow(en->startblk << em->block_bits, loff, + phys_off)) + return (EINTEGRITY); + return (0); + case EROFS_INODE_FLAT_INLINE: + tail_start = erofs_inline_tail_start(em, en); + if (loff < tail_start) { + block_rem = em->block_size - + (loff & (em->block_size - 1)); + *run_len = MIN(MIN(remain, tail_start - loff), + block_rem); + if (en->startblk == EROFS_NULL_ADDR) { + *hole = true; + return (0); + } + if (en->startblk > (UINT64_MAX >> em->block_bits) || + __builtin_add_overflow(en->startblk << em->block_bits, + loff, phys_off)) + return (EINTEGRITY); + return (0); + } + block_rem = em->block_size - + ((loff - tail_start) & (em->block_size - 1)); + *run_len = MIN(remain, block_rem); + if (__builtin_add_overflow(en->inode_off, en->inode_isize, + phys_off) || __builtin_add_overflow(*phys_off, en->xattr_isize, + phys_off) || __builtin_add_overflow(*phys_off, loff - tail_start, + phys_off)) + return (EINTEGRITY); + *metadata = true; + return (0); + case EROFS_INODE_COMPRESSED_FULL: + case EROFS_INODE_COMPRESSED_COMPACT: + return (EOPNOTSUPP); + default: + return (EOPNOTSUPP); + } +} + +/* + * Read a small range at a logical file offset into a contiguous buffer. + * Primarily used for directory block reads, lookup, and symlink fragment + * parsing. + */ +int +erofs_read_data(struct erofs_mount *em, struct erofs_node *en, uint64_t loff, + size_t len, void **bufp) +{ + char *out; + void *blk; + uint64_t phys_off; + unsigned int device_id; + size_t run_len, done, want; + bool hole, metadata; + int error; + + if (bufp == NULL) + return (EINVAL); + *bufp = NULL; + if (len == 0) { + return (0); + } + if (loff > UINT64_MAX - (uint64_t)len) + return (EOVERFLOW); + if (loff > en->size || (uint64_t)len > en->size - loff) + return (EINTEGRITY); + + /* Compressed file path */ + if (en->datalayout == EROFS_INODE_COMPRESSED_FULL || + en->datalayout == EROFS_INODE_COMPRESSED_COMPACT) + return (z_erofs_read_data(em, en, loff, len, bufp)); + + /* Uncompressed file path */ + out = malloc(len, M_EROFS, M_WAITOK); + done = 0; + while (done < len) { + error = erofs_map_blocks(em, en, loff + done, &phys_off, &device_id, + &run_len, &hole, &metadata); + if (error != 0) { + free(out, M_EROFS); + return (error); + } + if (run_len == 0) { + free(out, M_EROFS); + return (EINTEGRITY); + } + want = MIN(run_len, len - done); + if (hole) { + bzero(out + done, want); + } else { + if (metadata) + error = erofs_read_metadata(em, en->nid, phys_off, + want, &blk); + else + error = erofs_read_physical(em, device_id, phys_off, want, + &blk); + if (error != 0) { + free(out, M_EROFS); + return (error); + } + memcpy(out + done, blk, want); + erofs_brelse(blk); + } + done += want; + } + *bufp = out; + return (0); +} + +/* + * Transfer the logical content of an inode directly into a uio. + * Regular files and symlinks both use this read path. + */ +static int +erofs_read_uio(struct erofs_mount *em, struct erofs_node *en, struct uio *uio) +{ + char zerobuf[PAGE_SIZE]; + void *blk; + uint64_t phys_off; + unsigned int device_id; + size_t run_len, want, chunk; + bool hole, metadata; + int error; + + if (uio->uio_offset < 0) + return (EINVAL); + if ((uint64_t)uio->uio_offset >= en->size) + return (0); + + /* Compressed file path */ + if (en->datalayout == EROFS_INODE_COMPRESSED_FULL || + en->datalayout == EROFS_INODE_COMPRESSED_COMPACT) + return (z_erofs_read_uio(em, en, uio)); + + /* Uncompressed file path */ + bzero(zerobuf, sizeof(zerobuf)); + while (uio->uio_resid > 0 && (uint64_t)uio->uio_offset < en->size) { + error = erofs_map_blocks(em, en, uio->uio_offset, &phys_off, + &device_id, &run_len, &hole, &metadata); + if (error != 0) + return (error); + if (run_len == 0) + break; + want = MIN(run_len, (size_t)uio->uio_resid); + if (hole) { + chunk = want; + while (chunk > 0) { + size_t zlen = MIN(chunk, sizeof(zerobuf)); + + error = uiomove(zerobuf, zlen, uio); + if (error != 0) + return (error); + chunk -= zlen; + } + continue; + } + if (metadata) + error = erofs_read_metadata(em, en->nid, phys_off, want, + &blk); + else + error = erofs_read_physical(em, device_id, phys_off, want, + &blk); + if (error != 0) + return (error); + error = uiomove(blk, want, uio); + erofs_brelse(blk); + if (error != 0) + return (error); + } + return (0); +} + +/* Read symlink target string. */ +int +erofs_readlink_target(struct vnode *vp, struct uio *uio) +{ + return (erofs_read_uio(MTOE(vp->v_mount), VTOE(vp), uio)); +} + +/* Read regular file data. */ +int +erofs_read_file(struct vnode *vp, struct uio *uio, int ioflag) +{ + (void)ioflag; + return (erofs_read_uio(MTOE(vp->v_mount), VTOE(vp), uio)); +} diff --git a/decompressor.c b/decompressor.c new file mode 100644 index 0000000..3f3d84b --- /dev/null +++ b/decompressor.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2019 HUAWEI, Inc. + * https://www.huawei.com/ + * Copyright (C) 2024 Alibaba Cloud + */ + +#include +#include +#include +#include +#include + +#include "internal.h" + +static int +z_erofs_load_lz4_config(struct erofs_mount *em, + const struct erofs_super_block *dsb, const void *data, size_t size) +{ + const struct z_erofs_lz4_cfgs *lz4; + uint32_t max_pclusterblks; + uint16_t distance; + + if (data != NULL) { + if (size < sizeof(*lz4)) + return (EINVAL); + lz4 = data; + distance = le16toh(lz4->max_distance); + max_pclusterblks = le16toh(lz4->max_pclusterblks); + if (max_pclusterblks == 0) + max_pclusterblks = 1; + else if (max_pclusterblks > + (Z_EROFS_PCLUSTER_MAX_SIZE >> em->block_bits)) + return (EINVAL); + } else { + distance = le16toh(dsb->u1.lz4_max_distance); + if (distance == 0 && !erofs_sb_has_lz4_0padding(em)) + return (0); + max_pclusterblks = 1; + em->available_compr_algs = 1U << Z_EROFS_COMPRESSION_LZ4; + } + em->lz4.max_pclusterblks = max_pclusterblks; + em->lz4.max_distance_pages = distance != 0 ? + howmany(distance, PAGE_SIZE) + 1 : + howmany(UINT16_MAX, PAGE_SIZE) + 1; + return (0); +} + +static int +z_erofs_read_cfg(struct erofs_mount *em, uint64_t *offset, void **bufp, + size_t *sizep) +{ + uint8_t length_buf[2]; + uint64_t aligned; + uint16_t length; + void *buf; + int error; + + aligned = roundup2(*offset, 4); + if (aligned > UINT64_MAX - sizeof(length_buf)) + return (EOVERFLOW); + error = erofs_bread(em, aligned, sizeof(length_buf), &buf); + if (error != 0) + return (error); + memcpy(length_buf, buf, sizeof(length_buf)); + erofs_brelse(buf); + length = le16dec(length_buf); + *sizep = length != 0 ? length : UINT16_MAX + 1U; + if (*sizep > 65536 || aligned + sizeof(length_buf) > + UINT64_MAX - *sizep) + return (EOVERFLOW); + *offset = aligned + sizeof(length_buf); + error = erofs_bread(em, *offset, *sizep, bufp); + if (error == 0) + *offset += *sizep; + return (error); +} + +int +z_erofs_parse_cfgs(struct erofs_mount *em, + const struct erofs_super_block *dsb) +{ + uint64_t offset; + uint16_t algorithms; + void *data; + size_t size; + int algorithm, error; + + if (!erofs_sb_has_compr_cfgs(em)) + return (z_erofs_load_lz4_config(em, dsb, NULL, 0)); + algorithms = le16toh(dsb->u1.available_compr_algs); + em->available_compr_algs = algorithms; + if ((algorithms & ~Z_EROFS_ALL_COMPR_ALGS) != 0) + return (EOPNOTSUPP); + offset = EROFS_SUPER_OFFSET + em->sb_size; + for (algorithm = 0; algorithm < Z_EROFS_COMPRESSION_MAX; + ++algorithm) { + if ((algorithms & (1U << algorithm)) == 0) + continue; + error = z_erofs_read_cfg(em, &offset, &data, &size); + if (error != 0) + return (error); + switch (algorithm) { + case Z_EROFS_COMPRESSION_LZ4: + error = z_erofs_load_lz4_config(em, dsb, data, size); + break; + case Z_EROFS_COMPRESSION_LZMA: + error = z_erofs_load_lzma_config(em, data, size); + break; + case Z_EROFS_COMPRESSION_DEFLATE: + error = z_erofs_load_deflate_config(em, data, size); + break; + case Z_EROFS_COMPRESSION_ZSTD: + error = z_erofs_load_zstd_config(em, data, size); + break; + default: + error = EOPNOTSUPP; + break; + } + erofs_brelse(data); + if (error != 0) + return (error); + } + return (0); +} + +static int +z_erofs_transform_plain(struct erofs_mount *em, + const struct erofs_map_blocks *map, const uint8_t *src, size_t srclen, + uint8_t *dst, size_t dstlen) +{ + size_t first, offset; + + if (dstlen > srclen) + return (EINTEGRITY); + if (map->m_algorithmformat == Z_EROFS_COMPRESSION_SHIFTED) { + memmove(dst, src, dstlen); + return (0); + } + first = MIN((size_t)(em->block_size - + (map->m_la & (em->block_size - 1))), dstlen); + offset = (srclen - first) & (em->block_size - 1); + if (offset > srclen || first > srclen - offset) + return (EINTEGRITY); + memmove(dst, src + offset, first); + if (first < dstlen) + memmove(dst + first, src, dstlen - first); + return (0); +} + +int +z_erofs_decompress(struct erofs_mount *em, + const struct erofs_map_blocks *map, const void *src0, size_t srclen, + void *dst, size_t dstlen, bool partial) +{ + const uint8_t *src; + size_t padding, padding_limit; + int ret; + + if (map->m_algorithmformat == Z_EROFS_COMPRESSION_SHIFTED || + map->m_algorithmformat == Z_EROFS_COMPRESSION_INTERLACED) + return (z_erofs_transform_plain(em, map, src0, srclen, dst, + dstlen)); + if ((unsigned char)map->m_algorithmformat >= Z_EROFS_COMPRESSION_MAX) + return (EOPNOTSUPP); + + src = src0; + if (map->m_algorithmformat != Z_EROFS_COMPRESSION_LZ4 || + erofs_sb_has_lz4_0padding(em)) { + padding_limit = MIN(srclen, em->block_size - + (map->m_pa & (em->block_size - 1))); + for (padding = 0; padding < padding_limit && src[padding] == 0; + ++padding) + ; + if (padding == padding_limit) + return (EINTEGRITY); + src += padding; + srclen -= padding; + } + + switch (map->m_algorithmformat) { + case Z_EROFS_COMPRESSION_LZ4: + ret = lz4_decompress(__DECONST(void *, src), dst, srclen, + dstlen, partial); + break; + case Z_EROFS_COMPRESSION_LZMA: + if (em->lzma_dict_size == 0) + return (EINTEGRITY); + ret = lzma_decompress(src, srclen, dst, dstlen, + em->lzma_dict_size, partial); + break; + case Z_EROFS_COMPRESSION_DEFLATE: + ret = deflate_decompress(__DECONST(void *, src), srclen, dst, + dstlen, em->deflate_windowbits, partial); + break; + case Z_EROFS_COMPRESSION_ZSTD: + ret = zstd_decompress(__DECONST(void *, src), srclen, dst, + dstlen, em->zstd_windowlog + 10, partial); + break; + default: + return (EOPNOTSUPP); + } + return (ret == 0 ? 0 : EIO); +} diff --git a/decompressor_deflate.c b/decompressor_deflate.c new file mode 100644 index 0000000..f5696df --- /dev/null +++ b/decompressor_deflate.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* Minimal DEFLATE decompressor for EROFS FreeBSD */ +#include +#include +#include +#include + +#include "internal.h" + +int +z_erofs_load_deflate_config(struct erofs_mount *em, const void *data, + size_t size) +{ + const struct z_erofs_deflate_cfgs *deflate; + + if (size < sizeof(*deflate)) + return (EINVAL); + deflate = data; + if (deflate->windowbits < 8 || deflate->windowbits > 15) + return (EOPNOTSUPP); + em->deflate_windowbits = deflate->windowbits; + return (0); +} + +int +deflate_decompress(void *src, size_t srclen, void *dst, size_t dstlen, int n, + bool partial) +{ + z_stream strm; + uInt in_before, out_before; + int endret, ret; + + if (n < 8 || n > MAX_WBITS || srclen > (size_t)(uInt)-1 || + dstlen > (size_t)(uInt)-1 || dstlen == 0) + return (-1); + + bzero(&strm, sizeof(strm)); + strm.next_in = src; + strm.avail_in = srclen; + strm.next_out = dst; + strm.avail_out = dstlen; + + ret = inflateInit2(&strm, -n); + if (ret != Z_OK) + return (-1); + + ret = Z_OK; + while (strm.avail_out != 0) { + in_before = strm.avail_in; + out_before = strm.avail_out; + ret = inflate(&strm, Z_SYNC_FLUSH); + if (ret == Z_STREAM_END) + break; + if (ret != Z_OK || + (strm.avail_in == in_before && strm.avail_out == out_before)) + break; + } + endret = inflateEnd(&strm); + if (endret != Z_OK || strm.avail_out != 0) + return (-1); + if (partial) + return (ret == Z_OK || ret == Z_STREAM_END ? 0 : -1); + if (ret != Z_STREAM_END || strm.avail_in != 0) + return (-1); + + return (0); +} diff --git a/decompressor_lzma.c b/decompressor_lzma.c new file mode 100644 index 0000000..688acd7 --- /dev/null +++ b/decompressor_lzma.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * EROFS MicroLZMA wrapper around FreeBSD's bundled XZ Embedded decoder. + * The decoder source is compiled with private symbol names because the + * stock xz.ko does not enable its optional MicroLZMA entry points. + */ + +#include +#include +#include + +#include "internal.h" + +#define XZ_DEC_MICROLZMA +#define xz_dec_lzma2_create erofs_xz_dec_lzma2_create +#define xz_dec_lzma2_reset erofs_xz_dec_lzma2_reset +#define xz_dec_lzma2_run erofs_xz_dec_lzma2_run +#define xz_dec_lzma2_end erofs_xz_dec_lzma2_end +#define xz_dec_microlzma_alloc erofs_xz_dec_microlzma_alloc +#define xz_dec_microlzma_reset erofs_xz_dec_microlzma_reset +#define xz_dec_microlzma_run erofs_xz_dec_microlzma_run +#define xz_dec_microlzma_end erofs_xz_dec_microlzma_end +#define xz_malloc erofs_xz_malloc +#define xz_free erofs_xz_free + +static void * +erofs_xz_malloc(unsigned long size) +{ + return (malloc(size, M_EROFS, M_WAITOK)); +} + +static void +erofs_xz_free(void *ptr) +{ + free(ptr, M_EROFS); +} + +#include + +#undef bool +#undef false +#undef true +#undef min + +int +z_erofs_load_lzma_config(struct erofs_mount *em, const void *data, + size_t size) +{ + const struct z_erofs_lzma_cfgs *lzma; + uint32_t dict_size; + + if (size < sizeof(*lzma)) + return (EINVAL); + lzma = data; + if (le16toh(lzma->format) != 0) + return (EOPNOTSUPP); + dict_size = le32toh(lzma->dict_size); + if (dict_size < 4096 || dict_size > Z_EROFS_LZMA_MAX_DICT_SIZE) + return (EOPNOTSUPP); + em->lzma_dict_size = dict_size; + return (0); +} + +int +lzma_decompress(const void *src, size_t srclen, void *dst, size_t dstlen, + uint32_t dict_size, bool partial) +{ + struct xz_dec_microlzma *state; + struct xz_buf buffer; + enum xz_ret ret; + + if (srclen > UINT32_MAX || dstlen > UINT32_MAX) + return (-1); + state = xz_dec_microlzma_alloc(XZ_SINGLE, dict_size); + if (state == NULL) + return (-1); + bzero(&buffer, sizeof(buffer)); + buffer.in = src; + buffer.in_size = srclen; + buffer.out = dst; + buffer.out_size = dstlen; + xz_dec_microlzma_reset(state, (uint32_t)srclen, (uint32_t)dstlen, + !partial); + ret = xz_dec_microlzma_run(state, &buffer); + xz_dec_microlzma_end(state); + if (buffer.out_pos != dstlen) + return (-1); + if (partial) + return (ret == XZ_OK || ret == XZ_STREAM_END ? 0 : -1); + return (ret == XZ_STREAM_END && buffer.in_pos == srclen ? 0 : -1); +} diff --git a/decompressor_zstd.c b/decompressor_zstd.c new file mode 100644 index 0000000..52087e5 --- /dev/null +++ b/decompressor_zstd.c @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* Minimal zstd decompressor for EROFS FreeBSD */ +#include +#include +#include +#include + +#include "internal.h" + +#ifdef ZSTDIO +#define ZSTD_STATIC_LINKING_ONLY +#include +#endif + +bool +erofs_zstd_available(void) +{ +#ifdef ZSTDIO + return (true); +#else + return (false); +#endif +} + +int +z_erofs_load_zstd_config(struct erofs_mount *em, const void *data, + size_t size) +{ + const struct z_erofs_zstd_cfgs *zstd; + + if (!erofs_zstd_available()) { + vfs_mount_error(em->mnt, + "erofs: ZSTD compression requires ZSTDIO support"); + return (EOPNOTSUPP); + } + if (size < sizeof(*zstd)) + return (EINVAL); + zstd = data; + if (zstd->format != 0 || zstd->windowlog > 10) + return (EOPNOTSUPP); + em->zstd_windowlog = zstd->windowlog; + return (0); +} + +#ifdef ZSTDIO +static void * +zstd_alloc(void *opaque, size_t size) +{ + return (malloc(size, opaque, M_WAITOK)); +} + +static void +zstd_free(void *opaque, void *address) +{ + free(address, opaque); +} + +static const ZSTD_customMem zstd_erofs_alloc = { + .customAlloc = zstd_alloc, + .customFree = zstd_free, + .opaque = M_EROFS, +}; + +int +zstd_decompress(void *src, size_t srclen, void *dst, size_t dstlen, int n, + bool partial) +{ + ZSTD_DCtx *dctx; + ZSTD_inBuffer input; + ZSTD_outBuffer output; + size_t in_before, out_before, ret; + int error; + + if (n < 10 || n > 20 || dstlen == 0) + return (-1); + dctx = ZSTD_createDCtx_advanced(zstd_erofs_alloc); + if (dctx == NULL) + return (-1); + ret = ZSTD_DCtx_setParameter(dctx, ZSTD_d_windowLogMax, n); + if (ZSTD_isError(ret)) { + ZSTD_freeDCtx(dctx); + return (-1); + } + + input = (ZSTD_inBuffer) { + .src = src, + .size = srclen, + }; + output = (ZSTD_outBuffer) { + .dst = dst, + .size = dstlen, + }; + ret = 1; + while (output.pos != output.size) { + in_before = input.pos; + out_before = output.pos; + ret = ZSTD_decompressStream(dctx, &output, &input); + if (ZSTD_isError(ret) || + (input.pos == in_before && output.pos == out_before)) + break; + if (ret == 0) + break; + } + error = 0; + if (ZSTD_isError(ret) || output.pos != output.size) + error = -1; + else if (!partial && (ret != 0 || input.pos != input.size)) + error = -1; + ret = ZSTD_freeDCtx(dctx); + if (ZSTD_isError(ret)) + error = -1; + return (error); +} +#else +int +zstd_decompress(void *src, size_t srclen, void *dst, size_t dstlen, int n, + bool partial) +{ + (void)src; + (void)srclen; + (void)dst; + (void)dstlen; + (void)n; + (void)partial; + return (-1); +} +#endif diff --git a/dir.c b/dir.c new file mode 100644 index 0000000..1bfc3d7 --- /dev/null +++ b/dir.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017-2018 HUAWEI, Inc. + * https://www.huawei.com/ + * Copyright (C) 2022, Alibaba Cloud + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" +#include "erofs_defs.h" + +/* Map EROFS directory entry file type to FreeBSD dirent.d_type. */ +static unsigned char +erofs_ftype_to_dtype(uint8_t ftype) +{ + switch (ftype) { + case EROFS_FT_REG_FILE: + return (DT_REG); + case EROFS_FT_DIR: + return (DT_DIR); + case EROFS_FT_CHRDEV: + return (DT_CHR); + case EROFS_FT_BLKDEV: + return (DT_BLK); + case EROFS_FT_FIFO: + return (DT_FIFO); + case EROFS_FT_SOCK: + return (DT_SOCK); + case EROFS_FT_SYMLINK: + return (DT_LNK); + default: + return (DT_UNKNOWN); + } +} + +/* Validate one name slot and return its Linux-visible length. */ +int +erofs_dirent_namelen(const char *blk, uint32_t nameoff, uint32_t endoff, + bool trailing, size_t *namelenp) +{ + size_t namelen, span; + + if (endoff <= nameoff) + return (EINTEGRITY); + span = endoff - nameoff; + if (trailing) { + namelen = strnlen(blk + nameoff, span); + } else { + namelen = span; + if (memchr(blk + nameoff, '\0', span) != NULL) + return (EINTEGRITY); + } + if (namelen == 0 || namelen > EROFS_NAME_LEN) + return (EINTEGRITY); + for (size_t i = 0; i < namelen; i++) { + if (blk[nameoff + i] == '/') + return (EINTEGRITY); + } + *namelenp = namelen; + return (0); +} + +/* Validate the dirent array, name offsets, and names in a block. */ +int +erofs_validate_dirblock(const char *blk, uint32_t blksz, uint32_t maxsize, + uint32_t *ndirentsp) +{ + const struct erofs_dirent *de; + uint32_t endoff, first_nameoff, idx, nameoff, ndirents, prev_nameoff; + size_t namelen; + int error; + + if (blksz < EROFS_DIRENT_SIZE || maxsize < EROFS_DIRENT_SIZE || + maxsize > blksz) + return (EINTEGRITY); + de = (const struct erofs_dirent *)blk; + first_nameoff = le16toh(de[0].nameoff); + if (first_nameoff < EROFS_DIRENT_SIZE || first_nameoff >= maxsize || + (first_nameoff % EROFS_DIRENT_SIZE) != 0) + return (EINTEGRITY); + ndirents = first_nameoff / EROFS_DIRENT_SIZE; + prev_nameoff = 0; + for (idx = 0; idx < ndirents; idx++) { + nameoff = le16toh(de[idx].nameoff); + if ((idx == 0 && nameoff != first_nameoff) || + (idx != 0 && nameoff <= prev_nameoff) || + nameoff < first_nameoff || nameoff >= maxsize) + return (EINTEGRITY); + endoff = idx + 1 < ndirents ? + le16toh(de[idx + 1].nameoff) : maxsize; + if (endoff <= nameoff || endoff > maxsize) + return (EINTEGRITY); + error = erofs_dirent_namelen(blk, nameoff, endoff, + idx + 1 == ndirents, &namelen); + if (error != 0) + return (error); + prev_nameoff = nameoff; + } + *ndirentsp = ndirents; + return (0); +} + +/* Extract name, nid, and type of the idx'th directory entry from a block. */ +static int +erofs_dirent_name(const char *blk, uint32_t maxsize, + uint32_t idx, uint32_t ndirents, char *name, size_t namesz, uint64_t *nid, + uint8_t *ftype, size_t *namelenp) +{ + const struct erofs_dirent *de; + uint32_t nameoff, endoff; + size_t namelen; + int error; + + de = (const struct erofs_dirent *)blk; + nameoff = le16toh(de[idx].nameoff); + if (idx + 1 < ndirents) + endoff = le16toh(de[idx + 1].nameoff); + else + endoff = maxsize; + error = erofs_dirent_namelen(blk, nameoff, endoff, + idx + 1 == ndirents, &namelen); + if (error != 0) + return (error); + if (namelen >= namesz) + return (EINTEGRITY); + memcpy(name, blk + nameoff, namelen); + name[namelen] = '\0'; + *nid = le64toh(de[idx].nid); + *ftype = de[idx].file_type; + *namelenp = namelen; + return (0); +} + +/* Per-call state for readdir dirent/cookie output. */ +struct erofs_uiodir { + struct dirent *dirent; + uint64_t *cookies; + uint64_t last_cookie; + int ncookies; + int acookies; + int eofflag; +}; + +enum erofs_uiodir_result { + EROFS_UIODIR_BUFFER_FULL = -1, + EROFS_UIODIR_OK = 0, +}; + +/* Push a dirent and its cookie to the caller, modelled after UDF. */ +static int +erofs_uiodir(struct erofs_uiodir *uiodir, int de_size, struct uio *uio, + uint64_t cookie) +{ + int error; + + if (cookie <= uiodir->last_cookie) + return (EINTEGRITY); + if (uio->uio_resid < de_size || + (uiodir->cookies != NULL && + uiodir->acookies >= uiodir->ncookies)) { + return (EROFS_UIODIR_BUFFER_FULL); + } + error = uiomove(uiodir->dirent, de_size, uio); + if (error != 0) + return (error); + uiodir->last_cookie = cookie; + if (uiodir->cookies != NULL) + uiodir->cookies[uiodir->acookies++] = cookie; + return (EROFS_UIODIR_OK); +} + +/* + * Process directory entries within a single block and output them to uio. + * (Linux equivalent: erofs_fill_dentries in Linux's dir.c) + * + * Returns 0 on success (all entries consumed), -1 if uio is full, or a + * positive error code on corruption. + */ +static int +erofs_fill_dentries(struct erofs_uiodir *uiodir, struct uio *uio, + struct dirent *d, const char *blk, uint32_t maxsize, + uint32_t start_idx, uint32_t ndirents, uint64_t block_off, + uint64_t *logical_offp) +{ + char name[EROFS_NAME_LEN + 1]; + uint32_t idx; + uint64_t curpos, nextoff, nid; + size_t namelen; + uint8_t ftype; + int error; + + for (idx = start_idx; idx < ndirents; idx++) { + curpos = block_off + idx * EROFS_DIRENT_SIZE; + nextoff = (idx + 1 < ndirents) ? + (curpos + EROFS_DIRENT_SIZE) : + (block_off + maxsize); + error = erofs_dirent_name(blk, maxsize, idx, ndirents, name, + sizeof(name), &nid, &ftype, &namelen); + if (error != 0) + return (error); + bzero(d, sizeof(*d)); + d->d_fileno = nid; + d->d_type = erofs_ftype_to_dtype(ftype); + d->d_namlen = namelen; + d->d_reclen = GENERIC_DIRSIZ(d); + d->d_off = nextoff; + strlcpy(d->d_name, name, sizeof(d->d_name)); + error = erofs_uiodir(uiodir, d->d_reclen, uio, d->d_off); + if (error != 0) + return (error); + *logical_offp = nextoff; + uio->uio_offset = *logical_offp; + } + return (0); +} + +/* + * Read directory contents and output a FreeBSD dirent stream to uio. + * + * Key points: + * - On-disk entries use their logical file offsets as cookies; + * - A dot_omitted directory appends a synthetic "." at i_size, matching + * Linux, so existing on-disk cookies are not shifted; + * - The dirent array occupies only the front portion of a block, so after + * scanning all entries offset must jump to maxsize (the block end), + * otherwise the loop would get stuck on the same block; + * - Supports a_ncookies / a_cookies for NFS and other callers that need + * resumable iteration. + */ +int +erofs_readdir_block(struct vnode *vp, struct uio *uio, int *eofflag, + int *ncookies, uint64_t **cookies) +{ + struct erofs_node *dir; + struct erofs_mount *em; + struct erofs_uiodir uiodir; + struct dirent d; + uint64_t *cookiebuf; + char *blk; + uint64_t block_off, logical_off; + uint32_t block_pos, blksz, ndirents, start_idx, maxsize; + int error; + + dir = VTOE(vp); + em = MTOE(vp->v_mount); + blksz = em->block_size; + error = 0; + cookiebuf = NULL; + uiodir.eofflag = 0; + uiodir.acookies = 0; + uiodir.dirent = &d; + uiodir.cookies = NULL; + uiodir.ncookies = 0; + if (cookies != NULL && ncookies != NULL) { + *cookies = NULL; + *ncookies = 0; + uiodir.ncookies = MAX(1, uio->uio_resid / 8); + cookiebuf = malloc(sizeof(*uiodir.cookies) * uiodir.ncookies, + M_TEMP, M_WAITOK); + uiodir.cookies = cookiebuf; + } + + if (uio->uio_offset < 0) { + error = EINVAL; + goto out; + } + if (dir->dot_omitted && dir->size == (uint64_t)OFF_MAX) { + error = EINTEGRITY; + goto out; + } + + logical_off = uio->uio_offset; + uiodir.last_cookie = logical_off; + uio->uio_offset = logical_off; + + while (logical_off < dir->size) { + block_off = rounddown2(logical_off, (uint64_t)blksz); + maxsize = MIN((uint64_t)blksz, dir->size - block_off); + block_pos = logical_off - block_off; + if ((block_pos % EROFS_DIRENT_SIZE) != 0) { + block_pos = roundup(block_pos, EROFS_DIRENT_SIZE); + logical_off = block_off + block_pos; + uio->uio_offset = logical_off; + } + error = erofs_read_data(em, dir, block_off, maxsize, + (void **)&blk); + if (error != 0) + goto out; + error = erofs_validate_dirblock(blk, blksz, maxsize, &ndirents); + if (error != 0) { + erofs_brelse(blk); + goto out; + } + start_idx = block_pos / EROFS_DIRENT_SIZE; + if (start_idx >= ndirents) { + logical_off = block_off + maxsize; + uio->uio_offset = logical_off; + erofs_brelse(blk); + continue; + } + error = erofs_fill_dentries(&uiodir, uio, &d, blk, maxsize, + start_idx, ndirents, block_off, &logical_off); + erofs_brelse(blk); + if (error != 0) + goto out; + } + if (dir->dot_omitted && logical_off == dir->size) { + bzero(&d, sizeof(d)); + d.d_fileno = dir->nid; + d.d_type = DT_DIR; + d.d_namlen = 1; + d.d_reclen = GENERIC_DIRSIZ(&d); + d.d_off = dir->size + 1; + d.d_name[0] = '.'; + d.d_name[1] = '\0'; + error = erofs_uiodir(&uiodir, d.d_reclen, uio, d.d_off); + if (error != 0) + goto out; + logical_off++; + uio->uio_offset = logical_off; + } + uiodir.eofflag = 1; +out: + if (error == EROFS_UIODIR_BUFFER_FULL) + error = 0; + if (eofflag != NULL && error == 0) + *eofflag = uiodir.eofflag; + if (cookies != NULL && ncookies != NULL) { + if (error != 0) { + free(cookiebuf, M_TEMP); + } else { + *ncookies = uiodir.acookies; + *cookies = cookiebuf; + } + } + return (error); +} diff --git a/erofs_defs.h b/erofs_defs.h new file mode 100644 index 0000000..0a2f28c --- /dev/null +++ b/erofs_defs.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __EROFS_DEFS_H +#define __EROFS_DEFS_H + +/* CRC32C polynomial and seed */ +#define EROFS_CRC32C_SEED 0x5045b54aU + +/* Inode slot alignment */ + +/* Directory entry size */ +#define EROFS_DIRENT_SIZE sizeof(struct erofs_dirent) + +/* RC decoder constants */ + +/* LZ4 format constants */ +#define EROFS_LZ4_TOKEN_LITERAL_SHIFT 4 +#define EROFS_LZ4_TOKEN_MATCH_MASK 0x0F +#define EROFS_LZ4_MAX_RUN 15 +#define EROFS_LZ4_EXT_SENTINEL 255 +#define EROFS_LZ4_MIN_MATCH 4 +#define EROFS_LZ4_OFFSET_BYTES 2 + +#endif /* __EROFS_DEFS_H */ diff --git a/erofs_fs.h b/erofs_fs.h new file mode 100644 index 0000000..c45f26b --- /dev/null +++ b/erofs_fs.h @@ -0,0 +1,472 @@ +/* SPDX-License-Identifier: MIT */ +/* + * EROFS (Enhanced ROM File System) on-disk format definition + * + * Copyright (C) 2017-2018 HUAWEI, Inc. + * https://www.huawei.com/ + * Copyright (C) 2021, Alibaba Cloud + */ +#ifndef __EROFS_FS_H +#define __EROFS_FS_H + +#include +#include + +/* FreeBSD compatibility - Linux-style little-endian types */ +#ifndef __le16 +typedef uint16_t __le16; +typedef uint32_t __le32; +typedef uint64_t __le64; +typedef uint8_t __u8; +#endif + +/* to allow for x86 boot sectors and other oddities. */ +#define EROFS_SUPER_OFFSET 1024 + +#define EROFS_SUPER_MAGIC_V1 0xE0F5E1E2 + +#define EROFS_FEATURE_COMPAT_SB_CHKSUM 0x00000001 +#define EROFS_FEATURE_COMPAT_MTIME 0x00000002 +#define EROFS_FEATURE_COMPAT_XATTR_FILTER 0x00000004 +#define EROFS_FEATURE_COMPAT_SHARED_EA_IN_METABOX 0x00000008 +#define EROFS_FEATURE_COMPAT_PLAIN_XATTR_PFX 0x00000010 +#define EROFS_FEATURE_COMPAT_ISHARE_XATTRS 0x00000020 + +/* + * Any bits that aren't in EROFS_ALL_SUPPORTED_INCOMPAT should + * be incompatible with this kernel version. + */ +#define EROFS_FEATURE_INCOMPAT_LZ4_0PADDING 0x00000001 +#define EROFS_FEATURE_INCOMPAT_COMPR_CFGS 0x00000002 +#define EROFS_FEATURE_INCOMPAT_BIG_PCLUSTER 0x00000002 +#define EROFS_FEATURE_INCOMPAT_CHUNKED_FILE 0x00000004 +#define EROFS_FEATURE_INCOMPAT_DEVICE_TABLE 0x00000008 +#define EROFS_FEATURE_INCOMPAT_COMPR_HEAD2 0x00000008 +#define EROFS_FEATURE_INCOMPAT_ZTAILPACKING 0x00000010 +#define EROFS_FEATURE_INCOMPAT_FRAGMENTS 0x00000020 +#define EROFS_FEATURE_INCOMPAT_DEDUPE 0x00000020 +#define EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES 0x00000040 +#define EROFS_FEATURE_INCOMPAT_48BIT 0x00000080 +#define EROFS_FEATURE_INCOMPAT_METABOX 0x00000100 + +#define EROFS_DIRENT_NID_METABOX_BIT 63 +#define EROFS_DIRENT_NID_METABOX (1ULL << EROFS_DIRENT_NID_METABOX_BIT) +#define EROFS_DIRENT_NID_MASK ((1ULL << EROFS_DIRENT_NID_METABOX_BIT) - 1) + +#define EROFS_ALL_SUPPORTED_INCOMPAT \ + (EROFS_FEATURE_INCOMPAT_LZ4_0PADDING | EROFS_FEATURE_INCOMPAT_48BIT | \ + EROFS_FEATURE_INCOMPAT_COMPR_CFGS | \ + EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES | \ + EROFS_FEATURE_INCOMPAT_ZTAILPACKING | \ + EROFS_FEATURE_INCOMPAT_CHUNKED_FILE | \ + EROFS_FEATURE_INCOMPAT_COMPR_HEAD2 | \ + EROFS_FEATURE_INCOMPAT_FRAGMENTS | \ + EROFS_FEATURE_INCOMPAT_METABOX) + +#define EROFS_SB_EXTSLOT_SIZE 16 + +#define EROFS_NAME_LEN 255 + +/* EROFS inode datalayout (i_format in on-disk inode) */ +enum { + EROFS_INODE_FLAT_PLAIN = 0, + EROFS_INODE_COMPRESSED_FULL = 1, + EROFS_INODE_FLAT_INLINE = 2, + EROFS_INODE_COMPRESSED_COMPACT = 3, + EROFS_INODE_CHUNK_BASED = 4, + EROFS_INODE_DATALAYOUT_MAX +}; + +/* bit definitions of inode i_format */ +#define EROFS_I_VERSION_MASK 0x01 +#define EROFS_I_DATALAYOUT_MASK 0x07 + +#define EROFS_I_VERSION_BIT 0 +#define EROFS_I_DATALAYOUT_BIT 1 +#define EROFS_I_NLINK_1_BIT 4 /* non-directory compact inodes only */ +#define EROFS_I_DOT_OMITTED_BIT 4 /* (directories) omit the `.` dirent */ +#define EROFS_I_ALL ((1 << (EROFS_I_NLINK_1_BIT + 1)) - 1) + +/* file type definitions in directory entries */ +#define EROFS_FT_UNKNOWN 0 +#define EROFS_FT_REG_FILE 1 +#define EROFS_FT_DIR 2 +#define EROFS_FT_CHRDEV 3 +#define EROFS_FT_BLKDEV 4 +#define EROFS_FT_FIFO 5 +#define EROFS_FT_SOCK 6 +#define EROFS_FT_SYMLINK 7 + +/* represent a zeroed chunk (hole) */ +#define EROFS_NULL_ADDR ((uint64_t)-1) + +/* erofs on-disk super block (currently 144 bytes at maximum) */ +struct erofs_super_block { + uint32_t magic; + uint32_t checksum; + uint32_t feature_compat; + uint8_t blkszbits; + uint8_t sb_extslots; + union { + uint16_t rootnid_2b; + uint16_t blocks_hi; + } __packed rb; + uint64_t inos; + uint64_t epoch; + uint32_t fixed_nsec; + uint32_t blocks_lo; + uint32_t meta_blkaddr; + uint32_t xattr_blkaddr; + uint8_t uuid[16]; + uint8_t volume_name[16]; + uint32_t feature_incompat; + union { + uint16_t available_compr_algs; + uint16_t lz4_max_distance; + } __packed u1; + uint16_t extra_devices; + uint16_t devt_slotoff; + uint8_t dirblkbits; + uint8_t xattr_prefix_count; + uint32_t xattr_prefix_start; + uint64_t packed_nid; + uint8_t xattr_filter_reserved; + uint8_t ishare_xattr_prefix_id; + uint8_t reserved[2]; + uint32_t build_time; + uint64_t rootnid_8b; + uint64_t reserved2; + uint64_t metabox_nid; + uint64_t reserved3; +} __packed; + +struct erofs_inode_chunk_info { + __le16 format; + __le16 reserved; +} __packed; + +union erofs_inode_i_u { + __le32 blocks_lo; + __le32 startblk_lo; + __le32 rdev; + struct erofs_inode_chunk_info c; +}; + +union erofs_inode_i_nb { + uint16_t nlink; /* if EROFS_I_NLINK_1_BIT is unset */ + uint16_t blocks_hi; /* total blocks count MSB */ + uint16_t startblk_hi; /* starting block number MSB */ +} __packed; + +/* 32-byte reduced form of an ondisk inode */ +struct erofs_inode_compact { + uint16_t i_format; /* inode format hints */ + uint16_t i_xattr_icount; + uint16_t i_mode; + union erofs_inode_i_nb i_nb; + uint32_t i_size; + uint32_t i_mtime; + union erofs_inode_i_u i_u; + + uint32_t i_ino; /* only used for 32-bit stat compatibility */ + uint16_t i_uid; + uint16_t i_gid; + uint32_t i_reserved; +} __packed; + +/* 64-byte complete form of an ondisk inode */ +struct erofs_inode_extended { + uint16_t i_format; /* inode format hints */ + uint16_t i_xattr_icount; + uint16_t i_mode; + union erofs_inode_i_nb i_nb; + uint64_t i_size; + union erofs_inode_i_u i_u; + + uint32_t i_ino; /* only used for 32-bit stat compatibility */ + uint32_t i_uid; + uint32_t i_gid; + uint64_t i_mtime; + uint32_t i_mtime_nsec; + uint32_t i_nlink; + uint8_t i_reserved2[16]; +} __packed; + +/* dirent sorts in alphabet order, thus we can do binary search */ +struct erofs_dirent { + uint64_t nid; + uint16_t nameoff; + uint8_t file_type; + uint8_t reserved; +} __packed; + +/* + * inline xattrs (n == i_xattr_icount): + * erofs_xattr_ibody_header(1) + (n - 1) * 4 bytes + * 12 bytes / \ + * / \ + * /-----------------------\ + * | erofs_xattr_entries+ | + * +-----------------------+ + * inline xattrs must starts in erofs_xattr_ibody_header, + * for read-only fs, no need to introduce h_refcount + */ +struct erofs_xattr_ibody_header { + uint32_t h_name_filter; /* bit value 1 indicates not-present */ + uint8_t h_shared_count; + uint8_t h_reserved2[7]; + uint32_t h_shared_xattrs[0]; /* shared xattr id array */ +} __packed; + +/* Name indexes */ +#define EROFS_XATTR_INDEX_USER 1 +#define EROFS_XATTR_INDEX_POSIX_ACL_ACCESS 2 +#define EROFS_XATTR_INDEX_POSIX_ACL_DEFAULT 3 +#define EROFS_XATTR_INDEX_TRUSTED 4 +#define EROFS_XATTR_INDEX_LUSTRE 5 +#define EROFS_XATTR_INDEX_SECURITY 6 + +/* + * bit 7 of e_name_index is set when it refers to a long xattr name prefix, + * while the remained lower bits represent the index of the prefix. + */ +#define EROFS_XATTR_LONG_PREFIX 0x80 +#define EROFS_XATTR_LONG_PREFIX_MASK 0x7f + +/* long xattr name prefix */ +struct erofs_xattr_long_prefix { + uint8_t base_index; /* short xattr name prefix index */ + char infix[0]; /* infix apart from short prefix */ +} __packed; + +/* xattr entry (for both inline & shared xattrs) */ +struct erofs_xattr_entry { + uint8_t e_name_len; + uint8_t e_name_index; + uint16_t e_value_size; + char e_name[]; /* attribute name */ +} __packed; + +#define EROFS_XATTR_ALIGN(size) \ + (((size) + sizeof(struct erofs_xattr_entry) - 1) & \ + ~(sizeof(struct erofs_xattr_entry) - 1)) + +static inline unsigned int +erofs_xattr_entry_size(const struct erofs_xattr_entry *entry) +{ + return (EROFS_XATTR_ALIGN( + sizeof(*entry) + entry->e_name_len + le16toh(entry->e_value_size))); +} + +static inline unsigned int +erofs_xattr_ibody_size(uint16_t i_xattr_icount) +{ + if (!i_xattr_icount) + return 0; + + /* 1 header + n-1 * 4 bytes inline xattr to keep continuity */ + return (sizeof(struct erofs_xattr_ibody_header) + + sizeof(uint32_t) * (le16toh(i_xattr_icount) - 1)); +} + +/* compression algorithm types (for h_algorithmtype) */ +enum { + Z_EROFS_COMPRESSION_LZ4 = 0, + Z_EROFS_COMPRESSION_LZMA = 1, + Z_EROFS_COMPRESSION_DEFLATE = 2, + Z_EROFS_COMPRESSION_ZSTD = 3, + Z_EROFS_COMPRESSION_MAX +}; +#define Z_EROFS_ALL_COMPR_ALGS ((1 << Z_EROFS_COMPRESSION_MAX) - 1) + +#define Z_EROFS_PCLUSTER_MAX_SIZE (1024 * 1024) +#define Z_EROFS_PCLUSTER_MAX_DSIZE (12 * 1024 * 1024) + +/* 14 bytes (+ length field = 16 bytes) */ +struct z_erofs_lz4_cfgs { + __le16 max_distance; + __le16 max_pclusterblks; + uint8_t reserved[10]; +} __packed; + +/* 14 bytes (+ length field = 16 bytes) */ +struct z_erofs_lzma_cfgs { + __le32 dict_size; + __le16 format; + uint8_t reserved[8]; +} __packed; + +#define Z_EROFS_LZMA_MAX_DICT_SIZE (8 * Z_EROFS_PCLUSTER_MAX_SIZE) + +/* 6 bytes (+ length field = 8 bytes) */ +struct z_erofs_deflate_cfgs { + uint8_t windowbits; + uint8_t reserved[5]; +} __packed; + +/* 6 bytes (+ length field = 8 bytes) */ +struct z_erofs_zstd_cfgs { + uint8_t format; + uint8_t windowlog; + uint8_t reserved[4]; +} __packed; + +#define Z_EROFS_ZSTD_MAX_DICT_SIZE Z_EROFS_PCLUSTER_MAX_SIZE + +/* z_advise flags */ +#define Z_EROFS_ADVISE_COMPACTED_2B 0x0001 +#define Z_EROFS_ADVISE_EXTENTS 0x0001 +#define Z_EROFS_ADVISE_BIG_PCLUSTER_1 0x0002 +#define Z_EROFS_ADVISE_BIG_PCLUSTER_2 0x0004 +#define Z_EROFS_ADVISE_INLINE_PCLUSTER 0x0008 +#define Z_EROFS_ADVISE_INTERLACED_PCLUSTER 0x0010 +#define Z_EROFS_ADVISE_FRAGMENT_PCLUSTER 0x0020 +#define Z_EROFS_ADVISE_EXTRECSZ_BIT 1 +#define Z_EROFS_ADVISE_EXTRECSZ_MASK 0x3 + +#define Z_EROFS_FRAGMENT_INODE_BIT 7 + +/* Logical cluster types */ +enum { + Z_EROFS_LCLUSTER_TYPE_PLAIN = 0, + Z_EROFS_LCLUSTER_TYPE_HEAD1 = 1, + Z_EROFS_LCLUSTER_TYPE_NONHEAD = 2, + Z_EROFS_LCLUSTER_TYPE_HEAD2 = 3, + Z_EROFS_LCLUSTER_TYPE_MAX +}; + +#define Z_EROFS_LI_LCLUSTER_TYPE_MASK (Z_EROFS_LCLUSTER_TYPE_MAX - 1) +#define Z_EROFS_LI_PARTIAL_REF (1 << 15) +#define Z_EROFS_LI_D0_CBLKCNT (1 << 11) + +/* Compression extent index structures */ +struct z_erofs_lcluster_index { + __le16 di_advise; + __le16 di_clusterofs; + union { + __le32 blkaddr; + __le16 delta[2]; + } di_u; +} __packed; + +struct z_erofs_map_header { + union { + __le32 h_fragmentoff; + struct { + __le16 h_reserved1; + __le16 h_idata_size; + }; + __le32 h_extents_lo; + }; + __le16 h_advise; + union { + struct { + uint8_t h_algorithmtype; + uint8_t h_clusterbits; + } __packed; + __le16 h_extents_hi; + } __packed; +} __packed; + +#define Z_EROFS_MAP_HEADER_END(end) \ + (roundup2((end), 8) + sizeof(struct z_erofs_map_header)) +#define Z_EROFS_FULL_INDEX_START(end) (Z_EROFS_MAP_HEADER_END(end) + 8) + +#define Z_EROFS_EXTENT_PLEN_PARTIAL (1U << 27) +#define Z_EROFS_EXTENT_PLEN_FMT_BIT 28 +#define Z_EROFS_EXTENT_PLEN_MASK ((Z_EROFS_PCLUSTER_MAX_SIZE << 1) - 1) +struct z_erofs_extent { + __le32 plen; + __le32 pstart_lo; + __le32 pstart_hi; + __le32 lstart_lo; + __le32 lstart_hi; + uint8_t reserved[12]; +} __packed; + +static inline unsigned int +z_erofs_extent_recsize(unsigned int advise) +{ + return (4U << ((advise >> Z_EROFS_ADVISE_EXTRECSZ_BIT) & + Z_EROFS_ADVISE_EXTRECSZ_MASK)); +} + +/* Chunk-based file definitions */ +#define EROFS_CHUNK_FORMAT_BLKBITS_MASK 0x001F +#define EROFS_CHUNK_FORMAT_INDEXES 0x0020 +#define EROFS_CHUNK_FORMAT_48BIT 0x0040 +#define EROFS_CHUNK_FORMAT_ALL ((EROFS_CHUNK_FORMAT_48BIT << 1) - 1) +#define EROFS_CHUNK_FORMAT_INDEXES_FLAG EROFS_CHUNK_FORMAT_INDEXES +#define EROFS_BLOCK_MAP_ENTRY_SIZE sizeof(__le32) + +struct erofs_inode_chunk_index { + __le16 startblk_hi; + __le16 device_id; + __le32 startblk_lo; +} __packed; + +/* Device table slot (128 bytes) */ +#define EROFS_DEVT_SLOT_SIZE 128 +struct erofs_deviceslot { + uint8_t tag[64]; + __le32 blocks_lo; + __le32 uniaddr_lo; + __le16 blocks_hi; + __le16 uniaddr_hi; + uint8_t reserved[52]; +} __packed; + +_Static_assert(sizeof(struct erofs_super_block) == 144, + "EROFS super block ABI size"); +_Static_assert(sizeof(struct erofs_inode_compact) == 32, + "EROFS compact inode ABI size"); +_Static_assert(sizeof(struct erofs_inode_extended) == 64, + "EROFS extended inode ABI size"); +_Static_assert(sizeof(struct erofs_xattr_ibody_header) == 12, + "EROFS xattr ibody header ABI size"); +_Static_assert(sizeof(struct erofs_xattr_entry) == 4, + "EROFS xattr entry ABI size"); +_Static_assert(sizeof(struct erofs_inode_chunk_info) == 4, + "EROFS chunk info ABI size"); +_Static_assert(sizeof(struct erofs_inode_chunk_index) == 8, + "EROFS chunk index ABI size"); +_Static_assert(sizeof(struct z_erofs_map_header) == 8, + "EROFS zmap header ABI size"); +_Static_assert(sizeof(struct z_erofs_lcluster_index) == 8, + "EROFS lcluster index ABI size"); +_Static_assert(sizeof(struct z_erofs_extent) == 32, + "EROFS compression extent ABI size"); +_Static_assert(sizeof(struct erofs_dirent) == 12, + "EROFS dirent ABI size"); +_Static_assert(sizeof(struct erofs_deviceslot) == EROFS_DEVT_SLOT_SIZE, + "EROFS device slot ABI size"); +_Static_assert(__builtin_offsetof(struct erofs_super_block, extra_devices) == 86, + "EROFS extra device count ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_super_block, devt_slotoff) == 88, + "EROFS device table slot offset ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_super_block, rootnid_8b) == 112, + "EROFS 48-bit root nid ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_super_block, metabox_nid) == 128, + "EROFS metabox nid ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_inode_compact, i_u) == 16, + "EROFS compact inode union ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_inode_extended, i_u) == 16, + "EROFS extended inode union ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_inode_extended, i_reserved2) == 48, + "EROFS extended inode reserved ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_inode_chunk_index, device_id) == 2, + "EROFS chunk device id ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_inode_chunk_index, startblk_lo) == 4, + "EROFS chunk start block ABI offset"); +_Static_assert(__builtin_offsetof(struct z_erofs_map_header, h_advise) == 4, + "EROFS zmap advise ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_deviceslot, blocks_lo) == 64, + "EROFS device blocks ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_deviceslot, uniaddr_lo) == 68, + "EROFS device unified address ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_deviceslot, blocks_hi) == 72, + "EROFS device blocks high ABI offset"); +_Static_assert(__builtin_offsetof(struct erofs_deviceslot, uniaddr_hi) == 74, + "EROFS device unified address high ABI offset"); + +#endif diff --git a/erofs_vnops.c b/erofs_vnops.c new file mode 100644 index 0000000..b233f13 --- /dev/null +++ b/erofs_vnops.c @@ -0,0 +1,468 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "internal.h" +#include "xattr.h" + +static vop_access_t erofs_access; +static vop_aclcheck_t erofs_aclcheck; +static vop_bmap_t erofs_bmap; +static vop_deleteextattr_t erofs_deleteextattr; +/* vop_fhtovp removed in FreeBSD 15.0 */ +static vop_getacl_t erofs_vop_getacl; +static vop_getextattr_t erofs_getextattr; +static vop_getattr_t erofs_getattr; +static vop_inactive_t erofs_inactive; +static vop_listextattr_t erofs_listextattr; +static vop_open_t erofs_open; +static vop_pathconf_t erofs_pathconf; +static vop_read_t erofs_read; +static vop_readdir_t erofs_readdir; +static vop_readlink_t erofs_readlink; +static vop_reclaim_t erofs_reclaim; +static vop_setacl_t erofs_setacl; +static vop_setattr_t erofs_setattr; +static vop_setextattr_t erofs_setextattr; +static vop_vptofh_t erofs_vptofh; + +/* Access check: data nodes are read-only, but device/FIFO nodes are not denied + * writes. */ +static int +erofs_access(struct vop_access_args *ap) +{ + struct vnode *vp; + struct erofs_node *en; + struct acl *acl; + accmode_t accmode; + int error; + + vp = ap->a_vp; + en = VTOE(vp); + accmode = ap->a_accmode; + if ((accmode & VMODIFY_PERMS) != 0) { + switch (vp->v_type) { + case VDIR: + case VLNK: + case VREG: + return (EROFS); + default: + break; + } + } + error = vfs_unixify_accmode(&accmode); + if (error != 0) + return (error); + if ((vp->v_mount->mnt_flag & MNT_ACLS) == 0) + return (vaccess(vp->v_type, en->mode & ALLPERMS, en->uid, + en->gid, accmode, ap->a_cred)); + + acl = acl_alloc(M_WAITOK); + error = erofs_get_acl(vp, ACL_TYPE_ACCESS, acl); + if (error == 0) + error = vaccess_acl_posix1e(vp->v_type, en->uid, en->gid, acl, + accmode, ap->a_cred); + acl_free(acl); + return (error); +} + +/* + * Tell the generic pager that EROFS does not provide block-level bmap. + * + * Returning EOPNOTSUPP prevents the pager from assuming a bufobj/strategy is + * available, avoiding “No strategy for buffer” errors. VM will correctly + * fall back to the VOP_READ-based page-in path. + */ +static int +erofs_bmap(struct vop_bmap_args *ap) +{ + (void)ap; + return (EOPNOTSUPP); +} + +/* No dirty writeback on last ref release, so inactive is a no-op. */ +static int +erofs_inactive(struct vop_inactive_args *ap) +{ + (void)ap; + return (0); +} + +/* + * Create VM object when opening a regular vnode. + * + * The FreeBSD local vnode pager services synchronous and asynchronous faults + * through VOP_READ without requiring a block strategy method. + */ +static int +erofs_open(struct vop_open_args *ap) +{ + struct vnode *vp; + struct erofs_node *en; + + vp = ap->a_vp; + en = VTOE(vp); + if (VN_ISDEV(vp)) + return (EOPNOTSUPP); + if (vp->v_type == VREG) { + if (vnode_create_vobject(vp, en->size, ap->a_td) != 0) + return (ENOMEM); + } + return (0); +} + +static int +erofs_getattr(struct vop_getattr_args *ap) +{ + struct vnode *vp; + struct erofs_node *en; + struct erofs_mount *em; + struct vattr *vap; + + vp = ap->a_vp; + en = VTOE(vp); + em = MTOE(vp->v_mount); + vap = ap->a_vap; + VATTR_NULL(vap); + vap->va_type = vp->v_type; + vap->va_mode = en->mode & ALLPERMS; + vap->va_nlink = en->nlink; + vap->va_uid = en->uid; + vap->va_gid = en->gid; + vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0]; + vap->va_fileid = en->nid; + vap->va_size = en->size; + vap->va_blocksize = em->block_size; + vap->va_atime.tv_sec = en->mtime; + vap->va_mtime.tv_sec = en->mtime; + vap->va_ctime.tv_sec = en->mtime; + vap->va_atime.tv_nsec = en->mtime_nsec; + vap->va_mtime.tv_nsec = en->mtime_nsec; + vap->va_ctime.tv_nsec = en->mtime_nsec; + vap->va_gen = en->generation; + vap->va_flags = 0; + vap->va_rdev = VN_ISDEV(vp) ? en->rdev : NODEV; + if (en->data_blocks > (UINT64_MAX >> em->block_bits)) + return (EINTEGRITY); + vap->va_bytes = en->data_blocks << em->block_bits; + vap->va_filerev = 0; + return (0); +} + +/* + * Read-only xattr get entry point. + * + * Delegates to erofs_getxattr() for two namespaces: + * - EXTATTR_NAMESPACE_USER + * - EXTATTR_NAMESPACE_SYSTEM (trusted.* / security.*) + */ +static int +erofs_getextattr(struct vop_getextattr_args *ap) +{ + int error; + + if (VN_ISDEV(ap->a_vp)) + return (EOPNOTSUPP); + error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace, ap->a_cred, + ap->a_td, VREAD); + if (error != 0) + return (error); + if (ap->a_name == NULL || ap->a_name[0] == '\0') + return (EINVAL); + if (strlen(ap->a_name) > EXTATTR_MAXNAMELEN) + return (EINVAL); + + switch (ap->a_attrnamespace) { + case EXTATTR_NAMESPACE_USER: + case EXTATTR_NAMESPACE_SYSTEM: + break; + default: + return (EOPNOTSUPP); + } + + return (erofs_getxattr(ap->a_vp, ap->a_attrnamespace, ap->a_name, + ap->a_uio, ap->a_size)); +} + +/* + * Read-only xattr list entry point. + * + * Delegates to erofs_listxattr() for two namespaces: + * - EXTATTR_NAMESPACE_USER + * - EXTATTR_NAMESPACE_SYSTEM (trusted.* / security.*) + */ +static int +erofs_listextattr(struct vop_listextattr_args *ap) +{ + int error; + + if (VN_ISDEV(ap->a_vp)) + return (EOPNOTSUPP); + error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace, ap->a_cred, + ap->a_td, VREAD); + if (error != 0) + return (error); + + switch (ap->a_attrnamespace) { + case EXTATTR_NAMESPACE_USER: + case EXTATTR_NAMESPACE_SYSTEM: + break; + default: + return (EOPNOTSUPP); + } + + return (erofs_listxattr(ap->a_vp, ap->a_attrnamespace, ap->a_uio, + ap->a_size)); +} + +static int +erofs_deleteextattr(struct vop_deleteextattr_args *ap) +{ + if (VN_ISDEV(ap->a_vp)) + return (EOPNOTSUPP); + return (EROFS); +} + +static int +erofs_setextattr(struct vop_setextattr_args *ap) +{ + if (VN_ISDEV(ap->a_vp)) + return (EOPNOTSUPP); + return (EROFS); +} + +/* EROFS is read-only; mutations on regular files/dirs/symlinks are denied, size + * changes on special vnodes are treated as no-ops per read-only convention. */ +static int +erofs_setattr(struct vop_setattr_args *ap) +{ + struct vnode *vp; + struct vattr *vap; + + vp = ap->a_vp; + vap = ap->a_vap; + if (vap->va_mode != (mode_t)VNOVAL || vap->va_uid != (uid_t)VNOVAL || + vap->va_gid != (gid_t)VNOVAL || vap->va_atime.tv_sec != VNOVAL || + vap->va_atime.tv_nsec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL || + vap->va_mtime.tv_nsec != VNOVAL || vap->va_flags != VNOVAL) + return (EROFS); + if (vap->va_size != VNOVAL) { + switch (vp->v_type) { + case VDIR: + return (EISDIR); + case VLNK: + case VREG: + return (EROFS); + case VCHR: + case VBLK: + case VSOCK: + case VFIFO: + case VNON: + case VBAD: + case VMARKER: + return (0); + } + } + return (0); +} + +static int +erofs_read(struct vop_read_args *ap) +{ + switch (ap->a_vp->v_type) { + case VREG: + return (erofs_read_file(ap->a_vp, ap->a_uio, ap->a_ioflag)); + case VDIR: + return (EISDIR); + default: + return (EINVAL); + } +} + +static int +erofs_readdir(struct vop_readdir_args *ap) +{ + if (ap->a_vp->v_type != VDIR) + return (ENOTDIR); + return (erofs_readdir_block(ap->a_vp, ap->a_uio, ap->a_eofflag, + ap->a_ncookies, ap->a_cookies)); +} + +static int +erofs_readlink(struct vop_readlink_args *ap) +{ + if (ap->a_vp->v_type != VLNK) + return (EINVAL); + return (erofs_readlink_target(ap->a_vp, ap->a_uio)); +} + +static int +erofs_pathconf(struct vop_pathconf_args *ap) +{ + switch (ap->a_name) { + case _PC_NAME_MAX: + *ap->a_retval = EROFS_NAME_LEN; + return (0); + case _PC_PATH_MAX: + *ap->a_retval = PATH_MAX; + return (0); + case _PC_FILESIZEBITS: + *ap->a_retval = 64; + return (0); + case _PC_LINK_MAX: + *ap->a_retval = INT_MAX; + return (0); + case _PC_CHOWN_RESTRICTED: + case _PC_NO_TRUNC: + *ap->a_retval = 1; + return (0); + case _PC_ACL_EXTENDED: + *ap->a_retval = + ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) != 0) ? 1 : 0; + return (0); + case _PC_ACL_PATH_MAX: + *ap->a_retval = + ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) != 0) ? + ACL_MAX_ENTRIES : 3; + return (0); + case _PC_ACL_NFS4: + *ap->a_retval = 0; + return (0); + default: + return (vop_stdpathconf(ap)); + } +} + +static int +erofs_vop_getacl(struct vop_getacl_args *ap) +{ + if (VN_ISDEV(ap->a_vp)) + return (EOPNOTSUPP); + if ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0) + return (EOPNOTSUPP); + return (erofs_get_acl(ap->a_vp, ap->a_type, ap->a_aclp)); +} + +static int +erofs_aclcheck(struct vop_aclcheck_args *ap) +{ + if (VN_ISDEV(ap->a_vp)) + return (EOPNOTSUPP); + if ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0) + return (EOPNOTSUPP); + if (ap->a_aclp == NULL) + return (EINVAL); + switch (ap->a_type) { + case ACL_TYPE_ACCESS: + break; + case ACL_TYPE_DEFAULT: + if (ap->a_vp->v_type != VDIR) + return (EINVAL); + break; + default: + return (EINVAL); + } + return (acl_posix1e_check(ap->a_aclp)); +} + +static int +erofs_setacl(struct vop_setacl_args *ap) +{ + if (VN_ISDEV(ap->a_vp)) + return (EOPNOTSUPP); + return (EROFS); +} + +static int +erofs_reclaim(struct vop_reclaim_args *ap) +{ + struct vnode *vp; + struct erofs_node *en; + + vp = ap->a_vp; + en = VTOE(vp); + if (en != NULL) { + vfs_hash_remove(vp); + free(en, M_EROFS); + vp->v_data = NULL; + } + return (0); +} + +/* Vnode pointer to persistent EROFS file handle. */ +static int +erofs_vptofh(struct vop_vptofh_args *ap) +{ + struct erofs_fid efid; + struct erofs_node *en; + + en = VTOE(ap->a_vp); + bzero(&efid, sizeof(efid)); + efid.len = sizeof(efid); + efid.nid_hi = en->nid >> 32; + efid.nid_lo = en->nid; + efid.gen = en->generation; + memcpy(ap->a_fhp, &efid, sizeof(efid)); + return (0); +} + +struct vop_vector erofs_vnodeops = { + .vop_default = &default_vnodeops, + .vop_access = erofs_access, + .vop_aclcheck = erofs_aclcheck, + .vop_bmap = erofs_bmap, + .vop_cachedlookup = erofs_lookup, + .vop_deleteextattr = erofs_deleteextattr, + .vop_getacl = erofs_vop_getacl, + .vop_getextattr = erofs_getextattr, + .vop_getattr = erofs_getattr, + .vop_getpages = vnode_pager_local_getpages, + .vop_getpages_async = vnode_pager_local_getpages_async, + .vop_inactive = erofs_inactive, + .vop_listextattr = erofs_listextattr, + .vop_lookup = vfs_cache_lookup, + .vop_open = erofs_open, + .vop_pathconf = erofs_pathconf, + .vop_read = erofs_read, + .vop_readdir = erofs_readdir, + .vop_readlink = erofs_readlink, + .vop_reclaim = erofs_reclaim, + .vop_setacl = erofs_setacl, + .vop_setattr = erofs_setattr, + .vop_setextattr = erofs_setextattr, + .vop_vptofh = erofs_vptofh, +}; +VFS_VOP_VECTOR_REGISTER(erofs_vnodeops); + +struct vop_vector erofs_fifoops = { + .vop_default = &fifo_specops, + .vop_access = erofs_access, + .vop_aclcheck = erofs_aclcheck, + .vop_deleteextattr = erofs_deleteextattr, + .vop_getacl = erofs_vop_getacl, + .vop_getextattr = erofs_getextattr, + .vop_getattr = erofs_getattr, + .vop_listextattr = erofs_listextattr, + .vop_pathconf = erofs_pathconf, + .vop_reclaim = erofs_reclaim, + .vop_setacl = erofs_setacl, + .vop_setattr = erofs_setattr, + .vop_setextattr = erofs_setextattr, + .vop_vptofh = erofs_vptofh, +}; +VFS_VOP_VECTOR_REGISTER(erofs_fifoops); diff --git a/inode.c b/inode.c new file mode 100644 index 0000000..ca5d33c --- /dev/null +++ b/inode.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017-2018 HUAWEI, Inc. + * https://www.huawei.com/ + * Copyright (C) 2021, Alibaba Cloud + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" + +static bool +erofs_is_48bit(const struct erofs_mount *em) +{ + return ((em->feature_incompat & EROFS_FEATURE_INCOMPAT_48BIT) != 0); +} + +static uint64_t +erofs_addrmask(const struct erofs_mount *em) +{ + if (erofs_is_48bit(em)) + return ((1ULL << 48) - 1); + return (UINT32_MAX); +} + +static dev_t +erofs_decode_dev(uint32_t dev) +{ + unsigned int major, minor; + + major = (dev & 0xfff00) >> 8; + minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); + return (makedev(major, minor)); +} + +static uint32_t +erofs_inode_generation(const struct erofs_mount *em, uint64_t nid, + const void *inode, size_t inode_size) +{ + uint8_t encoded_nid[sizeof(nid)]; + uint32_t generation; + + le64enc(encoded_nid, nid); + generation = fnv_32_buf(encoded_nid, sizeof(encoded_nid), + em->generation_seed); + generation = fnv_32_buf(inode, inode_size, generation); + return (generation != 0 ? generation : 1); +} + +static int +erofs_set_timestamp(struct erofs_node *en, uint64_t seconds, + uint32_t nanoseconds) +{ + if (nanoseconds >= 1000000000 || seconds > (uint64_t)INT64_MAX) + return (EINTEGRITY); + en->mtime = seconds; + en->mtime_nsec = nanoseconds; + return (0); +} + +static int +erofs_set_data_blocks(const struct erofs_mount *em, struct erofs_node *en, + uint64_t compressed_blocks) +{ + if (en->datalayout == EROFS_INODE_COMPRESSED_FULL || + en->datalayout == EROFS_INODE_COMPRESSED_COMPACT) { + en->data_blocks = compressed_blocks; + return (0); + } + if (en->size == 0) { + en->data_blocks = 0; + return (0); + } + if (en->size > UINT64_MAX - (em->block_size - 1)) + return (EINTEGRITY); + en->data_blocks = roundup2(en->size, (uint64_t)em->block_size) >> + em->block_bits; + return (0); +} + +static int +erofs_validate_inline_data(const struct erofs_mount *em, + const struct erofs_node *en) +{ + uint64_t image_size, inline_end, inline_off, inline_size, tail_start; + + if (en->datalayout != EROFS_INODE_FLAT_INLINE || en->size == 0) + return (0); + tail_start = roundup2(en->size, (uint64_t)em->block_size) - + em->block_size; + inline_size = en->size - tail_start; + if (__builtin_add_overflow(en->inode_off, en->inode_isize, &inline_off) || + __builtin_add_overflow(inline_off, en->xattr_isize, &inline_off) || + __builtin_add_overflow(inline_off, inline_size, &inline_end)) + return (EINTEGRITY); + if ((inline_off & (em->block_size - 1)) + inline_size > em->block_size) + return (EINTEGRITY); + if (erofs_nid_in_metabox(en->nid)) { + if (em->metabox_en == NULL || inline_end > em->metabox_en->size) + return (EINTEGRITY); + return (0); + } + if (em->blocks > (UINT64_MAX >> em->block_bits)) + return (EINTEGRITY); + image_size = em->blocks << em->block_bits; + if (inline_end > image_size || inline_end > em->dif0.mediasize) + return (EINTEGRITY); + return (0); +} + +/* + * Convert a logical nid to its inode-table byte offset. Normal NIDs are + * relative to the primary metadata area. For metabox NIDs, bit 63 selects + * the metabox backing inode and the remaining bits are relative to its data. + * EROFS_NULL_ADDR is returned when the address cannot be represented. + */ +uint64_t +erofs_iloc(struct erofs_mount *em, uint64_t nid) +{ + uint64_t meta_offset, nid_lo, result; + bool in_metabox; + + in_metabox = erofs_nid_in_metabox(nid); + if (in_metabox && !erofs_sb_has_metabox(em)) + return (EROFS_NULL_ADDR); + nid_lo = nid & EROFS_DIRENT_NID_MASK; + if (nid_lo > (UINT64_MAX >> 5)) + return (EROFS_NULL_ADDR); + result = nid_lo << 5; + if (in_metabox) + return (result); + + if (em->block_bits > 58) + return (EROFS_NULL_ADDR); + meta_offset = (uint64_t)em->meta_blkaddr << em->block_bits; + if (result > UINT64_MAX - meta_offset) + return (EROFS_NULL_ADDR); + + return (meta_offset + result); +} + +/* + * Check that a NID can address at least one compact inode slot without + * crossing the declared primary image or metabox backing-file boundary. + */ +bool +erofs_nid_is_valid(struct erofs_mount *em, uint64_t nid) +{ + uint64_t image_size, off; + + off = erofs_iloc(em, nid); + if (off == EROFS_NULL_ADDR) + return (false); + if (erofs_nid_in_metabox(nid)) { + if (em->metabox_en == NULL || off > em->metabox_en->size) + return (false); + return (sizeof(struct erofs_inode_compact) <= + em->metabox_en->size - off); + } + if (em->blocks > (UINT64_MAX >> em->block_bits)) + return (false); + image_size = em->blocks << em->block_bits; + if (off > image_size || sizeof(struct erofs_inode_compact) > + image_size - off) + return (false); + if (off > em->dif0.mediasize || sizeof(struct erofs_inode_compact) > + em->dif0.mediasize - off) + return (false); + return (true); +} + +/* + * Read and decode a disk inode. + * + * Currently supports: + * - compact / extended inode; + * - plain / inline uncompressed layouts; + * - basic 48-bit address parsing; + * - compact inode epoch/fixed_nsec timestamp semantics; + * - dot_omitted / nlink==1 i_format details. + */ +int +erofs_read_inode(struct erofs_mount *em, uint64_t nid, struct erofs_node *en) +{ + struct erofs_inode_compact *dic; + struct erofs_inode_extended *die; + struct erofs_inode_chunk_info chunk_info; + union erofs_inode_i_nb inode_nb; + void *buf; + uint64_t addrmask, mtime, off, startblk; + uint64_t compressed_blocks; + uint32_t raw_rdev, startblk_lo; + uint16_t ifmt, startblk_hi; + int error; + + if (!erofs_nid_is_valid(em, nid)) + return (EINTEGRITY); + off = erofs_iloc(em, nid); + error = erofs_read_metadata(em, nid, off, + sizeof(struct erofs_inode_compact), &buf); + if (error != 0) + return (error); + + bzero(&en->size, sizeof(*en) - offsetof(struct erofs_node, size)); + en->nid = nid; + en->inode_off = off; + ifmt = le16toh(*(uint16_t *)buf); + if ((ifmt & ~EROFS_I_ALL) != 0) { + erofs_brelse(buf); + return (EOPNOTSUPP); + } + en->datalayout = erofs_inode_datalayout(ifmt); + if (en->datalayout >= EROFS_INODE_DATALAYOUT_MAX) { + erofs_brelse(buf); + return (EOPNOTSUPP); + } + en->compact_inode = (erofs_inode_version(ifmt) == 0); + if (!en->compact_inode) { + erofs_brelse(buf); + error = erofs_read_metadata(em, nid, off, + sizeof(struct erofs_inode_extended), &buf); + if (error != 0) + return (error); + } + addrmask = erofs_addrmask(em); + startblk = EROFS_NULL_ADDR; + startblk_lo = 0; + startblk_hi = 0; + compressed_blocks = 0; + raw_rdev = 0; + bzero(&inode_nb, sizeof(inode_nb)); + dic = buf; + if (en->compact_inode) { + en->inode_isize = sizeof(struct erofs_inode_compact); + en->generation = erofs_inode_generation(em, nid, buf, + en->inode_isize); + en->mode = le16toh(dic->i_mode); + en->size = le32toh(dic->i_size); + en->ino = le32toh(dic->i_ino); + en->uid = le16toh(dic->i_uid); + en->gid = le16toh(dic->i_gid); + en->xattr_isize = erofs_xattr_ibody_size(dic->i_xattr_icount); + if (__builtin_add_overflow(em->epoch, + (uint64_t)le32toh(dic->i_mtime), &mtime)) { + erofs_brelse(buf); + return (EINTEGRITY); + } + error = erofs_set_timestamp(en, mtime, em->fixed_nsec); + if (error != 0) { + erofs_brelse(buf); + return (error); + } + startblk_lo = le32toh(dic->i_u.startblk_lo); + compressed_blocks = le32toh(dic->i_u.blocks_lo); + raw_rdev = le32toh(dic->i_u.rdev); + if (!S_ISDIR(en->mode) && + ((ifmt >> EROFS_I_NLINK_1_BIT) & 0x1) != 0) { + en->nlink = 1; + inode_nb = dic->i_nb; + } else { + en->nlink = le16toh(dic->i_nb.nlink); + addrmask = UINT32_MAX; + } + } else { + die = buf; + en->inode_isize = sizeof(struct erofs_inode_extended); + en->generation = erofs_inode_generation(em, nid, buf, + en->inode_isize); + en->mode = le16toh(die->i_mode); + en->size = le64toh(die->i_size); + en->ino = le32toh(die->i_ino); + en->uid = le32toh(die->i_uid); + en->gid = le32toh(die->i_gid); + en->nlink = le32toh(die->i_nlink); + inode_nb = die->i_nb; + en->xattr_isize = erofs_xattr_ibody_size(die->i_xattr_icount); + error = erofs_set_timestamp(en, le64toh(die->i_mtime), + le32toh(die->i_mtime_nsec)); + if (error != 0) { + erofs_brelse(buf); + return (error); + } + startblk_lo = le32toh(die->i_u.startblk_lo); + compressed_blocks = le32toh(die->i_u.blocks_lo); + raw_rdev = le32toh(die->i_u.rdev); + } + startblk_hi = le16toh(inode_nb.startblk_hi); + compressed_blocks |= (uint64_t)le16toh(inode_nb.blocks_hi) << 32; + if (en->size > (uint64_t)OFF_MAX) { + erofs_brelse(buf); + return (EINTEGRITY); + } + + en->vtype = IFTOVT(en->mode); + if (en->mode != 0 && en->vtype == VNON) { + erofs_brelse(buf); + return (EINTEGRITY); + } + en->inline_data = (en->datalayout == EROFS_INODE_FLAT_INLINE); + en->dot_omitted = (en->vtype == VDIR) && + (((ifmt >> EROFS_I_DOT_OMITTED_BIT) & 0x1) != 0); + + if (en->datalayout == EROFS_INODE_COMPRESSED_FULL || + en->datalayout == EROFS_INODE_COMPRESSED_COMPACT) { + error = z_erofs_fill_inode(em, en); + if (error != 0) { + erofs_brelse(buf); + return (error); + } + } else if (en->datalayout == EROFS_INODE_CHUNK_BASED) { + if (!erofs_sb_has_chunked_file(em) || en->vtype != VREG) { + erofs_brelse(buf); + return (EINTEGRITY); + } + if (en->compact_inode) + chunk_info = dic->i_u.c; + else + chunk_info = die->i_u.c; + if (le16toh(chunk_info.reserved) != 0) { + erofs_brelse(buf); + return (EINTEGRITY); + } + en->chunkformat = le16toh(chunk_info.format); + if (en->chunkformat & ~EROFS_CHUNK_FORMAT_ALL) { + erofs_brelse(buf); + return (EOPNOTSUPP); + } + if ((en->chunkformat & EROFS_CHUNK_FORMAT_48BIT) != 0 && + (en->chunkformat & EROFS_CHUNK_FORMAT_INDEXES) == 0) { + erofs_brelse(buf); + return (EINTEGRITY); + } + en->chunkbits = em->block_bits + + (en->chunkformat & EROFS_CHUNK_FORMAT_BLKBITS_MASK); + if (en->chunkbits >= 64) { + erofs_brelse(buf); + return (EINTEGRITY); + } + } else if (en->datalayout != EROFS_INODE_FLAT_PLAIN && + en->datalayout != EROFS_INODE_FLAT_INLINE) { + erofs_brelse(buf); + return (EOPNOTSUPP); + } + + switch (en->vtype) { + case VREG: + case VDIR: + case VLNK: + if (en->datalayout == EROFS_INODE_CHUNK_BASED) { + en->startblk = EROFS_NULL_ADDR; + en->rdev = NODEV; + break; + } + startblk = startblk_lo | ((uint64_t)startblk_hi << 32); + if (en->datalayout == EROFS_INODE_FLAT_PLAIN && + ((startblk ^ EROFS_NULL_ADDR) & addrmask) == 0) + startblk = EROFS_NULL_ADDR; + en->startblk = startblk; + en->rdev = NODEV; + break; + case VCHR: + case VBLK: + en->startblk = EROFS_NULL_ADDR; + en->rdev = erofs_decode_dev(raw_rdev); + break; + case VFIFO: + case VSOCK: + en->startblk = EROFS_NULL_ADDR; + en->rdev = NODEV; + break; + default: + erofs_brelse(buf); + return (EINTEGRITY); + } + error = erofs_set_data_blocks(em, en, compressed_blocks); + if (error == 0) + error = erofs_validate_inline_data(em, en); + if (error != 0) { + erofs_brelse(buf); + return (error); + } + + erofs_brelse(buf); + return (0); +} + +static u_int +erofs_vfs_hash(uint64_t nid) +{ + + return (fnv_32_buf(&nid, sizeof(nid), FNV1_32_INIT)); +} + +static int +erofs_vfs_hash_cmp(struct vnode *vp, void *pnid) +{ + struct erofs_node *en; + + en = VTOE(vp); + return (en == NULL || en->nid != *(uint64_t *)pnid); +} + +/* + * Get vnode by raw on-disk nid. The raw nid is also the FreeBSD fileid and + * hash identity, so the metabox selector bit remains collision-free. + * Uses the standard FreeBSD vfs_hash API. + * (Linux equivalent: erofs_iget in Linux's inode.c) + */ +int +erofs_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp) +{ + struct erofs_mount *em; + struct erofs_node *en; + struct thread *td; + struct vnode *vp; + uint64_t nid; + u_int hash; + bool shared; + int error; + + td = curthread; + nid = (uint64_t)ino; + shared = (flags & LK_TYPE_MASK) == LK_SHARED; + hash = erofs_vfs_hash(nid); + error = vfs_hash_get(mp, hash, flags, td, vpp, erofs_vfs_hash_cmp, + &nid); + if (error != 0 || *vpp != NULL) + return (error); + + em = MTOE(mp); + en = malloc(sizeof(*en), M_EROFS, M_WAITOK | M_ZERO); + error = getnewvnode("erofs", mp, &erofs_vnodeops, &vp); + if (error != 0) { + free(en, M_EROFS); + *vpp = NULL; + return (error); + } + vp->v_data = en; + en->vnode = vp; + en->nid = nid; + lockmgr(vp->v_vnlock, LK_EXCLUSIVE, NULL); + error = insmntque(vp, mp); + if (error != 0) { + free(en, M_EROFS); + vp->v_data = NULL; + *vpp = NULL; + return (error); + } + error = vfs_hash_insert(vp, hash, flags, td, vpp, erofs_vfs_hash_cmp, + &nid); + if (error != 0 || *vpp != NULL) + return (error); + + error = erofs_read_inode(em, nid, en); + if (error != 0) { + *vpp = NULL; + vgone(vp); + vput(vp); + return (error); + } + vp->v_type = en->vtype; + if (vp->v_type == VFIFO) + vp->v_op = &erofs_fifoops; + if ((uint64_t)ino == em->root_nid) + vp->v_vflag |= VV_ROOT; + vn_set_state(vp, VSTATE_CONSTRUCTED); + if (shared) + VOP_LOCK(vp, LK_DOWNGRADE); + *vpp = vp; + return (0); +} diff --git a/internal.h b/internal.h new file mode 100644 index 0000000..cfa15de --- /dev/null +++ b/internal.h @@ -0,0 +1,351 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2017-2018 HUAWEI, Inc. + * https://www.huawei.com/ + * Copyright (C) 2021, Alibaba Cloud + */ + +#ifndef __EROFS_INTERNAL_H +#define __EROFS_INTERNAL_H + +#include +#include // MUST FIRST +#include +#include +#include +#include +#include + +#include "erofs_fs.h" + +MALLOC_DECLARE(M_EROFS); + +struct cdev; +struct g_consumer; +struct erofs_device_info; + +/* EROFS_SUPER_MAGIC_V1 to represent the whole file system */ +#define EROFS_SUPER_MAGIC EROFS_SUPER_MAGIC_V1 + +typedef uint64_t erofs_nid_t; +typedef uint64_t erofs_off_t; +typedef uint64_t erofs_blk_t; +#define EROFS_FEATURE_FUNCS(name, compat, feature) \ + static inline bool erofs_sb_has_##name(struct erofs_mount *em) \ + { \ + return ( \ + (em->feature_##compat & EROFS_FEATURE_##feature) != 0); \ + } + +#define EROFS_MOUNT_XATTR_USER 0x00000010 +#define EROFS_MOUNT_POSIX_ACL 0x00000020 + +#define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option) +#define set_opt(opt, option) ((opt)->mount_opt |= EROFS_MOUNT_##option) +#define test_opt(opt, option) ((opt)->mount_opt & EROFS_MOUNT_##option) + +struct erofs_mount_opts { + unsigned int mount_opt; +}; + +enum { + EROFS_SYNC_DECOMPRESS_AUTO, + EROFS_SYNC_DECOMPRESS_FORCE_ON, + EROFS_SYNC_DECOMPRESS_FORCE_OFF +}; + +enum { + EROFS_ZIP_CACHE_DISABLED, + EROFS_ZIP_CACHE_READAHEAD, + EROFS_ZIP_CACHE_READAROUND +}; + +struct erofs_sb_lz4_info { + uint16_t max_distance_pages; + uint16_t max_pclusterblks; +}; + +struct erofs_buf { + void *base; + erofs_off_t off; +}; +#define __EROFS_BUF_INITIALIZER ((struct erofs_buf) { .base = NULL }) + +#define EROFS_MAP_MAPPED 0x0001 +#define EROFS_MAP_META 0x0002 +#define EROFS_MAP_PARTIAL_MAPPED 0x0004 +#define EROFS_MAP_PARTIAL_REF 0x0008 +#define EROFS_MAP_FRAGMENT 0x0010 +#define EROFS_MAP_FULL(f) \ + (!((f) & (EROFS_MAP_PARTIAL_MAPPED | EROFS_MAP_PARTIAL_REF))) + +struct erofs_map_blocks { + struct erofs_buf buf; + + erofs_off_t m_pa, m_la; + uint64_t m_plen, m_llen; + + unsigned short m_deviceid; + char m_algorithmformat; + unsigned int m_flags; +}; + +#define EROFS_GET_BLOCKS_FIEMAP 0x0001 +#define EROFS_GET_BLOCKS_READMORE 0x0002 +#define EROFS_GET_BLOCKS_FINDTAIL 0x0004 + +enum { + Z_EROFS_COMPRESSION_SHIFTED = Z_EROFS_COMPRESSION_MAX, + Z_EROFS_COMPRESSION_INTERLACED, + Z_EROFS_COMPRESSION_RUNTIME_MAX +}; + +struct erofs_map_dev { + struct erofs_mount *m_em; + struct erofs_device_info *m_dif; + erofs_off_t m_pa; + uint64_t m_plen; + unsigned int m_deviceid; +}; + +struct erofs_device_info { + struct vnode *devvp; + struct cdev *dev; + struct g_consumer *cp; + erofs_blk_t blocks; + erofs_blk_t uniaddr; + uint64_t mediasize; + uint32_t sectorsize; +}; + +struct erofs_xattr_prefix_item { + uint8_t base_index; + uint8_t infix_len; + char *infix; +}; + +struct erofs_zextent_cache { + void *data; + erofs_nid_t m_nid; + erofs_off_t m_pa; + erofs_off_t m_la; + uint64_t m_plen; + uint64_t m_llen; + unsigned int m_deviceid; + unsigned int m_flags; + unsigned char m_algorithmformat; +}; + +struct erofs_mount { + struct mount *mnt; + struct erofs_device_info dif0; + + uint32_t block_size; + uint32_t sb_size; + uint8_t block_bits; + uint32_t meta_blkaddr; + uint32_t xattr_blkaddr; + uint32_t xattr_prefix_start; + uint8_t xattr_prefix_count; + uint64_t packed_nid; + uint64_t metabox_nid; + struct erofs_node *metabox_en; + struct erofs_node *packed_inode; + struct erofs_xattr_prefix_item *xattr_prefixes; + uint64_t blocks; + uint64_t inos; + uint64_t root_nid; + uint64_t epoch; + uint32_t fixed_nsec; + uint32_t generation_seed; + uint32_t feature_compat; + uint32_t feature_incompat; + char volume_name[17]; + + struct erofs_mount_opts opt; + struct erofs_sb_lz4_info lz4; + uint16_t available_compr_algs; + uint32_t lzma_dict_size; + uint8_t deflate_windowbits; + uint8_t zstd_windowlog; + + /* Device table */ + uint16_t extra_devices; + uint16_t device_id_mask; + bool flatdev; + erofs_blk_t total_blocks; + erofs_blk_t flatdev_blocks; + struct erofs_device_info *devs; + struct mtx z_extent_cache_lock; + struct erofs_zextent_cache z_extent_cache; + bool z_extent_cache_initialized; +}; + +struct erofs_node { + struct vnode *vnode; + uint64_t nid; + uint64_t size; + uint64_t data_blocks; + /* Absolute device offset, or metabox-file offset when bit 63 is set. */ + uint64_t inode_off; + uint64_t startblk; + uint32_t ino; + uint32_t generation; + uint32_t nlink; + uid_t uid; + gid_t gid; + mode_t mode; + __enum_uint8(vtype) vtype; + dev_t rdev; + uint64_t mtime; + uint32_t mtime_nsec; + uint8_t datalayout; + uint8_t inode_isize; + uint32_t xattr_isize; + bool inline_data; + bool compact_inode; + bool dot_omitted; + /* Compression fields */ + uint16_t z_advise; + uint8_t z_algorithmtype[2]; + uint8_t z_lclusterbits; + uint16_t z_idata_size; + uint64_t z_fragmentoff; + uint64_t z_tailextent_headlcn; + uint64_t z_extents; + bool z_initialized; + /* Chunk-based fields */ + uint16_t chunkformat; + uint8_t chunkbits; + /* Fragment fields */ + bool fragment; +}; + +struct erofs_fid { + uint16_t len; + uint16_t pad; + uint32_t nid_hi; + uint32_t nid_lo; + uint32_t gen; +}; + +_Static_assert(sizeof(struct erofs_fid) == 16, + "EROFS file handle ABI must be 16 bytes"); +_Static_assert(sizeof(struct erofs_fid) <= sizeof(struct fid), + "struct erofs_fid must fit within struct fid"); + +#define VTOE(vp) ((struct erofs_node *)(vp)->v_data) +#define MTOE(mp) ((struct erofs_mount *)(mp)->mnt_data) + +EROFS_FEATURE_FUNCS(lz4_0padding, incompat, INCOMPAT_LZ4_0PADDING) +EROFS_FEATURE_FUNCS(compr_cfgs, incompat, INCOMPAT_COMPR_CFGS) +EROFS_FEATURE_FUNCS(big_pcluster, incompat, INCOMPAT_BIG_PCLUSTER) +EROFS_FEATURE_FUNCS(chunked_file, incompat, INCOMPAT_CHUNKED_FILE) +EROFS_FEATURE_FUNCS(device_table, incompat, INCOMPAT_DEVICE_TABLE) +EROFS_FEATURE_FUNCS(compr_head2, incompat, INCOMPAT_COMPR_HEAD2) +EROFS_FEATURE_FUNCS(ztailpacking, incompat, INCOMPAT_ZTAILPACKING) +EROFS_FEATURE_FUNCS(fragments, incompat, INCOMPAT_FRAGMENTS) +EROFS_FEATURE_FUNCS(dedupe, incompat, INCOMPAT_DEDUPE) +EROFS_FEATURE_FUNCS(xattr_prefixes, incompat, INCOMPAT_XATTR_PREFIXES) +EROFS_FEATURE_FUNCS(48bit, incompat, INCOMPAT_48BIT) +EROFS_FEATURE_FUNCS(metabox, incompat, INCOMPAT_METABOX) +EROFS_FEATURE_FUNCS(sb_chksum, compat, COMPAT_SB_CHKSUM) +EROFS_FEATURE_FUNCS(xattr_filter, compat, COMPAT_XATTR_FILTER) +EROFS_FEATURE_FUNCS(shared_ea_in_metabox, compat, COMPAT_SHARED_EA_IN_METABOX) +EROFS_FEATURE_FUNCS(plain_xattr_pfx, compat, COMPAT_PLAIN_XATTR_PFX) +EROFS_FEATURE_FUNCS(ishare_xattrs, compat, COMPAT_ISHARE_XATTRS) +EROFS_FEATURE_FUNCS(mtime, compat, COMPAT_MTIME) + +static inline bool +erofs_is_fileio_mode(struct erofs_mount *em __unused) +{ + return (false); +} + +static inline unsigned int +erofs_inode_version(unsigned int ifmt) +{ + return ((ifmt >> EROFS_I_VERSION_BIT) & EROFS_I_VERSION_MASK); +} + +static inline unsigned int +erofs_inode_datalayout(unsigned int ifmt) +{ + return ((ifmt >> EROFS_I_DATALAYOUT_BIT) & EROFS_I_DATALAYOUT_MASK); +} + +static inline bool +erofs_nid_in_metabox(erofs_nid_t nid) +{ + return ((nid & EROFS_DIRENT_NID_METABOX) != 0); +} + +int erofs_bread(struct erofs_mount *em, uint64_t off, size_t len, void **bufp); +int erofs_read_physical(struct erofs_mount *em, unsigned int device_id, + uint64_t off, size_t len, void **bufp); +void erofs_brelse(void *buf); +int erofs_read_metadata(struct erofs_mount *em, erofs_nid_t nid, + uint64_t off, size_t len, void **bufp); + +int erofs_read_inode(struct erofs_mount *em, uint64_t nid, + struct erofs_node *en); + +int erofs_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp); + +int erofs_read_data(struct erofs_mount *em, struct erofs_node *en, + uint64_t loff, size_t len, void **bufp); +int erofs_read_file(struct vnode *vp, struct uio *uio, int ioflag); + +int erofs_readdir_block(struct vnode *vp, struct uio *uio, int *eofflag, + int *ncookies, uint64_t **cookies); + +int erofs_dirent_namelen(const char *blk, uint32_t nameoff, uint32_t endoff, + bool trailing, size_t *namelenp); +int erofs_validate_dirblock(const char *blk, uint32_t blksz, uint32_t maxsize, + uint32_t *ndirentsp); + +int erofs_readlink_target(struct vnode *vp, struct uio *uio); + +int erofs_lookup(struct vop_cachedlookup_args *ap); + +uint64_t erofs_iloc(struct erofs_mount *em, uint64_t nid); +bool erofs_nid_is_valid(struct erofs_mount *em, uint64_t nid); + +int erofs_map_blocks(struct erofs_mount *em, struct erofs_node *en, + uint64_t loff, uint64_t *phys_off, unsigned int *device_id, + size_t *run_len, bool *hole, bool *metadata); +int erofs_map_dev(struct erofs_mount *em, struct erofs_map_dev *map); +int z_erofs_fill_inode(struct erofs_mount *em, struct erofs_node *en); +int z_erofs_map_blocks_iter(struct erofs_mount *em, struct erofs_node *en, + struct erofs_map_blocks *map, int flags); +int z_erofs_read_data(struct erofs_mount *em, struct erofs_node *en, + uint64_t loff, size_t len, void **bufp); +int z_erofs_read_uio(struct erofs_mount *em, struct erofs_node *en, + struct uio *uio); +void z_erofs_extent_cache_init(struct erofs_mount *em); +void z_erofs_extent_cache_fini(struct erofs_mount *em); +int z_erofs_decompress(struct erofs_mount *em, + const struct erofs_map_blocks *map, const void *src, size_t srclen, + void *dst, size_t dstlen, bool partial); +int z_erofs_parse_cfgs(struct erofs_mount *em, + const struct erofs_super_block *dsb); +int lz4_decompress(void *src, void *dst, size_t srclen, size_t dstlen, + int partial); +int z_erofs_load_lzma_config(struct erofs_mount *em, const void *data, + size_t size); +int lzma_decompress(const void *src, size_t srclen, void *dst, size_t dstlen, + uint32_t dict_size, bool partial); +int z_erofs_load_deflate_config(struct erofs_mount *em, const void *data, + size_t size); +int deflate_decompress(void *src, size_t srclen, void *dst, size_t dstlen, + int windowbits, bool partial); +bool erofs_zstd_available(void); +int z_erofs_load_zstd_config(struct erofs_mount *em, const void *data, + size_t size); +int zstd_decompress(void *src, size_t srclen, void *dst, size_t dstlen, + int windowlog, bool partial); + +extern struct vop_vector erofs_vnodeops; +extern struct vop_vector erofs_fifoops; + +#endif /* __EROFS_INTERNAL_H */ diff --git a/lz4.c b/lz4.c new file mode 100644 index 0000000..a3a94c3 --- /dev/null +++ b/lz4.c @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* Minimal LZ4 decompressor for EROFS FreeBSD */ +#include +#include + +#include "erofs_defs.h" +#include "internal.h" + +static int +lz4_finish(const uint8_t *ip, const uint8_t *iend, int partial) +{ + if (partial) + return (0); + while (ip < iend) { + if (*ip++ != 0) + return (-1); + } + return (0); +} + +int +lz4_decompress(void *src, void *dst, size_t srclen, size_t dstlen, int partial) +{ + const uint8_t *ip, *iend; + uint8_t *op, *oend; + unsigned int token; + size_t length, copylen; + size_t offset; + + ip = src; + iend = ip + srclen; + op = dst; + oend = op + dstlen; + if (iend < ip || oend < op) + return (-1); + + while (ip < iend) { + token = *ip++; + length = token >> EROFS_LZ4_TOKEN_LITERAL_SHIFT; + if (length == EROFS_LZ4_MAX_RUN) { + unsigned int value; + do { + if (ip >= iend) + return (-1); + value = *ip++; + if (length > SIZE_MAX - value) + return (-1); + length += value; + } while (value == EROFS_LZ4_EXT_SENTINEL); + } + if (length > (size_t)(iend - ip)) + return (-1); + if (!partial && length > (size_t)(oend - op)) + return (-1); + copylen = MIN(length, (size_t)(oend - op)); + memcpy(op, ip, copylen); + ip += length; + op += copylen; + if (op == oend) + return (lz4_finish(ip, iend, partial)); + if (ip >= iend) + break; + if (ip + EROFS_LZ4_OFFSET_BYTES > iend) + return (-1); + offset = ip[0] | (ip[1] << 8); + ip += EROFS_LZ4_OFFSET_BYTES; + if (offset == 0 || offset > (size_t)(op - (uint8_t *)dst)) + return (-1); + length = token & EROFS_LZ4_TOKEN_MATCH_MASK; + if (length == EROFS_LZ4_MAX_RUN) { + unsigned int value; + do { + if (ip >= iend) + return (-1); + value = *ip++; + if (length > SIZE_MAX - value) + return (-1); + length += value; + } while (value == EROFS_LZ4_EXT_SENTINEL); + } + if (length > SIZE_MAX - EROFS_LZ4_MIN_MATCH) + return (-1); + length += EROFS_LZ4_MIN_MATCH; + if (!partial && length > (size_t)(oend - op)) + return (-1); + copylen = MIN(length, (size_t)(oend - op)); + while (copylen-- != 0) { + *op = *(op - offset); + ++op; + } + if (op == oend) + return (lz4_finish(ip, iend, partial)); + } + return (op == oend ? lz4_finish(ip, iend, partial) : -1); +} diff --git a/namei.c b/namei.c new file mode 100644 index 0000000..dce1830 --- /dev/null +++ b/namei.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017-2018 HUAWEI, Inc. + * https://www.huawei.com/ + * Copyright (C) 2022, Alibaba Cloud + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" + +/* + * Compare two directory entry names using an already-matched prefix. + * (Linux equivalent: erofs_dirnamecmp in Linux's namei.c) + * + * qn_name / qn_len: search key (not necessarily null-terminated). + * qd_name / qd_end: on-disk name range (may not be null-terminated). + * matched: in/out count of prefix characters already known to match. + * + * Returns 0 if equal, 1 if qn > qd, -1 if qn < qd. + */ +static int +erofs_dirnamecmp(const char *qn_name, size_t qn_len, const char *qd_name, + const char *qd_end, unsigned int *matched) +{ + size_t dname_span; + unsigned int i; + + dname_span = qd_end - qd_name; + i = MIN(*matched, qn_len); + i = MIN(i, dname_span); + while (i < qn_len && i < dname_span && qd_name[i] != '\0') { + if ((unsigned char)qn_name[i] != (unsigned char)qd_name[i]) { + *matched = i; + return ((unsigned char)qn_name[i] > + (unsigned char)qd_name[i] ? 1 : -1); + } + ++i; + } + *matched = i; + if (i == qn_len) + return (i == dname_span || qd_name[i] == '\0' ? 0 : -1); + return (1); +} + +/* + * Binary search within a directory block for the target name. + * + * Returns a pointer to the matching dirent, or NULL on miss. + */ +static struct erofs_dirent * +find_target_dirent(const char *name, size_t namelen, char *data, + uint32_t datasize, uint32_t ndirents) +{ + uint32_t head, back; + unsigned int startprfx, endprfx; + struct erofs_dirent *const de = (struct erofs_dirent *)data; + + /* The 1st dirent has already been evaluated by the caller. */ + head = 1; + back = ndirents - 1; + startprfx = endprfx = 0; + + while (head <= back) { + const uint32_t mid = head + (back - head) / 2; + const uint32_t nameoff = le16toh(de[mid].nameoff); + unsigned int matched = MIN(startprfx, endprfx); + const char *dname_start = data + nameoff; + const char *dname_end; + + if (mid >= ndirents - 1) + dname_end = data + datasize; + else + dname_end = data + le16toh(de[mid + 1].nameoff); + + /* String comparison without already matched prefix */ + int ret = erofs_dirnamecmp(name, namelen, dname_start, + dname_end, &matched); + + if (ret == 0) + return (de + mid); + else if (ret > 0) { + head = mid + 1; + startprfx = matched; + } else { + back = mid - 1; + endprfx = matched; + } + } + + return (NULL); +} + +/* + * Find the directory block most likely to contain the target name. + * + * Uses two-level binary search: first across blocks, then within the + * candidate block via find_target_dirent(). + * + * Returns the block buffer on success (caller must erofs_brelse), + * or NULL on error. *_ndirents is set to the number of dirents in + * the returned block (0 means the first entry is the match). + * On error, *errorp is set to a positive errno. + */ +static char * +erofs_find_target_block(struct erofs_mount *em, struct erofs_node *dir, + const char *name, size_t namelen, uint32_t *_ndirents, uint32_t *_datasize, + int *errorp) +{ + uint32_t bsz = em->block_size; + uint64_t head, back; + unsigned int startprfx = 0, endprfx = 0; + char *candidate = NULL; + int error; + + *errorp = 0; + *_ndirents = 0; + *_datasize = 0; + + if (dir->size == 0) + return (NULL); + + head = 0; + back = (dir->size - 1) / bsz; + + while (head <= back) { + const uint64_t mid = head + (back - head) / 2; + uint64_t block_off; + uint32_t maxsize; + const struct erofs_dirent *de; + char *blk; + int diff; + uint32_t ndirents; + uint32_t nameoff; + unsigned int matched; + const char *dname_start, *dname_end; + + if (__builtin_mul_overflow(mid, (uint64_t)bsz, &block_off) || + block_off >= dir->size) { + *errorp = EINTEGRITY; + goto out; + } + maxsize = MIN((uint64_t)bsz, dir->size - block_off); + error = erofs_read_data(em, dir, block_off, maxsize, + (void **)&blk); + if (error != 0) { + *errorp = error; + goto out; + } + error = erofs_validate_dirblock(blk, bsz, maxsize, &ndirents); + if (error != 0) { + erofs_brelse(blk); + *errorp = error; + goto out; + } + de = (const struct erofs_dirent *)blk; + nameoff = le16toh(de[0].nameoff); + + matched = MIN(startprfx, endprfx); + dname_start = blk + nameoff; + if (ndirents == 1) + dname_end = blk + maxsize; + else + dname_end = blk + le16toh(de[1].nameoff); + + /* String comparison without already matched prefix */ + diff = erofs_dirnamecmp(name, namelen, dname_start, dname_end, + &matched); + + if (diff < 0) { + erofs_brelse(blk); + if (mid == 0) + break; + back = mid - 1; + endprfx = matched; + continue; + } + + /* diff >= 0: this block is a candidate. */ + if (candidate != NULL) + erofs_brelse(candidate); + candidate = blk; + if (diff == 0) { + *_ndirents = 0; + *_datasize = maxsize; + return (candidate); + } + head = mid + 1; + startprfx = matched; + *_ndirents = ndirents; + *_datasize = maxsize; + } + return (candidate); +out: + if (candidate != NULL) + erofs_brelse(candidate); + return (NULL); +} + +/* + * Look up a name in a directory and return its nid and d_type. + * (Linux equivalent: erofs_namei in Linux's namei.c) + */ +static int +erofs_namei(struct erofs_mount *em, struct erofs_node *dir, const char *name, + size_t namelen, uint64_t *nid, uint8_t *d_type) +{ + int error; + uint32_t ndirents; + uint32_t datasize; + char *blk; + struct erofs_dirent *de; + + if (dir->size == 0) + return (ENOENT); + + blk = erofs_find_target_block(em, dir, name, namelen, &ndirents, + &datasize, &error); + if (blk == NULL) + return (error != 0 ? error : ENOENT); + + de = (struct erofs_dirent *)blk; + if (ndirents > 0) + de = find_target_dirent(name, namelen, blk, datasize, + ndirents); + + if (de != NULL) { + *nid = le64toh(de->nid); + *d_type = de->file_type; + } + erofs_brelse(blk); + return (de != NULL ? 0 : ENOENT); +} + +/* + * Directory name lookup (VOP_CACHEDLOOKUP entry point). + * + * FreeBSD-side API requirements: + * - "." must be returned under the caller's requested lock mode; + * - ".." must go through vn_vget_ino() to avoid holding a child lock while + * acquiring the parent directory lock in reverse; + * - Both hit and miss must correctly update the namecache. + */ +int +erofs_lookup(struct vop_cachedlookup_args *ap) +{ + struct vnode *dvp, *vp; + struct erofs_node *dir; + struct erofs_mount *em; + struct componentname *cnp; + uint64_t nid; + uint8_t dtype; + int error, ltype; + + dvp = ap->a_dvp; + cnp = ap->a_cnp; + *ap->a_vpp = NULL; + if ((cnp->cn_flags & ISLASTCN) != 0 && + (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) + return (EROFS); + if (cnp->cn_namelen < 0) + return (EINVAL); + if (cnp->cn_namelen > EROFS_NAME_LEN) + return (ENAMETOOLONG); + if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') { + vref(dvp); + ltype = cnp->cn_lkflags & LK_TYPE_MASK; + if (ltype != VOP_ISLOCKED(dvp)) { + if (ltype == LK_EXCLUSIVE) + vn_lock(dvp, LK_UPGRADE | LK_RETRY); + else if (ltype == LK_SHARED) + vn_lock(dvp, LK_DOWNGRADE | LK_RETRY); + } + *ap->a_vpp = dvp; + return (0); + } + + dir = VTOE(dvp); + em = MTOE(dvp->v_mount); + error = erofs_namei(em, dir, cnp->cn_nameptr, cnp->cn_namelen, &nid, + &dtype); + if (error != 0) { + if (error == ENOENT && (cnp->cn_flags & MAKEENTRY) != 0) + cache_enter(dvp, NULL, cnp); + if (error == ENOENT && (cnp->cn_flags & ISLASTCN) != 0 && + (cnp->cn_nameiop == CREATE || cnp->cn_nameiop == RENAME)) + return (EROFS); + return (error); + } + + if ((cnp->cn_flags & ISDOTDOT) != 0) + error = vn_vget_ino(dvp, nid, cnp->cn_lkflags, &vp); + else + error = erofs_vget(dvp->v_mount, nid, cnp->cn_lkflags, &vp); + if (error != 0) + return (error); + *ap->a_vpp = vp; + if ((cnp->cn_flags & MAKEENTRY) != 0) + cache_enter(dvp, vp, cnp); + return (0); +} diff --git a/super.c b/super.c new file mode 100644 index 0000000..2295545 --- /dev/null +++ b/super.c @@ -0,0 +1,884 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017-2018 HUAWEI, Inc. + * https://www.huawei.com/ + * Copyright (C) 2021, Alibaba Cloud + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "internal.h" +#include "xattr.h" +#include "erofs_defs.h" + +MALLOC_DEFINE(M_EROFS, "erofs", "EROFS filesystem"); + +static const char *erofs_opts[] = { + "export", + "from", + NULL, +}; + +static vfs_mount_t erofs_mount; +static vfs_root_t erofs_root; +static vfs_statfs_t erofs_statfs; +static vfs_unmount_t erofs_unmount; +static vfs_vget_t erofs_vgetf; +static vfs_fhtovp_t erofs_fhtovp; + +#define EROFS_DEVICE_OPT_PREFIX "device." + +struct erofs_device_arg { + uint16_t slot; + char *path; +}; + +static int +erofs_load_generation_seed(struct erofs_mount *em, uint32_t sb_size, + uint32_t *seedp) +{ + uint32_t seed; + void *buf; + int error; + + error = erofs_bread(em, EROFS_SUPER_OFFSET, sb_size, &buf); + if (error != 0) + return (error); + seed = fnv_32_buf(buf, sb_size, FNV1_32_INIT); + erofs_brelse(buf); + *seedp = seed != 0 ? seed : 1; + return (0); +} + +static void +erofs_free_device_args(struct erofs_device_arg *args, unsigned int count) +{ + unsigned int i; + + if (args == NULL) + return; + for (i = 0; i < count; ++i) + free(args[i].path, M_EROFS); + free(args, M_EROFS); +} + +static int +erofs_parse_device_slot(const char *name, uint16_t *slotp) +{ + const char *p; + unsigned int slot; + + if (strncmp(name, EROFS_DEVICE_OPT_PREFIX, + sizeof(EROFS_DEVICE_OPT_PREFIX) - 1) != 0) + return (ENOENT); + p = name + sizeof(EROFS_DEVICE_OPT_PREFIX) - 1; + if (*p < '1' || *p > '9') + return (EINVAL); + slot = 0; + for (; *p != '\0'; ++p) { + if (*p < '0' || *p > '9' || slot > (UINT16_MAX - (*p - '0')) / 10) + return (EINVAL); + slot = slot * 10 + (*p - '0'); + } + if (slot == 0 || slot > UINT16_MAX) + return (EINVAL); + *slotp = slot; + return (0); +} + +static int +erofs_parse_device_options(struct mount *mp, struct erofs_device_arg **argsp, + unsigned int *countp) +{ + struct erofs_device_arg *args; + struct vfsopt *opt; + char name[32]; + unsigned int count, i; + uint16_t slot; + int error; + + *argsp = NULL; + *countp = 0; + count = 0; + TAILQ_FOREACH(opt, mp->mnt_optnew, link) { + error = erofs_parse_device_slot(opt->name, &slot); + if (error == ENOENT) + continue; + if (error != 0 || opt->value == NULL || opt->len <= 1 || + ((char *)opt->value)[opt->len - 1] != '\0') { + vfs_mount_error(mp, "erofs: invalid external device option %s", + opt->name); + return (EINVAL); + } + if (count == UINT16_MAX) + return (E2BIG); + ++count; + } + if (count == 0) + return (0); + + args = mallocarray(count, sizeof(*args), M_EROFS, M_WAITOK | M_ZERO); + i = 0; + TAILQ_FOREACH(opt, mp->mnt_optnew, link) { + error = erofs_parse_device_slot(opt->name, &slot); + if (error == ENOENT) + continue; + KASSERT(error == 0, ("validated EROFS device option changed")); + args[i].slot = slot; + args[i].path = malloc(opt->len, M_EROFS, M_WAITOK); + memcpy(args[i].path, opt->value, opt->len); + ++i; + } + for (i = 0; i < count; ++i) { + snprintf(name, sizeof(name), EROFS_DEVICE_OPT_PREFIX "%u", + args[i].slot); + vfs_deleteopt(mp->mnt_optnew, name); + } + *argsp = args; + *countp = count; + return (0); +} + +static void +erofs_release_device_info(struct erofs_device_info *dif) +{ + if (dif->cp != NULL) { + g_topology_lock(); + g_vfs_close(dif->cp); + g_topology_unlock(); + dif->cp = NULL; + } + if (dif->devvp != NULL) { + vrele(dif->devvp); + dif->devvp = NULL; + } + if (dif->dev != NULL) { + dev_rel(dif->dev); + dif->dev = NULL; + } +} + +static bool +erofs_provider_is_duplicate(struct erofs_mount *em, struct g_provider *pp) +{ + unsigned int i; + + if (em == NULL) + return (false); + if (em->dif0.cp != NULL && em->dif0.cp->provider == pp) + return (true); + for (i = 0; i < em->extra_devices; ++i) { + if (em->devs[i].cp != NULL && em->devs[i].cp->provider == pp) + return (true); + } + return (false); +} + +static int +erofs_open_device(struct erofs_mount *em, const char *path, + struct erofs_device_info *dif) +{ + struct g_provider *pp; + struct nameidata nd; + struct vnode *devvp; + struct cdev *dev; + int error; + + bzero(dif, sizeof(*dif)); + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, path); + error = namei(&nd); + if (error != 0) + return (error); + devvp = nd.ni_vp; + NDFREE_PNBUF(&nd); + if (!vn_isdisk_error(devvp, &error)) { + vput(devvp); + return (error); + } + error = VOP_ACCESS(devvp, VREAD, curthread->td_ucred, curthread); + if (error != 0) + error = priv_check(curthread, PRIV_VFS_MOUNT_PERM); + if (error != 0) { + vput(devvp); + return (error); + } + dev = devvp->v_rdev; + dev_ref(dev); + g_topology_lock(); + pp = g_dev_getprovider(dev); + if (pp == NULL) + error = ENXIO; + else if (erofs_provider_is_duplicate(em, pp)) + error = EINVAL; + else + error = g_vfs_open(devvp, &dif->cp, "erofs", 0); + if (error == 0) { + dif->mediasize = dif->cp->provider->mediasize; + dif->sectorsize = dif->cp->provider->sectorsize; + } + g_topology_unlock(); + VOP_UNLOCK(devvp); + if (error != 0) { + dev_rel(dev); + vrele(devvp); + return (error); + } + dif->devvp = devvp; + dif->dev = dev; + if (dif->sectorsize == 0 || + (dif->sectorsize & (dif->sectorsize - 1)) != 0) { + erofs_release_device_info(dif); + return (EINVAL); + } + return (0); +} + +static void +erofs_update_iosize_max(struct mount *mp, const struct erofs_device_info *dif) +{ + u_long iosize; + + iosize = dif->dev != NULL && dif->dev->si_iosize_max != 0 ? + dif->dev->si_iosize_max : MAXPHYS; + mp->mnt_iosize_max = MIN(mp->mnt_iosize_max, MIN(iosize, (u_long)MAXPHYS)); +} + +static void +erofs_free_dev_context(struct erofs_mount *em) +{ + unsigned int i; + + if (em->devs != NULL) { + for (i = em->extra_devices; i > 0; --i) + erofs_release_device_info(&em->devs[i - 1]); + free(em->devs, M_EROFS); + } +} + +static void +erofs_drop_internal_inodes(struct erofs_mount *em) +{ + if (em->metabox_en != NULL) + free(em->metabox_en, M_EROFS); + if (em->packed_inode != NULL) + free(em->packed_inode, M_EROFS); +} + +static void +erofs_sb_free(struct erofs_mount *em) +{ + if (em == NULL) + return; + z_erofs_extent_cache_fini(em); + erofs_xattr_prefixes_cleanup(em); + erofs_drop_internal_inodes(em); + erofs_free_dev_context(em); + erofs_release_device_info(&em->dif0); + free(em, M_EROFS); +} + +static int +erofs_superblock_csum_verify(struct erofs_mount *em, + const struct erofs_super_block *dsb) +{ + uint32_t expected, crc; + size_t len; + void *buf; + int error; + + if ((le32toh(dsb->feature_compat) & EROFS_FEATURE_COMPAT_SB_CHKSUM) == 0) + return (0); + + len = 1u << dsb->blkszbits; + if (len > EROFS_SUPER_OFFSET) + len -= EROFS_SUPER_OFFSET; + + buf = NULL; + error = erofs_bread(em, EROFS_SUPER_OFFSET, len, &buf); + if (error != 0) + return (error); + + crc = calculate_crc32c(EROFS_CRC32C_SEED, + (const uint8_t *)buf + offsetof(struct erofs_super_block, checksum) + + sizeof(dsb->checksum), + len - offsetof(struct erofs_super_block, checksum) - + sizeof(dsb->checksum)); + expected = le32toh(dsb->checksum); + erofs_brelse(buf); + + if (crc != expected) { + vfs_mount_error(em->mnt, + "erofs: invalid superblock checksum 0x%08x, " + "0x%08x expected", crc, expected); + return (EINTEGRITY); + } + return (0); +} + +static void +erofs_sb_blocks_root(const struct erofs_super_block *dsb, uint32_t incompat, + uint64_t *blocks, uint64_t *root_nid) +{ + *blocks = le32toh(dsb->blocks_lo); + if ((incompat & EROFS_FEATURE_INCOMPAT_48BIT) != 0 && + dsb->rootnid_8b != 0) { + *blocks |= (uint64_t)le16toh(dsb->rb.blocks_hi) << 32; + *root_nid = le64toh(dsb->rootnid_8b); + } else { + *root_nid = le16toh(dsb->rb.rootnid_2b); + } +} + +static int +erofs_validate_device_size(struct erofs_mount *em, + struct erofs_device_info *dif, erofs_blk_t blocks) +{ + uint64_t bytes; + + if (blocks == 0) + return (EINTEGRITY); + if (em->block_size < dif->sectorsize || + em->block_size % dif->sectorsize != 0) + return (EINVAL); + if (blocks > (UINT64_MAX >> em->block_bits)) + return (EINTEGRITY); + bytes = blocks << em->block_bits; + if (bytes > dif->mediasize) + return (ENXIO); + return (0); +} + +static int +erofs_init_device(struct erofs_mount *em, struct erofs_device_info *dif, + const char *path) +{ + struct erofs_device_info opened; + erofs_blk_t blocks, uniaddr; + int error; + + blocks = dif->blocks; + uniaddr = dif->uniaddr; + error = erofs_open_device(em, path, &opened); + if (error != 0) + return (error); + erofs_update_iosize_max(em->mnt, &opened); + opened.blocks = blocks; + opened.uniaddr = uniaddr; + *dif = opened; + return (erofs_validate_device_size(em, dif, dif->blocks)); +} + +static const char * +erofs_device_arg_path(const struct erofs_device_arg *args, unsigned int count, + unsigned int slot) +{ + unsigned int i; + + for (i = 0; i < count; ++i) { + if (args[i].slot == slot) + return (args[i].path); + } + return (NULL); +} + +static int +erofs_scan_devices(struct erofs_mount *em, const struct erofs_super_block *dsb, + const struct erofs_device_arg *args, unsigned int arg_count) +{ + struct erofs_deviceslot *slots; + struct erofs_device_info *dif; + const char *path; + uint64_t devt_off, devt_size, image_size, end, other_end; + erofs_blk_t maxend; + unsigned int i, j, mask; + void *buf; + int error; + + em->total_blocks = em->dif0.blocks; + em->flatdev_blocks = em->dif0.blocks; + if (em->extra_devices == 0) { + if (arg_count != 0) { + vfs_mount_error(em->mnt, + "erofs: external devices given without a device table"); + return (EINVAL); + } + return (0); + } + devt_off = (uint64_t)le16toh(dsb->devt_slotoff) * EROFS_DEVT_SLOT_SIZE; + devt_size = (uint64_t)em->extra_devices * EROFS_DEVT_SLOT_SIZE; + if (em->dif0.blocks > (UINT64_MAX >> em->block_bits)) + return (EINTEGRITY); + image_size = em->dif0.blocks << em->block_bits; + if (devt_off > image_size || devt_size > image_size - devt_off || + devt_size > SIZE_MAX) + return (EINTEGRITY); + error = erofs_bread(em, devt_off, (size_t)devt_size, &buf); + if (error != 0) + return (error); + slots = buf; + em->devs = mallocarray(em->extra_devices, sizeof(*em->devs), M_EROFS, + M_WAITOK | M_ZERO); + maxend = em->dif0.blocks; + for (i = 0; i < em->extra_devices; ++i) { + dif = &em->devs[i]; + dif->blocks = le32toh(slots[i].blocks_lo); + dif->uniaddr = le32toh(slots[i].uniaddr_lo); + if (erofs_sb_has_48bit(em)) { + dif->blocks |= (uint64_t)le16toh(slots[i].blocks_hi) << 32; + dif->uniaddr |= (uint64_t)le16toh(slots[i].uniaddr_hi) << 32; + } + if (dif->blocks == 0 || + __builtin_add_overflow(dif->uniaddr, dif->blocks, &end)) { + error = EINTEGRITY; + goto out; + } + if (end > + (erofs_sb_has_48bit(em) ? (1ULL << 48) : (1ULL << 32))) { + error = EINTEGRITY; + goto out; + } + if (dif->uniaddr != 0 && dif->uniaddr < em->dif0.blocks) { + error = EINTEGRITY; + goto out; + } + for (j = 0; j < i; ++j) { + if (dif->uniaddr == 0 || em->devs[j].uniaddr == 0) + continue; + if (__builtin_add_overflow(em->devs[j].uniaddr, + em->devs[j].blocks, &other_end)) { + error = EINTEGRITY; + goto out; + } + if (dif->uniaddr < other_end && em->devs[j].uniaddr < end) { + error = EINTEGRITY; + goto out; + } + } + if (__builtin_add_overflow(em->total_blocks, dif->blocks, + &em->total_blocks)) { + error = EOVERFLOW; + goto out; + } + maxend = MAX(maxend, (erofs_blk_t)end); + } + erofs_brelse(buf); + buf = NULL; + em->flatdev_blocks = maxend; + mask = 1; + while (mask < (unsigned int)em->extra_devices + 1) + mask <<= 1; + em->device_id_mask = mask - 1; + em->flatdev = arg_count == 0; + if (em->flatdev) + return (erofs_validate_device_size(em, &em->dif0, + em->flatdev_blocks)); + if (arg_count != em->extra_devices) { + vfs_mount_error(em->mnt, + "erofs: external devices don't match (ondisk %u, given %u)", + em->extra_devices, arg_count); + return (arg_count < em->extra_devices ? ENXIO : EINVAL); + } + for (i = 0; i < arg_count; ++i) { + if (args[i].slot == 0 || args[i].slot > em->extra_devices) + return (EINVAL); + } + for (i = 0; i < em->extra_devices; ++i) { + path = erofs_device_arg_path(args, arg_count, i + 1); + if (path == NULL) + return (ENXIO); + error = erofs_init_device(em, &em->devs[i], path); + if (error != 0) + return (error); + } + return (0); +out: + erofs_brelse(buf); + return (error); +} + +static int +erofs_init_packed_inode(struct erofs_mount *em) +{ + int error; + + /* Load the packed carrier before any fragment-backed metabox inode. */ + if ((em->feature_incompat & EROFS_FEATURE_INCOMPAT_FRAGMENTS) != 0 && + em->packed_nid > 0) { + em->packed_inode = malloc(sizeof(*em->packed_inode), M_EROFS, + M_WAITOK | M_ZERO); + if (em->packed_inode == NULL) + return (ENOMEM); + error = erofs_read_inode(em, em->packed_nid, em->packed_inode); + if (error != 0) { + free(em->packed_inode, M_EROFS); + em->packed_inode = NULL; + return (error); + } + if (em->packed_inode->vtype != VREG || em->packed_inode->fragment) { + vfs_mount_error(em->mnt, + "erofs: packed inode nid=%ju is not a non-recursive regular file", + (uintmax_t)em->packed_nid); + return (EINTEGRITY); + } + } + return (0); +} + +static int +erofs_init_metabox_inode(struct erofs_mount *em) +{ + int error; + + /* + * METABOX NIDs address inode slots in this backing inode's data. The + * packed carrier is ready first so a compressed metabox may legally end in + * a fragment pcluster without reading an uninitialized dependency. + */ + if (erofs_sb_has_metabox(em)) { + struct erofs_map_blocks map; + + em->metabox_en = malloc(sizeof(*em->metabox_en), M_EROFS, + M_WAITOK | M_ZERO); + if (em->metabox_en == NULL) + return (ENOMEM); + error = erofs_read_inode(em, em->metabox_nid, em->metabox_en); + if (error != 0) + return (error); + if (em->metabox_en->vtype != VREG) { + vfs_mount_error(em->mnt, + "erofs: metabox inode nid=%ju is not a regular file", + (uintmax_t)em->metabox_nid); + return (EINTEGRITY); + } + if (em->metabox_en->fragment) { + if (em->packed_inode == NULL || + em->packed_inode->nid == em->metabox_en->nid || + em->metabox_en->size == 0) + return (EINTEGRITY); + bzero(&map, sizeof(map)); + map.m_la = em->metabox_en->size - 1; + error = z_erofs_map_blocks_iter(em, em->metabox_en, &map, + EROFS_GET_BLOCKS_FIEMAP); + if (error != 0 || (map.m_flags & EROFS_MAP_FRAGMENT) == 0) + return (error != 0 ? error : EINTEGRITY); + } + } + return (0); +} + +static int +erofs_mountfs(struct erofs_device_info *primary, struct mount *mp, + const struct erofs_device_arg *args, unsigned int arg_count) +{ + struct erofs_mount *em; + struct erofs_super_block *dsb; + uint32_t unsupported; + void *buf; + int error; + + em = malloc(sizeof(*em), M_EROFS, M_WAITOK | M_ZERO); + em->mnt = mp; + z_erofs_extent_cache_init(em); + em->dif0 = *primary; + bzero(primary, sizeof(*primary)); + buf = NULL; + + error = erofs_bread(em, EROFS_SUPER_OFFSET, sizeof(*dsb), &buf); + if (error != 0) + goto fail; + dsb = buf; + if (le32toh(dsb->magic) != EROFS_SUPER_MAGIC_V1) { + error = EINVAL; + goto fail; + } + if (dsb->blkszbits < 9 || dsb->blkszbits > PAGE_SHIFT) { + error = EINVAL; + goto fail; + } + if (dsb->dirblkbits != 0) { + error = EOPNOTSUPP; + goto fail; + } + em->feature_compat = le32toh(dsb->feature_compat); + em->feature_incompat = le32toh(dsb->feature_incompat); + em->packed_nid = le64toh(dsb->packed_nid); + em->extra_devices = erofs_sb_has_device_table(em) ? + le16toh(dsb->extra_devices) : 0; + unsupported = em->feature_incompat & ~EROFS_ALL_SUPPORTED_INCOMPAT; + /* + * Narrowly allow one extra combination: long xattr prefixes enabled + * with non-plain prefix table stored in a packed inode, which adds + * the FRAGMENTS (0x20) incompat bit. This is NOT a declaration of + * general fragments support; per-inode data layout is still gated + * by plain/inline checks in erofs_read_inode(). + */ + if (unsupported != 0) { + if (unsupported != EROFS_FEATURE_INCOMPAT_FRAGMENTS || + (em->feature_incompat & + EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES) == 0 || + (em->feature_compat & + EROFS_FEATURE_COMPAT_PLAIN_XATTR_PFX) != 0 || + em->packed_nid == 0) { + error = EOPNOTSUPP; + goto fail; + } + } + em->block_bits = dsb->blkszbits; + em->block_size = 1u << em->block_bits; + em->sb_size = 128 + dsb->sb_extslots * EROFS_SB_EXTSLOT_SIZE; + if (em->sb_size > PAGE_SIZE - EROFS_SUPER_OFFSET) { + error = EINVAL; + goto fail; + } + em->meta_blkaddr = le32toh(dsb->meta_blkaddr); + em->xattr_blkaddr = le32toh(dsb->xattr_blkaddr); + em->xattr_prefix_start = le32toh(dsb->xattr_prefix_start); + em->xattr_prefix_count = dsb->xattr_prefix_count; + if (erofs_sb_has_ishare_xattrs(em) && + dsb->ishare_xattr_prefix_id >= em->xattr_prefix_count) { + error = EINTEGRITY; + goto fail; + } + /* A non-zero reserved value disables the current name-filter format. */ + if (erofs_sb_has_xattr_filter(em) && dsb->xattr_filter_reserved != 0) + em->feature_compat &= ~EROFS_FEATURE_COMPAT_XATTR_FILTER; + erofs_sb_blocks_root(dsb, em->feature_incompat, &em->blocks, + &em->root_nid); + em->dif0.blocks = em->blocks; + error = erofs_validate_device_size(em, &em->dif0, em->dif0.blocks); + if (error != 0) + goto fail; + error = erofs_superblock_csum_verify(em, dsb); + if (error != 0) + goto fail; + em->inos = le64toh(dsb->inos); + em->epoch = le64toh(dsb->epoch); + em->fixed_nsec = le32toh(dsb->fixed_nsec); + if (em->fixed_nsec >= 1000000000) { + error = EINTEGRITY; + goto fail; + } + error = erofs_load_generation_seed(em, em->sb_size, + &em->generation_seed); + if (error != 0) + goto fail; + if (em->packed_nid != 0 && erofs_nid_in_metabox(em->packed_nid)) { + error = EINTEGRITY; + goto fail; + } + if (erofs_sb_has_metabox(em)) { + if (em->sb_size <= offsetof(struct erofs_super_block, metabox_nid)) { + error = EINTEGRITY; + goto fail; + } + em->metabox_nid = le64toh(dsb->metabox_nid); + if (erofs_nid_in_metabox(em->metabox_nid)) { + error = EINTEGRITY; + goto fail; + } + } + + error = z_erofs_parse_cfgs(em, dsb); + if (error != 0) + goto fail; + error = erofs_scan_devices(em, dsb, args, arg_count); + if (error != 0) + goto fail; + + if (erofs_sb_has_shared_ea_in_metabox(em) && + !erofs_sb_has_metabox(em)) { + error = EINTEGRITY; + goto fail; + } + + error = erofs_init_packed_inode(em); + if (error != 0) + goto fail; + error = erofs_init_metabox_inode(em); + if (error != 0) + goto fail; + error = erofs_xattr_prefixes_init(em); + if (error != 0) + goto fail; + set_opt(&em->opt, POSIX_ACL); + memcpy(em->volume_name, dsb->volume_name, 16); + em->volume_name[16] = '\0'; + + erofs_brelse(buf); + buf = NULL; + mp->mnt_data = em; + mp->mnt_stat.f_fsid.val[0] = dev2udev(em->dif0.devvp->v_rdev); + mp->mnt_stat.f_fsid.val[1] = mp->mnt_vfc->vfc_typenum; + MNT_ILOCK(mp); + mp->mnt_flag |= MNT_LOCAL | MNT_RDONLY | MNT_ACLS; + mp->mnt_kern_flag |= MNTK_LOOKUP_SHARED | MNTK_EXTENDED_SHARED | + MNTK_USES_BCACHE; + MNT_IUNLOCK(mp); + return (0); +fail: + if (buf != NULL) + erofs_brelse(buf); + erofs_sb_free(em); + return (error); +} + +static int +erofs_mount(struct mount *mp) +{ + struct erofs_device_arg *args; + struct erofs_device_info primary; + char *fspec; + unsigned int arg_count; + int error, len; + + MNT_ILOCK(mp); + mp->mnt_flag |= MNT_RDONLY; + MNT_IUNLOCK(mp); + if (mp->mnt_flag & MNT_UPDATE) { + if (vfs_flagopt(mp->mnt_optnew, "export", NULL, 0)) + return (0); + return (EOPNOTSUPP); + } + args = NULL; + arg_count = 0; + error = erofs_parse_device_options(mp, &args, &arg_count); + if (error != 0) + return (error); + if (vfs_filteropt(mp->mnt_optnew, erofs_opts) != 0) { + erofs_free_device_args(args, arg_count); + return (EINVAL); + } + fspec = NULL; + error = vfs_getopt(mp->mnt_optnew, "from", (void **)&fspec, &len); + if (error != 0 || fspec == NULL || len == 0 || + fspec[len - 1] != '\0') { + erofs_free_device_args(args, arg_count); + return (EINVAL); + } + mp->mnt_iosize_max = MAXPHYS; + error = erofs_open_device(NULL, fspec, &primary); + if (error != 0) { + erofs_free_device_args(args, arg_count); + return (error); + } + erofs_update_iosize_max(mp, &primary); + error = erofs_mountfs(&primary, mp, args, arg_count); + erofs_free_device_args(args, arg_count); + if (error != 0) + return (error); + vfs_mountedfrom(mp, fspec); + return (erofs_statfs(mp, &mp->mnt_stat)); +} + +static int +erofs_root(struct mount *mp, int flags, struct vnode **vpp) +{ + int error; + + error = erofs_vget(mp, MTOE(mp)->root_nid, flags, vpp); + if (error != 0) + vfs_mount_error(mp, "erofs: failed to load root nid %ju: error %d", + (uintmax_t)MTOE(mp)->root_nid, error); + return (error); +} + +static int +erofs_statfs(struct mount *mp, struct statfs *sbp) +{ + struct erofs_mount *em; + + em = MTOE(mp); + sbp->f_bsize = em->block_size; + sbp->f_iosize = em->block_size; + sbp->f_blocks = em->total_blocks; + sbp->f_bfree = 0; + sbp->f_bavail = 0; + sbp->f_files = em->inos; + sbp->f_ffree = 0; + return (0); +} + +static int +erofs_unmount(struct mount *mp, int mntflags) +{ + struct erofs_mount *em; + int error, flags; + + flags = ((mntflags & MNT_FORCE) != 0) ? FORCECLOSE : 0; + error = vflush(mp, 0, flags, curthread); + if (error != 0) + return (error); + em = MTOE(mp); + mp->mnt_data = NULL; + erofs_sb_free(em); + return (0); +} + +static int +erofs_vgetf(struct mount *mp, ino_t ino, int flags, struct vnode **vpp) +{ + return (erofs_vget(mp, ino, flags, vpp)); +} + +/* Persistent EROFS file handle to locked vnode. */ +static int +erofs_fhtovp(struct mount *mp, struct fid *fhp, int flags, struct vnode **vpp) +{ + struct erofs_fid efid; + struct erofs_node *en; + struct vnode *vp; + uint64_t nid; + int error; + + *vpp = NULLVP; + bzero(&efid, sizeof(efid)); + memcpy(&efid, fhp, sizeof(efid)); + if (efid.len != sizeof(efid) || efid.pad != 0) + return (EINVAL); + nid = ((uint64_t)efid.nid_hi << 32) | efid.nid_lo; + if (!erofs_nid_is_valid(MTOE(mp), nid)) + return (ESTALE); + error = VFS_VGET(mp, (ino_t)nid, flags, &vp); + if (error != 0) + return (error); + en = VTOE(vp); + if (en->mode == 0 || en->nlink == 0 || en->nid != nid || + en->generation != efid.gen) { + vput(vp); + return (ESTALE); + } + *vpp = vp; + return (0); +} + +static struct vfsops erofs_vfsops = { + .vfs_fhtovp = erofs_fhtovp, + .vfs_mount = erofs_mount, + .vfs_root = erofs_root, + .vfs_statfs = erofs_statfs, + .vfs_unmount = erofs_unmount, + .vfs_vget = erofs_vgetf, +}; +VFS_SET(erofs_vfsops, erofs, VFCF_READONLY); +MODULE_DEPEND(erofs, acl_posix1e, 1, 1, 1); +MODULE_DEPEND(erofs, zlib, 1, 1, 1); +MODULE_VERSION(erofs, 1); diff --git a/xattr.c b/xattr.c new file mode 100644 index 0000000..8bc805c --- /dev/null +++ b/xattr.c @@ -0,0 +1,842 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2017-2018 HUAWEI, Inc. + * https://www.huawei.com/ + * Copyright (C) 2021-2022, Alibaba Cloud + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" +#include "xattr.h" + +struct posix_acl_xattr_entry { + uint16_t e_tag; + uint16_t e_perm; + uint32_t e_id; +}; + +struct posix_acl_xattr_header { + uint32_t a_version; +}; + +#define POSIX_ACL_XATTR_VERSION 0x0002 +#define EROFS_XATTR_FILTER_POSIX_ACL \ + ((1U << 21) | (1U << 30)) + +static int +erofs_xattr_backing_size(struct erofs_mount *em, struct erofs_node *backing_en, + uint64_t *sizep) +{ + if (backing_en != NULL) { + *sizep = backing_en->size; + return (0); + } + if (em->blocks > (UINT64_MAX >> em->block_bits)) + return (EOVERFLOW); + *sizep = em->blocks << em->block_bits; + return (0); +} + +static int +erofs_xattr_read_backing(struct erofs_mount *em, + struct erofs_node *backing_en, uint64_t off, size_t len, void **bufp) +{ + uint64_t backing_size; + int error; + + error = erofs_xattr_backing_size(em, backing_en, &backing_size); + if (error != 0) + return (error); + if (off > backing_size || (uint64_t)len > backing_size - off) + return (EINTEGRITY); + if (backing_en != NULL) + return (erofs_read_data(em, backing_en, off, len, bufp)); + if (off > INT64_MAX) + return (EOVERFLOW); + return (erofs_bread(em, (off_t)off, len, bufp)); +} + +/* + * Read one prefix table metadata record. + * + * When backing_en == NULL the record is in the physical metadata area; + * otherwise it lives in the selected metadata carrier's logical data stream. + */ +static int +erofs_xattr_read_metadata(struct erofs_mount *em, struct erofs_node *backing_en, + uint64_t *offp, void **bufp, size_t *lenp) +{ + uint16_t raw_len; + void *buf, *hdrbuf; + uint64_t off; + size_t len; + int error; + + if (*offp > UINT64_MAX - (sizeof(struct erofs_xattr_entry) - 1)) + return (EOVERFLOW); + off = roundup2(*offp, sizeof(struct erofs_xattr_entry)); + error = erofs_xattr_read_backing(em, backing_en, off, sizeof(raw_len), + &hdrbuf); + if (error != 0) + return (error); + raw_len = le16toh(*(uint16_t *)hdrbuf); + erofs_brelse(hdrbuf); + len = (raw_len == 0) ? (size_t)UINT16_MAX + 1 : raw_len; + if (len < sizeof(struct erofs_xattr_long_prefix) || + len > EROFS_NAME_LEN + sizeof(struct erofs_xattr_long_prefix)) + return (EINTEGRITY); + if (off > UINT64_MAX - sizeof(raw_len)) + return (EOVERFLOW); + error = erofs_xattr_read_backing(em, backing_en, + off + sizeof(raw_len), len, &buf); + if (error != 0) + return (error); + *offp = off + sizeof(raw_len) + len; + *bufp = buf; + *lenp = len; + return (0); +} + +void +erofs_xattr_prefixes_cleanup(struct erofs_mount *em) +{ + if (em->xattr_prefixes == NULL) + return; + for (uint8_t i = 0; i < em->xattr_prefix_count; i++) + free(em->xattr_prefixes[i].infix, M_EROFS); + free(em->xattr_prefixes, M_EROFS); + em->xattr_prefixes = NULL; +} + +int +erofs_xattr_prefixes_init(struct erofs_mount *em) +{ + struct erofs_xattr_long_prefix *prefix = NULL; + struct erofs_node packed_en, *prefix_en; + uint64_t off; + size_t infix_len, len; + int error; + + if ((em->feature_incompat & EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES) == + 0 || + em->xattr_prefix_count == 0) + return (0); + prefix_en = NULL; + if ((em->feature_compat & EROFS_FEATURE_COMPAT_PLAIN_XATTR_PFX) == 0) { + if (erofs_sb_has_metabox(em)) { + if (em->metabox_en == NULL) + return (EINTEGRITY); + prefix_en = em->metabox_en; + } else if (em->packed_inode != NULL) { + prefix_en = em->packed_inode; + } else if (em->packed_nid != 0) { + error = erofs_read_inode(em, em->packed_nid, &packed_en); + if (error != 0) + return (error); + if (packed_en.vtype != VREG) + return (EINTEGRITY); + prefix_en = &packed_en; + } + } + em->xattr_prefixes = malloc(sizeof(*em->xattr_prefixes) * + em->xattr_prefix_count, + M_EROFS, M_WAITOK | M_ZERO); + off = (uint64_t)em->xattr_prefix_start << 2; + for (uint8_t i = 0; i < em->xattr_prefix_count; i++) { + error = erofs_xattr_read_metadata(em, prefix_en, &off, + (void **)&prefix, &len); + if (error != 0) + goto fail; + infix_len = len - sizeof(*prefix); + em->xattr_prefixes[i].base_index = prefix->base_index; + em->xattr_prefixes[i].infix_len = infix_len; + em->xattr_prefixes[i].infix = malloc(infix_len + 1, M_EROFS, + M_WAITOK); + memcpy(em->xattr_prefixes[i].infix, prefix->infix, infix_len); + em->xattr_prefixes[i].infix[infix_len] = '\0'; + erofs_brelse(prefix); + prefix = NULL; + } + return (0); +fail: + if (prefix != NULL) + erofs_brelse(prefix); + erofs_xattr_prefixes_cleanup(em); + return (error); +} + +static int +erofs_xattr_move(void *value, size_t value_size, struct uio *uio, size_t *sizep) +{ + if (sizep != NULL) + *sizep = value_size; + if (uio == NULL || value_size == 0) + return (0); + return (uiomove(value, value_size, uio)); +} + +static int +erofs_xattr_load_body(struct erofs_mount *em, struct erofs_node *en, + char **bodyp, struct erofs_xattr_ibody_header **ihp, size_t *header_sizep) +{ + struct erofs_xattr_ibody_header *ih; + char *body; + uint64_t body_off; + size_t header_size; + int error; + + if (en->xattr_isize < sizeof(*ih)) + return (EINTEGRITY); + if (en->inode_off > UINT64_MAX - en->inode_isize) + return (EINTEGRITY); + body_off = en->inode_off + en->inode_isize; + error = erofs_xattr_read_backing(em, + erofs_nid_in_metabox(en->nid) ? em->metabox_en : NULL, body_off, + en->xattr_isize, (void **)&body); + if (error != 0) + return (error); + ih = (struct erofs_xattr_ibody_header *)body; + if (en->xattr_isize == sizeof(*ih)) { + error = EOPNOTSUPP; + goto fail; + } + header_size = sizeof(*ih) + sizeof(uint32_t) * ih->h_shared_count; + if (header_size > en->xattr_isize) { + error = EINTEGRITY; + goto fail; + } + *bodyp = body; + *ihp = ih; + *header_sizep = header_size; + return (0); +fail: + erofs_brelse(body); + return (error); +} + +static int +erofs_xattr_validate_entry(struct erofs_xattr_entry *entry, size_t remaining, + size_t *entry_sizep, size_t *value_sizep) +{ + size_t entry_size, min_size, value_size; + + if (remaining < sizeof(*entry)) + return (EINTEGRITY); + value_size = le16toh(entry->e_value_size); + min_size = sizeof(*entry) + entry->e_name_len + value_size; + if (min_size > remaining) + return (EINTEGRITY); + entry_size = erofs_xattr_entry_size(entry); + if (entry_size > remaining) + return (EINTEGRITY); + if (entry_sizep != NULL) + *entry_sizep = entry_size; + if (value_sizep != NULL) + *value_sizep = value_size; + return (0); +} + +static bool +erofs_xattr_prefix(uint8_t base_index, int *namespacep, + const char **prefixp, size_t *prefix_lenp) +{ + switch (base_index) { + case EROFS_XATTR_INDEX_USER: + *namespacep = EXTATTR_NAMESPACE_USER; + *prefixp = NULL; + *prefix_lenp = 0; + return (true); + case EROFS_XATTR_INDEX_POSIX_ACL_ACCESS: + *namespacep = EXTATTR_NAMESPACE_SYSTEM; + *prefixp = "posix_acl_access"; + *prefix_lenp = sizeof("posix_acl_access") - 1; + return (true); + case EROFS_XATTR_INDEX_POSIX_ACL_DEFAULT: + *namespacep = EXTATTR_NAMESPACE_SYSTEM; + *prefixp = "posix_acl_default"; + *prefix_lenp = sizeof("posix_acl_default") - 1; + return (true); + case EROFS_XATTR_INDEX_TRUSTED: + *namespacep = EXTATTR_NAMESPACE_SYSTEM; + *prefixp = "trusted."; + *prefix_lenp = sizeof("trusted.") - 1; + return (true); + case EROFS_XATTR_INDEX_SECURITY: + *namespacep = EXTATTR_NAMESPACE_SYSTEM; + *prefixp = "security."; + *prefix_lenp = sizeof("security.") - 1; + return (true); + case EROFS_XATTR_INDEX_LUSTRE: + default: + return (false); + } +} + +static int +erofs_xattr_namespace_prefix(int attrnamespace, uint8_t base_index, + const char **prefixp, size_t *prefix_lenp) +{ + int mapped_namespace; + + if (attrnamespace != EXTATTR_NAMESPACE_USER && + attrnamespace != EXTATTR_NAMESPACE_SYSTEM) + return (EOPNOTSUPP); + if (!erofs_xattr_prefix(base_index, &mapped_namespace, prefixp, + prefix_lenp)) + return (ENOATTR); + if (mapped_namespace != attrnamespace) + return (ENOATTR); + return (0); +} + +static int +erofs_xattr_list_move(const char *namespace_prefix, size_t namespace_prefix_len, + const char *infix, size_t infix_len, const char *name, uint8_t name_len, + struct uio *uio, size_t *sizep) +{ + uint8_t total_name_len; + int error; + + if (namespace_prefix_len + infix_len + name_len > EROFS_NAME_LEN) + return (EINTEGRITY); + total_name_len = namespace_prefix_len + infix_len + name_len; + if (sizep != NULL) { + *sizep += total_name_len + 1; + return (0); + } + if (uio == NULL) + return (0); + error = uiomove(__DECONST(void *, &total_name_len), 1, uio); + if (error != 0) + return (error); + if (namespace_prefix_len != 0) { + error = uiomove(__DECONST(void *, namespace_prefix), + namespace_prefix_len, uio); + if (error != 0) + return (error); + } + if (infix_len != 0) { + error = uiomove(__DECONST(void *, infix), infix_len, uio); + if (error != 0) + return (error); + } + return (uiomove(__DECONST(void *, name), name_len, uio)); +} + +static int +erofs_xattr_resolve_name(struct erofs_mount *em, + const struct erofs_xattr_entry *entry, uint8_t *base_indexp, + const char **infixp, size_t *infix_lenp) +{ + struct erofs_xattr_prefix_item *prefix; + uint8_t prefix_id; + + if ((entry->e_name_index & EROFS_XATTR_LONG_PREFIX) == 0) { + *base_indexp = entry->e_name_index; + *infixp = NULL; + *infix_lenp = 0; + return (0); + } + if (em->xattr_prefixes == NULL) + return (ENOATTR); + prefix_id = entry->e_name_index & EROFS_XATTR_LONG_PREFIX_MASK; + if (prefix_id >= em->xattr_prefix_count) + return (ENOATTR); + prefix = &em->xattr_prefixes[prefix_id]; + *base_indexp = prefix->base_index; + *infixp = prefix->infix; + *infix_lenp = prefix->infix_len; + return (0); +} + +static bool +erofs_xattr_name_match(const char *namespace_prefix, + size_t namespace_prefix_len, const char *infix, size_t infix_len, + const struct erofs_xattr_entry *entry, const char *name, size_t name_len) +{ + if (name_len != namespace_prefix_len + infix_len + entry->e_name_len) + return (false); + if (namespace_prefix_len != 0 && + memcmp(name, namespace_prefix, namespace_prefix_len) != 0) + return (false); + if (infix_len != 0 && + memcmp(name + namespace_prefix_len, infix, infix_len) != 0) + return (false); + return (memcmp(name + namespace_prefix_len + infix_len, entry->e_name, + entry->e_name_len) == 0); +} + +static int +erofs_xattr_shared_entry_offset(struct erofs_mount *em, uint32_t shared_id, + uint64_t *phys_offp) +{ + uint64_t base, relative; + + if (em->xattr_blkaddr > (UINT64_MAX >> em->block_bits)) + return (EOVERFLOW); + base = (uint64_t)em->xattr_blkaddr << em->block_bits; + relative = (uint64_t)shared_id * sizeof(uint32_t); + if (relative > UINT64_MAX - base) + return (EOVERFLOW); + *phys_offp = base + relative; + return (0); +} + +static int +erofs_xattr_load_shared_entry(struct erofs_mount *em, uint32_t shared_id, + struct erofs_xattr_entry **entryp, size_t *entry_sizep, size_t *value_sizep) +{ + struct erofs_xattr_entry *entry; + struct erofs_node *backing_en; + void *hdrbuf; + uint64_t off; + size_t entry_size, value_size; + int error; + + backing_en = erofs_sb_has_shared_ea_in_metabox(em) ? em->metabox_en : NULL; + if (erofs_sb_has_shared_ea_in_metabox(em) && backing_en == NULL) + return (EINTEGRITY); + + error = erofs_xattr_shared_entry_offset(em, shared_id, &off); + if (error != 0) + return (error); + + error = erofs_xattr_read_backing(em, backing_en, off, sizeof(*entry), + &hdrbuf); + if (error != 0) + return (error); + entry = hdrbuf; + value_size = le16toh(entry->e_value_size); + entry_size = erofs_xattr_entry_size(entry); + + erofs_brelse(hdrbuf); + + error = erofs_xattr_read_backing(em, backing_en, off, entry_size, + (void **)entryp); + if (error != 0) + return (error); + if (entry_sizep != NULL) + *entry_sizep = entry_size; + if (value_sizep != NULL) + *value_sizep = value_size; + return (0); +} + +static int +erofs_inode_has_noacl(struct erofs_mount *em, struct erofs_node *en, + bool *noaclp) +{ + struct erofs_xattr_ibody_header *ih; + struct erofs_node *backing_en; + uint64_t body_off; + uint32_t name_filter; + int error; + + *noaclp = false; + if (en->xattr_isize < sizeof(*ih)) { + *noaclp = true; + return (0); + } + if (!erofs_sb_has_xattr_filter(em)) + return (0); + if (en->inode_off > UINT64_MAX - en->inode_isize) + return (EINTEGRITY); + body_off = en->inode_off + en->inode_isize; + backing_en = erofs_nid_in_metabox(en->nid) ? em->metabox_en : NULL; + error = erofs_xattr_read_backing(em, backing_en, body_off, sizeof(*ih), + (void **)&ih); + if (error != 0) + return (error); + name_filter = le32toh(ih->h_name_filter); + erofs_brelse(ih); + *noaclp = (name_filter & EROFS_XATTR_FILTER_POSIX_ACL) == + EROFS_XATTR_FILTER_POSIX_ACL; + return (0); +} + +static void +erofs_acl_from_mode(struct erofs_node *en, acl_type_t type, struct acl *aclp) +{ + if (type == ACL_TYPE_DEFAULT) { + aclp->acl_cnt = 0; + return; + } + aclp->acl_cnt = 3; + aclp->acl_entry[0].ae_tag = ACL_USER_OBJ; + aclp->acl_entry[0].ae_id = ACL_UNDEFINED_ID; + aclp->acl_entry[0].ae_perm = (en->mode >> 6) & ACL_PERM_BITS; + aclp->acl_entry[1].ae_tag = ACL_GROUP_OBJ; + aclp->acl_entry[1].ae_id = ACL_UNDEFINED_ID; + aclp->acl_entry[1].ae_perm = (en->mode >> 3) & ACL_PERM_BITS; + aclp->acl_entry[2].ae_tag = ACL_OTHER; + aclp->acl_entry[2].ae_id = ACL_UNDEFINED_ID; + aclp->acl_entry[2].ae_perm = en->mode & ACL_PERM_BITS; +} + +struct erofs_xattr_iter { + struct erofs_mount *em; + struct erofs_node *en; + int attrnamespace; + const char *name; + size_t name_len; + struct uio *uio; + size_t *sizep; +}; + +static int +erofs_getxattr_foreach(struct erofs_xattr_iter *it, + struct erofs_xattr_entry *entry, size_t value_size) +{ + const char *infix, *namespace_prefix; + size_t infix_len, namespace_prefix_len; + uint8_t base_index; + int error; + + error = erofs_xattr_resolve_name(it->em, entry, &base_index, &infix, + &infix_len); + if (error != 0) + return (error); + error = erofs_xattr_namespace_prefix(it->attrnamespace, base_index, + &namespace_prefix, &namespace_prefix_len); + if (error != 0) + return (error); + if (!erofs_xattr_name_match(namespace_prefix, namespace_prefix_len, + infix, infix_len, entry, it->name, it->name_len)) + return (ENOATTR); + return (erofs_xattr_move(entry->e_name + entry->e_name_len, value_size, + it->uio, it->sizep)); +} + +static int +erofs_listxattr_foreach(struct erofs_xattr_iter *it, + struct erofs_xattr_entry *entry) +{ + const char *infix, *namespace_prefix; + size_t infix_len, namespace_prefix_len; + uint8_t base_index; + int error; + + error = erofs_xattr_resolve_name(it->em, entry, &base_index, &infix, + &infix_len); + if (error == ENOATTR) + return (0); + if (error != 0) + return (error); + error = erofs_xattr_namespace_prefix(it->attrnamespace, base_index, + &namespace_prefix, &namespace_prefix_len); + if (error == ENOATTR) + return (0); + if (error != 0) + return (error); + return (erofs_xattr_list_move(namespace_prefix, namespace_prefix_len, + infix, infix_len, entry->e_name, entry->e_name_len, it->uio, + it->sizep)); +} + +static int +erofs_xattr_iter_inline(struct erofs_xattr_iter *it, char *body, + size_t header_size, bool get) +{ + struct erofs_xattr_entry *entry; + char *cursor; + size_t entry_size, remaining, value_size; + int error; + + remaining = it->en->xattr_isize - header_size; + cursor = body + header_size; + while (remaining != 0) { + entry = (struct erofs_xattr_entry *)cursor; + error = erofs_xattr_validate_entry(entry, remaining, + &entry_size, get ? &value_size : NULL); + if (error != 0) + return (error); + if (get) + error = erofs_getxattr_foreach(it, entry, value_size); + else + error = erofs_listxattr_foreach(it, entry); + if (get) { + if (error != ENOATTR) + return (error); + } else if (error != 0) { + return (error); + } + cursor += entry_size; + remaining -= entry_size; + } + return (get ? ENOATTR : 0); +} + +static int +erofs_xattr_iter_shared(struct erofs_xattr_iter *it, + struct erofs_xattr_ibody_header *ih, bool get) +{ + struct erofs_xattr_entry *entry; + uint32_t shared_id; + size_t value_size; + int error; + + for (uint8_t i = 0; i < ih->h_shared_count; i++) { + shared_id = le32toh(ih->h_shared_xattrs[i]); + error = erofs_xattr_load_shared_entry(it->em, shared_id, &entry, + NULL, get ? &value_size : NULL); + if (error != 0) + return (error); + if (get) + error = erofs_getxattr_foreach(it, entry, value_size); + else + error = erofs_listxattr_foreach(it, entry); + erofs_brelse(entry); + if (get) { + if (error != ENOATTR) + return (error); + } else if (error != 0) { + return (error); + } + } + return (get ? ENOATTR : 0); +} + +/* + * Look up one inline/shared xattr by name. + * + * Name exposure rules: + * - user namespace: bare name, no "user." prefix; + * - system namespace: exposes full "trusted.*" / "security.*" names. + */ +int +erofs_getxattr(struct vnode *vp, int attrnamespace, const char *name, + struct uio *uio, size_t *sizep) +{ + struct erofs_mount *em; + struct erofs_node *en; + struct erofs_xattr_ibody_header *ih; + struct erofs_xattr_iter it; + char *body; + size_t header_size, name_len; + int error; + + em = MTOE(vp->v_mount); + en = VTOE(vp); + if (name == NULL || name[0] == '\0') + return (EINVAL); + name_len = strlen(name); + if (name_len > EROFS_NAME_LEN) + return (EINVAL); + if (en->xattr_isize == 0) + return (ENOATTR); + error = erofs_xattr_load_body(em, en, &body, &ih, &header_size); + if (error != 0) + return (error); + it.em = em; + it.en = en; + it.attrnamespace = attrnamespace; + it.name = name; + it.name_len = name_len; + it.uio = uio; + it.sizep = sizep; + error = erofs_xattr_iter_inline(&it, body, header_size, true); + if (error == ENOATTR) + error = erofs_xattr_iter_shared(&it, ih, true); + erofs_brelse(body); + return (error); +} + +/* + * Enumerate inline/shared xattr names for a given namespace. + * + * Return format: 1-byte name length followed by non-NUL-terminated name bytes. + */ +int +erofs_listxattr(struct vnode *vp, int attrnamespace, struct uio *uio, + size_t *sizep) +{ + struct erofs_mount *em; + struct erofs_node *en; + struct erofs_xattr_ibody_header *ih; + struct erofs_xattr_iter it; + char *body; + size_t header_size; + int error; + + em = MTOE(vp->v_mount); + en = VTOE(vp); + if (sizep != NULL) + *sizep = 0; + if (en->xattr_isize == 0) + return (0); + error = erofs_xattr_load_body(em, en, &body, &ih, &header_size); + if (error != 0) + return (error); + it.em = em; + it.en = en; + it.attrnamespace = attrnamespace; + it.name = NULL; + it.name_len = 0; + it.uio = uio; + it.sizep = sizep; + error = erofs_xattr_iter_inline(&it, body, header_size, false); + if (error == 0) + error = erofs_xattr_iter_shared(&it, ih, false); + erofs_brelse(body); + return (error); +} + +int +erofs_get_acl(struct vnode *vp, acl_type_t type, struct acl *aclp) +{ + struct erofs_mount *em; + struct erofs_node *en; + const char *xattr_name; + struct uio auio; + struct iovec aiov; + struct posix_acl_xattr_header hdr; + struct posix_acl_xattr_entry entry; + uint8_t buf[sizeof(hdr) + sizeof(entry) * ACL_MAX_ENTRIES]; + size_t size; + uint32_t id; + bool noacl; + int error, count, i, j, phase; + + em = MTOE(vp->v_mount); + if (!test_opt(&em->opt, POSIX_ACL)) + return (EOPNOTSUPP); + + en = VTOE(vp); + + switch (type) { + case ACL_TYPE_ACCESS: + xattr_name = "posix_acl_access"; + break; + case ACL_TYPE_DEFAULT: + if (vp->v_type != VDIR) + return (EINVAL); + xattr_name = "posix_acl_default"; + break; + default: + return (EINVAL); + } + error = erofs_inode_has_noacl(em, en, &noacl); + if (error != 0) + return (error); + if (noacl) { + erofs_acl_from_mode(en, type, aclp); + return (0); + } + + error = erofs_getxattr(vp, EXTATTR_NAMESPACE_SYSTEM, xattr_name, NULL, + &size); + if (error == ENOATTR) { + erofs_acl_from_mode(en, type, aclp); + return (0); + } + if (error != 0) + return (error); + if (size > sizeof(buf)) + return (EINTEGRITY); + + aiov.iov_base = buf; + aiov.iov_len = size; + auio.uio_iov = &aiov; + auio.uio_iovcnt = 1; + auio.uio_offset = 0; + auio.uio_resid = size; + auio.uio_segflg = UIO_SYSSPACE; + auio.uio_rw = UIO_READ; + auio.uio_td = curthread; + error = erofs_getxattr(vp, EXTATTR_NAMESPACE_SYSTEM, xattr_name, &auio, + NULL); + if (error != 0) + return (error); + if (auio.uio_resid != 0) + return (EINTEGRITY); + + if (size < sizeof(hdr) || (size - sizeof(hdr)) % sizeof(entry) != 0) + return (EINTEGRITY); + + memcpy(&hdr, buf, sizeof(hdr)); + if (le32toh(hdr.a_version) != POSIX_ACL_XATTR_VERSION) + return (EINTEGRITY); + + count = (size - sizeof(hdr)) / sizeof(entry); + if (count > ACL_MAX_ENTRIES) + return (EINTEGRITY); + if (count == 0) { + erofs_acl_from_mode(en, type, aclp); + return (0); + } + + aclp->acl_cnt = count; + phase = 0; + for (i = 0; i < count; i++) { + uint16_t tag, perm; + + memcpy(&entry, buf + sizeof(hdr) + i * sizeof(entry), + sizeof(entry)); + tag = le16toh(entry.e_tag); + perm = le16toh(entry.e_perm); + + id = le32toh(entry.e_id); + if ((perm & ~ACL_PERM_BITS) != 0) + return (EINTEGRITY); + switch (tag) { + case ACL_USER_OBJ: + if (phase != 0 || id != UINT32_MAX) + return (EINTEGRITY); + phase = 1; + break; + case ACL_USER: + if ((phase != 1 && phase != 2) || id == UINT32_MAX) + return (EINTEGRITY); + phase = 2; + break; + case ACL_GROUP_OBJ: + if ((phase != 1 && phase != 2) || id != UINT32_MAX) + return (EINTEGRITY); + phase = 3; + break; + case ACL_GROUP: + if ((phase != 3 && phase != 4) || id == UINT32_MAX) + return (EINTEGRITY); + phase = 4; + break; + case ACL_MASK: + if ((phase != 3 && phase != 4) || id != UINT32_MAX) + return (EINTEGRITY); + phase = 5; + break; + case ACL_OTHER: + if ((phase != 3 && phase != 4 && phase != 5) || + id != UINT32_MAX) + return (EINTEGRITY); + phase = 6; + break; + default: + return (EINTEGRITY); + } + if (tag == ACL_USER || tag == ACL_GROUP) { + for (j = 0; j < i; j++) { + if (aclp->acl_entry[j].ae_tag == tag && + aclp->acl_entry[j].ae_id == id) + return (EINTEGRITY); + } + } + + aclp->acl_entry[i].ae_tag = tag; + aclp->acl_entry[i].ae_perm = perm; + aclp->acl_entry[i].ae_id = (id == UINT32_MAX) ? ACL_UNDEFINED_ID : id; + } + if (phase != 6 || acl_posix1e_check(aclp) != 0) + return (EINTEGRITY); + + return (0); +} diff --git a/xattr.h b/xattr.h new file mode 100644 index 0000000..7d52b02 --- /dev/null +++ b/xattr.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2017-2018 HUAWEI, Inc. + * https://www.huawei.com/ + */ +#ifndef __EROFS_XATTR_H +#define __EROFS_XATTR_H + +#include "internal.h" + +int erofs_xattr_prefixes_init(struct erofs_mount *em); +void erofs_xattr_prefixes_cleanup(struct erofs_mount *em); +int erofs_getxattr(struct vnode *vp, int attrnamespace, const char *name, + struct uio *uio, size_t *sizep); +int erofs_listxattr(struct vnode *vp, int attrnamespace, struct uio *uio, + size_t *sizep); +int erofs_get_acl(struct vnode *vp, acl_type_t type, struct acl *aclp); +#endif diff --git a/zdata.c b/zdata.c new file mode 100644 index 0000000..34b3da7 --- /dev/null +++ b/zdata.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2018-2019 HUAWEI, Inc. + * https://www.huawei.com/ + */ + +#include +#include +#include +#include +#include +#include + +#include "internal.h" + +static bool +z_erofs_extent_cache_match(const struct erofs_zextent_cache *cache, + const struct erofs_node *en, const struct erofs_map_blocks *map) +{ + + return (cache->data != NULL && cache->m_nid == en->nid && + cache->m_pa == map->m_pa && + cache->m_la == map->m_la && cache->m_plen == map->m_plen && + cache->m_llen == map->m_llen && + cache->m_deviceid == map->m_deviceid && + cache->m_flags == map->m_flags && + cache->m_algorithmformat == + (unsigned char)map->m_algorithmformat); +} + +static bool +z_erofs_extent_cache_copy(struct erofs_mount *em, struct erofs_node *en, + struct erofs_map_blocks *map, uint64_t mapoff, size_t len, void *dst) +{ + bool matched; + + KASSERT(len <= MAXPHYS, ("erofs extent cache copy exceeds MAXPHYS")); + if (!em->z_extent_cache_initialized) + return (false); + mtx_lock(&em->z_extent_cache_lock); + matched = z_erofs_extent_cache_match(&em->z_extent_cache, en, map); + if (matched) + memcpy(dst, (char *)em->z_extent_cache.data + (size_t)mapoff, len); + mtx_unlock(&em->z_extent_cache_lock); + return (matched); +} + +static void +z_erofs_extent_cache_publish(struct erofs_mount *em, struct erofs_node *en, + struct erofs_map_blocks *map, uint64_t mapoff, size_t len, void *decoded, + void *dst) +{ + void *old; + + KASSERT(len <= MAXPHYS, ("erofs extent cache publish exceeds MAXPHYS")); + mtx_lock(&em->z_extent_cache_lock); + if (z_erofs_extent_cache_match(&em->z_extent_cache, en, map)) { + memcpy(dst, (char *)em->z_extent_cache.data + (size_t)mapoff, len); + mtx_unlock(&em->z_extent_cache_lock); + free(decoded, M_EROFS); + return; + } + old = em->z_extent_cache.data; + em->z_extent_cache.data = decoded; + em->z_extent_cache.m_nid = en->nid; + em->z_extent_cache.m_pa = map->m_pa; + em->z_extent_cache.m_la = map->m_la; + em->z_extent_cache.m_plen = map->m_plen; + em->z_extent_cache.m_llen = map->m_llen; + em->z_extent_cache.m_deviceid = map->m_deviceid; + em->z_extent_cache.m_flags = map->m_flags; + em->z_extent_cache.m_algorithmformat = + (unsigned char)map->m_algorithmformat; + memcpy(dst, (char *)decoded + (size_t)mapoff, len); + mtx_unlock(&em->z_extent_cache_lock); + free(old, M_EROFS); +} + +void +z_erofs_extent_cache_init(struct erofs_mount *em) +{ + + mtx_init(&em->z_extent_cache_lock, "erofs zextent", NULL, MTX_DEF); + em->z_extent_cache_initialized = true; +} + +void +z_erofs_extent_cache_fini(struct erofs_mount *em) +{ + void *data; + + if (!em->z_extent_cache_initialized) + return; + mtx_lock(&em->z_extent_cache_lock); + data = em->z_extent_cache.data; + em->z_extent_cache.data = NULL; + mtx_unlock(&em->z_extent_cache_lock); + free(data, M_EROFS); + mtx_destroy(&em->z_extent_cache_lock); + em->z_extent_cache_initialized = false; +} + +static bool +z_erofs_extent_cache_eligible(const struct erofs_mount *em, + const struct erofs_node *en, const struct erofs_map_blocks *map, + size_t len) +{ + + return (len <= MAXPHYS && (map->m_flags & (EROFS_MAP_META | + EROFS_MAP_PARTIAL_MAPPED | EROFS_MAP_PARTIAL_REF | + EROFS_MAP_FRAGMENT)) == 0 && + map->m_algorithmformat == Z_EROFS_COMPRESSION_LZMA && + em->z_extent_cache_initialized && en != em->packed_inode && + en != em->metabox_en); +} + +static int +z_erofs_read_extent(struct erofs_mount *em, struct erofs_node *en, + struct erofs_map_blocks *map, size_t decoded_len, void **bufp) +{ + void *compressed, *decoded; + bool partial; + int error; + + *bufp = NULL; + if ((map->m_flags & EROFS_MAP_FRAGMENT) != 0) + return (EINTEGRITY); + if ((map->m_flags & EROFS_MAP_MAPPED) == 0) + return (EINTEGRITY); +#if SIZE_MAX < UINT64_MAX + if (map->m_plen > SIZE_MAX || map->m_llen > SIZE_MAX) + return (EOVERFLOW); +#endif + if (decoded_len == 0 || decoded_len > map->m_llen) + return (EINTEGRITY); + partial = (map->m_flags & EROFS_MAP_PARTIAL_REF) != 0; + if (!partial && decoded_len != map->m_llen) + return (EINTEGRITY); + + if ((map->m_flags & EROFS_MAP_META) != 0) + error = erofs_read_metadata(em, en->nid, map->m_pa, + (size_t)map->m_plen, &compressed); + else + error = erofs_read_physical(em, map->m_deviceid, map->m_pa, + (size_t)map->m_plen, &compressed); + if (error != 0) + return (error); + + decoded = malloc(decoded_len, M_EROFS, M_WAITOK | M_ZERO); + error = z_erofs_decompress(em, map, compressed, (size_t)map->m_plen, + decoded, decoded_len, partial); + erofs_brelse(compressed); + if (error != 0) { + free(decoded, M_EROFS); + return (error); + } + *bufp = decoded; + return (0); +} + +static int +z_erofs_do_read(struct erofs_mount *em, struct erofs_node *en, + uint64_t loff, size_t len, char *out) +{ + struct erofs_map_blocks map; + void *decoded, *fragment; + uint64_t mapoff; + size_t decoded_len, done, want; + int error; + + done = 0; + while (done < len) { + bzero(&map, sizeof(map)); + map.m_la = loff + done; + error = z_erofs_map_blocks_iter(em, en, &map, + EROFS_GET_BLOCKS_FIEMAP); + if (error != 0) + return (error); + if (map.m_llen == 0 || map.m_la > loff + done || + loff + done - map.m_la >= map.m_llen) + return (EINTEGRITY); + mapoff = loff + done - map.m_la; +#if SIZE_MAX < UINT64_MAX + if (mapoff > SIZE_MAX) + return (EOVERFLOW); + if (map.m_llen - mapoff > SIZE_MAX) + return (EOVERFLOW); +#endif + want = MIN((size_t)(map.m_llen - mapoff), len - done); + if (want == 0) + return (EINTEGRITY); + + if ((map.m_flags & EROFS_MAP_FRAGMENT) != 0) { + if (em->packed_inode == NULL || + em->packed_inode->nid == en->nid || + en->z_fragmentoff > UINT64_MAX - mapoff) + return (EINTEGRITY); + error = erofs_read_data(em, em->packed_inode, + en->z_fragmentoff + mapoff, want, &fragment); + if (error != 0) + return (error); + memcpy(out + done, fragment, want); + erofs_brelse(fragment); + } else if ((map.m_flags & EROFS_MAP_MAPPED) == 0) { + bzero(out + done, want); + } else { + decoded_len = (size_t)map.m_llen; + if ((map.m_flags & EROFS_MAP_PARTIAL_REF) != 0) { + if (mapoff > SIZE_MAX - want) + return (EOVERFLOW); + decoded_len = (size_t)mapoff + want; + } + if (z_erofs_extent_cache_eligible(em, en, &map, want) && + z_erofs_extent_cache_copy(em, en, &map, mapoff, want, + out + done)) { + done += want; + continue; + } + error = z_erofs_read_extent(em, en, &map, decoded_len, + &decoded); + if (error != 0) + return (error); + if (z_erofs_extent_cache_eligible(em, en, &map, want)) + z_erofs_extent_cache_publish(em, en, &map, mapoff, want, + decoded, out + done); + else { + memcpy(out + done, (char *)decoded + mapoff, want); + free(decoded, M_EROFS); + } + } + done += want; + } + return (0); +} + +int +z_erofs_read_data(struct erofs_mount *em, struct erofs_node *en, + uint64_t loff, size_t len, void **bufp) +{ + char *out; + int error; + + if (bufp == NULL) + return (EINVAL); + *bufp = NULL; + if (len == 0) + return (0); + if (loff > UINT64_MAX - len) + return (EOVERFLOW); + if (loff > en->size || len > en->size - loff) + return (EINTEGRITY); + + out = malloc(len, M_EROFS, M_WAITOK); + error = z_erofs_do_read(em, en, loff, len, out); + if (error != 0) { + free(out, M_EROFS); + return (error); + } + *bufp = out; + return (0); +} + +int +z_erofs_read_uio(struct erofs_mount *em, struct erofs_node *en, + struct uio *uio) +{ + char *buf; + size_t want; + int error; + + if (uio->uio_offset < 0) + return (EINVAL); + while (uio->uio_resid > 0 && (uint64_t)uio->uio_offset < en->size) { + want = MIN((size_t)uio->uio_resid, + (size_t)MIN((uint64_t)MAXPHYS, + en->size - (uint64_t)uio->uio_offset)); + buf = malloc(want, M_EROFS, M_WAITOK); + error = z_erofs_do_read(em, en, (uint64_t)uio->uio_offset, + want, buf); + if (error == 0) + error = uiomove(buf, want, uio); + free(buf, M_EROFS); + if (error != 0) + return (error); + } + return (0); +} diff --git a/zmap.c b/zmap.c new file mode 100644 index 0000000..5117074 --- /dev/null +++ b/zmap.c @@ -0,0 +1,928 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2018-2019 HUAWEI, Inc. + * https://www.huawei.com/ + */ + +#include +#include +#include +#include +#include + +#include "internal.h" + +struct z_erofs_maprecorder { + struct erofs_mount *em; + struct erofs_node *en; + 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 int +z_erofs_read_index(struct z_erofs_maprecorder *m, uint64_t pos, size_t len, + void **bufp) +{ + return (erofs_read_metadata(m->em, m->en->nid, pos, len, bufp)); +} + +static int +z_erofs_load_full_lcluster(struct z_erofs_maprecorder *m, uint64_t lcn) +{ + struct erofs_node *en; + struct z_erofs_lcluster_index *di; + uint64_t base, pos; + unsigned int advise; + void *buf; + int error; + + en = m->en; + base = en->inode_off + en->inode_isize + en->xattr_isize; + if (base < en->inode_off) + return (EOVERFLOW); + base = Z_EROFS_FULL_INDEX_START(base); + if (lcn > (UINT64_MAX - base) / sizeof(*di)) + return (EOVERFLOW); + pos = base + lcn * sizeof(*di); + error = z_erofs_read_index(m, pos, sizeof(*di), &buf); + if (error != 0) + return (error); + + di = buf; + m->lcn = lcn; + m->nextpackoff = pos + sizeof(*di); + 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 << en->z_lclusterbits; + m->delta[0] = le16toh(di->di_u.delta[0]); + if ((m->delta[0] & Z_EROFS_LI_D0_CBLKCNT) != 0) { + if ((en->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 | + Z_EROFS_ADVISE_BIG_PCLUSTER_2)) == 0) { + erofs_brelse(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_brelse(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_node *en; + uint64_t ebase, pos, totalidx, original_lcn; + unsigned int compacted_4b_initial, compacted_2b, amortizedshift; + unsigned int vcnt, lo, lobits, encodebits, nblk, bytes, packsize; + bool big_pcluster; + uint8_t *in, type; + void *buf; + int distance, error, i; + + en = m->en; + ebase = Z_EROFS_MAP_HEADER_END(en->inode_off + en->inode_isize + + en->xattr_isize); + totalidx = roundup2(en->size, 1ULL << en->z_lclusterbits) >> + en->z_lclusterbits; + if (lcn >= totalidx || en->z_lclusterbits > 14) + return (EINVAL); + + original_lcn = lcn; + m->lcn = lcn; + compacted_4b_initial = ((32 - ebase % 32) / 4) & 7; + compacted_2b = 0; + if ((en->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 && en->z_lclusterbits <= 14) + vcnt = 2; + else if (amortizedshift == 1 && en->z_lclusterbits <= 12) + vcnt = 16; + else + return (EOPNOTSUPP); + + packsize = vcnt << amortizedshift; + bytes = pos & (packsize - 1); + pos -= bytes; + error = z_erofs_read_index(m, pos, packsize, &buf); + if (error != 0) + return (error); + in = buf; + m->nextpackoff = pos + packsize; + lobits = MAX(en->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 << en->z_lclusterbits; + if (lookahead) { + distance = get_compacted_la_distance(lobits, encodebits, + vcnt, in, i); + if (distance < 0) { + erofs_brelse(buf); + return (EINTEGRITY); + } + m->delta[1] = distance; + } + big_pcluster = + (en->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0; + if ((lo & Z_EROFS_LI_D0_CBLKCNT) != 0) { + if (!big_pcluster) { + erofs_brelse(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_brelse(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_brelse(buf); + return (m->delta[0] == 0 ? EINTEGRITY : 0); + } + + m->clusterofs = lo; + m->delta[0] = 0; + big_pcluster = + (en->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) { + if (i == 0) { + erofs_brelse(buf); + return (EINTEGRITY); + } + --i; + nblk += lo & ~Z_EROFS_LI_D0_CBLKCNT; + continue; + } + if (lo <= 1) { + erofs_brelse(buf); + return (EINTEGRITY); + } + i -= lo - 2; + continue; + } + ++nblk; + } + } + m->pblk = le32dec(in + packsize - sizeof(uint32_t)) + nblk; + erofs_brelse(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 error; + + if (m->en->datalayout == EROFS_INODE_COMPRESSED_COMPACT) + error = z_erofs_load_compact_lcluster(m, lcn, lookahead); + else if (m->en->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->en->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; + m->map->m_la = (lcn << m->en->z_lclusterbits) | + m->clusterofs; + return (0); + } + return (EINTEGRITY); +} + +static int +z_erofs_get_extent_compressedlen(struct z_erofs_maprecorder *m, + uint64_t initial_lcn) +{ + struct erofs_node *en; + bool bigpcl1, bigpcl2; + uint64_t lcn; + int error; + + en = m->en; + bigpcl1 = (en->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0; + bigpcl2 = (en->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_2) != 0; + lcn = m->lcn + 1; + if ((m->headtype == Z_EROFS_LCLUSTER_TYPE_HEAD1 && !bigpcl1) || + ((m->headtype == Z_EROFS_LCLUSTER_TYPE_PLAIN || + m->headtype == Z_EROFS_LCLUSTER_TYPE_HEAD2) && !bigpcl2) || + (lcn << en->z_lclusterbits) >= en->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 (m->compressedblks > (UINT64_MAX >> m->em->block_bits)) + return (EOVERFLOW); + m->map->m_plen = m->compressedblks << m->em->block_bits; + (void)initial_lcn; + return (0); +} + +static int +z_erofs_get_extent_decompressedlen(struct z_erofs_maprecorder *m) +{ + struct erofs_node *en; + struct erofs_map_blocks *map; + uint64_t lcn, headlcn; + int error; + + en = m->en; + map = m->map; + lcn = m->lcn; + headlcn = map->m_la >> en->z_lclusterbits; + for (;;) { + if ((lcn << en->z_lclusterbits) >= en->size) { + map->m_llen = en->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 (lcn > UINT64_MAX - m->delta[1]) + return (EOVERFLOW); + lcn += m->delta[1]; + } + map->m_llen = (lcn << en->z_lclusterbits) + m->clusterofs - + map->m_la; + return (0); +} + +static int +z_erofs_map_blocks_fo(struct erofs_mount *em, struct erofs_node *en, + 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 error; + + fragment = (en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0; + ztailpacking = en->z_idata_size != 0; + bzero(&m, sizeof(m)); + m.em = em; + m.en = en; + m.map = map; + + if (en->size == 0) { + map->m_la = 0; + map->m_llen = 0; + map->m_flags = 0; + return (0); + } + ofs = (flags & EROFS_GET_BLOCKS_FINDTAIL) != 0 ? + en->size - 1 : map->m_la; + if (fragment && (flags & EROFS_GET_BLOCKS_FINDTAIL) == 0 && + en->z_tailextent_headlcn == 0) { + map->m_la = 0; + map->m_llen = en->size; + map->m_flags = EROFS_MAP_FRAGMENT; + return (0); + } + initial_lcn = ofs >> en->z_lclusterbits; + endoff = ofs & ((1U << en->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) + en->z_fragmentoff = m.nextpackoff; + + map->m_flags = EROFS_MAP_MAPPED | EROFS_MAP_PARTIAL_MAPPED; + end = (m.lcn + 1) << en->z_lclusterbits; + if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD && + endoff >= m.clusterofs) { + m.headtype = m.type; + map->m_la = (m.lcn << en->z_lclusterbits) | m.clusterofs; + if (ztailpacking && end > en->size) + end = en->size; + } else { + if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD) { + end = (m.lcn << en->z_lclusterbits) | m.clusterofs; + 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) { + en->z_tailextent_headlcn = m.lcn; + if (fragment && + en->datalayout == EROFS_INODE_COMPRESSED_FULL) + en->z_fragmentoff |= m.pblk << 32; + } + if (ztailpacking && m.lcn == en->z_tailextent_headlcn) { + map->m_flags |= EROFS_MAP_META; + map->m_pa = en->z_fragmentoff; + map->m_plen = en->z_idata_size; + if ((map->m_pa & (em->block_size - 1)) + map->m_plen > + em->block_size) + return (EINTEGRITY); + } else if (fragment && m.lcn == en->z_tailextent_headlcn) { + map->m_flags = EROFS_MAP_FRAGMENT; + } else { + if (m.pblk > (UINT64_MAX >> em->block_bits)) + return (EOVERFLOW); + map->m_pa = m.pblk << em->block_bits; + 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 = + (en->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 = en->z_algorithmtype[1]; + } else { + map->m_algorithmformat = en->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 >= em->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_read_extent(struct erofs_mount *em, struct erofs_node *en, + uint64_t pos, unsigned int recsz, struct z_erofs_extent *ext) +{ + void *buf; + int error; + + bzero(ext, sizeof(*ext)); + error = erofs_read_metadata(em, en->nid, pos, recsz, &buf); + if (error != 0) + return (error); + memcpy(ext, buf, recsz); + erofs_brelse(buf); + 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_node *en, unsigned int recsz, + uint64_t *result) +{ + uint64_t pos; + + if (z_erofs_extent_add(en->inode_off, en->inode_isize, &pos) != 0 || + z_erofs_extent_add(pos, en->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_node *en, uint64_t table_pos, + unsigned int recsz, uint64_t index, uint64_t *result) +{ + uint64_t offset; + + if (index >= en->z_extents || + __builtin_mul_overflow(index, recsz, &offset) || + z_erofs_extent_add(table_pos, offset, result) != 0) + return (EINTEGRITY); + 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_mount *em, struct erofs_node *en, + unsigned int recsz) +{ + struct z_erofs_extent ext; + uint64_t extent_pos, index, last_pos, lstart, previous; + int error; + + if (en->z_extents == 0) + return (en->size == 0 ? 0 : EINTEGRITY); + error = z_erofs_extent_table_pos(en, 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(en, extent_pos, recsz, + en->z_extents - 1, &last_pos); + if (error != 0 || z_erofs_extent_add(last_pos, recsz, &last_pos) != 0) + return (EINTEGRITY); + if (recsz <= offsetof(struct z_erofs_extent, pstart_hi)) + return (0); + if (en->size == 0) + return (EINTEGRITY); + + previous = 0; + for (index = 0; index < en->z_extents; index++) { + error = z_erofs_extent_record_pos(en, extent_pos, recsz, index, + &last_pos); + if (error != 0) + return (error); + error = z_erofs_read_extent(em, en, last_pos, recsz, &ext); + if (error != 0) + return (error); + lstart = z_erofs_extent_lstart(&ext, recsz); + if (lstart >= en->size || (index != 0 && lstart <= previous)) + return (EINTEGRITY); + previous = lstart; + } + return (0); +} + +static int +z_erofs_map_blocks_ext(struct erofs_mount *em, struct erofs_node *en, + struct erofs_map_blocks *map, int flags) +{ + struct z_erofs_extent ext; + unsigned int recsz, bmask, fmt; + uint64_t cluster_size, extent_idx, extent_pos, next, pos, rounded_lend; + uint64_t lend, l, r, mid, pa, la, lstart, table_pos; + bool interlaced, last; + void *buf; + int error; + + (void)flags; + interlaced = + (en->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) != 0; + recsz = z_erofs_extent_recsize(en->z_advise); + error = z_erofs_extent_table_pos(en, recsz, &table_pos); + if (error != 0) + return (error); + pos = table_pos; + bmask = em->block_size - 1; + lend = en->size; + cluster_size = 1ULL << en->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(em, en->nid, pos, + sizeof(uint64_t), &buf); + if (error != 0) + return (error); + pa = le64dec(buf); + erofs_brelse(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 >> en->z_lclusterbits; + pa = EROFS_NULL_ADDR; + } + for (;;) { + error = z_erofs_extent_record_pos(en, pos, recsz, + extent_idx, &extent_pos); + if (error != 0) + return (error); + error = z_erofs_read_extent(em, en, 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 = en->z_extents; l < r;) { + mid = l + (r - l) / 2; + error = z_erofs_extent_record_pos(en, table_pos, recsz, mid, + &extent_pos); + if (error != 0) + return (error); + error = z_erofs_read_extent(em, en, 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 >= en->z_extents; + } + + if (lstart < lend) { + map->m_la = lstart; + if (last && + (en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) { + map->m_flags = EROFS_MAP_FRAGMENT; + en->z_fragmentoff = map->m_plen; + if (recsz > offsetof(struct z_erofs_extent, pstart_lo)) + en->z_fragmentoff |= map->m_pa << 32; + } else if ((map->m_plen & Z_EROFS_EXTENT_PLEN_MASK) != 0) { + map->m_flags = EROFS_MAP_MAPPED; + fmt = 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 = 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_mount *em, struct erofs_node *en) +{ + struct z_erofs_map_header *h; + struct erofs_map_blocks map; + uint64_t cluster_size, raw, pos, rounded_size; + unsigned int recsz; + void *buf; + int error; + + if (en->z_initialized) + return (0); + if (z_erofs_extent_add(en->inode_off, en->inode_isize, &pos) != 0 || + z_erofs_extent_add(pos, en->xattr_isize, &pos) != 0 || + z_erofs_extent_roundup(pos, 8, &pos) != 0) + return (EINTEGRITY); + error = erofs_read_metadata(em, en->nid, pos, sizeof(*h), &buf); + if (error != 0) + return (error); + h = buf; + if ((h->h_clusterbits & (1U << Z_EROFS_FRAGMENT_INODE_BIT)) != 0) { + if (!erofs_sb_has_fragments(em) || em->packed_nid == 0) { + erofs_brelse(buf); + return (EINTEGRITY); + } + raw = le64dec(h); + en->z_advise = Z_EROFS_ADVISE_FRAGMENT_PCLUSTER; + en->z_fragmentoff = raw ^ (1ULL << 63); + en->z_tailextent_headlcn = 0; + en->fragment = true; + erofs_brelse(buf); + en->z_initialized = true; + return (0); + } + + en->z_advise = le16toh(h->h_advise); + en->z_lclusterbits = em->block_bits + (h->h_clusterbits & 15); + if (en->z_lclusterbits >= 31) { + erofs_brelse(buf); + return (EINTEGRITY); + } + if (en->datalayout == EROFS_INODE_COMPRESSED_FULL && + (en->z_advise & Z_EROFS_ADVISE_EXTENTS) != 0) { + recsz = z_erofs_extent_recsize(en->z_advise); + if (recsz <= offsetof(struct z_erofs_extent, pstart_hi)) { + cluster_size = 1ULL << en->z_lclusterbits; + if (z_erofs_extent_roundup(en->size, cluster_size, + &rounded_size) != 0) { + erofs_brelse(buf); + return (EINTEGRITY); + } + en->z_extents = rounded_size >> en->z_lclusterbits; + } else { + en->z_extents = le32toh(h->h_extents_lo) | + ((uint64_t)le16toh(h->h_extents_hi) << 32); + } + en->fragment = + (en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0; + erofs_brelse(buf); + if (en->fragment && + (!erofs_sb_has_fragments(em) || em->packed_nid == 0)) + return (EINTEGRITY); + if (recsz > offsetof(struct z_erofs_extent, pstart_hi) && + en->z_extents == 0 && en->size != 0) + return (EINTEGRITY); + error = z_erofs_validate_extent_table(em, en, recsz); + if (error != 0) + return (error); + en->z_initialized = true; + return (0); + } + en->z_algorithmtype[0] = h->h_algorithmtype & 15; + en->z_algorithmtype[1] = h->h_algorithmtype >> 4; + if ((en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) + en->z_fragmentoff = le32toh(h->h_fragmentoff); + else if ((en->z_advise & Z_EROFS_ADVISE_INLINE_PCLUSTER) != 0) + en->z_idata_size = le16toh(h->h_idata_size); + erofs_brelse(buf); + + if (!erofs_sb_has_big_pcluster(em) && + (en->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 | + Z_EROFS_ADVISE_BIG_PCLUSTER_2)) != 0) + return (EINTEGRITY); + if (en->datalayout == EROFS_INODE_COMPRESSED_COMPACT && + (((en->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0) != + ((en->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_2) != 0))) + return (EINTEGRITY); + if (en->z_idata_size != 0 || + (en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) { + if ((en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0 && + (!erofs_sb_has_fragments(em) || em->packed_nid == 0)) + return (EINTEGRITY); + bzero(&map, sizeof(map)); + error = z_erofs_map_blocks_fo(em, en, &map, + EROFS_GET_BLOCKS_FINDTAIL); + if (error != 0) + return (error); + } + en->fragment = + (en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0; + en->z_initialized = true; + return (0); +} + +static int +z_erofs_map_sanity_check(struct erofs_mount *em, struct erofs_node *en, + struct erofs_map_blocks *map) +{ + uint64_t pend; + + if ((map->m_flags & EROFS_MAP_FRAGMENT) != 0) { + if ((map->m_flags & (EROFS_MAP_MAPPED | EROFS_MAP_META)) != 0 || + em->packed_inode == NULL || em->packed_inode->nid == en->nid || + en->z_fragmentoff > em->packed_inode->size || + map->m_llen > em->packed_inode->size - en->z_fragmentoff) + return (EINTEGRITY); + return (0); + } + if ((map->m_flags & EROFS_MAP_MAPPED) == 0) + return (0); + if ((unsigned char)map->m_algorithmformat >= + Z_EROFS_COMPRESSION_RUNTIME_MAX) + return (EOPNOTSUPP); + if (map->m_algorithmformat < Z_EROFS_COMPRESSION_MAX) { + if ((em->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 (__builtin_add_overflow(map->m_pa, map->m_plen, &pend)) + return (EINTEGRITY); + if ((pend >> em->block_bits) >= (1ULL << 48)) + return (EINTEGRITY); + (void)en; + return (0); +} + +int +z_erofs_map_blocks_iter(struct erofs_mount *em, struct erofs_node *en, + struct erofs_map_blocks *map, int flags) +{ + int error; + + if (map->m_la >= en->size) { + map->m_llen = map->m_la + 1 - en->size; + map->m_la = en->size; + map->m_flags = 0; + return (0); + } + error = z_erofs_fill_inode(em, en); + if (error == 0) { + if (en->datalayout == EROFS_INODE_COMPRESSED_FULL && + (en->z_advise & Z_EROFS_ADVISE_EXTENTS) != 0) + error = z_erofs_map_blocks_ext(em, en, map, flags); + else + error = z_erofs_map_blocks_fo(em, en, map, flags); + } + if (error == 0) + error = z_erofs_map_sanity_check(em, en, map); + if (error != 0) + map->m_llen = 0; + return (error); +}