From 0bbc2491557e560fadb339822a02356feca91ba5 Mon Sep 17 00:00:00 2001 From: imrcpan Date: Fri, 14 Aug 2026 12:01:38 +0200 Subject: [PATCH] update --- .clang-format | 199 +++++++++ .gitignore | 18 + Makefile | 33 ++ data.c | 542 ++++++++++++++++++++++++ decompressor.c | 204 +++++++++ decompressor_deflate.c | 67 +++ decompressor_lzma.c | 91 ++++ decompressor_zstd.c | 127 ++++++ dir.c | 346 +++++++++++++++ erofs_defs.h | 23 + erofs_fs.h | 472 +++++++++++++++++++++ erofs_vnops.c | 468 +++++++++++++++++++++ inode.c | 481 +++++++++++++++++++++ internal.h | 351 ++++++++++++++++ lz4.c | 95 +++++ namei.c | 310 ++++++++++++++ super.c | 884 +++++++++++++++++++++++++++++++++++++++ xattr.c | 842 +++++++++++++++++++++++++++++++++++++ xattr.h | 18 + zdata.c | 287 +++++++++++++ zmap.c | 928 +++++++++++++++++++++++++++++++++++++++++ 21 files changed, 6786 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 data.c create mode 100644 decompressor.c create mode 100644 decompressor_deflate.c create mode 100644 decompressor_lzma.c create mode 100644 decompressor_zstd.c create mode 100644 dir.c create mode 100644 erofs_defs.h create mode 100644 erofs_fs.h create mode 100644 erofs_vnops.c create mode 100644 inode.c create mode 100644 internal.h create mode 100644 lz4.c create mode 100644 namei.c create mode 100644 super.c create mode 100644 xattr.c create mode 100644 xattr.h create mode 100644 zdata.c create mode 100644 zmap.c 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); +}