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