diff options
Diffstat (limited to 'kernel/power/tuxonice_file.c')
-rw-r--r-- | kernel/power/tuxonice_file.c | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/kernel/power/tuxonice_file.c b/kernel/power/tuxonice_file.c new file mode 100644 index 000000000..607246051 --- /dev/null +++ b/kernel/power/tuxonice_file.c @@ -0,0 +1,484 @@ +/* + * kernel/power/tuxonice_file.c + * + * Copyright (C) 2005-2015 Nigel Cunningham (nigel at nigelcunningham com au) + * + * Distributed under GPLv2. + * + * This file encapsulates functions for usage of a simple file as a + * backing store. It is based upon the swapallocator, and shares the + * same basic working. Here, though, we have nothing to do with + * swapspace, and only one device to worry about. + * + * The user can just + * + * echo TuxOnIce > /path/to/my_file + * + * dd if=/dev/zero bs=1M count=<file_size_desired> >> /path/to/my_file + * + * and + * + * echo /path/to/my_file > /sys/power/tuxonice/file/target + * + * then put what they find in /sys/power/tuxonice/resume + * as their resume= parameter in lilo.conf (and rerun lilo if using it). + * + * Having done this, they're ready to hibernate and resume. + * + * TODO: + * - File resizing. + */ + +#include <linux/blkdev.h> +#include <linux/mount.h> +#include <linux/fs.h> +#include <linux/fs_uuid.h> + +#include "tuxonice.h" +#include "tuxonice_modules.h" +#include "tuxonice_bio.h" +#include "tuxonice_alloc.h" +#include "tuxonice_builtin.h" +#include "tuxonice_sysfs.h" +#include "tuxonice_ui.h" +#include "tuxonice_io.h" + +#define target_is_normal_file() (S_ISREG(target_inode->i_mode)) + +static struct toi_module_ops toi_fileops; + +static struct file *target_file; +static struct block_device *toi_file_target_bdev; +static unsigned long pages_available, pages_allocated; +static char toi_file_target[256]; +static struct inode *target_inode; +static int file_target_priority; +static int used_devt; +static int target_claim; +static dev_t toi_file_dev_t; +static int sig_page_index; + +/* For test_toi_file_target */ +static struct toi_bdev_info *file_chain; + +static int has_contiguous_blocks(struct toi_bdev_info *dev_info, int page_num) +{ + int j; + sector_t last = 0; + + for (j = 0; j < dev_info->blocks_per_page; j++) { + sector_t this = bmap(target_inode, + page_num * dev_info->blocks_per_page + j); + + if (!this || (last && (last + 1) != this)) + break; + + last = this; + } + + return j == dev_info->blocks_per_page; +} + +static unsigned long get_usable_pages(struct toi_bdev_info *dev_info) +{ + unsigned long result = 0; + struct block_device *bdev = dev_info->bdev; + int i; + + switch (target_inode->i_mode & S_IFMT) { + case S_IFSOCK: + case S_IFCHR: + case S_IFIFO: /* Socket, Char, Fifo */ + return -1; + case S_IFREG: /* Regular file: current size - holes + free + space on part */ + for (i = 0; i < (target_inode->i_size >> PAGE_SHIFT) ; i++) { + if (has_contiguous_blocks(dev_info, i)) + result++; + } + break; + case S_IFBLK: /* Block device */ + if (!bdev->bd_disk) { + toi_message(TOI_IO, TOI_VERBOSE, 0, + "bdev->bd_disk null."); + return 0; + } + + result = (bdev->bd_part ? + bdev->bd_part->nr_sects : + get_capacity(bdev->bd_disk)) >> (PAGE_SHIFT - 9); + } + + + return result; +} + +static int toi_file_register_storage(void) +{ + struct toi_bdev_info *devinfo; + int result = 0; + struct fs_info *fs_info; + + toi_message(TOI_IO, TOI_VERBOSE, 0, "toi_file_register_storage."); + if (!strlen(toi_file_target)) { + toi_message(TOI_IO, TOI_VERBOSE, 0, "Register file storage: " + "No target filename set."); + return 0; + } + + target_file = filp_open(toi_file_target, O_RDONLY|O_LARGEFILE, 0); + toi_message(TOI_IO, TOI_VERBOSE, 0, "filp_open %s returned %p.", + toi_file_target, target_file); + + if (IS_ERR(target_file) || !target_file) { + target_file = NULL; + toi_file_dev_t = name_to_dev_t(toi_file_target); + if (!toi_file_dev_t) { + struct kstat stat; + int error = vfs_stat(toi_file_target, &stat); + printk(KERN_INFO "Open file %s returned %p and " + "name_to_devt failed.\n", + toi_file_target, target_file); + if (error) { + printk(KERN_INFO "Stating the file also failed." + " Nothing more we can do.\n"); + return 0; + } else + toi_file_dev_t = stat.rdev; + } + + toi_file_target_bdev = toi_open_by_devnum(toi_file_dev_t); + if (IS_ERR(toi_file_target_bdev)) { + printk(KERN_INFO "Got a dev_num (%lx) but failed to " + "open it.\n", + (unsigned long) toi_file_dev_t); + toi_file_target_bdev = NULL; + return 0; + } + used_devt = 1; + target_inode = toi_file_target_bdev->bd_inode; + } else + target_inode = target_file->f_mapping->host; + + toi_message(TOI_IO, TOI_VERBOSE, 0, "Succeeded in opening the target."); + if (S_ISLNK(target_inode->i_mode) || S_ISDIR(target_inode->i_mode) || + S_ISSOCK(target_inode->i_mode) || S_ISFIFO(target_inode->i_mode)) { + printk(KERN_INFO "File support works with regular files," + " character files and block devices.\n"); + /* Cleanup routine will undo the above */ + return 0; + } + + if (!used_devt) { + if (S_ISBLK(target_inode->i_mode)) { + toi_file_target_bdev = I_BDEV(target_inode); + if (!blkdev_get(toi_file_target_bdev, FMODE_WRITE | + FMODE_READ, NULL)) + target_claim = 1; + } else + toi_file_target_bdev = target_inode->i_sb->s_bdev; + if (!toi_file_target_bdev) { + printk(KERN_INFO "%s is not a valid file allocator " + "target.\n", toi_file_target); + return 0; + } + toi_file_dev_t = toi_file_target_bdev->bd_dev; + } + + devinfo = toi_kzalloc(39, sizeof(struct toi_bdev_info), GFP_ATOMIC); + if (!devinfo) { + printk("Failed to allocate a toi_bdev_info struct for the file allocator.\n"); + return -ENOMEM; + } + + devinfo->bdev = toi_file_target_bdev; + devinfo->allocator = &toi_fileops; + devinfo->allocator_index = 0; + + fs_info = fs_info_from_block_dev(toi_file_target_bdev); + if (fs_info && !IS_ERR(fs_info)) { + memcpy(devinfo->uuid, &fs_info->uuid, 16); + free_fs_info(fs_info); + } else + result = (int) PTR_ERR(fs_info); + + /* Unlike swap code, only complain if fs_info_from_block_dev returned + * -ENOMEM. The 'file' might be a full partition, so might validly not + * have an identifiable type, UUID etc. + */ + if (result) + printk(KERN_DEBUG "Failed to get fs_info for file device (%d).\n", + result); + devinfo->dev_t = toi_file_dev_t; + devinfo->prio = file_target_priority; + devinfo->bmap_shift = target_inode->i_blkbits - 9; + devinfo->blocks_per_page = + (1 << (PAGE_SHIFT - target_inode->i_blkbits)); + sprintf(devinfo->name, "file %s", toi_file_target); + file_chain = devinfo; + toi_message(TOI_IO, TOI_VERBOSE, 0, "Dev_t is %lx. Prio is %d. Bmap " + "shift is %d. Blocks per page %d.", + devinfo->dev_t, devinfo->prio, devinfo->bmap_shift, + devinfo->blocks_per_page); + + /* Keep one aside for the signature */ + pages_available = get_usable_pages(devinfo) - 1; + + toi_message(TOI_IO, TOI_VERBOSE, 0, "Registering file storage, %lu " + "pages.", pages_available); + + toi_bio_ops.register_storage(devinfo); + return 0; +} + +static unsigned long toi_file_storage_available(void) +{ + return pages_available; +} + +static int toi_file_allocate_storage(struct toi_bdev_info *chain, + unsigned long request) +{ + unsigned long available = pages_available - pages_allocated; + unsigned long to_add = min(available, request); + + toi_message(TOI_IO, TOI_VERBOSE, 0, "Pages available is %lu. Allocated " + "is %lu. Allocating %lu pages from file.", + pages_available, pages_allocated, to_add); + pages_allocated += to_add; + + return to_add; +} + +/** + * __populate_block_list - add an extent to the chain + * @min: Start of the extent (first physical block = sector) + * @max: End of the extent (last physical block = sector) + * + * If TOI_TEST_BIO is set, print a debug message, outputting the min and max + * fs block numbers. + **/ +static int __populate_block_list(struct toi_bdev_info *chain, int min, int max) +{ + if (test_action_state(TOI_TEST_BIO)) + toi_message(TOI_IO, TOI_VERBOSE, 0, "Adding extent %d-%d.", + min << chain->bmap_shift, + ((max + 1) << chain->bmap_shift) - 1); + + return toi_add_to_extent_chain(&chain->blocks, min, max); +} + +static int get_main_pool_phys_params(struct toi_bdev_info *chain) +{ + int i, extent_min = -1, extent_max = -1, result = 0, have_sig_page = 0; + unsigned long pages_mapped = 0; + + toi_message(TOI_IO, TOI_VERBOSE, 0, "Getting file allocator blocks."); + + if (chain->blocks.first) + toi_put_extent_chain(&chain->blocks); + + if (!target_is_normal_file()) { + result = (pages_available > 0) ? + __populate_block_list(chain, chain->blocks_per_page, + (pages_allocated + 1) * + chain->blocks_per_page - 1) : 0; + return result; + } + + /* + * FIXME: We are assuming the first page is contiguous. Is that + * assumption always right? + */ + + for (i = 0; i < (target_inode->i_size >> PAGE_SHIFT); i++) { + sector_t new_sector; + + if (!has_contiguous_blocks(chain, i)) + continue; + + if (!have_sig_page) { + have_sig_page = 1; + sig_page_index = i; + continue; + } + + pages_mapped++; + + /* Ignore first page - it has the header */ + if (pages_mapped == 1) + continue; + + new_sector = bmap(target_inode, (i * chain->blocks_per_page)); + + /* + * I'd love to be able to fill in holes and resize + * files, but not yet... + */ + + if (new_sector == extent_max + 1) + extent_max += chain->blocks_per_page; + else { + if (extent_min > -1) { + result = __populate_block_list(chain, + extent_min, extent_max); + if (result) + return result; + } + + extent_min = new_sector; + extent_max = extent_min + + chain->blocks_per_page - 1; + } + + if (pages_mapped == pages_allocated) + break; + } + + if (extent_min > -1) { + result = __populate_block_list(chain, extent_min, extent_max); + if (result) + return result; + } + + return 0; +} + +static void toi_file_free_storage(struct toi_bdev_info *chain) +{ + pages_allocated = 0; + file_chain = NULL; +} + +/** + * toi_file_print_debug_stats - print debug info + * @buffer: Buffer to data to populate + * @size: Size of the buffer + **/ +static int toi_file_print_debug_stats(char *buffer, int size) +{ + int len = scnprintf(buffer, size, "- File Allocator active.\n"); + + len += scnprintf(buffer+len, size-len, " Storage available for " + "image: %lu pages.\n", pages_available); + + return len; +} + +static void toi_file_cleanup(int finishing_cycle) +{ + if (toi_file_target_bdev) { + if (target_claim) { + blkdev_put(toi_file_target_bdev, FMODE_WRITE | FMODE_READ); + target_claim = 0; + } + + if (used_devt) { + blkdev_put(toi_file_target_bdev, + FMODE_READ | FMODE_NDELAY); + used_devt = 0; + } + toi_file_target_bdev = NULL; + target_inode = NULL; + } + + if (target_file) { + filp_close(target_file, NULL); + target_file = NULL; + } + + pages_available = 0; +} + +/** + * test_toi_file_target - sysfs callback for /sys/power/tuxonince/file/target + * + * Test wheter the target file is valid for hibernating. + **/ +static void test_toi_file_target(void) +{ + int result = toi_file_register_storage(); + sector_t sector; + char buf[50]; + struct fs_info *fs_info; + + if (result || !file_chain) + return; + + /* This doesn't mean we're in business. Is any storage available? */ + if (!pages_available) + goto out; + + toi_file_allocate_storage(file_chain, 1); + result = get_main_pool_phys_params(file_chain); + if (result) + goto out; + + + sector = bmap(target_inode, sig_page_index * + file_chain->blocks_per_page) << file_chain->bmap_shift; + + /* Use the uuid, or the dev_t if that fails */ + fs_info = fs_info_from_block_dev(toi_file_target_bdev); + if (!fs_info || IS_ERR(fs_info)) { + bdevname(toi_file_target_bdev, buf); + sprintf(resume_file, "/dev/%s:%llu", buf, + (unsigned long long) sector); + } else { + int i; + hex_dump_to_buffer(fs_info->uuid, 16, 32, 1, buf, 50, 0); + + /* Remove the spaces */ + for (i = 1; i < 16; i++) { + buf[2 * i] = buf[3 * i]; + buf[2 * i + 1] = buf[3 * i + 1]; + } + buf[32] = 0; + sprintf(resume_file, "UUID=%s:0x%llx", buf, + (unsigned long long) sector); + free_fs_info(fs_info); + } + + toi_attempt_to_parse_resume_device(0); +out: + toi_file_free_storage(file_chain); + toi_bio_ops.free_storage(); +} + +static struct toi_sysfs_data sysfs_params[] = { + SYSFS_STRING("target", SYSFS_RW, toi_file_target, 256, + SYSFS_NEEDS_SM_FOR_WRITE, test_toi_file_target), + SYSFS_INT("enabled", SYSFS_RW, &toi_fileops.enabled, 0, 1, 0, NULL), + SYSFS_INT("priority", SYSFS_RW, &file_target_priority, -4095, + 4096, 0, NULL), +}; + +static struct toi_bio_allocator_ops toi_bio_fileops = { + .register_storage = toi_file_register_storage, + .storage_available = toi_file_storage_available, + .allocate_storage = toi_file_allocate_storage, + .bmap = get_main_pool_phys_params, + .free_storage = toi_file_free_storage, +}; + +static struct toi_module_ops toi_fileops = { + .type = BIO_ALLOCATOR_MODULE, + .name = "file storage", + .directory = "file", + .module = THIS_MODULE, + .print_debug_info = toi_file_print_debug_stats, + .cleanup = toi_file_cleanup, + .bio_allocator_ops = &toi_bio_fileops, + + .sysfs_data = sysfs_params, + .num_sysfs_entries = sizeof(sysfs_params) / + sizeof(struct toi_sysfs_data), +}; + +/* ---- Registration ---- */ +static __init int toi_file_load(void) +{ + return toi_register_module(&toi_fileops); +} + +late_initcall(toi_file_load); |