| #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; |