| /* |
| * 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; |
| } |