summaryrefslogtreecommitdiffstats
path: root/lib/ext2fs/sparse_io.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ext2fs/sparse_io.c')
-rw-r--r--lib/ext2fs/sparse_io.c554
1 files changed, 554 insertions, 0 deletions
diff --git a/lib/ext2fs/sparse_io.c b/lib/ext2fs/sparse_io.c
new file mode 100644
index 0000000..f287e76
--- /dev/null
+++ b/lib/ext2fs/sparse_io.c
@@ -0,0 +1,554 @@
+#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;