697 lines
18 KiB
C
697 lines
18 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/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"
|
|
|
|
#define EROFS_DIR_READAHEAD_BYTES (1024 * 1024)
|
|
#define EROFS_DIR_READAHEAD_SLOTS (EROFS_DIR_READAHEAD_BYTES / PAGE_SIZE)
|
|
|
|
/*
|
|
* 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 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_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(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 (dif->devvp == NULL || dif->cp == NULL)
|
|
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 (dif->devvp == NULL || dif->cp == NULL)
|
|
return (ENODEV);
|
|
erofs_fill_from_devinfo(map, dif, map->m_pa - start);
|
|
break;
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* Map chunk-based file to physical extent */
|
|
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, idx_off, chunk_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_off = loff & (chunk_size - 1);
|
|
|
|
if ((vi->chunkformat & EROFS_CHUNK_FORMAT_INDEXES) != 0)
|
|
entry_size = sizeof(struct erofs_inode_chunk_index);
|
|
else
|
|
entry_size = EROFS_BLOCK_MAP_ENTRY_SIZE;
|
|
if (vi->inode_off > UINT64_MAX - vi->inode_isize ||
|
|
vi->inode_off + vi->inode_isize > UINT64_MAX - vi->xattr_isize)
|
|
return (EOVERFLOW);
|
|
idx_base = vi->inode_off + vi->inode_isize + vi->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(vi->nid)) {
|
|
if (sbi->metabox_en == NULL || idx_off > sbi->metabox_en->size ||
|
|
entry_size > sbi->metabox_en->size - idx_off)
|
|
return (EINTEGRITY);
|
|
} else {
|
|
if (sbi->blocks > (UINT64_MAX >> sbi->blkszbits))
|
|
return (EOVERFLOW);
|
|
image_size = sbi->blocks << sbi->blkszbits;
|
|
if (idx_off > image_size || entry_size > image_size - idx_off)
|
|
return (EINTEGRITY);
|
|
}
|
|
|
|
error = erofs_read_metadata(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_pa = 0;
|
|
map->m_llen = MIN(chunk_size - chunk_off, vi->size - loff);
|
|
map->m_plen = map->m_llen;
|
|
return (0);
|
|
}
|
|
|
|
map->m_llen = MIN(chunk_size - chunk_off, vi->size - loff);
|
|
map->m_plen = map->m_llen;
|
|
map->m_deviceid = raw_device_id & sbi->device_id_mask;
|
|
if (blkaddr > (UINT64_MAX >> sbi->blkszbits))
|
|
return (EOVERFLOW);
|
|
map->m_pa = blkaddr << sbi->blkszbits;
|
|
if (chunk_off > UINT64_MAX - map->m_pa)
|
|
return (EOVERFLOW);
|
|
map->m_pa += chunk_off;
|
|
map->m_flags |= EROFS_MAP_MAPPED;
|
|
return (0);
|
|
}
|
|
|
|
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 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 >> sbi->blkszbits))
|
|
return (EINTEGRITY);
|
|
limit = blocks << sbi->blkszbits;
|
|
if (end > limit)
|
|
return (EINTEGRITY);
|
|
}
|
|
if (end > dif->mediasize)
|
|
return (ENXIO);
|
|
if (off > INT64_MAX || 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);
|
|
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);
|
|
if (done == 0 && racnt != 0)
|
|
error = breadn(dif->devvp, btodb(blkoff), iosize,
|
|
rablkno, rabsize, racnt, NOCRED, &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);
|
|
}
|
|
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));
|
|
}
|
|
|
|
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[EROFS_DIR_READAHEAD_SLOTS];
|
|
int rabsize[EROFS_DIR_READAHEAD_SLOTS];
|
|
erofs_off_t step;
|
|
erofs_blk_t blocks;
|
|
unsigned int count;
|
|
int error;
|
|
|
|
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;
|
|
rablocks = MIN(rablocks, (unsigned int)nitems(rablkno));
|
|
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;
|
|
rablkno[count] = btodb(future.m_pa);
|
|
rabsize[count] = sbi->block_size;
|
|
}
|
|
return (erofs_bread_device(sbi, current.m_dif, blocks, current.m_pa, len,
|
|
rablkno, rabsize, count, bufp));
|
|
}
|
|
|
|
/* Release a contiguous buffer returned by erofs_bread(). */
|
|
void
|
|
erofs_brelse(void *buf)
|
|
{
|
|
free(buf, M_EROFS);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* Read inode metadata from either the primary image or the metabox file. */
|
|
int
|
|
erofs_read_metadata(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 (EINTEGRITY);
|
|
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_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 (EINTEGRITY);
|
|
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 (EINTEGRITY);
|
|
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 (EINTEGRITY);
|
|
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 error;
|
|
|
|
if (erofs_inode_is_data_compressed(vi->datalayout)) {
|
|
error = z_erofs_map_blocks(sbi, vi, &next);
|
|
} else {
|
|
error = erofs_map_blocks_flatmode(sbi, vi, &next);
|
|
}
|
|
*map = next;
|
|
return (error);
|
|
}
|
|
/*
|
|
* Read a small range at a logical file offset into a contiguous buffer.
|
|
* Primarily used for directory block reads, lookup, and symlink fragment
|
|
* parsing.
|
|
*/
|
|
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;
|
|
size_t done, want;
|
|
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 > vi->size || (uint64_t)len > vi->size - loff)
|
|
return (EINTEGRITY);
|
|
|
|
/* Compressed file path */
|
|
if (erofs_inode_is_data_compressed(vi->datalayout))
|
|
return (z_erofs_read_data(sbi, vi, loff, len, bufp));
|
|
|
|
/* 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) {
|
|
free(out, M_EROFS);
|
|
return (EINTEGRITY);
|
|
}
|
|
want = MIN((size_t)MIN(map.m_llen, (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) {
|
|
error = erofs_read_metadata(sbi, vi->nid, map.m_pa,
|
|
want, &buf);
|
|
} else if (done == 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 {
|
|
error = erofs_read_physical(sbi, map.m_deviceid, map.m_pa,
|
|
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 remaining;
|
|
unsigned int rablocks;
|
|
|
|
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) {
|
|
remaining = vi->size - loff - len;
|
|
rablocks = MIN(howmany(remaining, sbi->block_size),
|
|
(uint64_t)EROFS_DIR_READAHEAD_SLOTS);
|
|
}
|
|
return (erofs_read_data_impl(sbi, vi, loff, len, rablocks, bufp));
|
|
}
|
|
|
|
/*
|
|
* 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_sb_info *sbi, struct erofs_inode *vi, struct uio *uio)
|
|
{
|
|
struct erofs_buf buf = EROFS_BUF_INITIALIZER;
|
|
struct erofs_map_blocks map;
|
|
char zerobuf[PAGE_SIZE];
|
|
void *blk;
|
|
size_t want, chunk;
|
|
int error;
|
|
|
|
if (uio->uio_offset < 0)
|
|
return (EINVAL);
|
|
if ((uint64_t)uio->uio_offset >= vi->size)
|
|
return (0);
|
|
|
|
/* Compressed file path */
|
|
if (erofs_inode_is_data_compressed(vi->datalayout))
|
|
return (z_erofs_read_uio(sbi, vi, uio));
|
|
|
|
/* Uncompressed file path */
|
|
bzero(zerobuf, sizeof(zerobuf));
|
|
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)
|
|
break;
|
|
want = MIN((size_t)MIN(map.m_llen, (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, sizeof(zerobuf));
|
|
|
|
error = uiomove(zerobuf, zlen, uio);
|
|
if (error != 0)
|
|
return (error);
|
|
chunk -= zlen;
|
|
}
|
|
continue;
|
|
}
|
|
if ((map.m_flags & EROFS_MAP_META) != 0) {
|
|
error = erofs_read_metadata(sbi, vi->nid, map.m_pa, want,
|
|
&buf);
|
|
} else {
|
|
error = erofs_read_physical(sbi, map.m_deviceid, map.m_pa,
|
|
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, int ioflag)
|
|
{
|
|
(void)ioflag;
|
|
return (erofs_read_uio(MTOE(vp->v_mount), VTOE(vp), uio));
|
|
}
|
|
|
|
int
|
|
erofs_validate_symlink_target(struct erofs_sb_info *sbi,
|
|
struct erofs_inode *vi)
|
|
{
|
|
void *target;
|
|
int error;
|
|
|
|
if (vi->vtype != VLNK)
|
|
return (EINVAL);
|
|
if (vi->size == 0)
|
|
return (EINTEGRITY);
|
|
if (vi->size > MAXPATHLEN)
|
|
return (ENAMETOOLONG);
|
|
error = erofs_read_data(sbi, vi, 0, (size_t)vi->size, &target);
|
|
if (error != 0)
|
|
return (error);
|
|
if (memchr(target, '\0', (size_t)vi->size) != NULL)
|
|
error = EINTEGRITY;
|
|
erofs_brelse(target);
|
|
return (error);
|
|
}
|
|
|
|
/* 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 (EINTEGRITY);
|
|
if (vi->size > MAXPATHLEN)
|
|
return (ENAMETOOLONG);
|
|
return (erofs_read_uio(MTOE(vp->v_mount), vi, uio));
|
|
}
|