288 lines
7.8 KiB
C
288 lines
7.8 KiB
C
// 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);
|
|
}
|