298 lines
7.0 KiB
C
298 lines
7.0 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* erofs-utils/lib/blobchunk.c
|
|
*
|
|
* Copyright (C) 2021, Alibaba Cloud
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include "erofs/hashmap.h"
|
|
#include "erofs/blobchunk.h"
|
|
#include "erofs/block_list.h"
|
|
#include "erofs/cache.h"
|
|
#include "erofs/io.h"
|
|
#include <unistd.h>
|
|
|
|
void erofs_sha256(const unsigned char *in, unsigned long in_size,
|
|
unsigned char out[32]);
|
|
|
|
struct erofs_blobchunk {
|
|
struct hashmap_entry ent;
|
|
char sha256[32];
|
|
unsigned int chunksize;
|
|
erofs_blk_t blkaddr;
|
|
};
|
|
|
|
static struct hashmap blob_hashmap;
|
|
static FILE *blobfile;
|
|
static erofs_blk_t remapped_base;
|
|
static bool multidev;
|
|
static struct erofs_buffer_head *bh_devt;
|
|
|
|
static struct erofs_blobchunk *erofs_blob_getchunk(int fd,
|
|
unsigned int chunksize)
|
|
{
|
|
static u8 zeroed[EROFS_BLKSIZ];
|
|
u8 *chunkdata, sha256[32];
|
|
int ret;
|
|
unsigned int hash;
|
|
erofs_off_t blkpos;
|
|
struct erofs_blobchunk *chunk;
|
|
|
|
chunkdata = malloc(chunksize);
|
|
if (!chunkdata)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
ret = read(fd, chunkdata, chunksize);
|
|
if (ret < chunksize) {
|
|
chunk = ERR_PTR(-EIO);
|
|
goto out;
|
|
}
|
|
erofs_sha256(chunkdata, chunksize, sha256);
|
|
hash = memhash(sha256, sizeof(sha256));
|
|
chunk = hashmap_get_from_hash(&blob_hashmap, hash, sha256);
|
|
if (chunk) {
|
|
DBG_BUGON(chunksize != chunk->chunksize);
|
|
goto out;
|
|
}
|
|
chunk = malloc(sizeof(struct erofs_blobchunk));
|
|
if (!chunk) {
|
|
chunk = ERR_PTR(-ENOMEM);
|
|
goto out;
|
|
}
|
|
|
|
chunk->chunksize = chunksize;
|
|
blkpos = ftell(blobfile);
|
|
DBG_BUGON(erofs_blkoff(blkpos));
|
|
chunk->blkaddr = erofs_blknr(blkpos);
|
|
memcpy(chunk->sha256, sha256, sizeof(sha256));
|
|
hashmap_entry_init(&chunk->ent, hash);
|
|
hashmap_add(&blob_hashmap, chunk);
|
|
|
|
erofs_dbg("Writing chunk (%u bytes) to %u", chunksize, chunk->blkaddr);
|
|
ret = fwrite(chunkdata, chunksize, 1, blobfile);
|
|
if (ret == 1 && erofs_blkoff(chunksize))
|
|
ret = fwrite(zeroed, EROFS_BLKSIZ - erofs_blkoff(chunksize),
|
|
1, blobfile);
|
|
if (ret < 1) {
|
|
struct hashmap_entry key;
|
|
|
|
hashmap_entry_init(&key, hash);
|
|
hashmap_remove(&blob_hashmap, &key, sha256);
|
|
free(chunk);
|
|
chunk = ERR_PTR(-ENOSPC);
|
|
goto out;
|
|
}
|
|
out:
|
|
free(chunkdata);
|
|
return chunk;
|
|
}
|
|
|
|
static int erofs_blob_hashmap_cmp(const void *a, const void *b,
|
|
const void *key)
|
|
{
|
|
const struct erofs_blobchunk *ec1 =
|
|
container_of((struct hashmap_entry *)a,
|
|
struct erofs_blobchunk, ent);
|
|
const struct erofs_blobchunk *ec2 =
|
|
container_of((struct hashmap_entry *)b,
|
|
struct erofs_blobchunk, ent);
|
|
|
|
return memcmp(ec1->sha256, key ? key : ec2->sha256,
|
|
sizeof(ec1->sha256));
|
|
}
|
|
|
|
int erofs_blob_write_chunk_indexes(struct erofs_inode *inode,
|
|
erofs_off_t off)
|
|
{
|
|
struct erofs_inode_chunk_index idx = {0};
|
|
erofs_blk_t extent_start = EROFS_NULL_ADDR;
|
|
erofs_blk_t extent_end, extents_blks;
|
|
unsigned int dst, src, unit;
|
|
bool first_extent = true;
|
|
erofs_blk_t base_blkaddr = 0;
|
|
|
|
if (multidev) {
|
|
idx.device_id = 1;
|
|
inode->u.chunkformat |= EROFS_CHUNK_FORMAT_INDEXES;
|
|
} else {
|
|
base_blkaddr = remapped_base;
|
|
}
|
|
|
|
if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)
|
|
unit = sizeof(struct erofs_inode_chunk_index);
|
|
else
|
|
unit = EROFS_BLOCK_MAP_ENTRY_SIZE;
|
|
|
|
for (dst = src = 0; dst < inode->extent_isize;
|
|
src += sizeof(void *), dst += unit) {
|
|
struct erofs_blobchunk *chunk;
|
|
|
|
chunk = *(void **)(inode->chunkindexes + src);
|
|
|
|
idx.blkaddr = base_blkaddr + chunk->blkaddr;
|
|
if (extent_start != EROFS_NULL_ADDR &&
|
|
idx.blkaddr == extent_end + 1) {
|
|
extent_end = idx.blkaddr;
|
|
} else {
|
|
if (extent_start != EROFS_NULL_ADDR) {
|
|
erofs_droid_blocklist_write_extent(inode,
|
|
extent_start,
|
|
(extent_end - extent_start) + 1,
|
|
first_extent, false);
|
|
first_extent = false;
|
|
}
|
|
extent_start = idx.blkaddr;
|
|
extent_end = idx.blkaddr;
|
|
}
|
|
if (unit == EROFS_BLOCK_MAP_ENTRY_SIZE)
|
|
memcpy(inode->chunkindexes + dst, &idx.blkaddr, unit);
|
|
else
|
|
memcpy(inode->chunkindexes + dst, &idx, sizeof(idx));
|
|
}
|
|
off = roundup(off, unit);
|
|
|
|
if (extent_start == EROFS_NULL_ADDR)
|
|
extents_blks = 0;
|
|
else
|
|
extents_blks = (extent_end - extent_start) + 1;
|
|
erofs_droid_blocklist_write_extent(inode, extent_start, extents_blks,
|
|
first_extent, true);
|
|
|
|
return dev_write(inode->chunkindexes, off, inode->extent_isize);
|
|
}
|
|
|
|
int erofs_blob_write_chunked_file(struct erofs_inode *inode)
|
|
{
|
|
unsigned int chunksize = 1 << cfg.c_chunkbits;
|
|
unsigned int count = DIV_ROUND_UP(inode->i_size, chunksize);
|
|
struct erofs_inode_chunk_index *idx;
|
|
erofs_off_t pos, len;
|
|
unsigned int unit;
|
|
int fd, ret;
|
|
|
|
inode->u.chunkformat |= inode->u.chunkbits - LOG_BLOCK_SIZE;
|
|
|
|
if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)
|
|
unit = sizeof(struct erofs_inode_chunk_index);
|
|
else
|
|
unit = EROFS_BLOCK_MAP_ENTRY_SIZE;
|
|
|
|
inode->extent_isize = count * unit;
|
|
idx = malloc(count * max(sizeof(*idx), sizeof(void *)));
|
|
if (!idx)
|
|
return -ENOMEM;
|
|
inode->chunkindexes = idx;
|
|
|
|
fd = open(inode->i_srcpath, O_RDONLY | O_BINARY);
|
|
if (fd < 0) {
|
|
ret = -errno;
|
|
goto err;
|
|
}
|
|
|
|
for (pos = 0; pos < inode->i_size; pos += len) {
|
|
struct erofs_blobchunk *chunk;
|
|
|
|
len = min_t(u64, inode->i_size - pos, chunksize);
|
|
chunk = erofs_blob_getchunk(fd, len);
|
|
if (IS_ERR(chunk)) {
|
|
ret = PTR_ERR(chunk);
|
|
close(fd);
|
|
goto err;
|
|
}
|
|
*(void **)idx++ = chunk;
|
|
}
|
|
inode->datalayout = EROFS_INODE_CHUNK_BASED;
|
|
close(fd);
|
|
return 0;
|
|
err:
|
|
free(inode->chunkindexes);
|
|
inode->chunkindexes = NULL;
|
|
return ret;
|
|
}
|
|
|
|
int erofs_blob_remap(void)
|
|
{
|
|
struct erofs_buffer_head *bh;
|
|
ssize_t length;
|
|
erofs_off_t pos_in, pos_out;
|
|
ssize_t ret;
|
|
|
|
fflush(blobfile);
|
|
length = ftell(blobfile);
|
|
if (multidev) {
|
|
struct erofs_deviceslot dis = {
|
|
.blocks = erofs_blknr(length),
|
|
};
|
|
|
|
pos_out = erofs_btell(bh_devt, false);
|
|
ret = dev_write(&dis, pos_out, sizeof(dis));
|
|
if (ret)
|
|
return ret;
|
|
|
|
bh_devt->op = &erofs_drop_directly_bhops;
|
|
erofs_bdrop(bh_devt, false);
|
|
return 0;
|
|
}
|
|
bh = erofs_balloc(DATA, length, 0, 0);
|
|
if (IS_ERR(bh))
|
|
return PTR_ERR(bh);
|
|
|
|
erofs_mapbh(bh->block);
|
|
pos_out = erofs_btell(bh, false);
|
|
pos_in = 0;
|
|
remapped_base = erofs_blknr(pos_out);
|
|
ret = erofs_copy_file_range(fileno(blobfile), &pos_in,
|
|
erofs_devfd, &pos_out, length);
|
|
bh->op = &erofs_drop_directly_bhops;
|
|
erofs_bdrop(bh, false);
|
|
return ret < length ? -EIO : 0;
|
|
}
|
|
|
|
void erofs_blob_exit(void)
|
|
{
|
|
if (blobfile)
|
|
fclose(blobfile);
|
|
|
|
hashmap_free(&blob_hashmap, 1);
|
|
}
|
|
|
|
int erofs_blob_init(const char *blobfile_path)
|
|
{
|
|
if (!blobfile_path) {
|
|
#ifdef HAVE_TMPFILE64
|
|
blobfile = tmpfile64();
|
|
#else
|
|
blobfile = tmpfile();
|
|
#endif
|
|
multidev = false;
|
|
} else {
|
|
blobfile = fopen(blobfile_path, "wb");
|
|
multidev = true;
|
|
}
|
|
if (!blobfile)
|
|
return -EACCES;
|
|
|
|
hashmap_init(&blob_hashmap, erofs_blob_hashmap_cmp, 0);
|
|
return 0;
|
|
}
|
|
|
|
int erofs_generate_devtable(void)
|
|
{
|
|
struct erofs_deviceslot dis;
|
|
|
|
if (!multidev)
|
|
return 0;
|
|
|
|
bh_devt = erofs_balloc(DEVT, sizeof(dis), 0, 0);
|
|
if (IS_ERR(bh_devt))
|
|
return PTR_ERR(bh_devt);
|
|
|
|
dis = (struct erofs_deviceslot) {};
|
|
erofs_mapbh(bh_devt->block);
|
|
bh_devt->op = &erofs_skip_write_bhops;
|
|
sbi.devt_slotoff = erofs_btell(bh_devt, false) / EROFS_DEVT_SLOT_SIZE;
|
|
sbi.extra_devices = 1;
|
|
erofs_sb_set_device_table();
|
|
return 0;
|
|
}
|