481 lines
13 KiB
C
481 lines
13 KiB
C
/*
|
|
* 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
|