| // 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; |
| } |