[PATCH 13/19] TuxOnIce: File allocator.
From: Nigel Cunningham
Date: Wed May 06 2009 - 10:56:03 EST
Support for writing an image to a single (non-swap) file using the block
I/O backend. The main advantage over swap support is that it works
irrespective of how much swap is in use.
Signed-off-by: Nigel Cunningham <nigel@xxxxxxxxxxxx>
---
kernel/power/Kconfig | 9 +
kernel/power/Makefile | 2 +
kernel/power/tuxonice_file.c | 1235 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1246 insertions(+), 0 deletions(-)
create mode 100644 kernel/power/tuxonice_file.c
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 1b474a6..51d57d4 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -192,6 +192,15 @@ menuconfig TOI_CORE
comment "Image Storage (you need at least one allocator)"
depends on TOI_CORE
+ config TOI_FILE
+ bool "File Allocator"
+ depends on TOI_CORE
+ default y
+ ---help---
+ This option enables support for storing an image in a
+ simple file. This should be possible, but we're still
+ testing it.
+
comment "General Options"
depends on TOI_CORE
diff --git a/kernel/power/Makefile b/kernel/power/Makefile
index 3803866..41daada 100644
--- a/kernel/power/Makefile
+++ b/kernel/power/Makefile
@@ -17,6 +17,8 @@ endif
obj-$(CONFIG_TOI_CORE) += tuxonice_core.o
obj-$(CONFIG_TOI_CRYPTO) += tuxonice_compress.o
+obj-$(CONFIG_TOI_FILE) += tuxonice_block_io.o tuxonice_file.o
+
obj-$(CONFIG_PM) += main.o
obj-$(CONFIG_PM_SLEEP) += console.o
obj-$(CONFIG_FREEZER) += process.o
diff --git a/kernel/power/tuxonice_file.c b/kernel/power/tuxonice_file.c
new file mode 100644
index 0000000..5ceb20a
--- /dev/null
+++ b/kernel/power/tuxonice_file.c
@@ -0,0 +1,1235 @@
+/*
+ * kernel/power/tuxonice_file.c
+ *
+ * Copyright (C) 2005-2008 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/suspend.h>
+#include <linux/blkdev.h>
+#include <linux/file.h>
+#include <linux/stat.h>
+#include <linux/mount.h>
+#include <linux/statfs.h>
+#include <linux/syscalls.h>
+#include <linux/namei.h>
+#include <linux/fs.h>
+#include <linux/root_dev.h>
+
+#include "tuxonice.h"
+#include "tuxonice_sysfs.h"
+#include "tuxonice_modules.h"
+#include "tuxonice_ui.h"
+#include "tuxonice_extent.h"
+#include "tuxonice_io.h"
+#include "tuxonice_storage.h"
+#include "tuxonice_block_io.h"
+#include "tuxonice_alloc.h"
+#include "tuxonice_builtin.h"
+
+static struct toi_module_ops toi_fileops;
+
+/* Details of our target. */
+
+static char toi_file_target[256];
+static struct inode *target_inode;
+static struct file *target_file;
+static struct block_device *toi_file_target_bdev;
+static dev_t resume_file_dev_t;
+static int used_devt;
+static int setting_toi_file_target;
+static sector_t target_firstblock, target_header_start;
+static int target_storage_available;
+static int target_claim;
+
+/* Old signatures */
+static char HaveImage[] = "HaveImage\n";
+static char NoImage[] = "TuxOnIce\n";
+#define sig_size (sizeof(HaveImage) + 1)
+
+struct toi_file_header {
+ char sig[sig_size];
+ int resumed_before;
+ unsigned long first_header_block;
+ int have_image;
+};
+
+/* Header Page Information */
+static int header_pages_reserved;
+
+/* Main Storage Pages */
+static int main_pages_allocated, main_pages_requested;
+
+#define target_is_normal_file() (S_ISREG(target_inode->i_mode))
+
+static struct toi_bdev_info devinfo;
+
+/* Extent chain for blocks */
+static struct hibernate_extent_chain block_chain;
+
+/* Signature operations */
+enum {
+ GET_IMAGE_EXISTS,
+ INVALIDATE,
+ MARK_RESUME_ATTEMPTED,
+ UNMARK_RESUME_ATTEMPTED,
+};
+
+/**
+ * set_devinfo - populate device information
+ * @bdev: Block device on which the file is.
+ * @target_blkbits: Number of bits in the page block size of the target
+ * file inode.
+ *
+ * Populate the devinfo structure about the target device.
+ *
+ * Background: a sector represents a fixed amount of data (generally 512 bytes).
+ * The hard drive sector size and the filesystem block size may be different.
+ * If fs_blksize mesures the filesystem block size and hd_blksize the hard drive
+ * sector size:
+ *
+ * sector << (fs_blksize - hd_blksize) converts hd sector into fs block
+ * fs_block >> (fs_blksize - hd_blksize) converts fs block into hd sector number
+ *
+ * Here target_blkbits == fs_blksize and hd_blksize == 9, hence:
+ *
+ * (fs_blksize - hd_blksize) == devinfo.bmap_shift
+ *
+ * The memory page size is defined by PAGE_SHIFT. devinfo.blocks_per_page is the
+ * number of filesystem blocks per memory page.
+ *
+ * Note that blocks are stored after >>. They are used after being <<.
+ * We always only use PAGE_SIZE aligned blocks.
+ *
+ * Side effects:
+ * devinfo.bdev, devinfo.bmap_shift and devinfo.blocks_per_page are set.
+ */
+static void set_devinfo(struct block_device *bdev, int target_blkbits)
+{
+ devinfo.bdev = bdev;
+ if (!target_blkbits) {
+ devinfo.bmap_shift = 0;
+ devinfo.blocks_per_page = 0;
+ } else {
+ /* We are assuming a hard disk with 512 (2^9) bytes/sector */
+ devinfo.bmap_shift = target_blkbits - 9;
+ devinfo.blocks_per_page = (1 << (PAGE_SHIFT - target_blkbits));
+ }
+}
+
+static long raw_to_real(long raw)
+{
+ long result;
+
+ result = raw - (raw * (sizeof(unsigned long) + sizeof(int)) +
+ (PAGE_SIZE + sizeof(unsigned long) + sizeof(int) + 1)) /
+ (PAGE_SIZE + sizeof(unsigned long) + sizeof(int));
+
+ return result < 0 ? 0 : result;
+}
+
+static int toi_file_storage_available(void)
+{
+ int result = 0;
+ struct block_device *bdev = toi_file_target_bdev;
+
+ if (!target_inode)
+ return 0;
+
+ 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 */
+ result = target_storage_available;
+ break;
+ case S_IFBLK: /* Block device */
+ if (!bdev->bd_disk) {
+ printk(KERN_INFO "bdev->bd_disk null.\n");
+ return 0;
+ }
+
+ result = (bdev->bd_part ?
+ bdev->bd_part->nr_sects :
+ get_capacity(bdev->bd_disk)) >> (PAGE_SHIFT - 9);
+ }
+
+ return raw_to_real(result);
+}
+
+static int has_contiguous_blocks(int page_num)
+{
+ int j;
+ sector_t last = 0;
+
+ for (j = 0; j < devinfo.blocks_per_page; j++) {
+ sector_t this = bmap(target_inode,
+ page_num * devinfo.blocks_per_page + j);
+
+ if (!this || (last && (last + 1) != this))
+ break;
+
+ last = this;
+ }
+
+ return j == devinfo.blocks_per_page;
+}
+
+static int size_ignoring_ignored_pages(void)
+{
+ int mappable = 0, i;
+
+ if (!target_is_normal_file())
+ return toi_file_storage_available();
+
+ for (i = 0; i < (target_inode->i_size >> PAGE_SHIFT) ; i++)
+ if (has_contiguous_blocks(i))
+ mappable++;
+
+ return mappable;
+}
+
+/**
+ * __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(int min, int max)
+{
+ if (test_action_state(TOI_TEST_BIO))
+ printk(KERN_INFO "Adding extent %d-%d.\n",
+ min << devinfo.bmap_shift,
+ ((max + 1) << devinfo.bmap_shift) - 1);
+
+ return toi_add_to_extent_chain(&block_chain, min, max);
+}
+
+static int apply_header_reservation(void)
+{
+ int i;
+
+ /* Apply header space reservation */
+ toi_extent_state_goto_start(&toi_writer_posn);
+
+ for (i = 0; i < header_pages_reserved; i++)
+ if (toi_bio_ops.forward_one_page(1, 0))
+ return -ENOSPC;
+
+ /* The end of header pages will be the start of pageset 2 */
+ toi_extent_state_save(&toi_writer_posn, &toi_writer_posn_save[2]);
+
+ return 0;
+}
+
+static int populate_block_list(void)
+{
+ int i, extent_min = -1, extent_max = -1, got_header = 0, result = 0;
+
+ if (block_chain.first)
+ toi_put_extent_chain(&block_chain);
+
+ if (!target_is_normal_file()) {
+ result = (target_storage_available > 0) ?
+ __populate_block_list(devinfo.blocks_per_page,
+ (target_storage_available + 1) *
+ devinfo.blocks_per_page - 1) : 0;
+ if (result)
+ return result;
+ goto out;
+ }
+
+ for (i = 0; i < (target_inode->i_size >> PAGE_SHIFT); i++) {
+ sector_t new_sector;
+
+ if (!has_contiguous_blocks(i))
+ continue;
+
+ new_sector = bmap(target_inode, (i * devinfo.blocks_per_page));
+
+ /*
+ * Ignore the first block in the file.
+ * It gets the header.
+ */
+ if (new_sector == target_firstblock >> devinfo.bmap_shift) {
+ got_header = 1;
+ continue;
+ }
+
+ /*
+ * I'd love to be able to fill in holes and resize
+ * files, but not yet...
+ */
+
+ if (new_sector == extent_max + 1)
+ extent_max += devinfo.blocks_per_page;
+ else {
+ if (extent_min > -1) {
+ result = __populate_block_list(extent_min,
+ extent_max);
+ if (result)
+ return result;
+ }
+
+ extent_min = new_sector;
+ extent_max = extent_min +
+ devinfo.blocks_per_page - 1;
+ }
+ }
+
+ if (extent_min > -1) {
+ result = __populate_block_list(extent_min, extent_max);
+ if (result)
+ return result;
+ }
+
+out:
+ return apply_header_reservation();
+}
+
+static void toi_file_cleanup(int finishing_cycle)
+{
+ if (toi_file_target_bdev) {
+ if (target_claim) {
+ bd_release(toi_file_target_bdev);
+ 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;
+ set_devinfo(NULL, 0);
+ target_storage_available = 0;
+ }
+
+ if (target_file && !IS_ERR(target_file))
+ filp_close(target_file, NULL);
+
+ target_file = NULL;
+}
+
+/**
+ * reopen_resume_devt - reset the devinfo struct
+ *
+ * Having opened resume= once, we remember the major and
+ * minor nodes and use them to reopen the bdev for checking
+ * whether an image exists (possibly when starting a resume).
+ **/
+static void reopen_resume_devt(void)
+{
+ toi_file_target_bdev = toi_open_by_devnum(resume_file_dev_t,
+ FMODE_READ | FMODE_NDELAY);
+ if (IS_ERR(toi_file_target_bdev)) {
+ printk(KERN_INFO "Got a dev_num (%lx) but failed to open it.\n",
+ (unsigned long) resume_file_dev_t);
+ return;
+ }
+ target_inode = toi_file_target_bdev->bd_inode;
+ set_devinfo(toi_file_target_bdev, target_inode->i_blkbits);
+}
+
+static void toi_file_get_target_info(char *target, int get_size,
+ int resume_param)
+{
+ if (target_file)
+ toi_file_cleanup(0);
+
+ if (!target || !strlen(target))
+ return;
+
+ target_file = filp_open(target, O_RDONLY|O_LARGEFILE, 0);
+
+ if (IS_ERR(target_file) || !target_file) {
+
+ if (!resume_param) {
+ printk(KERN_INFO "Open file %s returned %p.\n",
+ target, target_file);
+ target_file = NULL;
+ return;
+ }
+
+ target_file = NULL;
+ wait_for_device_probe();
+ resume_file_dev_t = name_to_dev_t(target);
+ if (!resume_file_dev_t) {
+ struct kstat stat;
+ int error = vfs_stat(target, &stat);
+ printk(KERN_INFO "Open file %s returned %p and "
+ "name_to_devt failed.\n", target,
+ target_file);
+ if (error)
+ printk(KERN_INFO "Stating the file also failed."
+ " Nothing more we can do.\n");
+ else
+ resume_file_dev_t = stat.rdev;
+ return;
+ }
+
+ toi_file_target_bdev = toi_open_by_devnum(resume_file_dev_t,
+ FMODE_READ | FMODE_NDELAY);
+ if (IS_ERR(toi_file_target_bdev)) {
+ printk(KERN_INFO "Got a dev_num (%lx) but failed to "
+ "open it.\n",
+ (unsigned long) resume_file_dev_t);
+ return;
+ }
+ used_devt = 1;
+ target_inode = toi_file_target_bdev->bd_inode;
+ } else
+ target_inode = target_file->f_mapping->host;
+
+ 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");
+ goto cleanup;
+ }
+
+ if (!used_devt) {
+ if (S_ISBLK(target_inode->i_mode)) {
+ toi_file_target_bdev = I_BDEV(target_inode);
+ if (!bd_claim(toi_file_target_bdev, &toi_fileops))
+ target_claim = 1;
+ } else
+ toi_file_target_bdev = target_inode->i_sb->s_bdev;
+ resume_file_dev_t = toi_file_target_bdev->bd_dev;
+ }
+
+ set_devinfo(toi_file_target_bdev, target_inode->i_blkbits);
+
+ if (get_size)
+ target_storage_available = size_ignoring_ignored_pages();
+
+ if (!resume_param)
+ target_firstblock = bmap(target_inode, 0) << devinfo.bmap_shift;
+
+ return;
+cleanup:
+ target_inode = NULL;
+ if (target_file) {
+ filp_close(target_file, NULL);
+ target_file = NULL;
+ }
+ set_devinfo(NULL, 0);
+ target_storage_available = 0;
+}
+
+static void toi_file_noresume_reset(void)
+{
+ toi_bio_ops.rw_cleanup(READ);
+}
+
+/**
+ * parse_signature - check if the file is suitable for resuming
+ * @header: Signature of the file
+ *
+ * Given a file header, check the content of the file. Return true if it
+ * contains a valid hibernate image.
+ * TOI_RESUMED_BEFORE is set accordingly.
+ **/
+static int parse_signature(struct toi_file_header *header)
+{
+ int have_image = !memcmp(HaveImage, header->sig, sizeof(HaveImage) - 1);
+ int no_image_header = !memcmp(NoImage, header->sig,
+ sizeof(NoImage) - 1);
+ int binary_sig = !memcmp(tuxonice_signature, header->sig,
+ sizeof(tuxonice_signature));
+
+ if (no_image_header || (binary_sig && !header->have_image))
+ return 0;
+
+ if (!have_image && !binary_sig)
+ return -1;
+
+ if (header->resumed_before)
+ set_toi_state(TOI_RESUMED_BEFORE);
+ else
+ clear_toi_state(TOI_RESUMED_BEFORE);
+
+ target_header_start = header->first_header_block;
+ return 1;
+}
+
+/**
+ * prepare_signature - populate the signature structure
+ * @current_header: Signature structure to populate
+ * @first_header_block: Sector with the header containing the extents
+ **/
+static int prepare_signature(struct toi_file_header *current_header,
+ unsigned long first_header_block)
+{
+ memcpy(current_header->sig, tuxonice_signature,
+ sizeof(tuxonice_signature));
+ current_header->resumed_before = 0;
+ current_header->first_header_block = first_header_block;
+ current_header->have_image = 1;
+ return 0;
+}
+
+static int toi_file_storage_allocated(void)
+{
+ if (!target_inode)
+ return 0;
+
+ if (target_is_normal_file())
+ return (int) raw_to_real(target_storage_available);
+ else
+ return (int) raw_to_real(main_pages_requested);
+}
+
+/**
+ * toi_file_release_storage - deallocate the block chain
+ **/
+static int toi_file_release_storage(void)
+{
+ toi_put_extent_chain(&block_chain);
+
+ header_pages_reserved = 0;
+ main_pages_allocated = 0;
+ main_pages_requested = 0;
+ return 0;
+}
+
+static void toi_file_reserve_header_space(int request)
+{
+ header_pages_reserved = request;
+}
+
+static int toi_file_allocate_storage(int main_space_requested)
+{
+ int result = 0;
+
+ int extra_pages = DIV_ROUND_UP(main_space_requested *
+ (sizeof(unsigned long) + sizeof(int)), PAGE_SIZE);
+ int pages_to_get = main_space_requested + extra_pages +
+ header_pages_reserved;
+ int blocks_to_get = pages_to_get - block_chain.size;
+
+ /* Only release_storage reduces the size */
+ if (blocks_to_get < 1)
+ return apply_header_reservation();
+
+ result = populate_block_list();
+
+ if (result)
+ return result;
+
+ toi_message(TOI_WRITER, TOI_MEDIUM, 0,
+ "Finished with block_chain.size == %d.\n",
+ block_chain.size);
+
+ if (block_chain.size < pages_to_get) {
+ printk(KERN_INFO "Block chain size (%d) < header pages (%d) + "
+ "extra pages (%d) + main pages (%d) (=%d "
+ "pages).\n",
+ block_chain.size, header_pages_reserved,
+ extra_pages, main_space_requested,
+ pages_to_get);
+ result = -ENOSPC;
+ }
+
+ main_pages_requested = main_space_requested;
+ main_pages_allocated = main_space_requested + extra_pages;
+ return result;
+}
+
+/**
+ * toi_file_write_header_init - save the header on the image
+ **/
+static int toi_file_write_header_init(void)
+{
+ int result;
+
+ toi_bio_ops.rw_init(WRITE, 0);
+ toi_writer_buffer_posn = 0;
+
+ /* Info needed to bootstrap goes at the start of the header.
+ * First we save the basic info needed for reading, including the number
+ * of header pages. Then we save the structs containing data needed
+ * for reading the header pages back.
+ * Note that even if header pages take more than one page, when we
+ * read back the info, we will have restored the location of the
+ * next header page by the time we go to use it.
+ */
+
+ result = toi_bio_ops.rw_header_chunk(WRITE, &toi_fileops,
+ (char *) &toi_writer_posn_save,
+ sizeof(toi_writer_posn_save));
+
+ if (result)
+ return result;
+
+ result = toi_bio_ops.rw_header_chunk(WRITE, &toi_fileops,
+ (char *) &devinfo, sizeof(devinfo));
+
+ if (result)
+ return result;
+
+ /* Flush the chain */
+ toi_serialise_extent_chain(&toi_fileops, &block_chain);
+
+ return 0;
+}
+
+static int toi_file_write_header_cleanup(void)
+{
+ struct toi_file_header *header;
+ int result, result2;
+ unsigned long sig_page = toi_get_zeroed_page(38, TOI_ATOMIC_GFP);
+
+ /* Write any unsaved data */
+ result = toi_bio_ops.write_header_chunk_finish();
+
+ if (result)
+ goto out;
+
+ toi_extent_state_goto_start(&toi_writer_posn);
+ toi_bio_ops.forward_one_page(1, 1);
+
+ /* Adjust image header */
+ result = toi_bio_ops.bdev_page_io(READ, toi_file_target_bdev,
+ target_firstblock,
+ virt_to_page(sig_page));
+ if (result)
+ goto out;
+
+ header = (struct toi_file_header *) sig_page;
+
+ prepare_signature(header,
+ toi_writer_posn.current_offset <<
+ devinfo.bmap_shift);
+
+ result = toi_bio_ops.bdev_page_io(WRITE, toi_file_target_bdev,
+ target_firstblock,
+ virt_to_page(sig_page));
+
+out:
+ result2 = toi_bio_ops.finish_all_io();
+ toi_free_page(38, sig_page);
+
+ return result ? result : result2;
+}
+
+/* HEADER READING */
+
+/**
+ * toi_file_read_header_init - check content of signature
+ *
+ * Entry point of the resume path.
+ * 1. Attempt to read the device specified with resume=.
+ * 2. Check the contents of the header for our signature.
+ * 3. Warn, ignore, reset and/or continue as appropriate.
+ * 4. If continuing, read the toi_file configuration section
+ * of the header and set up block device info so we can read
+ * the rest of the header & image.
+ *
+ * Returns:
+ * May not return if user choose to reboot at a warning.
+ * -EINVAL if cannot resume at this time. Booting should continue
+ * normally.
+ **/
+static int toi_file_read_header_init(void)
+{
+ int result;
+ struct block_device *tmp;
+
+ /* Allocate toi_writer_buffer */
+ toi_bio_ops.read_header_init();
+
+ /*
+ * Read toi_file configuration (header containing metadata).
+ * target_header_start is the first sector of the header. It has been
+ * set when checking if the file was suitable for resuming, see
+ * do_toi_step(STEP_RESUME_CAN_RESUME).
+ */
+ result = toi_bio_ops.bdev_page_io(READ, toi_file_target_bdev,
+ target_header_start,
+ virt_to_page((unsigned long) toi_writer_buffer));
+
+ if (result) {
+ printk(KERN_ERR "FileAllocator read header init: Failed to "
+ "initialise reading the first page of data.\n");
+ toi_bio_ops.rw_cleanup(READ);
+ return result;
+ }
+
+ /* toi_writer_posn_save[0] contains the header */
+ memcpy(&toi_writer_posn_save, toi_writer_buffer,
+ sizeof(toi_writer_posn_save));
+
+ /* Save the position in the buffer */
+ toi_writer_buffer_posn = sizeof(toi_writer_posn_save);
+
+ tmp = devinfo.bdev;
+
+ /* See tuxonice_block_io.h */
+ memcpy(&devinfo,
+ toi_writer_buffer + toi_writer_buffer_posn,
+ sizeof(devinfo));
+
+ devinfo.bdev = tmp;
+ toi_writer_buffer_posn += sizeof(devinfo);
+
+ /* Reinitialize the extent pointer */
+ toi_extent_state_goto_start(&toi_writer_posn);
+ /* Jump to the next page */
+ toi_bio_ops.set_extra_page_forward();
+
+ /* Bring back the chain from disk: this will read
+ * all extents.
+ */
+ return toi_load_extent_chain(&block_chain);
+}
+
+static int toi_file_read_header_cleanup(void)
+{
+ toi_bio_ops.rw_cleanup(READ);
+ return 0;
+}
+
+/**
+ * toi_file_signature_op - perform an operation on the file signature
+ * @op: operation to perform
+ *
+ * op is either GET_IMAGE_EXISTS, INVALIDATE, MARK_RESUME_ATTEMPTED or
+ * UNMARK_RESUME_ATTEMPTED.
+ * If the signature is changed, an I/O operation is performed.
+ * The signature exists iff toi_file_signature_op(GET_IMAGE_EXISTS)>-1.
+ **/
+static int toi_file_signature_op(int op)
+{
+ char *cur;
+ int result = 0, result2, changed = 0;
+ struct toi_file_header *header;
+
+ if (!toi_file_target_bdev || IS_ERR(toi_file_target_bdev))
+ return -1;
+
+ cur = (char *) toi_get_zeroed_page(17, TOI_ATOMIC_GFP);
+ if (!cur) {
+ printk(KERN_INFO "Unable to allocate a page for reading the "
+ "image signature.\n");
+ return -ENOMEM;
+ }
+
+ result = toi_bio_ops.bdev_page_io(READ, toi_file_target_bdev,
+ target_firstblock,
+ virt_to_page(cur));
+
+ if (result)
+ goto out;
+
+ header = (struct toi_file_header *) cur;
+ result = parse_signature(header);
+
+ switch (op) {
+ case INVALIDATE:
+ if (result == -1)
+ goto out;
+
+ memcpy(header->sig, tuxonice_signature,
+ sizeof(tuxonice_signature));
+ header->resumed_before = 0;
+ header->have_image = 0;
+ result = 1;
+ changed = 1;
+ break;
+ case MARK_RESUME_ATTEMPTED:
+ if (result == 1) {
+ header->resumed_before = 1;
+ changed = 1;
+ }
+ break;
+ case UNMARK_RESUME_ATTEMPTED:
+ if (result == 1) {
+ header->resumed_before = 0;
+ changed = 1;
+ }
+ break;
+ }
+
+ if (changed) {
+ int io_result = toi_bio_ops.bdev_page_io(WRITE,
+ toi_file_target_bdev, target_firstblock,
+ virt_to_page(cur));
+ if (io_result)
+ result = io_result;
+ }
+
+out:
+ result2 = toi_bio_ops.finish_all_io();
+ toi_free_page(17, (unsigned long) cur);
+ return result ? result : result2;
+}
+
+/**
+ * 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 = 0;
+
+ if (toiActiveAllocator != &toi_fileops) {
+ len = scnprintf(buffer, size,
+ "- FileAllocator inactive.\n");
+ return len;
+ }
+
+ len = scnprintf(buffer, size, "- FileAllocator active.\n");
+
+ len += scnprintf(buffer+len, size-len, " Storage available for "
+ "image: %d pages.\n",
+ toi_file_storage_allocated());
+
+ return len;
+}
+
+/**
+ * toi_file_storage_needed - storage needed
+ *
+ * Returns amount of space in the image header required
+ * for the toi_file's data.
+ *
+ * We ensure the space is allocated, but actually save the
+ * data from write_header_init and therefore don't also define a
+ * save_config_info routine.
+ **/
+static int toi_file_storage_needed(void)
+{
+ return strlen(toi_file_target) + 1 +
+ sizeof(toi_writer_posn_save) +
+ sizeof(devinfo) +
+ 2 * sizeof(int) +
+ (2 * sizeof(unsigned long) * block_chain.num_extents);
+}
+
+/**
+ * toi_file_remove_image - invalidate the image
+ **/
+static int toi_file_remove_image(void)
+{
+ toi_file_release_storage();
+ return toi_file_signature_op(INVALIDATE);
+}
+
+/**
+ * toi_file_image_exists - test if an image exists
+ *
+ * Repopulate toi_file_target_bdev if needed.
+ **/
+static int toi_file_image_exists(int quiet)
+{
+ if (!toi_file_target_bdev)
+ reopen_resume_devt();
+ return toi_file_signature_op(GET_IMAGE_EXISTS);
+}
+
+/**
+ * toi_file_mark_resume_attempted - mark resume attempted if so
+ * @mark: attempted flag
+ *
+ * Record that we tried to resume from this image. Resuming
+ * multiple times from the same image may be dangerous
+ * (possible filesystem corruption).
+ **/
+static int toi_file_mark_resume_attempted(int mark)
+{
+ return toi_file_signature_op(mark ? MARK_RESUME_ATTEMPTED :
+ UNMARK_RESUME_ATTEMPTED);
+}
+
+/**
+ * toi_file_set_resume_param - validate the specified resume file
+ *
+ * Given a target filename, populate the resume parameter. This is
+ * meant to be used by the user to populate the kernel command line.
+ * By setting /sys/power/tuxonice/file/target, the valid resume
+ * parameter to use is set and accessible through
+ * /sys/power/tuxonice/resume.
+ *
+ * If the file could be located, we check if it contains a valid
+ * signature.
+ **/
+static void toi_file_set_resume_param(void)
+{
+ char *buffer = (char *) toi_get_zeroed_page(18, TOI_ATOMIC_GFP);
+ char *buffer2 = (char *) toi_get_zeroed_page(19, TOI_ATOMIC_GFP);
+ unsigned long sector = bmap(target_inode, 0);
+ int offset = 0;
+
+ if (!buffer || !buffer2) {
+ if (buffer)
+ toi_free_page(18, (unsigned long) buffer);
+ if (buffer2)
+ toi_free_page(19, (unsigned long) buffer2);
+ printk(KERN_ERR "TuxOnIce: Failed to allocate memory while "
+ "setting resume= parameter.\n");
+ return;
+ }
+
+ if (toi_file_target_bdev) {
+ set_devinfo(toi_file_target_bdev, target_inode->i_blkbits);
+
+ bdevname(toi_file_target_bdev, buffer2);
+ offset += snprintf(buffer + offset, PAGE_SIZE - offset,
+ "/dev/%s", buffer2);
+
+ if (sector)
+ /* The offset is: sector << (inode->i_blkbits - 9) */
+ offset += snprintf(buffer + offset, PAGE_SIZE - offset,
+ ":0x%lx", sector << devinfo.bmap_shift);
+ } else
+ offset += snprintf(buffer + offset, PAGE_SIZE - offset,
+ "%s is not a valid target.", toi_file_target);
+
+ sprintf(resume_file, "file:%s", buffer);
+
+ toi_free_page(18, (unsigned long) buffer);
+ toi_free_page(19, (unsigned long) buffer2);
+
+ toi_attempt_to_parse_resume_device(1);
+}
+
+/**
+ * __test_toi_file_target - is the file target valid for hibernating?
+ * @target: target file
+ * @resume_param: whether resume= has been specified
+ * @quiet: quiet flag
+ *
+ * Test whether the file target can be used for hibernating: valid target
+ * and signature.
+ * The resume parameter is set if needed.
+ **/
+static int __test_toi_file_target(char *target, int resume_param, int quiet)
+{
+ toi_file_get_target_info(target, 0, resume_param);
+ if (toi_file_signature_op(GET_IMAGE_EXISTS) > -1) {
+ if (!quiet)
+ printk(KERN_INFO "TuxOnIce: FileAllocator: File "
+ "signature found.\n");
+ if (!resume_param)
+ toi_file_set_resume_param();
+
+ toi_bio_ops.set_devinfo(&devinfo);
+ toi_writer_posn.chains = &block_chain;
+ toi_writer_posn.num_chains = 1;
+
+ if (!resume_param)
+ set_toi_state(TOI_CAN_HIBERNATE);
+ return 0;
+ }
+
+ /*
+ * Target unaccessible or no signature found
+ * Most errors have already been reported
+ */
+
+ clear_toi_state(TOI_CAN_HIBERNATE);
+
+ if (quiet)
+ return 1;
+
+ if (*target)
+ printk(KERN_INFO "TuxOnIce: FileAllocator: Sorry. No signature "
+ "found at %s.\n", target);
+ else
+ if (!resume_param)
+ printk(KERN_INFO "TuxOnIce: FileAllocator: Sorry. "
+ "Target is not set for hibernating.\n");
+
+ return 1;
+}
+
+/**
+ * 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)
+{
+ setting_toi_file_target = 1;
+
+ printk(KERN_INFO "TuxOnIce: Hibernating %sabled.\n",
+ __test_toi_file_target(toi_file_target, 0, 1) ?
+ "dis" : "en");
+
+ setting_toi_file_target = 0;
+}
+
+/**
+ * toi_file_parse_sig_location - parse image Location
+ * @commandline: the resume parameter
+ * @only_writer: ??
+ * @quiet: quiet flag
+ *
+ * Attempt to parse a resume= parameter.
+ * File Allocator accepts:
+ * resume=file:DEVNAME[:FIRSTBLOCK]
+ *
+ * Where:
+ * DEVNAME is convertable to a dev_t by name_to_dev_t
+ * FIRSTBLOCK is the location of the first block in the file.
+ * BLOCKSIZE is the logical blocksize >= SECTOR_SIZE &
+ * <= PAGE_SIZE,
+ * mod SECTOR_SIZE == 0 of the device.
+ *
+ * Data is validated by attempting to read a header from the
+ * location given. Failure will result in toi_file refusing to
+ * save an image, and a reboot with correct parameters will be
+ * necessary.
+ **/
+static int toi_file_parse_sig_location(char *commandline,
+ int only_writer, int quiet)
+{
+ char *thischar, *devstart = NULL, *colon = NULL, *at_symbol = NULL;
+ int result = -EINVAL, target_blocksize = 0;
+
+ if (strncmp(commandline, "file:", 5)) {
+ if (!only_writer)
+ return 1;
+ } else
+ commandline += 5;
+
+ /*
+ * Don't check signature again if we're beginning a cycle. If we already
+ * did the initialisation successfully, assume we'll be okay when it
+ * comes to resuming.
+ */
+ if (toi_file_target_bdev)
+ return 0;
+
+ devstart = commandline;
+ thischar = commandline;
+ while ((*thischar != ':') && (*thischar != '@') &&
+ ((thischar - commandline) < 250) && (*thischar))
+ thischar++;
+
+ if (*thischar == ':') {
+ colon = thischar;
+ *colon = 0;
+ thischar++;
+ }
+
+ while ((*thischar != '@') && ((thischar - commandline) < 250)
+ && (*thischar))
+ thischar++;
+
+ if (*thischar == '@') {
+ at_symbol = thischar;
+ *at_symbol = 0;
+ }
+
+ /*
+ * For the toi_file, you can be able to resume, but not hibernate,
+ * because the resume= is set correctly, but the toi_file_target
+ * isn't.
+ *
+ * We may have come here as a result of setting resume or
+ * toi_file_target. We only test the toi_file target in the
+ * former case (it's already done in the later), and we do it before
+ * setting the block number ourselves. It will overwrite the values
+ * given on the command line if we don't.
+ */
+
+ if (!setting_toi_file_target) /* Concurrent write via /sys? */
+ __test_toi_file_target(toi_file_target, 1, 0);
+
+ if (colon) {
+ unsigned long block;
+ result = strict_strtoul(colon + 1, 0, &block);
+ if (result)
+ goto out;
+ target_firstblock = (int) block;
+ } else
+ target_firstblock = 0;
+
+ if (at_symbol) {
+ unsigned long block_size;
+ result = strict_strtoul(at_symbol + 1, 0, &block_size);
+ if (result)
+ goto out;
+ target_blocksize = (int) block_size;
+ if (target_blocksize & (SECTOR_SIZE - 1)) {
+ printk(KERN_INFO "FileAllocator: Blocksizes are "
+ "multiples of %d.\n", SECTOR_SIZE);
+ result = -EINVAL;
+ goto out;
+ }
+ }
+
+ if (!quiet)
+ printk(KERN_INFO "TuxOnIce FileAllocator: Testing whether you "
+ "can resume:\n");
+
+ toi_file_get_target_info(commandline, 0, 1);
+
+ if (!toi_file_target_bdev || IS_ERR(toi_file_target_bdev)) {
+ toi_file_target_bdev = NULL;
+ result = -1;
+ goto out;
+ }
+
+ if (target_blocksize)
+ set_devinfo(toi_file_target_bdev, ffs(target_blocksize));
+
+ result = __test_toi_file_target(commandline, 1, quiet);
+
+out:
+ if (result)
+ clear_toi_state(TOI_CAN_HIBERNATE);
+
+ if (!quiet)
+ printk(KERN_INFO "Resuming %sabled.\n", result ? "dis" : "en");
+
+ if (colon)
+ *colon = ':';
+ if (at_symbol)
+ *at_symbol = '@';
+
+ return result;
+}
+
+/**
+ * toi_file_save_config_info - populate toi_file_target
+ * @buffer: Pointer to a buffer of size PAGE_SIZE.
+ *
+ * Save the target's name, not for resume time, but for
+ * all_settings.
+ * Returns:
+ * Number of bytes used for saving our data.
+ **/
+static int toi_file_save_config_info(char *buffer)
+{
+ strcpy(buffer, toi_file_target);
+ return strlen(toi_file_target) + 1;
+}
+
+/**
+ * toi_file_load_config_info - reload target's name
+ * @buffer: pointer to the start of the data
+ * @size: number of bytes that were saved
+ *
+ * toi_file_target is set to buffer.
+ **/
+static void toi_file_load_config_info(char *buffer, int size)
+{
+ strlcpy(toi_file_target, buffer, size);
+}
+
+static int toi_file_initialise(int starting_cycle)
+{
+ if (starting_cycle) {
+ if (toiActiveAllocator != &toi_fileops)
+ return 0;
+
+ if (starting_cycle & SYSFS_HIBERNATE && !*toi_file_target) {
+ printk(KERN_INFO "FileAllocator is the active writer, "
+ "but no filename has been set.\n");
+ return 1;
+ }
+ }
+
+ if (*toi_file_target)
+ toi_file_get_target_info(toi_file_target, starting_cycle, 0);
+
+ if (starting_cycle && (toi_file_image_exists(1) == -1)) {
+ printk("%s is does not have a valid signature for "
+ "hibernating.\n", toi_file_target);
+ return 1;
+ }
+
+ return 0;
+}
+
+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,
+ attempt_to_parse_resume_device2)
+};
+
+static struct toi_module_ops toi_fileops = {
+ .type = WRITER_MODULE,
+ .name = "file storage",
+ .directory = "file",
+ .module = THIS_MODULE,
+ .print_debug_info = toi_file_print_debug_stats,
+ .save_config_info = toi_file_save_config_info,
+ .load_config_info = toi_file_load_config_info,
+ .storage_needed = toi_file_storage_needed,
+ .initialise = toi_file_initialise,
+ .cleanup = toi_file_cleanup,
+
+ .noresume_reset = toi_file_noresume_reset,
+ .storage_available = toi_file_storage_available,
+ .storage_allocated = toi_file_storage_allocated,
+ .reserve_header_space = toi_file_reserve_header_space,
+ .allocate_storage = toi_file_allocate_storage,
+ .image_exists = toi_file_image_exists,
+ .mark_resume_attempted = toi_file_mark_resume_attempted,
+ .write_header_init = toi_file_write_header_init,
+ .write_header_cleanup = toi_file_write_header_cleanup,
+ .read_header_init = toi_file_read_header_init,
+ .read_header_cleanup = toi_file_read_header_cleanup,
+ .remove_image = toi_file_remove_image,
+ .parse_sig_location = toi_file_parse_sig_location,
+
+ .sysfs_data = sysfs_params,
+ .num_sysfs_entries = sizeof(sysfs_params) /
+ sizeof(struct toi_sysfs_data),
+};
+
+/* ---- Registration ---- */
+static __init int toi_file_load(void)
+{
+ toi_fileops.rw_init = toi_bio_ops.rw_init;
+ toi_fileops.rw_cleanup = toi_bio_ops.rw_cleanup;
+ toi_fileops.read_page = toi_bio_ops.read_page;
+ toi_fileops.write_page = toi_bio_ops.write_page;
+ toi_fileops.rw_header_chunk = toi_bio_ops.rw_header_chunk;
+ toi_fileops.rw_header_chunk_noreadahead =
+ toi_bio_ops.rw_header_chunk_noreadahead;
+ toi_fileops.io_flusher = toi_bio_ops.io_flusher;
+ toi_fileops.update_throughput_throttle =
+ toi_bio_ops.update_throughput_throttle;
+ toi_fileops.finish_all_io = toi_bio_ops.finish_all_io;
+
+ return toi_register_module(&toi_fileops);
+}
+
+late_initcall(toi_file_load);
--
1.5.6.3
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/