test code v1
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user