555 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			555 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
| #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;
 |