/* * undo_io.c --- This is the undo io manager that copies the old data that * copies the old data being overwritten into a tdb database * * Copyright IBM Corporation, 2007 * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> * * %Begin-Header% * This file may be redistributed under the terms of the GNU Public * License. * %End-Header% */ #define _LARGEFILE_SOURCE #define _LARGEFILE64_SOURCE #include <stdio.h> #include <string.h> #if HAVE_UNISTD_H #include <unistd.h> #endif #if HAVE_ERRNO_H #include <errno.h> #endif #include <fcntl.h> #include <time.h> #ifdef __linux__ #include <sys/utsname.h> #endif #if HAVE_SYS_STAT_H #include <sys/stat.h> #endif #if HAVE_SYS_TYPES_H #include <sys/types.h> #endif #if HAVE_SYS_RESOURCE_H #include <sys/resource.h> #endif #include "tdb.h" #include "ext2_fs.h" #include "ext2fs.h" #ifdef __GNUC__ #define ATTR(x) __attribute__(x) #else #define ATTR(x) #endif /* * For checking structure magic numbers... */ #define EXT2_CHECK_MAGIC(struct, code) \ if ((struct)->magic != (code)) return (code) struct undo_private_data { int magic; TDB_CONTEXT *tdb; char *tdb_file; /* The backing io channel */ io_channel real; int tdb_data_size; int tdb_written; /* to support offset in unix I/O manager */ ext2_loff_t offset; }; static errcode_t undo_open(const char *name, int flags, io_channel *channel); static errcode_t undo_close(io_channel channel); static errcode_t undo_set_blksize(io_channel channel, int blksize); static errcode_t undo_read_blk(io_channel channel, unsigned long block, int count, void *data); static errcode_t undo_write_blk(io_channel channel, unsigned long block, int count, const void *data); static errcode_t undo_flush(io_channel channel); static errcode_t undo_write_byte(io_channel channel, unsigned long offset, int size, const void *data); static errcode_t undo_set_option(io_channel channel, const char *option, const char *arg); static struct struct_io_manager struct_undo_manager = { EXT2_ET_MAGIC_IO_MANAGER, "Undo I/O Manager", undo_open, undo_close, undo_set_blksize, undo_read_blk, undo_write_blk, undo_flush, undo_write_byte, undo_set_option }; io_manager undo_io_manager = &struct_undo_manager; static io_manager undo_io_backing_manager ; static char *tdb_file; static int actual_size; static unsigned char mtime_key[] = "filesystem MTIME"; static unsigned char blksize_key[] = "filesystem BLKSIZE"; static unsigned char uuid_key[] = "filesystem UUID"; errcode_t set_undo_io_backing_manager(io_manager manager) { /* * We may want to do some validation later */ undo_io_backing_manager = manager; return 0; } errcode_t set_undo_io_backup_file(char *file_name) { tdb_file = strdup(file_name); if (tdb_file == NULL) { return EXT2_ET_NO_MEMORY; } return 0; } static errcode_t write_file_system_identity(io_channel undo_channel, TDB_CONTEXT *tdb) { errcode_t retval; struct ext2_super_block super; TDB_DATA tdb_key, tdb_data; struct undo_private_data *data; io_channel channel; int block_size ; data = (struct undo_private_data *) undo_channel->private_data; channel = data->real; block_size = channel->block_size; io_channel_set_blksize(channel, SUPERBLOCK_OFFSET); retval = io_channel_read_blk(channel, 1, -SUPERBLOCK_SIZE, &super); if (retval) goto err_out; /* Write to tdb file in the file system byte order */ tdb_key.dptr = mtime_key; tdb_key.dsize = sizeof(mtime_key); tdb_data.dptr = (unsigned char *) &(super.s_mtime); tdb_data.dsize = sizeof(super.s_mtime); retval = tdb_store(tdb, tdb_key, tdb_data, TDB_INSERT); if (retval == -1) { retval = EXT2_ET_TDB_SUCCESS + tdb_error(tdb); goto err_out; } tdb_key.dptr = uuid_key; tdb_key.dsize = sizeof(uuid_key); tdb_data.dptr = (unsigned char *)&(super.s_uuid); tdb_data.dsize = sizeof(super.s_uuid); retval = tdb_store(tdb, tdb_key, tdb_data, TDB_INSERT); if (retval == -1) { retval = EXT2_ET_TDB_SUCCESS + tdb_error(tdb); } err_out: io_channel_set_blksize(channel, block_size); return retval; } static errcode_t write_block_size(TDB_CONTEXT *tdb, int block_size) { errcode_t retval; TDB_DATA tdb_key, tdb_data; tdb_key.dptr = blksize_key; tdb_key.dsize = sizeof(blksize_key); tdb_data.dptr = (unsigned char *)&(block_size); tdb_data.dsize = sizeof(block_size); retval = tdb_store(tdb, tdb_key, tdb_data, TDB_INSERT); if (retval == -1) { retval = EXT2_ET_TDB_SUCCESS + tdb_error(tdb); } return retval; } static errcode_t undo_write_tdb(io_channel channel, unsigned long block, int count) { int size, sz; unsigned long block_num, backing_blk_num; errcode_t retval = 0; ext2_loff_t offset; struct undo_private_data *data; TDB_DATA tdb_key, tdb_data; unsigned char *read_ptr; unsigned long end_block; data = (struct undo_private_data *) channel->private_data; if (data->tdb == NULL) { /* * Transaction database not initialized */ return 0; } if (count == 1) size = channel->block_size; else { if (count < 0) size = -count; else size = count * channel->block_size; } /* * Data is stored in tdb database as blocks of tdb_data_size size * This helps in efficient lookup further. * * We divide the disk to blocks of tdb_data_size. */ offset = (block * channel->block_size) + data->offset ; block_num = offset / data->tdb_data_size; end_block = (offset + size) / data->tdb_data_size; tdb_transaction_start(data->tdb); while (block_num <= end_block ) { tdb_key.dptr = (unsigned char *)&block_num; tdb_key.dsize = sizeof(block_num); /* * Check if we have the record already */ if (tdb_exists(data->tdb, tdb_key)) { /* Try the next block */ block_num++; continue; } /* * Read one block using the backing I/O manager * The backing I/O manager block size may be * different from the tdb_data_size. * Also we need to recalcuate the block number with respect * to the backing I/O manager. */ offset = block_num * data->tdb_data_size; backing_blk_num = (offset - data->offset) / channel->block_size; count = data->tdb_data_size + ((offset - data->offset) % channel->block_size); retval = ext2fs_get_mem(count, &read_ptr); if (retval) { tdb_transaction_cancel(data->tdb); return retval; } memset(read_ptr, 0, count); actual_size = 0; if ((count % channel->block_size) == 0) sz = count / channel->block_size; else sz = -count; retval = io_channel_read_blk(data->real, backing_blk_num, sz, read_ptr); if (retval) { if (retval != EXT2_ET_SHORT_READ) { free(read_ptr); tdb_transaction_cancel(data->tdb); return retval; } /* * short read so update the record size * accordingly */ tdb_data.dsize = actual_size; } else { tdb_data.dsize = data->tdb_data_size; } tdb_data.dptr = read_ptr + ((offset - data->offset) % channel->block_size); #ifdef DEBUG printf("Printing with key %ld data %x and size %d\n", block_num, tdb_data.dptr, tdb_data.dsize); #endif if (!data->tdb_written) { data->tdb_written = 1; /* Write the blocksize to tdb file */ retval = write_block_size(data->tdb, data->tdb_data_size); if (retval) { tdb_transaction_cancel(data->tdb); retval = EXT2_ET_TDB_ERR_IO; free(read_ptr); return retval; } } retval = tdb_store(data->tdb, tdb_key, tdb_data, TDB_INSERT); if (retval == -1) { /* * TDB_ERR_EXISTS cannot happen because we * have already verified it doesn't exist */ tdb_transaction_cancel(data->tdb); retval = EXT2_ET_TDB_ERR_IO; free(read_ptr); return retval; } free(read_ptr); /* Next block */ block_num++; } tdb_transaction_commit(data->tdb); return retval; } static errcode_t undo_io_read_error(io_channel channel ATTR((unused)), unsigned long block ATTR((unused)), int count ATTR((unused)), void *data ATTR((unused)), size_t size ATTR((unused)), int actual, errcode_t error ATTR((unused))) { actual_size = actual; return error; } static void undo_err_handler_init(io_channel channel) { channel->read_error = undo_io_read_error; } static errcode_t undo_open(const char *name, int flags, io_channel *channel) { io_channel io = NULL; struct undo_private_data *data = NULL; errcode_t retval; if (name == 0) return EXT2_ET_BAD_DEVICE_NAME; retval = ext2fs_get_mem(sizeof(struct struct_io_channel), &io); if (retval) return retval; memset(io, 0, sizeof(struct struct_io_channel)); io->magic = EXT2_ET_MAGIC_IO_CHANNEL; retval = ext2fs_get_mem(sizeof(struct undo_private_data), &data); if (retval) goto cleanup; io->manager = undo_io_manager; retval = ext2fs_get_mem(strlen(name)+1, &io->name); if (retval) goto cleanup; strcpy(io->name, name); io->private_data = data; io->block_size = 1024; io->read_error = 0; io->write_error = 0; io->refcount = 1; memset(data, 0, sizeof(struct undo_private_data)); data->magic = EXT2_ET_MAGIC_UNIX_IO_CHANNEL; if (undo_io_backing_manager) { retval = undo_io_backing_manager->open(name, flags, &data->real); if (retval) goto cleanup; } else { data->real = 0; } /* setup the tdb file */ data->tdb = tdb_open(tdb_file, 0, TDB_CLEAR_IF_FIRST, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 0600); if (!data->tdb) { retval = errno; goto cleanup; } /* * setup err handler for read so that we know * when the backing manager fails do short read */ undo_err_handler_init(data->real); *channel = io; return 0; cleanup: if (data->real) io_channel_close(data->real); if (data) ext2fs_free_mem(&data); if (io) ext2fs_free_mem(&io); return retval; } static errcode_t undo_close(io_channel channel) { struct undo_private_data *data; errcode_t retval = 0; EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); data = (struct undo_private_data *) channel->private_data; EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); if (--channel->refcount > 0) return 0; /* Before closing write the file system identity */ retval = write_file_system_identity(channel, data->tdb); if (retval) return retval; if (data->real) retval = io_channel_close(data->real); if (data->tdb) tdb_close(data->tdb); ext2fs_free_mem(&channel->private_data); if (channel->name) ext2fs_free_mem(&channel->name); ext2fs_free_mem(&channel); return retval; } static errcode_t undo_set_blksize(io_channel channel, int blksize) { struct undo_private_data *data; errcode_t retval = 0; EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); data = (struct undo_private_data *) channel->private_data; EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); if (data->real) retval = io_channel_set_blksize(data->real, blksize); /* * Set the block size used for tdb */ if (!data->tdb_data_size) { data->tdb_data_size = blksize; } channel->block_size = blksize; return retval; } static errcode_t undo_read_blk(io_channel channel, unsigned long block, int count, void *buf) { errcode_t retval = 0; struct undo_private_data *data; EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); data = (struct undo_private_data *) channel->private_data; EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); if (data->real) retval = io_channel_read_blk(data->real, block, count, buf); return retval; } static errcode_t undo_write_blk(io_channel channel, unsigned long block, int count, const void *buf) { struct undo_private_data *data; errcode_t retval = 0; EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); data = (struct undo_private_data *) channel->private_data; EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); /* * First write the existing content into database */ retval = undo_write_tdb(channel, block, count); if (retval) return retval; if (data->real) retval = io_channel_write_blk(data->real, block, count, buf); return retval; } static errcode_t undo_write_byte(io_channel channel, unsigned long offset, int size, const void *buf) { struct undo_private_data *data; errcode_t retval = 0; ext2_loff_t location; unsigned long blk_num, count;; EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); data = (struct undo_private_data *) channel->private_data; EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); location = offset + data->offset; blk_num = location/channel->block_size; /* * the size specified may spread across multiple blocks * also make sure we account for the fact that block start * offset for tdb is different from the backing I/O manager * due to possible different block size */ count = (size + (location % channel->block_size) + channel->block_size -1)/channel->block_size; retval = undo_write_tdb(channel, blk_num, count); if (retval) return retval; if (data->real && data->real->manager->write_byte) retval = io_channel_write_byte(data->real, offset, size, buf); return retval; } /* * Flush data buffers to disk. */ static errcode_t undo_flush(io_channel channel) { errcode_t retval = 0; struct undo_private_data *data; EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); data = (struct undo_private_data *) channel->private_data; EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); if (data->real) retval = io_channel_flush(data->real); return retval; } static errcode_t undo_set_option(io_channel channel, const char *option, const char *arg) { errcode_t retval = 0; struct undo_private_data *data; unsigned long tmp; char *end; EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); data = (struct undo_private_data *) channel->private_data; EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); if (!strcmp(option, "tdb_data_size")) { if (!arg) return EXT2_ET_INVALID_ARGUMENT; tmp = strtoul(arg, &end, 0); if (*end) return EXT2_ET_INVALID_ARGUMENT; if (!data->tdb_data_size || !data->tdb_written) { data->tdb_data_size = tmp; } return 0; } /* * Need to support offset option to work with * Unix I/O manager */ if (data->real && data->real->manager->set_option) { retval = data->real->manager->set_option(data->real, option, arg); } if (!retval && !strcmp(option, "offset")) { if (!arg) return EXT2_ET_INVALID_ARGUMENT; tmp = strtoul(arg, &end, 0); if (*end) return EXT2_ET_INVALID_ARGUMENT; data->offset = tmp; } return retval; }