You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
555 lines
13 KiB
555 lines
13 KiB
#include "config.h"
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <stdint.h>
|
|
#include "ext2_fs.h"
|
|
#include "ext2fs.h"
|
|
|
|
#ifndef O_BINARY
|
|
#define O_BINARY 0
|
|
#endif
|
|
|
|
#if !defined(ENABLE_LIBSPARSE)
|
|
static errcode_t sparse_open(const char *name EXT2FS_ATTR((unused)),
|
|
int flags EXT2FS_ATTR((unused)),
|
|
io_channel *channel EXT2FS_ATTR((unused)))
|
|
{
|
|
return EXT2_ET_UNIMPLEMENTED;
|
|
}
|
|
static errcode_t sparse_close(io_channel channel EXT2FS_ATTR((unused)))
|
|
{
|
|
return EXT2_ET_UNIMPLEMENTED;
|
|
}
|
|
static struct struct_io_manager struct_sparse_manager = {
|
|
.magic = EXT2_ET_MAGIC_IO_MANAGER,
|
|
.name = "Android sparse I/O Manager",
|
|
.open = sparse_open,
|
|
.close = sparse_close,
|
|
};
|
|
static struct struct_io_manager struct_sparsefd_manager = {
|
|
.magic = EXT2_ET_MAGIC_IO_MANAGER,
|
|
.name = "Android sparse fd I/O Manager",
|
|
.open = sparse_open,
|
|
.close = sparse_close,
|
|
};
|
|
#else
|
|
#include <sparse/sparse.h>
|
|
|
|
struct sparse_map {
|
|
int fd;
|
|
char **blocks;
|
|
int block_size;
|
|
uint64_t blocks_count;
|
|
char *file;
|
|
struct sparse_file *sparse_file;
|
|
io_channel channel;
|
|
};
|
|
|
|
struct sparse_io_params {
|
|
int fd;
|
|
char *file;
|
|
uint64_t blocks_count;
|
|
unsigned int block_size;
|
|
};
|
|
|
|
static errcode_t sparse_write_blk(io_channel channel, unsigned long block,
|
|
int count, const void *buf);
|
|
|
|
static void free_sparse_blocks(struct sparse_map *sm)
|
|
{
|
|
uint64_t i;
|
|
|
|
for (i = 0; i < sm->blocks_count; ++i)
|
|
free(sm->blocks[i]);
|
|
free(sm->blocks);
|
|
sm->blocks = NULL;
|
|
}
|
|
|
|
static int sparse_import_segment(void *priv, const void *data, size_t len,
|
|
unsigned int block, unsigned int nr_blocks)
|
|
{
|
|
struct sparse_map *sm = priv;
|
|
|
|
/* Ignore chunk headers, only write the data */
|
|
if (!nr_blocks || len % sm->block_size)
|
|
return 0;
|
|
|
|
return sparse_write_blk(sm->channel, block, nr_blocks, data);
|
|
}
|
|
|
|
static errcode_t io_manager_import_sparse(struct sparse_io_params *params,
|
|
struct sparse_map *sm, io_channel io)
|
|
{
|
|
int fd;
|
|
errcode_t retval;
|
|
struct sparse_file *sparse_file;
|
|
|
|
if (params->fd < 0) {
|
|
fd = open(params->file, O_RDONLY);
|
|
if (fd < 0) {
|
|
retval = -1;
|
|
goto err_open;
|
|
}
|
|
} else
|
|
fd = params->fd;
|
|
sparse_file = sparse_file_import(fd, false, false);
|
|
if (!sparse_file) {
|
|
retval = -1;
|
|
goto err_sparse;
|
|
}
|
|
|
|
sm->block_size = sparse_file_block_size(sparse_file);
|
|
sm->blocks_count = (sparse_file_len(sparse_file, 0, 0) - 1)
|
|
/ sm->block_size + 1;
|
|
sm->blocks = calloc(sm->blocks_count, sizeof(char*));
|
|
if (!sm->blocks) {
|
|
retval = -1;
|
|
goto err_alloc;
|
|
}
|
|
io->block_size = sm->block_size;
|
|
|
|
retval = sparse_file_foreach_chunk(sparse_file, true, false,
|
|
sparse_import_segment, sm);
|
|
|
|
if (retval)
|
|
free_sparse_blocks(sm);
|
|
err_alloc:
|
|
sparse_file_destroy(sparse_file);
|
|
err_sparse:
|
|
close(fd);
|
|
err_open:
|
|
return retval;
|
|
}
|
|
|
|
static errcode_t io_manager_configure(struct sparse_io_params *params,
|
|
int flags, io_channel io)
|
|
{
|
|
errcode_t retval;
|
|
uint64_t img_size;
|
|
struct sparse_map *sm = calloc(1, sizeof(*sm));
|
|
if (!sm)
|
|
return EXT2_ET_NO_MEMORY;
|
|
|
|
sm->file = params->file;
|
|
sm->channel = io;
|
|
io->private_data = sm;
|
|
retval = io_manager_import_sparse(params, sm, io);
|
|
if (retval) {
|
|
if (!params->block_size || !params->blocks_count) {
|
|
retval = -EINVAL;
|
|
goto err_params;
|
|
}
|
|
sm->block_size = params->block_size;
|
|
sm->blocks_count = params->blocks_count;
|
|
sm->blocks = calloc(params->blocks_count, sizeof(void*));
|
|
if (!sm->blocks) {
|
|
retval = EXT2_ET_NO_MEMORY;
|
|
goto err_alloc;
|
|
}
|
|
}
|
|
io->block_size = sm->block_size;
|
|
img_size = (uint64_t)sm->block_size * sm->blocks_count;
|
|
|
|
if (flags & IO_FLAG_RW) {
|
|
sm->sparse_file = sparse_file_new(sm->block_size, img_size);
|
|
if (!sm->sparse_file) {
|
|
retval = EXT2_ET_NO_MEMORY;
|
|
goto err_alloc;
|
|
}
|
|
if (params->fd < 0) {
|
|
sm->fd = open(params->file, O_CREAT | O_RDWR | O_TRUNC | O_BINARY,
|
|
0644);
|
|
if (sm->fd < 0) {
|
|
retval = errno;
|
|
goto err_open;
|
|
}
|
|
} else
|
|
sm->fd = params->fd;
|
|
} else {
|
|
sm->fd = -1;
|
|
sm->sparse_file = NULL;
|
|
}
|
|
return 0;
|
|
|
|
err_open:
|
|
sparse_file_destroy(sm->sparse_file);
|
|
err_alloc:
|
|
free_sparse_blocks(sm);
|
|
err_params:
|
|
free(sm);
|
|
return retval;
|
|
}
|
|
|
|
static errcode_t sparse_open_channel(struct sparse_io_params *sparse_params,
|
|
int flags, io_channel *channel)
|
|
{
|
|
errcode_t retval;
|
|
io_channel io;
|
|
|
|
io = calloc(1, sizeof(struct struct_io_channel));
|
|
io->magic = EXT2_ET_MAGIC_IO_CHANNEL;
|
|
io->block_size = 0;
|
|
io->refcount = 1;
|
|
|
|
retval = io_manager_configure(sparse_params, flags, io);
|
|
if (retval) {
|
|
free(io);
|
|
return retval;
|
|
}
|
|
|
|
*channel = io;
|
|
return 0;
|
|
}
|
|
|
|
static errcode_t read_sparse_argv(const char *name, bool is_fd,
|
|
struct sparse_io_params *sparse_params)
|
|
{
|
|
int ret;
|
|
sparse_params->fd = -1;
|
|
sparse_params->block_size = 0;
|
|
sparse_params->blocks_count = 0;
|
|
|
|
sparse_params->file = malloc(strlen(name) + 1);
|
|
if (!sparse_params->file) {
|
|
fprintf(stderr, "failed to alloc %zu\n", strlen(name) + 1);
|
|
return EXT2_ET_NO_MEMORY;
|
|
}
|
|
|
|
if (is_fd) {
|
|
ret = sscanf(name, "(%d):%llu:%u", &sparse_params->fd,
|
|
(unsigned long long *)&sparse_params->blocks_count,
|
|
&sparse_params->block_size);
|
|
} else {
|
|
ret = sscanf(name, "(%[^)])%*[:]%llu%*[:]%u", sparse_params->file,
|
|
(unsigned long long *)&sparse_params->blocks_count,
|
|
&sparse_params->block_size);
|
|
}
|
|
|
|
if (ret < 1) {
|
|
free(sparse_params->file);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static errcode_t sparse_open(const char *name, int flags, io_channel *channel)
|
|
{
|
|
errcode_t retval;
|
|
struct sparse_io_params sparse_params;
|
|
|
|
retval = read_sparse_argv(name, false, &sparse_params);
|
|
if (retval)
|
|
return EXT2_ET_BAD_DEVICE_NAME;
|
|
|
|
retval = sparse_open_channel(&sparse_params, flags, channel);
|
|
if (retval)
|
|
return retval;
|
|
(*channel)->manager = sparse_io_manager;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static errcode_t sparsefd_open(const char *name, int flags, io_channel *channel)
|
|
{
|
|
errcode_t retval;
|
|
struct sparse_io_params sparse_params;
|
|
|
|
retval = read_sparse_argv(name, true, &sparse_params);
|
|
if (retval)
|
|
return EXT2_ET_BAD_DEVICE_NAME;
|
|
|
|
retval = sparse_open_channel(&sparse_params, flags, channel);
|
|
if (retval)
|
|
return retval;
|
|
(*channel)->manager = sparsefd_io_manager;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static errcode_t sparse_merge_blocks(struct sparse_map *sm, uint64_t start,
|
|
uint64_t num)
|
|
{
|
|
char *buf;
|
|
uint64_t i;
|
|
unsigned int block_size = sm->block_size;
|
|
errcode_t retval = 0;
|
|
|
|
buf = calloc(num, block_size);
|
|
if (!buf) {
|
|
fprintf(stderr, "failed to alloc %llu\n",
|
|
(unsigned long long)num * block_size);
|
|
return EXT2_ET_NO_MEMORY;
|
|
}
|
|
|
|
for (i = 0; i < num; i++) {
|
|
memcpy(buf + i * block_size, sm->blocks[start + i] , block_size);
|
|
free(sm->blocks[start + i]);
|
|
sm->blocks[start + i] = NULL;
|
|
}
|
|
|
|
/* free_sparse_blocks will release this buf. */
|
|
sm->blocks[start] = buf;
|
|
|
|
retval = sparse_file_add_data(sm->sparse_file, sm->blocks[start],
|
|
block_size * num, start);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static errcode_t sparse_close_channel(io_channel channel)
|
|
{
|
|
uint64_t i;
|
|
errcode_t retval = 0;
|
|
struct sparse_map *sm = channel->private_data;
|
|
|
|
if (sm->sparse_file) {
|
|
int64_t chunk_start = (sm->blocks[0] == NULL) ? -1 : 0;
|
|
for (i = 0; i < sm->blocks_count; ++i) {
|
|
if (!sm->blocks[i] && chunk_start != -1) {
|
|
retval = sparse_merge_blocks(sm, chunk_start, i - chunk_start);
|
|
chunk_start = -1;
|
|
} else if (sm->blocks[i] && chunk_start == -1) {
|
|
chunk_start = i;
|
|
}
|
|
if (retval)
|
|
goto ret;
|
|
}
|
|
if (chunk_start != -1) {
|
|
retval = sparse_merge_blocks(sm, chunk_start,
|
|
sm->blocks_count - chunk_start);
|
|
if (retval)
|
|
goto ret;
|
|
}
|
|
retval = sparse_file_write(sm->sparse_file, sm->fd,
|
|
/*gzip*/0, /*sparse*/1, /*crc*/0);
|
|
}
|
|
|
|
ret:
|
|
if (sm->sparse_file)
|
|
sparse_file_destroy(sm->sparse_file);
|
|
free_sparse_blocks(sm);
|
|
free(sm->file);
|
|
free(sm);
|
|
free(channel);
|
|
return retval;
|
|
}
|
|
|
|
static errcode_t sparse_close(io_channel channel)
|
|
{
|
|
errcode_t retval;
|
|
struct sparse_map *sm = channel->private_data;
|
|
int fd = sm->fd;
|
|
|
|
retval = sparse_close_channel(channel);
|
|
if (fd >= 0)
|
|
close(fd);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static errcode_t sparse_set_blksize(io_channel channel, int blksize)
|
|
{
|
|
channel->block_size = blksize;
|
|
return 0;
|
|
}
|
|
|
|
static blk64_t block_to_sparse_block(blk64_t block, blk64_t *offset,
|
|
io_channel channel, struct sparse_map *sm)
|
|
{
|
|
int ratio;
|
|
blk64_t ret = block;
|
|
|
|
ratio = sm->block_size / channel->block_size;
|
|
ret /= ratio;
|
|
*offset = (block % ratio) * channel->block_size;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static errcode_t check_block_size(io_channel channel, struct sparse_map *sm)
|
|
{
|
|
if (sm->block_size >= channel->block_size)
|
|
return 0;
|
|
return EXT2_ET_UNEXPECTED_BLOCK_SIZE;
|
|
}
|
|
|
|
static errcode_t sparse_read_blk64(io_channel channel, blk64_t block,
|
|
int count, void *buf)
|
|
{
|
|
int i;
|
|
char *out = buf;
|
|
blk64_t offset = 0, cur_block;
|
|
struct sparse_map *sm = channel->private_data;
|
|
|
|
if (check_block_size(channel, sm))
|
|
return EXT2_ET_UNEXPECTED_BLOCK_SIZE;
|
|
|
|
if (count < 0) { //partial read
|
|
count = -count;
|
|
cur_block = block_to_sparse_block(block, &offset, channel, sm);
|
|
if (sm->blocks[cur_block])
|
|
memcpy(out, (sm->blocks[cur_block]) + offset, count);
|
|
else
|
|
memset(out, 0, count);
|
|
} else {
|
|
for (i = 0; i < count; ++i) {
|
|
cur_block = block_to_sparse_block(block + i, &offset,
|
|
channel, sm);
|
|
if (sm->blocks[cur_block])
|
|
memcpy(out + (i * channel->block_size),
|
|
sm->blocks[cur_block] + offset,
|
|
channel->block_size);
|
|
else if (sm->blocks)
|
|
memset(out + (i * channel->block_size), 0,
|
|
channel->block_size);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static errcode_t sparse_read_blk(io_channel channel, unsigned long block,
|
|
int count, void *buf)
|
|
{
|
|
return sparse_read_blk64(channel, block, count, buf);
|
|
}
|
|
|
|
static errcode_t sparse_write_blk64(io_channel channel, blk64_t block,
|
|
int count, const void *buf)
|
|
{
|
|
int i;
|
|
blk64_t offset = 0, cur_block;
|
|
const char *in = buf;
|
|
struct sparse_map *sm = channel->private_data;
|
|
|
|
if (check_block_size(channel, sm))
|
|
return EXT2_ET_UNEXPECTED_BLOCK_SIZE;
|
|
|
|
if (count < 0) { //partial write
|
|
count = -count;
|
|
cur_block = block_to_sparse_block(block, &offset, channel,
|
|
sm);
|
|
if (!sm->blocks[cur_block]) {
|
|
sm->blocks[cur_block] = calloc(1, sm->block_size);
|
|
if (!sm->blocks[cur_block])
|
|
return EXT2_ET_NO_MEMORY;
|
|
}
|
|
memcpy(sm->blocks[cur_block] + offset, in, count);
|
|
} else {
|
|
for (i = 0; i < count; ++i) {
|
|
if (block + i >= sm->blocks_count)
|
|
return 0;
|
|
cur_block = block_to_sparse_block(block + i, &offset,
|
|
channel, sm);
|
|
if (!sm->blocks[cur_block]) {
|
|
sm->blocks[cur_block] =
|
|
calloc(1, sm->block_size);
|
|
if (!sm->blocks[cur_block])
|
|
return EXT2_ET_NO_MEMORY;
|
|
}
|
|
memcpy(sm->blocks[cur_block] + offset,
|
|
in + (i * channel->block_size),
|
|
channel->block_size);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static errcode_t sparse_write_blk(io_channel channel, unsigned long block,
|
|
int count, const void *buf)
|
|
{
|
|
return sparse_write_blk64(channel, block, count, buf);
|
|
}
|
|
|
|
static errcode_t sparse_discard(io_channel channel __attribute__((unused)),
|
|
blk64_t blk, unsigned long long count)
|
|
{
|
|
blk64_t cur_block, offset;
|
|
struct sparse_map *sm = channel->private_data;
|
|
|
|
if (check_block_size(channel, sm))
|
|
return EXT2_ET_UNEXPECTED_BLOCK_SIZE;
|
|
|
|
for (unsigned long long i = 0; i < count; ++i) {
|
|
if (blk + i >= sm->blocks_count)
|
|
return 0;
|
|
cur_block = block_to_sparse_block(blk + i, &offset, channel,
|
|
sm);
|
|
if (!sm->blocks[cur_block])
|
|
continue;
|
|
free(sm->blocks[cur_block]);
|
|
sm->blocks[cur_block] = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static errcode_t sparse_zeroout(io_channel channel, blk64_t blk,
|
|
unsigned long long count)
|
|
{
|
|
return sparse_discard(channel, blk, count);
|
|
}
|
|
|
|
static errcode_t sparse_flush(io_channel channel __attribute__((unused)))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static errcode_t sparse_set_option(io_channel channel __attribute__((unused)),
|
|
const char *option __attribute__((unused)),
|
|
const char *arg __attribute__((unused)))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static errcode_t sparse_cache_readahead(
|
|
io_channel channel __attribute__((unused)),
|
|
blk64_t blk __attribute__((unused)),
|
|
unsigned long long count __attribute__((unused)))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static struct struct_io_manager struct_sparse_manager = {
|
|
.magic = EXT2_ET_MAGIC_IO_MANAGER,
|
|
.name = "Android sparse I/O Manager",
|
|
.open = sparse_open,
|
|
.close = sparse_close,
|
|
.set_blksize = sparse_set_blksize,
|
|
.read_blk = sparse_read_blk,
|
|
.write_blk = sparse_write_blk,
|
|
.flush = sparse_flush,
|
|
.write_byte = NULL,
|
|
.set_option = sparse_set_option,
|
|
.get_stats = NULL,
|
|
.read_blk64 = sparse_read_blk64,
|
|
.write_blk64 = sparse_write_blk64,
|
|
.discard = sparse_discard,
|
|
.cache_readahead = sparse_cache_readahead,
|
|
.zeroout = sparse_zeroout,
|
|
};
|
|
|
|
static struct struct_io_manager struct_sparsefd_manager = {
|
|
.magic = EXT2_ET_MAGIC_IO_MANAGER,
|
|
.name = "Android sparse fd I/O Manager",
|
|
.open = sparsefd_open,
|
|
.close = sparse_close,
|
|
.set_blksize = sparse_set_blksize,
|
|
.read_blk = sparse_read_blk,
|
|
.write_blk = sparse_write_blk,
|
|
.flush = sparse_flush,
|
|
.write_byte = NULL,
|
|
.set_option = sparse_set_option,
|
|
.get_stats = NULL,
|
|
.read_blk64 = sparse_read_blk64,
|
|
.write_blk64 = sparse_write_blk64,
|
|
.discard = sparse_discard,
|
|
.cache_readahead = sparse_cache_readahead,
|
|
.zeroout = sparse_zeroout,
|
|
};
|
|
|
|
#endif
|
|
|
|
io_manager sparse_io_manager = &struct_sparse_manager;
|
|
io_manager sparsefd_io_manager = &struct_sparsefd_manager;
|