879 lines
24 KiB
C
879 lines
24 KiB
C
// 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/buf.h>
|
|
#include <sys/libkern.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/vnode.h>
|
|
|
|
#include <machine/vmparam.h>
|
|
#include <vm/vm.h>
|
|
|
|
#include "internal.h"
|
|
#include "trace.h"
|
|
|
|
#define EROFS_DIR_READAHEAD_SLOTS 256
|
|
|
|
void
|
|
erofs_put_metabuf(struct erofs_buf *buf)
|
|
{
|
|
void (*release)(void *);
|
|
void *data;
|
|
|
|
if (buf == NULL)
|
|
return;
|
|
data = buf->data;
|
|
release = buf->release;
|
|
buf->data = NULL;
|
|
buf->release = NULL;
|
|
if (data != NULL && release != NULL)
|
|
release(data);
|
|
}
|
|
|
|
/*
|
|
* bread() on a device vnode needs no vnode lock, but breadn() reads ahead
|
|
* through breada(), whose inmem() asserts that the vnode is locked. Take the
|
|
* lock shared (the mount's device vnode allows that) only around the request.
|
|
*
|
|
* A doomed vnode has become VBAD, which the buffer cache only accepts when it
|
|
* is locked, and it no longer has a device behind it, so fail the read.
|
|
*/
|
|
static int
|
|
erofs_breadn_devvp(struct vnode *devvp, daddr_t blkno, int size,
|
|
daddr_t *rablkno, int *rabsize, int racnt, struct buf **bpp)
|
|
{
|
|
int error;
|
|
|
|
vn_lock(devvp, LK_SHARED | LK_RETRY);
|
|
if (VN_IS_DOOMED(devvp)) {
|
|
VOP_UNLOCK(devvp);
|
|
*bpp = NULL;
|
|
return (ENXIO);
|
|
}
|
|
error = breadn(devvp, blkno, size, rablkno, rabsize, racnt, NOCRED,
|
|
bpp);
|
|
VOP_UNLOCK(devvp);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
erofs_bread_device(struct erofs_sb_info *sbi, struct erofs_device_info *dif,
|
|
erofs_blk_t blocks, erofs_off_t off, size_t len, daddr_t *rablkno,
|
|
int *rabsize, int racnt, void **bufp)
|
|
{
|
|
struct buf *bp;
|
|
erofs_off_t device_end, device_off, 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 (!erofs_source_is_open(dif))
|
|
return (ENODEV);
|
|
if (__builtin_add_overflow(off, (uint64_t)len, &end))
|
|
return (erofs_corrupt(sbi, EROFS_NID_NONE,
|
|
"read of %zu bytes at %ju overflows", len, (uintmax_t)off));
|
|
if (blocks != 0) {
|
|
if (blocks > (UINT64_MAX >> sbi->blkszbits))
|
|
return (erofs_corrupt(sbi, EROFS_NID_NONE,
|
|
"device size of %ju blocks overflows",
|
|
(uintmax_t)blocks));
|
|
limit = blocks << sbi->blkszbits;
|
|
if (end > limit)
|
|
return (erofs_corrupt(sbi, EROFS_NID_NONE,
|
|
"read of %zu bytes at %ju is beyond the device "
|
|
"(%ju bytes)", len, (uintmax_t)off,
|
|
(uintmax_t)limit));
|
|
}
|
|
if (__builtin_add_overflow(dif->fsoff, off, &device_off) ||
|
|
__builtin_add_overflow(device_off, (uint64_t)len, &device_end))
|
|
return (EOVERFLOW);
|
|
if (device_end > dif->mediasize)
|
|
return (ENXIO);
|
|
if (device_off > INT64_MAX || device_end > (uint64_t)INT64_MAX + 1)
|
|
return (EOVERFLOW);
|
|
|
|
iosize = sbi->block_size != 0 ? sbi->block_size : dif->sectorsize;
|
|
if (iosize == 0 || (iosize & (iosize - 1)) != 0)
|
|
return (EINVAL);
|
|
out = malloc(len, M_EROFS, M_WAITOK);
|
|
if (erofs_source_is_file(dif)) {
|
|
error = erofs_read_file_source(sbi, dif, device_off, len, out);
|
|
if (error != 0) {
|
|
free(out, M_EROFS);
|
|
return (error);
|
|
}
|
|
*bufp = out;
|
|
return (0);
|
|
}
|
|
done = 0;
|
|
while (done < len) {
|
|
current = (off_t)(device_off + done);
|
|
blkoff = rounddown2(current, (off_t)iosize);
|
|
blklen = MIN(iosize - (size_t)(current - blkoff), len - done);
|
|
if (done == 0 && racnt != 0)
|
|
error = erofs_breadn_devvp(dif->devvp, btodb(blkoff),
|
|
iosize, rablkno, rabsize, racnt, &bp);
|
|
else
|
|
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);
|
|
}
|
|
if (bp->b_resid < 0 || bp->b_bcount < 0 ||
|
|
(uintmax_t)bp->b_resid > (uintmax_t)bp->b_bcount ||
|
|
(uintmax_t)(current - blkoff) + (uintmax_t)blklen >
|
|
(uintmax_t)bp->b_bcount - (uintmax_t)bp->b_resid) {
|
|
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_sb_info *sbi, erofs_off_t off, size_t len, void **bufp)
|
|
{
|
|
return (erofs_bread_device(sbi, &sbi->dif0, sbi->dif0.blocks, off, len,
|
|
NULL, NULL, 0, bufp));
|
|
}
|
|
|
|
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_range(struct erofs_sb_info *sbi, erofs_nid_t nid,
|
|
erofs_off_t off, size_t len, struct erofs_buf *buf)
|
|
{
|
|
void *data;
|
|
int error;
|
|
|
|
if (!erofs_nid_in_metabox(nid)) {
|
|
if (off > INT64_MAX)
|
|
return (EOVERFLOW);
|
|
error = erofs_bread(sbi, (off_t)off, len, &data);
|
|
} else {
|
|
if (!erofs_sb_has_metabox(sbi) || sbi->metabox_en == NULL)
|
|
return (erofs_corrupt(sbi, nid,
|
|
"metabox nid, but the image has no metabox"));
|
|
error = erofs_read_data(sbi, sbi->metabox_en, off, len, &data);
|
|
}
|
|
if (error != 0)
|
|
return (error);
|
|
buf->data = data;
|
|
buf->release = erofs_brelse;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
erofs_map_blocks_chunk(struct erofs_sb_info *sbi, struct erofs_inode *vi,
|
|
struct erofs_map_blocks *map)
|
|
{
|
|
struct erofs_inode_chunk_index *idx;
|
|
struct erofs_buf buf = EROFS_BUF_INITIALIZER;
|
|
uint64_t chunk_idx, chunk_size, entry_size;
|
|
erofs_off_t loff, chunk_start, idx_off, idx_base;
|
|
uint64_t image_size, addrmask;
|
|
uint64_t blkaddr;
|
|
uint16_t raw_device_id;
|
|
int error;
|
|
|
|
loff = map->m_la;
|
|
chunk_size = 1ULL << vi->chunkbits;
|
|
chunk_idx = loff >> vi->chunkbits;
|
|
chunk_start = chunk_idx << vi->chunkbits;
|
|
|
|
if ((vi->chunkformat & EROFS_CHUNK_FORMAT_INDEXES) != 0)
|
|
entry_size = sizeof(struct erofs_inode_chunk_index);
|
|
else
|
|
entry_size = EROFS_BLOCK_MAP_ENTRY_SIZE;
|
|
/*
|
|
* inode_off is below 2^63 (the image fits its provider, whose size is
|
|
* an off_t; a metabox is at most OFF_MAX), isize is 32 or 64 and
|
|
* xattr_isize is below 2^19; chunk_idx is below 2^54 because
|
|
* loff < size <= OFF_MAX and a chunk has at least 512 bytes.
|
|
*/
|
|
idx_base = roundup2(vi->inode_off + vi->inode_isize + vi->xattr_isize,
|
|
entry_size);
|
|
idx_off = idx_base + chunk_idx * entry_size;
|
|
if (erofs_nid_in_metabox(vi->nid)) {
|
|
if (sbi->metabox_en == NULL ||
|
|
idx_off > sbi->metabox_en->size ||
|
|
entry_size > sbi->metabox_en->size - idx_off)
|
|
return (erofs_corrupt(sbi, vi->nid,
|
|
"chunk %ju: index entry at %ju is beyond the "
|
|
"metabox",
|
|
(uintmax_t)chunk_idx, (uintmax_t)idx_off));
|
|
} else {
|
|
image_size = sbi->blocks << sbi->blkszbits;
|
|
if (idx_off > image_size || entry_size > image_size - idx_off)
|
|
return (erofs_corrupt(sbi, vi->nid,
|
|
"chunk %ju: index entry at %ju is beyond the image",
|
|
(uintmax_t)chunk_idx, (uintmax_t)idx_off));
|
|
}
|
|
|
|
error = erofs_read_metadata_range(sbi, vi->nid, idx_off, entry_size,
|
|
&buf);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
idx = buf.data;
|
|
if ((vi->chunkformat & EROFS_CHUNK_FORMAT_INDEXES) != 0) {
|
|
blkaddr = le32toh(idx->startblk_lo);
|
|
if ((vi->chunkformat & EROFS_CHUNK_FORMAT_48BIT) != 0)
|
|
blkaddr |= (uint64_t)le16toh(idx->startblk_hi) << 32;
|
|
raw_device_id = le16toh(idx->device_id);
|
|
addrmask = (vi->chunkformat & EROFS_CHUNK_FORMAT_48BIT) != 0 ?
|
|
((1ULL << 48) - 1) : UINT32_MAX;
|
|
} else {
|
|
blkaddr = le32dec(idx);
|
|
raw_device_id = 0;
|
|
addrmask = UINT32_MAX;
|
|
}
|
|
erofs_put_metabuf(&buf);
|
|
|
|
if (!((blkaddr ^ EROFS_NULL_ADDR) & addrmask)) {
|
|
map->m_la = chunk_start;
|
|
map->m_pa = 0;
|
|
map->m_llen = MIN(chunk_size, vi->size - chunk_start);
|
|
map->m_plen = map->m_llen;
|
|
return (0);
|
|
}
|
|
|
|
map->m_la = chunk_start;
|
|
map->m_llen = MIN(chunk_size, vi->size - chunk_start);
|
|
map->m_plen = map->m_llen;
|
|
map->m_deviceid = raw_device_id & sbi->device_id_mask;
|
|
/* blkaddr has at most 48 bits and blkszbits at most 16. */
|
|
map->m_pa = blkaddr << sbi->blkszbits;
|
|
map->m_flags |= EROFS_MAP_MAPPED;
|
|
return (0);
|
|
}
|
|
|
|
static erofs_off_t
|
|
erofs_inline_tail_start(const struct erofs_sb_info *sbi,
|
|
const struct erofs_inode *vi)
|
|
{
|
|
if (vi->size == 0)
|
|
return (0);
|
|
return (roundup2(vi->size, (uint64_t)sbi->block_size) -
|
|
sbi->block_size);
|
|
}
|
|
|
|
static int
|
|
erofs_map_blocks_flatmode(struct erofs_sb_info *sbi, struct erofs_inode *vi,
|
|
struct erofs_map_blocks *map)
|
|
{
|
|
erofs_off_t loff, tail_start;
|
|
uint64_t remain, block_rem;
|
|
|
|
loff = map->m_la;
|
|
if (loff >= vi->size)
|
|
return (0);
|
|
|
|
remain = vi->size - loff;
|
|
switch (vi->datalayout) {
|
|
case EROFS_INODE_CHUNK_BASED:
|
|
return (erofs_map_blocks_chunk(sbi, vi, map));
|
|
case EROFS_INODE_FLAT_PLAIN:
|
|
map->m_llen = remain;
|
|
map->m_plen = map->m_llen;
|
|
if (vi->startblk == EROFS_NULL_ADDR)
|
|
return (0);
|
|
if (vi->startblk > (UINT64_MAX >> sbi->blkszbits) ||
|
|
__builtin_add_overflow(vi->startblk << sbi->blkszbits, loff,
|
|
&map->m_pa))
|
|
return (erofs_corrupt(sbi, vi->nid,
|
|
"start block %ju overflows",
|
|
(uintmax_t)vi->startblk));
|
|
map->m_flags |= EROFS_MAP_MAPPED;
|
|
return (0);
|
|
case EROFS_INODE_FLAT_INLINE:
|
|
tail_start = erofs_inline_tail_start(sbi, vi);
|
|
if (loff < tail_start) {
|
|
map->m_llen = MIN(remain, tail_start - loff);
|
|
map->m_plen = map->m_llen;
|
|
if (vi->startblk == EROFS_NULL_ADDR)
|
|
return (0);
|
|
if (vi->startblk > (UINT64_MAX >> sbi->blkszbits) ||
|
|
__builtin_add_overflow(
|
|
vi->startblk << sbi->blkszbits, loff, &map->m_pa))
|
|
return (erofs_corrupt(sbi, vi->nid,
|
|
"start block %ju overflows",
|
|
(uintmax_t)vi->startblk));
|
|
map->m_flags |= EROFS_MAP_MAPPED;
|
|
return (0);
|
|
}
|
|
block_rem = sbi->block_size -
|
|
((loff - tail_start) & (sbi->block_size - 1));
|
|
map->m_llen = MIN(remain, block_rem);
|
|
map->m_plen = map->m_llen;
|
|
if (__builtin_add_overflow(vi->inode_off, vi->inode_isize,
|
|
&map->m_pa) ||
|
|
__builtin_add_overflow(map->m_pa, vi->xattr_isize,
|
|
&map->m_pa) ||
|
|
__builtin_add_overflow(map->m_pa, loff - tail_start,
|
|
&map->m_pa))
|
|
return (erofs_corrupt(sbi, vi->nid,
|
|
"inline tail position overflows"));
|
|
map->m_flags |= EROFS_MAP_MAPPED | EROFS_MAP_META;
|
|
return (0);
|
|
case EROFS_INODE_COMPRESSED_FULL:
|
|
case EROFS_INODE_COMPRESSED_COMPACT:
|
|
return (EOPNOTSUPP);
|
|
default:
|
|
return (EOPNOTSUPP);
|
|
}
|
|
}
|
|
|
|
int
|
|
erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi,
|
|
struct erofs_map_blocks *map)
|
|
{
|
|
struct erofs_map_blocks next = { .m_la = map->m_la };
|
|
int err;
|
|
|
|
EROFS_TRACE_MAP_BLOCKS_ENTER(vi, map, 0);
|
|
if (erofs_inode_is_data_compressed(vi->datalayout)) {
|
|
#if WITH_EROFS_ZIP
|
|
err = z_erofs_map_blocks(sbi, vi, &next);
|
|
#else
|
|
err = EOPNOTSUPP;
|
|
#endif
|
|
} else {
|
|
err = erofs_map_blocks_flatmode(sbi, vi, &next);
|
|
}
|
|
*map = next;
|
|
EROFS_TRACE_MAP_BLOCKS_EXIT(vi, map, err);
|
|
return (err);
|
|
}
|
|
static int
|
|
erofs_check_device_range(const struct erofs_sb_info *sbi,
|
|
const struct erofs_device_info *dif, erofs_off_t off, uint64_t len)
|
|
{
|
|
erofs_off_t end, limit;
|
|
|
|
if (dif->blocks > (UINT64_MAX >> sbi->blkszbits) ||
|
|
__builtin_add_overflow(off, len, &end))
|
|
return (EINTEGRITY);
|
|
limit = dif->blocks << sbi->blkszbits;
|
|
return (end > limit ? EINTEGRITY : 0);
|
|
}
|
|
|
|
static void
|
|
erofs_fill_from_devinfo(struct erofs_map_dev *map,
|
|
struct erofs_device_info *dif, erofs_off_t pa)
|
|
{
|
|
map->m_dif = dif;
|
|
map->m_pa = pa;
|
|
}
|
|
|
|
static int
|
|
erofs_map_dev_locate(struct erofs_sb_info *sbi, struct erofs_map_dev *map)
|
|
{
|
|
struct erofs_device_info *dif;
|
|
uint64_t start;
|
|
unsigned int id;
|
|
int error;
|
|
|
|
erofs_fill_from_devinfo(map, &sbi->dif0, map->m_pa);
|
|
if (map->m_deviceid != 0) {
|
|
if (map->m_deviceid > sbi->extra_devices || sbi->devs == NULL)
|
|
return (ENODEV);
|
|
dif = &sbi->devs[map->m_deviceid - 1];
|
|
error = erofs_check_device_range(sbi, dif, map->m_pa,
|
|
map->m_plen);
|
|
if (error != 0)
|
|
return (error);
|
|
if (sbi->flatdev) {
|
|
if (dif->uniaddr > (UINT64_MAX >> sbi->blkszbits))
|
|
return (EINTEGRITY);
|
|
start = dif->uniaddr << sbi->blkszbits;
|
|
if (__builtin_add_overflow(map->m_pa, start,
|
|
&map->m_pa))
|
|
return (EINTEGRITY);
|
|
return (0);
|
|
}
|
|
if (!erofs_source_is_open(dif))
|
|
return (ENODEV);
|
|
erofs_fill_from_devinfo(map, dif, map->m_pa);
|
|
return (0);
|
|
}
|
|
|
|
if (sbi->extra_devices == 0)
|
|
return (0);
|
|
if (sbi->flatdev) {
|
|
error = erofs_check_device_range(sbi, &sbi->dif0, map->m_pa,
|
|
map->m_plen);
|
|
if (error == 0)
|
|
return (0);
|
|
for (id = 0; id < sbi->extra_devices; ++id) {
|
|
dif = &sbi->devs[id];
|
|
if (dif->uniaddr == 0 ||
|
|
dif->uniaddr > (UINT64_MAX >> sbi->blkszbits))
|
|
continue;
|
|
start = dif->uniaddr << sbi->blkszbits;
|
|
if (map->m_pa < start)
|
|
continue;
|
|
error = erofs_check_device_range(sbi, dif,
|
|
map->m_pa - start, map->m_plen);
|
|
if (error == 0)
|
|
return (0);
|
|
if (map->m_pa - start <
|
|
(dif->blocks << sbi->blkszbits))
|
|
return (error);
|
|
}
|
|
return (EINTEGRITY);
|
|
}
|
|
for (id = 0; id < sbi->extra_devices; ++id) {
|
|
dif = &sbi->devs[id];
|
|
if (dif->uniaddr == 0)
|
|
continue;
|
|
if (dif->uniaddr > (UINT64_MAX >> sbi->blkszbits))
|
|
return (EINTEGRITY);
|
|
start = dif->uniaddr << sbi->blkszbits;
|
|
if (map->m_pa >= start &&
|
|
map->m_pa - start < (dif->blocks << sbi->blkszbits)) {
|
|
error = erofs_check_device_range(sbi, dif,
|
|
map->m_pa - start, map->m_plen);
|
|
if (error != 0)
|
|
return (error);
|
|
if (!erofs_source_is_open(dif))
|
|
return (ENODEV);
|
|
erofs_fill_from_devinfo(map, dif, map->m_pa - start);
|
|
break;
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Resolve a physical range to a device. The lookup tries several devices
|
|
* and treats a miss on one as normal, so the corruption is reported here,
|
|
* once, when no device is able to serve the range.
|
|
*/
|
|
static int
|
|
erofs_map_dev(struct erofs_sb_info *sbi, struct erofs_map_dev *map)
|
|
{
|
|
uint64_t pa, plen;
|
|
unsigned int deviceid;
|
|
int error;
|
|
|
|
pa = map->m_pa;
|
|
plen = map->m_plen;
|
|
deviceid = map->m_deviceid;
|
|
error = erofs_map_dev_locate(sbi, map);
|
|
if (error == EINTEGRITY)
|
|
return (erofs_corrupt(sbi, EROFS_NID_NONE,
|
|
"range %ju+%ju of device %u is not backed by any device",
|
|
(uintmax_t)pa, (uintmax_t)plen, deviceid));
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
erofs_read_physical(struct erofs_sb_info *sbi, unsigned int device_id,
|
|
erofs_off_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_deviceid = device_id,
|
|
.m_plen = len,
|
|
};
|
|
error = erofs_map_dev(sbi, &map);
|
|
if (error != 0)
|
|
return (error);
|
|
blocks = map.m_dif->blocks;
|
|
if (map.m_dif == &sbi->dif0 && sbi->flatdev)
|
|
blocks = sbi->flatdev_blocks;
|
|
return (erofs_bread_device(sbi, map.m_dif, blocks, map.m_pa, len,
|
|
NULL, NULL, 0, bufp));
|
|
}
|
|
|
|
static int
|
|
erofs_read_physical_readahead(struct erofs_sb_info *sbi,
|
|
unsigned int device_id, erofs_off_t off, size_t len,
|
|
unsigned int rablocks, void **bufp)
|
|
{
|
|
struct erofs_map_dev current, future;
|
|
daddr_t *rablkno;
|
|
int *rabsize;
|
|
erofs_off_t device_pa, step;
|
|
erofs_blk_t blocks;
|
|
unsigned int count;
|
|
int error, ret;
|
|
|
|
current = (struct erofs_map_dev) {
|
|
.m_pa = off,
|
|
.m_deviceid = device_id,
|
|
.m_plen = len,
|
|
};
|
|
error = erofs_map_dev(sbi, ¤t);
|
|
if (error != 0)
|
|
return (error);
|
|
blocks = current.m_dif->blocks;
|
|
if (current.m_dif == &sbi->dif0 && sbi->flatdev)
|
|
blocks = sbi->flatdev_blocks;
|
|
if (rablocks == 0)
|
|
return (erofs_bread_device(sbi, current.m_dif, blocks,
|
|
current.m_pa, len, NULL, NULL, 0, bufp));
|
|
/*
|
|
* 256 slots are 2 KiB, which is a large part of a kernel stack inside
|
|
* the recursive read path, so keep the run lists off the stack.
|
|
*/
|
|
rablocks = MIN(rablocks, (unsigned int)EROFS_DIR_READAHEAD_SLOTS);
|
|
rablkno = malloc(rablocks * sizeof(*rablkno), M_EROFS, M_WAITOK);
|
|
rabsize = malloc(rablocks * sizeof(*rabsize), M_EROFS, M_WAITOK);
|
|
for (count = 0; count < rablocks; count++) {
|
|
step = (erofs_off_t)(count + 1) * sbi->block_size;
|
|
if (off > UINT64_MAX - step || current.m_pa > UINT64_MAX - step)
|
|
break;
|
|
future = (struct erofs_map_dev) {
|
|
.m_pa = off + step,
|
|
.m_deviceid = device_id,
|
|
.m_plen = sbi->block_size,
|
|
};
|
|
if (erofs_map_dev(sbi, &future) != 0 ||
|
|
future.m_dif != current.m_dif ||
|
|
future.m_pa != current.m_pa + step)
|
|
break;
|
|
if (__builtin_add_overflow(future.m_dif->fsoff, future.m_pa,
|
|
&device_pa) || device_pa > INT64_MAX)
|
|
break;
|
|
rablkno[count] = btodb(device_pa);
|
|
rabsize[count] = sbi->block_size;
|
|
}
|
|
ret = erofs_bread_device(sbi, current.m_dif, blocks, current.m_pa, len,
|
|
rablkno, rabsize, count, bufp);
|
|
free(rabsize, M_EROFS);
|
|
free(rablkno, M_EROFS);
|
|
return (ret);
|
|
}
|
|
|
|
static int
|
|
erofs_read_data_impl(struct erofs_sb_info *sbi, struct erofs_inode *vi,
|
|
erofs_off_t loff, size_t len, unsigned int rablocks, void **bufp)
|
|
{
|
|
struct erofs_buf buf = EROFS_BUF_INITIALIZER;
|
|
struct erofs_map_blocks map;
|
|
char *out;
|
|
void *blk;
|
|
erofs_off_t mapoff, physical;
|
|
size_t done, want;
|
|
int error;
|
|
|
|
if (bufp == NULL)
|
|
return (EINVAL);
|
|
*bufp = NULL;
|
|
if (len == 0) {
|
|
return (0);
|
|
}
|
|
EROFS_TRACE_READ_FOLIO(vi, loff, len,
|
|
!erofs_inode_is_data_compressed(vi->datalayout));
|
|
if (loff > vi->size || (uint64_t)len > vi->size - loff)
|
|
return (erofs_corrupt(sbi, vi->nid,
|
|
"read of %zu bytes at %ju is beyond the size %ju", len,
|
|
(uintmax_t)loff, (uintmax_t)vi->size));
|
|
|
|
/* Compressed file path */
|
|
if (erofs_inode_is_data_compressed(vi->datalayout))
|
|
#if WITH_EROFS_ZIP
|
|
return (z_erofs_read_data(sbi, vi, loff, len, bufp));
|
|
#else
|
|
return (EOPNOTSUPP);
|
|
#endif
|
|
|
|
/* Uncompressed file path */
|
|
out = malloc(len, M_EROFS, M_WAITOK);
|
|
done = 0;
|
|
while (done < len) {
|
|
map = (struct erofs_map_blocks) { .m_la = loff + done };
|
|
error = erofs_map_blocks(sbi, vi, &map);
|
|
if (error != 0) {
|
|
free(out, M_EROFS);
|
|
return (error);
|
|
}
|
|
if (map.m_llen == 0 || map.m_la > loff + done ||
|
|
loff + done - map.m_la >= map.m_llen) {
|
|
free(out, M_EROFS);
|
|
return (erofs_corrupt(sbi, vi->nid,
|
|
"mapping of offset %ju is empty or does not cover "
|
|
"it",
|
|
(uintmax_t)(loff + done)));
|
|
}
|
|
mapoff = loff + done - map.m_la;
|
|
want = MIN((size_t)MIN(map.m_llen - mapoff, (uint64_t)MAXPHYS),
|
|
len - done);
|
|
if ((map.m_flags & EROFS_MAP_MAPPED) == 0) {
|
|
bzero(out + done, want);
|
|
} else {
|
|
if ((map.m_flags & EROFS_MAP_META) != 0) {
|
|
if (mapoff > UINT64_MAX - map.m_pa) {
|
|
free(out, M_EROFS);
|
|
return (EOVERFLOW);
|
|
}
|
|
physical = map.m_pa + mapoff;
|
|
error = erofs_read_metadata_range(sbi, vi->nid,
|
|
physical, want, &buf);
|
|
} else if (done == 0 && mapoff == 0 && rablocks != 0 &&
|
|
map.m_flags == EROFS_MAP_MAPPED) {
|
|
error = erofs_read_physical_readahead(sbi,
|
|
map.m_deviceid, map.m_pa, want, rablocks,
|
|
&blk);
|
|
} else {
|
|
if (mapoff > UINT64_MAX - map.m_pa) {
|
|
free(out, M_EROFS);
|
|
return (EOVERFLOW);
|
|
}
|
|
physical = map.m_pa + mapoff;
|
|
error = erofs_read_physical(sbi, map.m_deviceid,
|
|
physical, want, &blk);
|
|
}
|
|
if (error != 0) {
|
|
free(out, M_EROFS);
|
|
return (error);
|
|
}
|
|
if ((map.m_flags & EROFS_MAP_META) != 0) {
|
|
memcpy(out + done, buf.data, want);
|
|
erofs_put_metabuf(&buf);
|
|
} else {
|
|
memcpy(out + done, blk, want);
|
|
erofs_brelse(blk);
|
|
}
|
|
}
|
|
done += want;
|
|
}
|
|
*bufp = out;
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
erofs_read_data(struct erofs_sb_info *sbi, struct erofs_inode *vi,
|
|
erofs_off_t loff, size_t len, void **bufp)
|
|
{
|
|
return (erofs_read_data_impl(sbi, vi, loff, len, 0, bufp));
|
|
}
|
|
|
|
int
|
|
erofs_read_data_readahead(struct erofs_sb_info *sbi, struct erofs_inode *vi,
|
|
erofs_off_t loff, size_t len, bool sequential, void **bufp)
|
|
{
|
|
uint64_t dir_ra_bytes, total_blocks;
|
|
uint64_t remaining;
|
|
unsigned int rablocks;
|
|
int error;
|
|
|
|
rablocks = 0;
|
|
if (sequential && vi->datalayout == EROFS_INODE_FLAT_PLAIN &&
|
|
sbi->block_size != 0 && (loff & (sbi->block_size - 1)) == 0 &&
|
|
len <= sbi->block_size && loff <= vi->size &&
|
|
len <= vi->size - loff) {
|
|
dir_ra_bytes = erofs_dir_ra_bytes(sbi);
|
|
total_blocks = dir_ra_bytes / sbi->block_size;
|
|
if (total_blocks > 1) {
|
|
remaining = vi->size - loff - len;
|
|
rablocks = MIN(total_blocks - 1,
|
|
howmany(remaining, sbi->block_size));
|
|
rablocks = MIN(rablocks,
|
|
(unsigned int)EROFS_DIR_READAHEAD_SLOTS);
|
|
}
|
|
}
|
|
EROFS_TRACE_READAHEAD(vi, loff >> PAGE_SHIFT,
|
|
howmany((uint64_t)len +
|
|
(uint64_t)rablocks * (uint64_t)sbi->block_size, PAGE_SIZE),
|
|
!erofs_inode_is_data_compressed(vi->datalayout));
|
|
error = erofs_read_data_impl(sbi, vi, loff, len, rablocks, bufp);
|
|
if (error == 0 && rablocks != 0)
|
|
erofs_record_dir_readahead(sbi, rablocks);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
erofs_read_uio(struct erofs_sb_info *sbi, struct erofs_inode *vi,
|
|
struct uio *uio)
|
|
{
|
|
struct erofs_buf buf = EROFS_BUF_INITIALIZER;
|
|
struct erofs_map_blocks map;
|
|
void *blk;
|
|
erofs_off_t mapoff, physical;
|
|
size_t want, chunk;
|
|
int error;
|
|
|
|
if (uio->uio_offset < 0)
|
|
return (EINVAL);
|
|
if ((uint64_t)uio->uio_offset >= vi->size)
|
|
return (0);
|
|
EROFS_TRACE_READ_FOLIO(vi, (uint64_t)uio->uio_offset,
|
|
(uint64_t)uio->uio_resid,
|
|
!erofs_inode_is_data_compressed(vi->datalayout));
|
|
|
|
/* Compressed file path */
|
|
if (erofs_inode_is_data_compressed(vi->datalayout))
|
|
#if WITH_EROFS_ZIP
|
|
return (z_erofs_read_uio(sbi, vi, uio));
|
|
#else
|
|
return (EOPNOTSUPP);
|
|
#endif
|
|
|
|
/* Uncompressed file path */
|
|
while (uio->uio_resid > 0 && (uint64_t)uio->uio_offset < vi->size) {
|
|
map = (struct erofs_map_blocks) { .m_la = uio->uio_offset };
|
|
error = erofs_map_blocks(sbi, vi, &map);
|
|
if (error != 0)
|
|
return (error);
|
|
if (map.m_llen == 0 || map.m_la > (uint64_t)uio->uio_offset ||
|
|
(uint64_t)uio->uio_offset - map.m_la >= map.m_llen)
|
|
return (erofs_corrupt(sbi, vi->nid,
|
|
"mapping of offset %jd is empty or does not cover "
|
|
"it",
|
|
(intmax_t)uio->uio_offset));
|
|
mapoff = (uint64_t)uio->uio_offset - map.m_la;
|
|
want = MIN((size_t)MIN(map.m_llen - mapoff, (uint64_t)MAXPHYS),
|
|
(size_t)uio->uio_resid);
|
|
if ((map.m_flags & EROFS_MAP_MAPPED) == 0) {
|
|
chunk = want;
|
|
while (chunk > 0) {
|
|
size_t zlen = MIN(chunk, ZERO_REGION_SIZE);
|
|
|
|
error = uiomove(__DECONST(void *, zero_region),
|
|
zlen, uio);
|
|
if (error != 0)
|
|
return (error);
|
|
chunk -= zlen;
|
|
}
|
|
continue;
|
|
}
|
|
if ((map.m_flags & EROFS_MAP_META) != 0) {
|
|
if (mapoff > UINT64_MAX - map.m_pa)
|
|
return (EOVERFLOW);
|
|
physical = map.m_pa + mapoff;
|
|
error = erofs_read_metadata_range(sbi, vi->nid,
|
|
physical, want, &buf);
|
|
} else {
|
|
if (mapoff > UINT64_MAX - map.m_pa)
|
|
return (EOVERFLOW);
|
|
physical = map.m_pa + mapoff;
|
|
error = erofs_read_physical(sbi, map.m_deviceid,
|
|
physical, want, &blk);
|
|
}
|
|
if (error != 0)
|
|
return (error);
|
|
if ((map.m_flags & EROFS_MAP_META) != 0) {
|
|
error = uiomove(buf.data, want, uio);
|
|
erofs_put_metabuf(&buf);
|
|
} else {
|
|
error = uiomove(blk, want, uio);
|
|
erofs_brelse(blk);
|
|
}
|
|
if (error != 0)
|
|
return (error);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* Read regular file data. */
|
|
int
|
|
erofs_read_file(struct vnode *vp, struct uio *uio)
|
|
{
|
|
return (erofs_read_uio(MTOE(vp->v_mount), VTOE(vp), uio));
|
|
}
|
|
|
|
int
|
|
erofs_file_llseek(struct vnode *vp, off_t *offset, bool seek_data)
|
|
{
|
|
struct erofs_sb_info *sbi;
|
|
struct erofs_inode *vi;
|
|
struct erofs_map_blocks map;
|
|
erofs_off_t pos, end;
|
|
bool data;
|
|
int error;
|
|
|
|
if (vp->v_type != VREG)
|
|
return (ENOTTY);
|
|
vi = VTOE(vp);
|
|
if (*offset < 0 || (uint64_t)*offset >= vi->size)
|
|
return (ENXIO);
|
|
|
|
sbi = MTOE(vp->v_mount);
|
|
pos = (erofs_off_t)*offset;
|
|
while (pos < vi->size) {
|
|
map = (struct erofs_map_blocks) { .m_la = pos };
|
|
error = erofs_map_blocks(sbi, vi, &map);
|
|
if (error != 0)
|
|
return (error);
|
|
if (map.m_la > pos || map.m_llen == 0 ||
|
|
map.m_la > UINT64_MAX - map.m_llen) {
|
|
return (erofs_corrupt(sbi, vi->nid,
|
|
"mapping of offset %ju is empty or does not cover "
|
|
"it",
|
|
(uintmax_t)pos));
|
|
}
|
|
end = map.m_la + map.m_llen;
|
|
if (pos >= end)
|
|
return (erofs_corrupt(sbi, vi->nid,
|
|
"mapping of offset %ju ends at %ju", (uintmax_t)pos,
|
|
(uintmax_t)end));
|
|
data = (map.m_flags &
|
|
(EROFS_MAP_MAPPED | EROFS_MAP_FRAGMENT)) != 0;
|
|
if (data == seek_data) {
|
|
*offset = (off_t)pos;
|
|
return (0);
|
|
}
|
|
pos = MIN(end, vi->size);
|
|
}
|
|
|
|
if (seek_data)
|
|
return (ENXIO);
|
|
if (vi->size > OFF_MAX)
|
|
return (EOVERFLOW);
|
|
*offset = (off_t)vi->size;
|
|
return (0);
|
|
}
|
|
|
|
/* Read symlink target string. */
|
|
int
|
|
erofs_readlink_target(struct vnode *vp, struct uio *uio)
|
|
{
|
|
struct erofs_inode *vi;
|
|
|
|
vi = VTOE(vp);
|
|
if (vi->size == 0)
|
|
return (erofs_corrupt(MTOE(vp->v_mount), vi->nid,
|
|
"empty symlink"));
|
|
return (erofs_read_uio(MTOE(vp->v_mount), vi, uio));
|
|
}
|