| /* |
| * kernel/power/tuxonice_file.c |
| * |
| * Copyright (C) 2005-2010 Nigel Cunningham (nigel at tuxonice net) |
| * |
| * 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); |
| } |
| |
| #ifdef MODULE |
| static __exit void toi_file_unload(void) |
| { |
| toi_unregister_module(&toi_fileops); |
| } |
| module_init(toi_file_load); |
| module_exit(toi_file_unload); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Nigel Cunningham"); |
| MODULE_DESCRIPTION("TuxOnIce FileAllocator"); |
| #else |
| late_initcall(toi_file_load); |
| #endif |