blob: 5fedb15a02ba6971d5c6a4bb5e5fa6c20c16c2a3 [file] [log] [blame]
/*
* resume.c
*
* A simple user space resume handler for swsusp.
*
* Copyright (C) 2005 Rafael J. Wysocki <rjw@sisk.pl>
*
* This file is released under the GPLv2.
*
*/
#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <time.h>
#include <syscall.h>
#include <libgen.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef CONFIG_COMPRESS
#include <lzo/lzo1x.h>
#endif
#include "swsusp.h"
#include "memalloc.h"
#include "config_parser.h"
#include "md5.h"
#include "splash.h"
#include "loglevel.h"
static char snapshot_dev_name[MAX_STR_LEN] = SNAPSHOT_DEVICE;
static char resume_dev_name[MAX_STR_LEN] = RESUME_DEVICE;
static loff_t resume_offset;
static int suspend_loglevel = SUSPEND_LOGLEVEL;
static int max_loglevel = MAX_LOGLEVEL;
static char splash_param;
#ifdef CONFIG_FBSPLASH
char fbsplash_theme[MAX_STR_LEN] = "";
#endif
static int use_platform_suspend;
static struct config_par parameters[] = {
{
.name = "snapshot device",
.fmt = "%s",
.ptr = snapshot_dev_name,
.len = MAX_STR_LEN
},
{
.name = "resume device",
.fmt ="%s",
.ptr = resume_dev_name,
.len = MAX_STR_LEN
},
{
.name = "resume offset",
.fmt = "%llu",
.ptr = &resume_offset,
},
{
.name = "suspend loglevel",
.fmt = "%d",
.ptr = &suspend_loglevel,
},
{
.name = "max loglevel",
.fmt = "%d",
.ptr = &max_loglevel,
},
{
.name = "image size",
.fmt = "%lu",
.ptr = NULL,
},
{
.name = "compute checksum",
.fmt = "%c",
.ptr = NULL,
},
#ifdef CONFIG_COMPRESS
{
.name = "compress",
.fmt = "%c",
.ptr = NULL,
},
#endif
#ifdef CONFIG_ENCRYPT
{
.name = "encrypt",
.fmt = "%c",
.ptr = NULL,
},
{
.name = "RSA key file",
.fmt = "%s",
.ptr = NULL,
},
#endif
{
.name = "early writeout",
.fmt = "%c",
.ptr = NULL,
},
{
.name = "splash",
.fmt = "%c",
.ptr = &splash_param,
},
{
.name = "shutdown method",
.fmt = "%s",
.ptr = NULL,
},
#ifdef CONFIG_FBSPLASH
{
.name = "fbsplash theme",
.fmt = "%s",
.ptr = fbsplash_theme,
.len = MAX_STR_LEN,
},
#endif
{
.name = "resume pause",
.fmt = "%d",
.ptr = NULL,
},
#ifdef CONFIG_THREADS
{
.name = "threads",
.fmt = "%c",
.ptr = NULL,
},
#endif
{
.name = "debug test file",
.fmt = "%s",
.ptr = NULL,
},
{
.name = "debug verify image",
.fmt = "%c",
.ptr = NULL,
},
#ifdef CONFIG_THREADS
{
.name = "threads",
.fmt = "%c",
.ptr = NULL,
},
#endif
{
.name = NULL,
.fmt = NULL,
.ptr = NULL,
.len = 0,
}
};
static inline int atomic_restore(int dev)
{
return ioctl(dev, SNAPSHOT_ATOMIC_RESTORE, 0);
}
static int open_resume_dev(char *resume_dev_name,
struct swsusp_header *swsusp_header)
{
ssize_t size = sizeof(struct swsusp_header);
off64_t shift = ((off64_t)resume_offset + 1) * page_size - size;
ssize_t ret;
int fd;
fd = open(resume_dev_name, O_RDWR);
if (fd < 0) {
ret = -errno;
fprintf(stderr, "%s: Could not open the resume device\n",
my_name);
return ret;
}
if (lseek64(fd, shift, SEEK_SET) != shift)
return -EIO;
ret = read(fd, swsusp_header, size);
if (ret == size) {
if (memcmp(SWSUSP_SIG, swsusp_header->sig, 10)) {
close(fd);
return -ENOMEDIUM;
}
} else {
ret = ret < 0 ? ret : -EIO;
return ret;
}
return fd;
}
static void pause_resume(int pause)
{
struct termios newtrm, savedtrm;
char message[SPLASH_GENERIC_MESSAGE_SIZE];
int wait_possible = !splash.prepare_abort(&savedtrm, &newtrm);
if (!wait_possible)
pause = -1;
sprintf(message, "Image successfully loaded\nPress " ENTER_KEY_NAME
" to continue\n");
splash.set_caption(message);
printf("%s: %s", my_name, message);
if (pause > 0)
printf("%s: Continuing automatically in %2d seconds",
my_name, pause);
while (pause) {
if (splash.key_pressed() == ENTER_KEY_CODE)
break;
sleep(1);
if (pause > 0)
printf("\b\b\b\b\b\b\b\b\b\b%2d seconds", --pause);
}
printf("\n");
if (wait_possible)
splash.restore_abort(&savedtrm);
}
static void reboot_question(char *message)
{
char c;
char full_message[SPLASH_GENERIC_MESSAGE_SIZE];
char *warning =
"\n\tYou can now boot the system and lose the saved state\n"
"\tor reboot and try again.\n\n"
"\t[Notice that if you decide to reboot, you MUST NOT mount\n"
"\tany filesystems before a successful resume.\n"
"\tResuming after some filesystems have been mounted\n"
"\twill badly damage these filesystems.]\n\n"
"\tDo you want to continue booting (Y/n)?";
snprintf(full_message, SPLASH_GENERIC_MESSAGE_SIZE, "%s\n%s",
message, warning);
c = splash.dialog(full_message);
if (c == 'n' || c == 'N') {
reboot();
fprintf(stderr, "%s: Reboot failed, please reboot manually.\n",
my_name);
while(1)
sleep(10);
}
}
static int read_image(int dev, int fd, loff_t start)
{
struct image_header_info *header;
int error;
header = getmem(page_size);
error = read_or_verify(dev, fd, header, start, 0, 0);
if (error) {
reboot_question(
"\nThe system snapshot image could not be read.\n\n"
"\tThis might be a result of booting a wrong "
"kernel.\n"
);
} else {
if (header->flags & PLATFORM_SUSPEND)
use_platform_suspend = 1;
}
if (error) {
char message[SPLASH_GENERIC_MESSAGE_SIZE];
sprintf(message, "%s: Error %d loading the image\nPress "
ENTER_KEY_NAME " to continue\n", my_name, error);
splash.dialog(message);
} else if (header->resume_pause != 0) {
pause_resume(header->resume_pause);
} else {
printf("%s: Image successfully loaded\n", my_name);
}
freemem(header);
return error;
}
static int reset_signature(int fd, struct swsusp_header *swsusp_header)
{
ssize_t ret, size = sizeof(struct swsusp_header);
off64_t shift = ((off64_t)resume_offset + 1) * page_size - size;
int error = 0;
/* Reset swap signature now */
memcpy(swsusp_header->sig, swsusp_header->orig_sig, 10);
if (lseek64(fd, shift, SEEK_SET) != shift) {
fprintf(stderr, "%s: Could not lseek() to the swap header",
my_name);
return -EIO;
}
ret = write(fd, swsusp_header, size);
if (ret == size) {
fsync(fd);
} else {
fprintf(stderr, "%s: Could not restore the swap header",
my_name);
error = -EIO;
}
return error;
}
/* Parse the command line and/or configuration file */
static inline int get_config(int argc, char *argv[])
{
static struct option options[] = {
{
"help\0\t\t\tthis text",
no_argument, NULL, 'h'
},
{
"version\0\t\t\tversion information",
no_argument, NULL, 'V'
},
{
"config\0\t\talternative configuration file.",
required_argument, NULL, 'f'
},
{
"resume_device\0device that contains swap area",
required_argument, NULL, 'r'
},
{
"resume_offset\0offset of swap file in resume device.",
required_argument, NULL, 'o'
},
{
"parameter\0\toverride config file parameter.",
required_argument, NULL, 'P'
},
{ NULL, 0, NULL, 0 }
};
int i, error;
char *conf_name = CONFIG_FILE;
const char *optstring = "hVf:o:r:P:";
struct stat stat_buf;
int fail_missing_config = 0;
/* parse only config file argument */
while ((i = getopt_long(argc, argv, optstring, options, NULL)) != -1) {
switch (i) {
case 'h':
usage(my_name, options, optstring);
exit(EXIT_SUCCESS);
case 'V':
version(my_name, NULL);
exit(EXIT_SUCCESS);
case 'f':
conf_name = optarg;
fail_missing_config = 1;
break;
}
}
if (stat(conf_name, &stat_buf)) {
if (fail_missing_config) {
fprintf(stderr, "%s: Could not stat configuration file\n",
my_name);
return -ENOENT;
}
}
else {
error = parse(my_name, conf_name, parameters);
if (error) {
fprintf(stderr, "%s: Could not parse config file\n", my_name);
return error;
}
}
optind = 0;
while ((i = getopt_long(argc, argv, optstring, options, NULL)) != -1) {
switch (i) {
case 'f':
/* already handled */
break;
case 'o':
resume_offset = atoll(optarg);
break;
case 'r':
strncpy(resume_dev_name, optarg, MAX_STR_LEN -1);
break;
case 'P':
error = parse_line(optarg, parameters);
if (error) {
fprintf(stderr,
"%s: Could not parse config string '%s'\n",
my_name, optarg);
return error;
}
break;
default:
usage(my_name, options, optstring);
return -EINVAL;
}
}
if (optind < argc)
strncpy(resume_dev_name, argv[optind], MAX_STR_LEN - 1);
return 0;
}
int main(int argc, char *argv[])
{
unsigned int mem_size;
struct stat stat_buf;
int dev, resume_dev;
int n, error, orig_loglevel;
static struct swsusp_header swsusp_header;
my_name = basename(argv[0]);
error = get_config(argc, argv);
if (error)
return -error;
if (splash_param != 'y' && splash_param != 'Y')
splash_param = 0;
else
splash_param = SPL_RESUME;
get_page_and_buffer_sizes();
mem_size = 2 * page_size + buffer_size;
#ifdef CONFIG_ENCRYPT
printf("%s: libgcrypt version: %s\n", my_name,
gcry_check_version(NULL));
gcry_control(GCRYCTL_INIT_SECMEM, page_size, 0);
mem_size += page_size;
#endif
#ifdef CONFIG_COMPRESS
/*
* The formula below follows from the worst-case expansion calculation
* for LZO1 (size / 16 + 67) and the fact that the size of the
* compressed data must be stored in the buffer (sizeof(size_t)).
*/
compress_buf_size = buffer_size +
round_up_page_size((buffer_size >> 4) + 67 +
sizeof(size_t));
mem_size += compress_buf_size +
round_up_page_size(LZO1X_1_MEM_COMPRESS);
#endif
error = init_memalloc(page_size, mem_size);
if (error) {
fprintf(stderr, "%s: Could not allocate memory\n", my_name);
return error;
}
open_printk();
orig_loglevel = get_kernel_console_loglevel();
set_kernel_console_loglevel(suspend_loglevel);
while (stat(resume_dev_name, &stat_buf)) {
fprintf(stderr,
"%s: Could not stat the resume device file '%s'\n"
"\tPlease type in the full path name to try again\n"
"\tor press ENTER to boot the system: ", my_name,
resume_dev_name);
fgets(resume_dev_name, MAX_STR_LEN - 1, stdin);
n = strlen(resume_dev_name) - 1;
if (n <= 0) {
error = EINVAL;
goto Free;
}
if (resume_dev_name[n] == '\n')
resume_dev_name[n] = '\0';
}
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
error = errno;
fprintf(stderr, "%s: Could not lock myself\n", my_name);
goto Free;
}
dev = open(snapshot_dev_name, O_WRONLY);
if (dev < 0) {
error = ENOENT;
goto Free;
}
resume_dev = open_resume_dev(resume_dev_name, &swsusp_header);
if (resume_dev == -ENOMEDIUM) {
error = 0;
goto Close;
} else if (resume_dev < 0) {
error = -resume_dev;
goto Close;
}
splash_prepare(&splash, splash_param);
splash.progress(5);
error = read_image(dev, resume_dev, swsusp_header.image);
if (error) {
error = -error;
fprintf(stderr, "%s: Could not read the image\n", my_name);
} else if (freeze(dev)) {
error = errno;
reboot_question("Processes could not be frozen, "
"cannot continue resuming.\n");
}
if (reset_signature(resume_dev, &swsusp_header))
fprintf(stderr, "%s: Swap signature has not been restored.\n"
"\tRun mkswap on the resume partition/file.\n",
my_name);
close(resume_dev);
if (error)
goto Close_splash;
if (use_platform_suspend) {
int err = platform_prepare(dev);
if (err) {
fprintf(stderr, "%s: Unable to use platform "
"hibernation support, error code %d\n",
my_name, err);
use_platform_suspend = 0;
}
}
atomic_restore(dev);
/* We only get here if the atomic restore fails. Clean up. */
if (use_platform_suspend)
platform_finish(dev);
unfreeze(dev);
Close_splash:
splash.finish();
Close:
close(dev);
Free:
if (error)
set_kernel_console_loglevel(max_loglevel);
else if (orig_loglevel >= 0)
set_kernel_console_loglevel(orig_loglevel);
close_printk();
free_memalloc();
return error;
}