update
This commit is contained in:
+199
@@ -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: '^<sys/cdefs\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 20
|
||||
- Regex: '^<sys/types\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 21
|
||||
- Regex: '^<sys/param\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 22
|
||||
- Regex: '^<sys/systm\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 23
|
||||
- Regex: '^<sys.*/'
|
||||
Priority: 2
|
||||
SortPriority: 24
|
||||
- Regex: '^<vm/vm\.h>'
|
||||
Priority: 3
|
||||
SortPriority: 30
|
||||
- Regex: '^<vm/'
|
||||
Priority: 3
|
||||
SortPriority: 31
|
||||
- Regex: '^<machine/'
|
||||
Priority: 4
|
||||
SortPriority: 40
|
||||
- Regex: '^<(x86|amd64|i386|xen)/'
|
||||
Priority: 5
|
||||
SortPriority: 50
|
||||
- Regex: '^<dev/'
|
||||
Priority: 6
|
||||
SortPriority: 60
|
||||
- Regex: '^<net.*/'
|
||||
Priority: 7
|
||||
SortPriority: 70
|
||||
- Regex: '^<protocols/'
|
||||
Priority: 7
|
||||
SortPriority: 71
|
||||
- Regex: '^<(fs|nfs(|client|server)|ufs)/'
|
||||
Priority: 8
|
||||
SortPriority: 80
|
||||
- Regex: '^<[^/].*\.h'
|
||||
Priority: 9
|
||||
SortPriority: 90
|
||||
- Regex: '^\".*\.h\"'
|
||||
Priority: 10
|
||||
SortPriority: 100
|
||||
# LLVM's header include ordering style is almost the exact opposite of ours.
|
||||
# Unfortunately, they have hard-coded their preferences into clang-format.
|
||||
# Clobbering this regular expression to avoid matching prevents non-system
|
||||
# headers from being forcibly moved to the top of the include list.
|
||||
# http://llvm.org/docs/CodingStandards.html#include-style
|
||||
IncludeIsMainRegex: 'BLAH_DONT_MATCH_ANYTHING'
|
||||
SortIncludes: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
TypenameMacros:
|
||||
- ARB_ELMTYPE
|
||||
- ARB_HEAD
|
||||
- ARB8_HEAD
|
||||
- ARB16_HEAD
|
||||
- ARB32_HEAD
|
||||
- ARB_ENTRY
|
||||
- ARB8_ENTRY
|
||||
- ARB16_ENTRY
|
||||
- ARB32_ENTRY
|
||||
- LIST_CLASS_ENTRY
|
||||
- LIST_CLASS_HEAD
|
||||
- LIST_ENTRY
|
||||
- LIST_HEAD
|
||||
- QUEUE_TYPEOF
|
||||
- RB_ENTRY
|
||||
- RB_HEAD
|
||||
- SLIST_CLASS_HEAD
|
||||
- SLIST_CLASS_ENTRY
|
||||
- SLIST_HEAD
|
||||
- SLIST_ENTRY
|
||||
- SMR_POINTER
|
||||
- SPLAY_ENTRY
|
||||
- SPLAY_HEAD
|
||||
- STAILQ_CLASS_ENTRY
|
||||
- STAILQ_CLASS_HEAD
|
||||
- STAILQ_ENTRY
|
||||
- STAILQ_HEAD
|
||||
- TAILQ_CLASS_ENTRY
|
||||
- TAILQ_CLASS_HEAD
|
||||
- TAILQ_ENTRY
|
||||
- TAILQ_HEAD
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
|
||||
i386
|
||||
machine
|
||||
x86
|
||||
.cache
|
||||
|
||||
export_syms
|
||||
|
||||
*.o
|
||||
*.ko
|
||||
|
||||
opt_global.h
|
||||
|
||||
vnode_if.h
|
||||
vnode_if_newproto.h
|
||||
vnode_if_typedef.h
|
||||
|
||||
compile_commands.json
|
||||
@@ -0,0 +1,33 @@
|
||||
KMOD= erofs
|
||||
|
||||
.if ${MACHINE_ARCH} != "amd64"
|
||||
.error erofs supports only MACHINE_ARCH=amd64
|
||||
.endif
|
||||
|
||||
WITH_ZSTDIO?= 0
|
||||
.if empty(WITH_ZSTDIO:M0) && empty(WITH_ZSTDIO:M1)
|
||||
.error WITH_ZSTDIO must be 0 or 1
|
||||
.endif
|
||||
|
||||
SRCS= super.c \
|
||||
inode.c \
|
||||
data.c \
|
||||
namei.c \
|
||||
dir.c \
|
||||
xattr.c \
|
||||
erofs_vnops.c \
|
||||
decompressor.c \
|
||||
zmap.c \
|
||||
zdata.c \
|
||||
lz4.c \
|
||||
decompressor_lzma.c \
|
||||
decompressor_deflate.c \
|
||||
decompressor_zstd.c \
|
||||
vnode_if.h
|
||||
|
||||
CFLAGS.decompressor_zstd.c+= -I${SYSDIR}/contrib/zstd/lib/freebsd
|
||||
.if ${WITH_ZSTDIO} == 1
|
||||
CFLAGS.decompressor_zstd.c+= -DZSTDIO
|
||||
.endif
|
||||
|
||||
.include <bsd.kmod.mk>
|
||||
@@ -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 <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bio.h>
|
||||
#include <sys/buf.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/vnode.h>
|
||||
#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));
|
||||
}
|
||||
+204
@@ -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 <sys/param.h>
|
||||
#include <sys/endian.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/systm.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/* Minimal DEFLATE decompressor for EROFS FreeBSD */
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <contrib/zlib/zlib.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -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 <sys/param.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/systm.h>
|
||||
|
||||
#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 <contrib/xz-embedded/linux/lib/xz/xz_dec_lzma2.c>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/* Minimal zstd decompressor for EROFS FreeBSD */
|
||||
#include <sys/param.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/kernel.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#ifdef ZSTDIO
|
||||
#define ZSTD_STATIC_LINKING_ONLY
|
||||
#include <contrib/zstd/lib/zstd.h>
|
||||
#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
|
||||
@@ -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 <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/dirent.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/limits.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -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 */
|
||||
+472
@@ -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 <sys/types.h>
|
||||
#include <sys/endian.h>
|
||||
|
||||
/* 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
|
||||
+468
@@ -0,0 +1,468 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/dirent.h>
|
||||
#include <sys/extattr.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/limits.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/namei.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/vnode.h>
|
||||
#include <sys/acl.h>
|
||||
|
||||
#include <vm/vnode_pager.h>
|
||||
|
||||
#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);
|
||||
@@ -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 <sys/param.h>
|
||||
#include <sys/endian.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/limits.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/fnv_hash.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
+351
@@ -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 <sys/types.h>
|
||||
#include <sys/param.h> // MUST FIRST
|
||||
#include <sys/systm.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#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 */
|
||||
@@ -0,0 +1,95 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/* Minimal LZ4 decompressor for EROFS FreeBSD */
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -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 <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/dirent.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/namei.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -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 <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/_maxphys.h>
|
||||
#include <sys/bio.h>
|
||||
#include <sys/buf.h>
|
||||
#include <sys/conf.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/fnv_hash.h>
|
||||
#include <sys/gsb_crc32.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/module.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/namei.h>
|
||||
#include <sys/priv.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include <geom/geom.h>
|
||||
#include <geom/geom_vfs.h>
|
||||
|
||||
#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);
|
||||
@@ -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 <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/dirent.h>
|
||||
#include <sys/extattr.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/vnode.h>
|
||||
#include <sys/acl.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,287 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2018-2019 HUAWEI, Inc.
|
||||
* https://www.huawei.com/
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/_maxphys.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,928 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2018-2019 HUAWEI, Inc.
|
||||
* https://www.huawei.com/
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/endian.h>
|
||||
#include <sys/libkern.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/systm.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user