[linux-pm] [RFC/RFT] suspend from userland

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I'd like to get some testing and comments on this. I know that
userland part is messy and will not work on x86-64 etc, and I should
obviously remove some extra printk's... but otherwise it should be okay.

Testing would be nice, too, but be a bit careful. It should not be
more dangerous than /sys/power/resume, but... If you suspect something
unusual, be sure to force fsck.

								Pavel

diff --git a/Documentation/power/uswsusp.txt b/Documentation/power/uswsusp.txt
new file mode 100644
--- /dev/null
+++ b/Documentation/power/uswsusp.txt
@@ -0,0 +1,43 @@
+
+	u-swsusp: do software suspend from userland
+	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	Copyright 2005 Pavel Machek <pavel@xxxxxxx>
+		(distribute under GPLv2)
+
+u-swsusp is next generation of swsusp -- it enables userland to do
+most of the work. That way graphical progress bar can be added, and
+you can have encryption, compression, suspend to regular files,
+suspend over network etc. More exotic stuff is possible, too: given
+enough memory and some device mapper magic, you could snapshot system
+every 5 minutes, and then provided unlimited, cross-application
+"undo".
+
+Notice that all warnings in swsusp.txt still apply. If you change your
+filesystem between suspend and resume, that's bye-bye-filesystem. Only
+shooting yourself into the foot has now became simpler, because you'll
+want to run userspace application to do the resume. Remember that even
+read-only mount of journalled filesystem changes it. That means that
+either you'll have to use ext2 for resume application, or put it into
+initrd. (initrd is certainly right solution, long term).
+
+Current userland support is _very_ limited, and provides very little
+safety measures. Be careful. It can only suspend to empty partition,
+and will happily overwrite anything there.
+
+Installation: place swsusp binary into /tmp/, along with
+swsusp-init. Add init=/tmp/swsusp-init to your kernel
+commandline. Modify swsusp-init with right device. To suspend, do
+something like:
+
+swapoff /dev/hda1; cat /dev/zero | head -c 4096 > /dev/hda1
+/tmp/swsusp /dev/hda1 -s -b
+
+. Good luck!
+
+Q: I hate /dev/kmem, it enables crackers to install rootkits.
+
+A: There are 1000 other ways to install rootkit if you have all the
+priviledges these days. Anyway, if you want to make hijacking
+/dev/kmem little harder, you can modify it to only work with pages
+that have "PageNosave | PageNosaveFree" set. swsusp application only
+needs to manipulate those.
diff --git a/drivers/char/mem.c b/drivers/char/mem.c
--- a/drivers/char/mem.c
+++ b/drivers/char/mem.c
@@ -27,6 +27,7 @@
 #include <linux/crash_dump.h>
 #include <linux/backing-dev.h>
 #include <linux/bootmem.h>
+#include <linux/suspend.h>
 
 #include <asm/uaccess.h>
 #include <asm/io.h>
@@ -559,6 +561,45 @@ static ssize_t write_port(struct file * 
 }
 #endif
 
+static int
+ioctl_kmem(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+        int retval = 0;
+
+        switch (cmd) {
+	case IOCTL_FREEZE:
+		retval = sys_freeze();
+		break;
+	case IOCTL_UNFREEZE:
+		retval = sys_unfreeze();
+		break;
+	case IOCTL_ATOMIC_SNAPSHOT:
+		retval = sys_atomic_snapshot(arg);
+		break;
+	case IOCTL_ATOMIC_RESTORE:
+		{
+			int pages;
+			void *pgdir;
+			get_user(pages, (long *) arg);
+			get_user(pgdir, (void **) (arg + 4));
+			retval = sys_atomic_restore(pgdir, pages);
+		}
+		break;
+	case IOCTL_KMALLOC:
+		retval = get_zeroed_page(GFP_KERNEL);
+		break;
+	case IOCTL_KFREE:
+		free_page(arg);
+		break;
+        default:
+                retval = -ENOTTY;
+                break;
+        }
+
+        return retval;
+}
+
+
 static ssize_t read_null(struct file * file, char __user * buf,
 			 size_t count, loff_t *ppos)
 {
@@ -769,6 +810,7 @@ static struct file_operations mem_fops =
 static struct file_operations kmem_fops = {
 	.llseek		= memory_lseek,
 	.read		= read_kmem,
+	.ioctl		= ioctl_kmem,
 	.write		= write_kmem,
 	.mmap		= mmap_kmem,
 	.open		= open_kmem,
diff --git a/include/linux/suspend.h b/include/linux/suspend.h
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -1,6 +1,7 @@
 #ifndef _LINUX_SWSUSP_H
 #define _LINUX_SWSUSP_H
 
+#ifdef __KERNEL__
 #if defined(CONFIG_X86) || defined(CONFIG_FRV) || defined(CONFIG_PPC32)
 #include <asm/suspend.h>
 #endif
@@ -9,6 +10,7 @@
 #include <linux/config.h>
 #include <linux/init.h>
 #include <linux/pm.h>
+#endif
 
 /* page backup entry */
 typedef struct pbe {
@@ -30,6 +32,7 @@ typedef struct pbe {
 #define for_each_pb_page(pbe, pblist) \
 	for (pbe = pblist ; pbe ; pbe = (pbe+PB_PAGE_SKIP)->next)
 
+#ifdef __KERNEL__
 
 #define SWAP_FILENAME_MAXLENGTH	32
 
@@ -79,4 +82,24 @@ unsigned long get_safe_page(gfp_t gfp_ma
  */
 #define PAGES_FOR_IO	512
 
+#endif
+
+struct restore_ioctl {
+	void *pgdir;
+	int nr_pages;
+};
+
+#define IOCTL_FREEZE _IO('3', 1)
+#define IOCTL_UNFREEZE _IO('3', 2)
+#define IOCTL_ATOMIC_SNAPSHOT _IOW('3', 3, void **)
+#define IOCTL_ATOMIC_RESTORE _IOR('3', 4, struct restore_ioctl)
+#define IOCTL_KMALLOC _IO('3', 5)
+#define IOCTL_KFREE _IOR('3', 6, void *)
+
+extern int sys_freeze(void);
+extern int sys_unfreeze(void);
+extern int sys_atomic_snapshot(void **pgdir);
+extern int sys_atomic_restore(void *pgdir, int pages);
+
+
 #endif /* _LINUX_SWSUSP_H */
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -82,18 +82,6 @@ config PM_STD_PARTITION
 	  suspended image to. It will simply pick the first available swap 
 	  device.
 
-config SWSUSP_ENCRYPT
-	bool "Encrypt suspend image"
-	depends on SOFTWARE_SUSPEND && CRYPTO=y && (CRYPTO_AES=y || CRYPTO_AES_586=y || CRYPTO_AES_X86_64=y)
-	default ""
-	---help---
-	  To prevent data gathering from swap after resume you can encrypt
-	  the suspend image with a temporary key that is deleted on
-	  resume.
-
-	  Note that the temporary key is stored unencrypted on disk while the
-	  system is suspended.
-
 config SUSPEND_SMP
 	bool
 	depends on HOTPLUG_CPU && X86 && PM
diff --git a/kernel/power/console.c b/kernel/power/console.c
--- a/kernel/power/console.c
+++ b/kernel/power/console.c
@@ -9,6 +9,7 @@
 #include <linux/console.h>
 #include "power.h"
 
+#undef SUSPEND_CONSOLE
 static int new_loglevel = 10;
 static int orig_loglevel;
 #ifdef SUSPEND_CONSOLE
diff --git a/kernel/power/disk.c b/kernel/power/disk.c
--- a/kernel/power/disk.c
+++ b/kernel/power/disk.c
@@ -84,20 +84,26 @@ static int in_suspend __nosavedata = 0;
 
 static void free_some_memory(void)
 {
-	unsigned int i = 0;
-	unsigned int tmp;
-	unsigned long pages = 0;
-	char *p = "-\\|/";
-
-	printk("Freeing memory...  ");
-	while ((tmp = shrink_all_memory(10000))) {
-		pages += tmp;
-		printk("\b%c", p[i++ % 4]);
+	int i;
+	for (i=0; i<5; i++) {
+		int i = 0, tmp;
+		long pages = 0;
+		char *p = "-\\|/";
+
+		printk("Freeing memory...  ");
+		while ((tmp = shrink_all_memory(10000))) {
+			pages += tmp;
+			printk("\b%c", p[i]);
+			i++;
+			if (i > 3)
+				i = 0;
+		}
+		printk("\bdone (%li pages freed)\n", pages);
+		msleep_interruptible(200);
 	}
-	printk("\bdone (%li pages freed)\n", pages);
 }
 
-
+/* FIXME: Call it when appropriate */
 static inline void platform_finish(void)
 {
 	if (pm_disk_mode == PM_DISK_PLATFORM) {
@@ -138,12 +144,25 @@ thaw:
 
 static void unprepare_processes(void)
 {
-	platform_finish();
 	thaw_processes();
 	enable_nonboot_cpus();
 	pm_restore_console();
 }
 
+
+int sys_freeze(void)
+{
+	return prepare_processes();
+}
+
+int sys_unfreeze(void)
+{
+	thaw_processes();
+	enable_nonboot_cpus();
+	pm_restore_console();
+	return 0;
+}
+
 /**
  *	pm_suspend_disk - The granpappy of power management.
  *
@@ -238,6 +257,9 @@ static int software_resume(void)
 	if ((error = swsusp_check()))
 		goto Done;
 
+	/* Prepare processes only after swsusp_check; we could do it before,
+	   but it would mean an ugly console switch even in case of normal boot.
+	 */
 	pr_debug("PM: Preparing processes for restore.\n");
 
 	if ((error = prepare_processes())) {
diff --git a/kernel/power/power.h b/kernel/power/power.h
--- a/kernel/power/power.h
+++ b/kernel/power/power.h
@@ -48,9 +48,6 @@ static struct subsys_attribute _name##_a
 
 extern struct subsystem power_subsys;
 
-extern int freeze_processes(void);
-extern void thaw_processes(void);
-
 extern int pm_prepare_console(void);
 extern void pm_restore_console(void);
 
diff --git a/kernel/power/process.c b/kernel/power/process.c
--- a/kernel/power/process.c
+++ b/kernel/power/process.c
@@ -83,7 +83,7 @@ int freeze_processes(void)
 		yield();			/* Yield is okay here */
 		if (todo && time_after(jiffies, start_time + TIMEOUT)) {
 			printk( "\n" );
-			printk(KERN_ERR " stopping tasks failed (%d tasks remaining)\n", todo );
+			printk(KERN_ERR " stopping tasks timed out (%d tasks remaining)\n", todo );
 			break;
 		}
 	} while(todo);
diff --git a/kernel/power/swsusp.c b/kernel/power/swsusp.c
--- a/kernel/power/swsusp.c
+++ b/kernel/power/swsusp.c
@@ -394,6 +394,7 @@ static void data_free(void)
 			break;
 	}
 }
+/* FIXME: we have data_write but write_pgdir */
 
 /**
  *	data_write - Write saved image to swap.
@@ -498,6 +499,8 @@ static int write_pagedir(void)
 
 	printk( "Writing pagedir...");
 	for_each_pb_page (pbe, pagedir_nosave) {
+		/* FIXME: pagedir only has 768 entries. We may overflow it,
+		   if we write around 768000 pages, thats ~4GB. */
 		if ((error = write_page((unsigned long)pbe, &swsusp_info.pagedir[n++])))
 			return error;
 	}
@@ -537,7 +540,10 @@ static int write_suspend_image(void)
 
 	if (!enough_swap(nr_copy_pages)) {
 		printk(KERN_ERR "swsusp: Not enough free swap\n");
+#if 0
+		/* FIXME: should be done earlier */
 		return -ENOSPC;
+#endif
 	}
 
 	init_header();
@@ -579,8 +588,6 @@ int swsusp_write(void)
 	return error;
 }
 
-
-
 int swsusp_suspend(void)
 {
 	int error;
@@ -608,6 +615,7 @@ int swsusp_suspend(void)
 	if ((error = swsusp_arch_suspend()))
 		printk(KERN_ERR "Error %d suspending\n", error);
 	/* Restore control flow magically appears here */
+	printk("back in swsusp_suspend\n");
 	restore_processor_state();
 Restore_highmem:
 	restore_highmem();
@@ -620,11 +628,24 @@ Enable_irqs:
 int swsusp_resume(void)
 {
 	int error;
+
+	printk("swsusp_resume: stop devices ");
 	local_irq_disable();
 	if (device_power_down(PMSG_FREEZE))
 		printk(KERN_ERR "Some devices failed to power down, very bad\n");
 	/* We'll ignore saved state, but this gets preempt count (etc) right */
 	save_processor_state();
+	/* swsusp_arch_resume takes pagedir_nosave as the only parameter */ 
+	printk("ok");
+
+	/* Takes pagedir_nosave as an argument. Does not need nr_copy_pages */ 
+	{
+		struct pbe *p = pagedir_nosave;
+		int i = 0;
+		for_each_pbe (p, pagedir_nosave)
+			i++;
+		printk("[%d pages]", i);
+	}
 	error = swsusp_arch_resume();
 	/* Code below is only ever reached in case of failure. Otherwise
 	 * execution continues at place where swsusp_arch_suspend was called
@@ -998,7 +1019,6 @@ int swsusp_read(void)
 /**
  *	swsusp_close - close swap device.
  */
-
 void swsusp_close(void)
 {
 	if (IS_ERR(resume_bdev)) {
@@ -1008,3 +1028,55 @@ void swsusp_close(void)
 
 	blkdev_put(resume_bdev);
 }
+
+static int in_suspend __nosavedata = 0;
+
+int sys_atomic_snapshot(void **pgdir)
+{
+	int err;
+
+	err = device_suspend(PMSG_FREEZE);
+	if (err)
+		return err;
+
+	in_suspend = 1;
+	err = swsusp_suspend();
+
+	*pgdir = pagedir_nosave; /* FIXME: put_user */
+
+	{
+		struct pbe *p = pagedir_nosave;
+		int i = 0;
+		for_each_pbe (p, pagedir_nosave)
+			i++;
+	}
+
+	if (!err)
+		err = nr_copy_pages;
+	if (in_suspend == 2) {
+		err = -ENOANO;
+	}
+
+	device_resume();
+	return err;
+}
+
+int sys_atomic_restore(void *pgdir, int pages)
+{
+	int err;
+	/* FIXME: we'll probably overwrite pagedir with itself in inconsistent state...
+	 ...no, pagedir is NOSAVE.
+	*/
+
+	err = device_suspend(PMSG_FREEZE);
+	if (err)
+		return err;
+
+	in_suspend = 2;
+	pagedir_nosave = pgdir;
+	nr_copy_pages = pages;
+
+	err = swsusp_resume();
+	printk(KERN_CRIT "This should never return\n");
+	return err;
+}
diff --git a/usr/swsusp-init b/usr/swsusp-init
new file mode 100755
--- /dev/null
+++ b/usr/swsusp-init
@@ -0,0 +1,9 @@
+#!/bin/bash
+#
+# swapoff /dev/hda1; cat /dev/zero | head -c 4096 > /dev/hda1
+# /tmp/swsusp /dev/hda1 -s -b
+/tmp/swsusp /dev/hda1 -r 
+exec /sbin/init
+exit
+
+
diff --git a/usr/swsusp.c b/usr/swsusp.c
new file mode 100755
--- /dev/null
+++ b/usr/swsusp.c
@@ -0,0 +1,529 @@
+#if 0
+#
+# Swsusp3 control program
+#
+# Copyright 2005 Pavel Machek <pavel@xxxxxxx>
+#
+# Distribute under GPLv2
+#
+gcc -g -Wall usr/swsusp.c -o /tmp/swsusp; cp -a usr/swsusp-init /tmp
+exit
+#
+#endif
+
+#define PAGE_SIZE 4096
+
+#include <unistd.h>
+#include <syscall.h>
+//#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <asm/fcntl.h>
+#include <string.h>
+
+extern __off64_t lseek64 (int __fd, __off64_t __offset, int __whence) __THROW;
+
+typedef long swp_entry_t;
+
+#include "/data/l/linux-sw3/include/linux/suspend.h"
+#include "/data/l/linux-sw3/include/linux/reboot.h"
+
+char forbidden_pages[(0xffffffff / PAGE_SIZE)+1];
+
+struct resume {
+	int nr_copy_pages;
+	void *pagedir;
+} resume;
+
+struct pbe_page {
+	unsigned long address;		/* address of the copy */
+	unsigned long orig_address;	/* original address of page */
+	swp_entry_t swap_address;	
+
+	struct pbe *next;	/* also used as scratch space at
+				 * end of page (see link, diskpage)
+				 */
+	char data[4096-16];
+};
+
+int kmem;
+
+void
+seek(unsigned long dest)
+{
+	if (lseek64(kmem, dest, SEEK_SET) != dest) {
+		fprintf(stderr, "Could not do intial seek to %lx: %m\n", dest);
+		fprintf(stderr, "lseek64(%d) returned: %lx\n", kmem, (long) lseek64(kmem, dest, SEEK_SET));
+		exit(1);
+	}
+}
+
+typedef int (*walker_t)(struct pbe *p, int i);
+typedef int (*walker2_t)(struct pbe_page *p, int i);
+
+int
+walk_chain(struct resume *r, walker_t w)
+{
+	struct pbe p;
+	int i = 0;
+	long pos;
+
+	seek(pos = (long) r->pagedir);
+	while (1) {
+		if (read(kmem, &p, sizeof(p)) != sizeof(p)) {
+			fprintf(stderr, "Could not read pbe #%d: %m\n", i);
+			exit(1);
+		}
+		if (w != NULL) {
+			w(&p, i);
+			seek(pos);
+			if (write(kmem, &p, sizeof(p)) != sizeof(p)) {
+				fprintf(stderr, "Could not write back pbe #%d: %m\n", i);
+				exit(1);
+			}
+		}
+		i++;
+		if (!p.next)
+			break;
+		seek(pos = (long) p.next);
+	}
+	return i;
+}
+
+void
+walk_pages_chain(struct resume *r, walker2_t w)
+{
+	struct pbe_page p;
+	int i = 0;
+	long pos;
+
+	seek(pos = (long) r->pagedir);
+	while (1) {
+		if (read(kmem, &p, sizeof(p)) != sizeof(p)) {
+			fprintf(stderr, "Could not read pbe #%d: %m\n", i);
+			exit(1);
+		}
+		if ((w != NULL) && !(pos & 0xfff)) {
+			w(&p, i);
+			seek(pos);
+			if (write(kmem, &p, sizeof(p)) != sizeof(p))
+				fprintf(stderr, "Could not write back pbe #%d: %m\n", i);
+		}
+		i++;
+		if (!p.next)
+			break;
+		seek(pos = (long) p.next);
+	}
+}
+
+
+int image_fd, image_pos = 4096;
+
+static int write_page(unsigned long addr, swp_entry_t * loc)
+{
+	swp_entry_t entry;
+
+	entry = image_pos;
+	image_pos += 4096;
+
+	{
+		char buf[4096];
+		seek(addr);
+		if (read(kmem, buf, 4096) != 4096) {
+			fprintf(stderr, "Could not read page #%lx: %m\n", addr);
+			exit(1);
+		}
+		*loc = image_pos;
+		if (lseek(image_fd, image_pos, SEEK_SET) != image_pos) {
+			fprintf(stderr, "Could not seek in image to #%d: %m\n", image_pos);
+			exit(1);
+		}
+		if (write(image_fd, buf, 4096) != 4096) {
+			fprintf(stderr, "Could not write to image at #%d: %m\n", image_pos);
+			exit(1);
+		}
+	}
+	return 0;
+}
+
+unsigned int mod;
+
+static int data_write_one(struct pbe *p, int i)
+{
+	int error;
+	if (!(i%mod))
+		printf( "\b\b\b\b%3d%%", i / mod );
+	if ((error = write_page(p->address, &(p->swap_address))))
+		return error;
+	return 0;
+}
+
+
+struct swsusp_info {
+	int			nr_copy_pages;
+	int			version_code;
+	char			signature[10];
+	swp_entry_t		pagedir[768];
+} __attribute__((aligned(4096)));
+
+struct swsusp_info swsusp_info, zeros;
+
+/**
+ *	data_write - Write saved image to swap.
+ */
+static int data_write(void)
+{
+	int error = 0;
+	mod = resume.nr_copy_pages / 100;
+
+	if (!mod)
+		mod = 1;
+
+	printf( "Writing data to swap (%d pages)...     ", resume.nr_copy_pages );
+	walk_chain(&resume, data_write_one);
+	printf("\b\b\b\bdone\n");
+	return error;
+}
+
+unsigned n = 0;
+
+static int pgdir_write_one(struct pbe_page *pbe, int i)
+{
+	int error;
+	if ((error = write_page((unsigned long)pbe, &swsusp_info.pagedir[n++])))
+		return error;
+	return 0;
+}
+
+/**
+ *	write_pagedir - Write the array of nr_copy_pages holding the page directory.
+ *	@last:	Last swap entry we write (needed for header).
+ */
+
+static int write_pagedir(void)
+{
+	int error = 0;
+
+	printf( "Writing pagedir...");
+	walk_pages_chain(&resume, pgdir_write_one);
+
+	swsusp_info.nr_copy_pages = n;
+	printf("done (%u pages)\n", n);
+	return error;
+}
+
+
+/**
+ *	write_suspend_image - Write entire image and metadata.
+ *
+ */
+static int write_suspend_image(void)
+{
+	int error;
+
+	if ((error = data_write()))
+		goto Done;
+
+	if ((error = write_pagedir()))
+		goto Done;
+
+	swsusp_info.nr_copy_pages = resume.nr_copy_pages;
+	swsusp_info.version_code = 1;
+	strcpy(swsusp_info.signature, "swsusp3");
+	lseek(image_fd, 0, SEEK_SET);
+	write(image_fd, &swsusp_info, 4096);
+ Done:
+	return error;
+}
+
+char *image;
+
+int
+do_suspend(void)
+{
+	kmem = open("/dev/kmem", O_RDWR | O_LARGEFILE);
+	image_fd = open(image, O_RDWR | O_CREAT, 0600);
+	resume.nr_copy_pages = -1;
+	resume.pagedir = NULL;
+
+	if (kmem < 0) {
+		fprintf(stderr, "Could not open /dev/kmem: %m\n");
+		exit(1);
+	}
+
+	if (ioctl(kmem, IOCTL_FREEZE, 0)) {
+		fprintf(stderr, "Could not freeze system: %m\n");
+		exit(1);	/* We do not want to reboot in case of failure */
+	}
+
+	resume.nr_copy_pages = ioctl(kmem, IOCTL_ATOMIC_SNAPSHOT, &resume.pagedir);
+	if (resume.nr_copy_pages < 0) {
+		fprintf(stderr, "Could not snapshot system: %m\n");
+
+		if (ioctl(kmem, IOCTL_UNFREEZE, 0)) {
+			fprintf(stderr, "Could not unfreeze system: %m\n");
+			return 1;
+		}
+		exit(1);	/* Stop infinite loop of reboots */
+	}
+
+	walk_chain(&resume, NULL);
+	/* Ouch, at this point we'll appear in ATOMIC_SNAPSHOT syscall, with no way to tell... */
+
+	printf("Snapshotted, have %d pages, pagedir at %lx\n", resume.nr_copy_pages, (long) resume.pagedir);
+	walk_chain(&resume, NULL);
+	write_suspend_image();
+	fsync(image_fd);
+
+	return 0;
+
+}
+
+
+
+/**
+ *	fill_pb_page - Create a list of PBEs on a given memory page
+ */
+
+static inline void fill_pb_page(struct pbe *pbpage)
+{
+	struct pbe *p;
+
+	p = pbpage;
+	pbpage += PB_PAGE_SKIP;
+	do
+		p->next = p + 1;
+	while (++p < pbpage);
+}
+
+unsigned long get_page(void)
+{
+	unsigned long ret;
+
+	do {
+		ret = ioctl(kmem, IOCTL_KMALLOC, 1);
+	} while(forbidden_pages[ret/PAGE_SIZE]);	
+
+	return ret;
+}
+
+/**
+ *	alloc_pagedir - Allocate the page directory.
+ *
+ *	First, determine exactly how many pages we need and
+ *	allocate them.
+ *
+ *	We arrange the pages in a chain: each page is an array of PBES_PER_PAGE
+ *	struct pbe elements (pbes) and the last element in the page points
+ *	to the next page.
+ *
+ *	On each page we set up a list of struct_pbe elements.
+ */
+
+static struct pbe * alloc_pagedir(unsigned nr_pages)
+{
+	unsigned num;
+	struct pbe *pblist;
+	struct pbe buf[PBES_PER_PAGE];
+	int i;
+
+	printf("alloc_pagedir(): nr_pages = %d\n", nr_pages);
+	resume.pagedir = pblist = (struct pbe *) get_page();
+	for (num = PBES_PER_PAGE; num < nr_pages;
+        		nr_pages -= PBES_PER_PAGE) {
+
+		for (i=0; i<PBES_PER_PAGE-1; i++)
+			buf[i].next = &pblist[i+1];
+		buf[PBES_PER_PAGE-1].next = (struct pbe *) get_page();
+
+		seek((long) pblist);
+		write(kmem, buf, PAGE_SIZE);
+		pblist = buf[PBES_PER_PAGE-1].next;
+	}
+
+	for (i=0; i<nr_pages-1; i++)
+		buf[i].next = &pblist[i+1];
+	buf[nr_pages-1].next = 0;
+
+	seek((long) pblist);
+	write(kmem, buf, PAGE_SIZE);
+	return NULL;
+}
+
+
+
+
+/**
+ *	read_pagedir - Read page backup list pages from swap
+ */
+
+static int read_pagedir_one(struct pbe *pbpage, int pos)
+{
+	struct pbe buf[PBES_PER_PAGE];
+	int error;
+	int i;
+	unsigned long offset = swsusp_info.pagedir[pos/PBES_PER_PAGE];
+
+	error = -1;
+	if (!offset)
+		printf("Something went very wrong at pagedir #%d\n", pos);
+
+	lseek(image_fd, offset, SEEK_SET);
+	error = (read(image_fd, (void *)buf, PAGE_SIZE) != PAGE_SIZE);
+
+	for (i=0; i<PBES_PER_PAGE; i++) {
+		pbpage[i].orig_address = buf[i].orig_address;
+		forbidden_pages[pbpage[i].orig_address / PAGE_SIZE] = 1;
+		pbpage[i].swap_address = buf[i].swap_address;
+		pbpage[i].address = buf[i].address;
+	}
+
+	return error;
+}
+
+
+
+/**
+ *	data_read - Read image pages from swap.
+ */
+static int data_read_one(struct pbe *p, int i)
+{
+	int error = 0;
+	char buf[PAGE_SIZE];
+
+	if (!(i % mod))
+		printf("\b\b\b\b%3d%%", i / mod);
+
+	lseek(image_fd, p->swap_address, SEEK_SET);
+
+	p->address = get_page();
+	error = (read(image_fd, buf, PAGE_SIZE) != PAGE_SIZE);
+	seek(p->address);
+	error = (write(kmem, buf, PAGE_SIZE) != PAGE_SIZE);
+
+	return error;
+}
+
+int
+do_resume(void)
+{
+	kmem = open("/dev/kmem", O_RDWR | O_LARGEFILE);
+	image_fd = open(image, O_RDWR);
+
+	if (kmem < 0) {
+		fprintf(stderr, "Could not open /dev/kmem: %m\n");
+		return 1;
+	}
+
+	memset(&swsusp_info, 0, sizeof(swsusp_info));
+	read(image_fd, &swsusp_info, sizeof(swsusp_info));
+	resume.nr_copy_pages = swsusp_info.nr_copy_pages;
+
+	if (strcmp("swsusp3", swsusp_info.signature))
+		exit(0);
+	if (lseek(image_fd, 0, SEEK_SET) != 0) {
+		printf("Could not seek to kill sig: %m\n");
+		exit(1);
+	}
+	if (write(image_fd, &zeros, sizeof(swsusp_info)) != sizeof(swsusp_info)) {
+		printf("Could not write to kill sig: %m\n");
+		exit(1);
+	}
+	if (fsync(image_fd)) {
+		printf("Could not fsync to kill sig: %m\n");
+		exit(1);
+	}
+	printf("Got image, %d pages, signature [%s]\n", resume.nr_copy_pages, swsusp_info.signature);
+
+	alloc_pagedir(resume.nr_copy_pages);
+	printf("Verifying allocated pagedir: %d pages\n", walk_chain(&resume, NULL));
+	printf("swsusp: Reading pagedir ");
+	walk_pages_chain(&resume, (void *) read_pagedir_one);
+	printf("ok\n");
+
+	/* Need to be done twice; so that forbidden_pages comes into effect */
+	alloc_pagedir(resume.nr_copy_pages);
+	printf("Verifying allocated pagedir: %d pages\n", walk_chain(&resume, NULL));
+	printf("swsusp: Reading pagedir ");
+	walk_pages_chain(&resume, (void *) read_pagedir_one);
+	printf("ok\n");
+
+	printf("Verifying allocated pagedir: %d pages\n", walk_chain(&resume, NULL));
+
+	/* FIXME: Need to relocate pages */
+	mod = swsusp_info.nr_copy_pages / 100;
+	if (!mod)
+		mod = 1;
+	printf("swsusp: Reading image data (%d pages):     ",
+			swsusp_info.nr_copy_pages);
+	walk_chain(&resume, data_read_one);
+	printf("\b\b\b\bdone\n");
+
+	if (ioctl(kmem, IOCTL_FREEZE, 0)) {
+		fprintf(stderr, "Could not freeze system: %m\n");
+		return 1;
+	}
+
+	if (ioctl(kmem, IOCTL_ATOMIC_RESTORE, &resume)) {
+		fprintf(stderr, "Could not restore system: %m\n");
+	}
+	/* Ouch, at this point we'll appear in ATOMIC_SNAPSHOT syscall, if
+	   things went ok... */
+
+	return 0;
+}
+
+/*
+#define  LINUX_REBOOT_CMD_RESTART        0x01234567
+#define  LINUX_REBOOT_CMD_HALT           0xCDEF0123
+#define  LINUX_REBOOT_CMD_POWER_OFF      0x4321FEDC
+#define  LINUX_REBOOT_CMD_RESTART2       0xA1B2C3D4
+#define  LINUX_REBOOT_CMD_SW_SUSPEND     0xD000FCE2
+*/
+
+int reboot(unsigned long todo)
+{
+	syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, todo, 0);
+	return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+	int error;
+
+	sync();
+	setvbuf(stdout, NULL, _IONBF, 0);
+	setvbuf(stderr, NULL, _IONBF, 0);
+
+	if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
+		fprintf(stderr, "Could not lock myself: %m\n");
+		return 1;
+	}
+
+	image = argv[1];
+
+	while (argv[2]) {
+
+		if (!strcmp(argv[2], "-s"))
+			error = do_suspend();
+
+		if (!strcmp(argv[2], "-b"))
+			reboot(LINUX_REBOOT_CMD_RESTART);
+
+		if (!strcmp(argv[2], "-h"))
+			reboot(LINUX_REBOOT_CMD_HALT);
+
+		if (!strcmp(argv[2], "-o"))
+			reboot(LINUX_REBOOT_CMD_POWER_OFF);
+
+		if (!strcmp(argv[2], "-r"))
+			error = do_resume();
+
+		argv++;
+	}
+	return error;
+}
+

-- 
Thanks, Sharp!

[Index of Archives]     [Linux ACPI]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [CPU Freq]     [Kernel Newbies]     [Fedora Kernel]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux