/* * 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= >> /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 #include #include #include #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);