blob: 20d5db1d25db33e61c1db6e50b686f20fcb953d2 [file] [log] [blame]
/*
* suspend.c
*
* A simple user space suspend 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/mount.h>
#include <sys/vt.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <time.h>
#include <linux/kd.h>
#include <linux/tiocl.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>
#include <signal.h>
#include <termios.h>
#ifdef CONFIG_THREADS
#include <pthread.h>
#endif
#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 "vt.h"
#include "loglevel.h"
#ifdef CONFIG_BOTH
#include "s2ram.h"
#endif
static char test_file_name[MAX_STR_LEN] = "";
static loff_t test_image_size;
#define suspend_error(msg, args...) \
do { \
fprintf(stderr, "%s: " msg " Reason: %m\n", my_name, ## args); \
} while (0)
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 loff_t pref_image_size = IMAGE_SIZE;
static int suspend_loglevel = SUSPEND_LOGLEVEL;
static char compute_checksum;
#ifdef CONFIG_COMPRESS
static char do_compress;
#else
#define do_compress 0
#endif
#ifdef CONFIG_ENCRYPT
static char do_encrypt;
static char use_RSA;
static char key_name[MAX_STR_LEN] = SUSPEND_KEY_FILE_PATH;
static char password[PASSBUF_SIZE];
static unsigned long encrypt_buf_size;
#else
#define do_encrypt 0
#define key_name NULL
#define encrypt_buf_size 0
#endif
#ifdef CONFIG_BOTH
static char s2ram;
static char s2ram_kms;
#endif
static char early_writeout;
static char splash_param;
#ifdef CONFIG_FBSPLASH
char fbsplash_theme[MAX_STR_LEN] = "";
#endif
#define SHUTDOWN_LEN 16
static char shutdown_method_value[SHUTDOWN_LEN] = "";
static enum {
SHUTDOWN_METHOD_SHUTDOWN,
SHUTDOWN_METHOD_PLATFORM,
SHUTDOWN_METHOD_REBOOT
} shutdown_method = SHUTDOWN_METHOD_PLATFORM;
static int resume_pause;
static char verify_image;
#ifdef CONFIG_THREADS
static char use_threads;
#else
#define use_threads 0
#endif
static int suspend_swappiness = SUSPEND_SWAPPINESS;
static struct vt_mode orig_vtm;
static int vfd;
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 = "image size",
.fmt = "%lu",
.ptr = &pref_image_size,
},
{
.name = "suspend loglevel",
.fmt = "%d",
.ptr = &suspend_loglevel,
},
{
.name = "max loglevel",
.fmt = "%d",
.ptr = NULL,
},
{
.name = "compute checksum",
.fmt = "%c",
.ptr = &compute_checksum,
},
#ifdef CONFIG_COMPRESS
{
.name = "compress",
.fmt = "%c",
.ptr = &do_compress,
},
#endif
#ifdef CONFIG_ENCRYPT
{
.name = "encrypt",
.fmt = "%c",
.ptr = &do_encrypt,
},
{
.name = "RSA key file",
.fmt = "%s",
.ptr = key_name,
.len = MAX_STR_LEN
},
#endif
{
.name = "early writeout",
.fmt = "%c",
.ptr = &early_writeout,
},
{
.name = "splash",
.fmt = "%c",
.ptr = &splash_param,
},
{
.name = "shutdown method",
.fmt = "%s",
.ptr = shutdown_method_value,
.len = SHUTDOWN_LEN,
},
#ifdef CONFIG_FBSPLASH
{
.name = "fbsplash theme",
.fmt = "%s",
.ptr = fbsplash_theme,
.len = MAX_STR_LEN,
},
#endif
{
.name = "resume pause",
.fmt = "%d",
.ptr = &resume_pause,
},
{
.name = "debug test file",
.fmt = "%s",
.ptr = test_file_name,
.len = MAX_STR_LEN
},
{
.name = "debug verify image",
.fmt = "%c",
.ptr = &verify_image,
},
#ifdef CONFIG_THREADS
{
.name = "threads",
.fmt = "%c",
.ptr = &use_threads,
},
#endif
{
.name = NULL,
.fmt = NULL,
.ptr = NULL,
.len = 0,
}
};
static loff_t check_free_swap(int dev)
{
int error;
loff_t free_swap;
error = ioctl(dev, SNAPSHOT_AVAIL_SWAP_SIZE, &free_swap);
if (error && errno == ENOTTY) {
report_unsupported_ioctl("SNAPSHOT_AVAIL_SWAP_SIZE");
error = ioctl(dev, SNAPSHOT_AVAIL_SWAP, &free_swap);
}
if (!error)
return free_swap;
suspend_error("check_free_swap failed.");
return 0;
}
static loff_t get_image_size(int dev)
{
int error;
loff_t image_size;
error = ioctl(dev, SNAPSHOT_GET_IMAGE_SIZE, &image_size);
if (!error)
return image_size;
if (errno == ENOTTY)
report_unsupported_ioctl("SNAPSHOT_GET_IMAGE_SIZE");
suspend_error("get_image_size failed.");
return 0;
}
static loff_t alloc_swap_page(int dev, int verbose)
{
int error;
loff_t offset;
error = ioctl(dev, SNAPSHOT_ALLOC_SWAP_PAGE, &offset);
if (error && errno == ENOTTY) {
if (verbose)
report_unsupported_ioctl("SNAPSHOT_ALLOC_SWAP_PAGE");
error = ioctl(dev, SNAPSHOT_GET_SWAP_PAGE, &offset);
}
if (!error)
return offset;
return 0;
}
static inline loff_t get_swap_page(int dev)
{
return alloc_swap_page(dev, 0);
}
static inline int free_swap_pages(int dev)
{
return ioctl(dev, SNAPSHOT_FREE_SWAP_PAGES, 0);
}
static int set_swap_file(int dev, u_int32_t blkdev, loff_t offset)
{
struct resume_swap_area swap;
int error;
swap.dev = blkdev;
swap.offset = offset;
error = ioctl(dev, SNAPSHOT_SET_SWAP_AREA, &swap);
if (error && !offset)
error = ioctl(dev, SNAPSHOT_SET_SWAP_FILE, blkdev);
return error;
}
static int atomic_snapshot(int dev, int *in_suspend)
{
int error;
error = ioctl(dev, SNAPSHOT_CREATE_IMAGE, in_suspend);
if (error && errno == ENOTTY) {
report_unsupported_ioctl("SNAPSHOT_CREATE_IMAGE");
error = ioctl(dev, SNAPSHOT_ATOMIC_SNAPSHOT, in_suspend);
}
return error;
}
static inline int free_snapshot(int dev)
{
return ioctl(dev, SNAPSHOT_FREE, 0);
}
static int set_image_size(int dev, loff_t size)
{
int error;
error = ioctl(dev, SNAPSHOT_PREF_IMAGE_SIZE, size);
if (error && errno == ENOTTY) {
report_unsupported_ioctl("SNAPSHOT_PREF_IMAGE_SIZE");
error = ioctl(dev, SNAPSHOT_SET_IMAGE_SIZE, size);
}
return error;
}
static inline int suspend_to_ram(int dev)
{
return ioctl(dev, SNAPSHOT_S2RAM, 0);
}
static int platform_enter(int dev)
{
int error;
error = ioctl(dev, SNAPSHOT_POWER_OFF, 0);
if (error && errno == ENOTTY) {
report_unsupported_ioctl("SNAPSHOT_POWER_OFF");
error = ioctl(dev, SNAPSHOT_PMOPS, PMOPS_ENTER);
}
return error;
}
/**
* alloc_swap - allocate a number of swap pages
* @dev: Swap device to use for allocations.
* @extents: Array of extents to track the allocations.
* @nr_extents: Number of extents already in the array.
* @size_p: Points to the number of bytes to allocate, used to
* return the number of allocated bytes.
*
* Allocate the number of swap pages sufficient for saving the number of
* bytes pointed to by @size_p. Use the array @extents to track the
* allocations. This array has to be page_size big and may already
* contain some initial elements (in that case @nr_extents must be the
* number of these elements).
* Each element of the array represents an area of allocated swap space.
* These areas may be extended when swap pages that can be added to them
* are found. They also can be merged with one another.
* The function returns when the requested amount of swap space is
* allocated or if there is no room for more extents. In the latter case
* the last extent created is put at the end of the array and may be passed
* to alloc_swap() as the initial extent when it is invoked next time.
*/
static int
alloc_swap(int dev, struct extent *extents, int nr_extents, loff_t *size_p)
{
const int max_extents = page_size / sizeof(struct extent) - 1;
loff_t size, total_size, offset;
total_size = *size_p;
if (nr_extents <= 0) {
offset = get_swap_page(dev);
if (!offset)
return -ENOSPC;
extents->start = offset;
extents->end = offset + page_size;
nr_extents = 1;
size = page_size;
} else {
size = 0;
}
while (size < total_size && nr_extents <= max_extents) {
int i, j;
offset = get_swap_page(dev);
if (!offset)
return -ENOSPC;
/* Check if we have a matching extent. */
i = 0;
j = nr_extents - 1;
do {
struct extent *ext;
int k = (i + j) / 2;
Repeat:
ext = extents + k;
if (offset == ext->start - page_size) {
ext->start = offset;
/* Check if we can merge extents */
if (k > 0 && extents[k-1].end == offset) {
extents[k-1].end = ext->end;
/* Pull the 'later' extents forward */
memmove(ext, ext + 1,
(nr_extents - k - 1) *
sizeof(*ext));
nr_extents--;
}
offset = 0;
break;
} else if (offset == ext->end) {
ext->end += page_size;
/* Check if we can merge extents */
if (k + 1 < nr_extents
&& ext->end == extents[k+1].start) {
ext->end = extents[k+1].end;
/* Pull the 'later' extents forward */
memmove(ext + 1, ext + 2,
(nr_extents - k - 2) *
sizeof(*ext));
nr_extents--;
}
offset = 0;
break;
} else if (offset > ext->end) {
if (i == k) {
if (i < j) {
/* This means i == j + 1 */
k = j;
i = j;
goto Repeat;
}
} else {
i = k;
}
} else {
/* offset < ext->start - page_size */
j = k;
}
} while (i < j);
if (offset > 0) {
/* No match. Create a new extent. */
struct extent *ext;
if (nr_extents < max_extents) {
ext = extents + i;
/*
* We want to always replace the extent 'i' with
* the new one.
*/
if (offset > ext->end) {
i++;
ext++;
}
/* Push the 'later' extents backwards. */
memmove(ext + 1, ext,
(nr_extents - i) * sizeof(*ext));
} else {
ext = extents + nr_extents;
}
ext->start = offset;
ext->end = offset + page_size;
nr_extents++;
}
size += page_size;
}
*size_p = size;
return nr_extents;
}
/**
* write_page - Write page_size data to given swap location.
* @fd: File handle of the resume partition.
* @buf: Pointer to the area we're writing.
* @offset: Offset of the swap page we're writing to.
*/
static int write_page(int fd, void *buf, loff_t offset)
{
int res = 0;
ssize_t cnt = 0;
if (!offset)
return -EINVAL;
if (lseek64(fd, offset, SEEK_SET) == offset)
cnt = write(fd, buf, page_size);
if (cnt != page_size)
res = -EIO;
return res;
}
/*
* The swap_writer structure is used for handling swap in a file-alike way.
*
* @extents: Array of extents used for trackig swap allocations. It is
* page_size bytes large and holds at most
* (page_size / sizeof(struct extent) - 1) extents. The last slot
* is used to hold the extent that will be used as an initial one
* for the next batch of allocations.
*
* @nr_extents: Number of entries in @extents actually used.
*
* @cur_extent: The extent currently used as the source of swap pages.
*
* @cur_extent_idx: The index of @cur_extent.
*
* @cur_offset: The offset of the swap page that will be used next.
*
* @swap_needed: The amount of swap needed for saving the image.
*
* @written_data: The amount of data actually saved.
*
* @extents_spc: The swap page to which to save @extents.
*
* @buffer: Buffer used for storing image data pages.
*
* @write_buffer: If compression is used, the compressed contents of
* @buffer are stored here. Otherwise, it is equal to
* @buffer.
*
* @page_ptr: Address to write the next image page to.
*
* @dev: Snapshot device handle used for reading image pages and
* invoking ioctls.
*
* @fd: File handle associated with the swap.
*
* @ctx: Used for checksum computing, if so configured.
*
* @lzo_work_buffer: Work buffer used for compression.
*
* @encrypt_buffer: Buffer for storing encrypted data (page_size bytes).
*
* @encrypt_ptr: Address to store the next encrypted page at.
*/
struct swap_writer {
struct extent *extents;
int nr_extents;
struct extent *cur_extent;
int cur_extent_idx;
loff_t cur_offset;
loff_t swap_needed;
loff_t written_data;
loff_t extents_spc;
void *buffer;
void *write_buffer;
void *page_ptr;
int dev, fd, input;
struct md5_ctx ctx;
void *lzo_work_buffer;
void *encrypt_buffer;
void *encrypt_ptr;
};
/**
* free_swap_writer - free memory allocated for saving the image
* @handle: Structure containing pointers to memory buffers to free.
*/
static void free_swap_writer(struct swap_writer *handle)
{
if (handle->write_buffer != handle->buffer)
freemem(handle->write_buffer);
if (do_compress)
freemem(handle->lzo_work_buffer);
if (handle->encrypt_buffer)
freemem(handle->encrypt_buffer);
freemem(handle->buffer);
freemem(handle->extents);
}
/**
* init_swap_writer - initialize the structure used for saving the image
* @handle: Structure to initialize.
* @dev: Special device file to read image pages from.
* @fd: File descriptor associated with the swap.
*
* It doesn't preallocate swap, so preallocate_swap() has to be called on
* @handle after this.
*/
static int init_swap_writer(struct swap_writer *handle, int dev, int fd, int in)
{
loff_t offset;
unsigned int write_buf_size = 0;
handle->extents = getmem(page_size);
handle->buffer = getmem(buffer_size);
handle->page_ptr = handle->buffer;
if (do_encrypt) {
handle->encrypt_buffer = getmem(encrypt_buf_size);
handle->encrypt_ptr = handle->encrypt_buffer;
} else {
handle->encrypt_buffer = NULL;
}
if (do_compress) {
handle->lzo_work_buffer = getmem(LZO1X_1_MEM_COMPRESS);
write_buf_size = compress_buf_size;
if (use_threads)
write_buf_size +=
(WRITE_BUFFERS - 1) * compress_buf_size;
}
if (write_buf_size > 0)
handle->write_buffer = getmem(write_buf_size);
else if (use_threads)
handle->write_buffer = getmem(buffer_size * WRITE_BUFFERS);
else
handle->write_buffer = handle->buffer;
handle->dev = dev;
handle->fd = fd;
handle->input = (in >= 0) ? in : dev;
handle->written_data = 0;
memset(handle->extents, 0, page_size);
handle->nr_extents = 0;
offset = get_swap_page(dev);
if (!offset) {
free_swap_writer(handle);
return -ENOSPC;
}
handle->extents_spc = offset;
if (compute_checksum || verify_image)
md5_init_ctx(&handle->ctx);
return 0;
}
/**
* preallocate_swap - use alloc_swap() to preallocate the number of pages
* given by @handle->swap_needed
* @handle: Pointer to the structure in which to store information
* about the preallocated swap pool.
*
* Returns the offset of the first swap page available from the
* preallocated pool.
*/
static loff_t preallocate_swap(struct swap_writer *handle)
{
const int max = page_size / sizeof(struct extent) - 1;
loff_t size;
int nr_extents;
if (handle->swap_needed < page_size)
return 0;
size = handle->swap_needed;
if (do_compress && size > page_size)
size /= 2;
nr_extents = alloc_swap(handle->dev, handle->extents,
handle->nr_extents, &size);
if (nr_extents <= 0)
return 0;
handle->nr_extents = nr_extents < max ? nr_extents : max;
handle->cur_extent = handle->extents;
handle->cur_extent_idx = 0;
handle->cur_offset = handle->cur_extent->start;
return handle->cur_offset;
}
/**
* save_extents - save the array of extents
* handle: Structure holding the pointer to the array of extents etc.
* finish: If set, the last element of the extents array has to be filled
* with zeros.
*
* Save the buffer (page) holding the array of extents to the swap
* location pointed to by @handle->extents_spc (this must be allocated
* earlier). Before saving the last element of the array is used to store
* the swap offset of the next extents page (we allocate a swap page for
* this purpose).
*/
static int save_extents(struct swap_writer *handle, int finish)
{
loff_t offset = 0;
int error;
if (!finish) {
struct extent *last_extent;
offset = get_swap_page(handle->dev);
if (!offset)
return -ENOSPC;
last_extent = handle->extents +
page_size / sizeof(struct extent) - 1;
last_extent->start = offset;
}
error = write_page(handle->fd, handle->extents, handle->extents_spc);
handle->extents_spc = offset;
return error;
}
/**
* next_swap_page - take one swap page out of the pool allocated using
* alloc_swap() before
* @handle: Pointer to the structure containing information about
* the preallocated swap pool.
*/
static loff_t next_swap_page(struct swap_writer *handle)
{
struct extent ext;
handle->cur_offset += page_size;
if (handle->cur_offset < handle->cur_extent->end)
return handle->cur_offset;
/* We have exhausted the current extent. Forward to the next one */
handle->cur_extent++;
handle->cur_extent_idx++;
if (handle->cur_extent_idx < handle->nr_extents) {
handle->cur_offset = handle->cur_extent->start;
return handle->cur_offset;
}
/* No more extents. Is there anything to pass to alloc_swap()? */
if (handle->cur_extent->start < handle->cur_extent->end) {
ext = *handle->cur_extent;
memset(handle->cur_extent, 0, sizeof(struct extent));
handle->nr_extents = 1;
} else {
memset(&ext, 0, sizeof(struct extent));
handle->nr_extents = 0;
}
if (save_extents(handle, 0))
return 0;
memset(handle->extents, 0, page_size);
*handle->extents = ext;
return preallocate_swap(handle);
}
/**
* save_page - save one page of data to the swap
* @handle: Pointer to the structure containing information about
* the swap.
* @src: Pointer to the data.
*/
static int save_page(struct swap_writer *handle, void *src)
{
loff_t offset;
int error;
offset = next_swap_page(handle);
if (!offset)
return -ENOSPC;
error = write_page(handle->fd, src, offset);
if (error)
return error;
handle->swap_needed -= page_size;
handle->written_data += page_size;
return 0;
}
#ifdef CONFIG_THREADS
/*
* If threads are used for saving the image with compression and encryption,
* there are three of them.
*
* The main one reads image pages from the kernel and puts them into a work
* buffer. When the work buffer is full, it gets compressed, but that's not an
* in-place compression, so the result has to be stored somewhere else. There
* are four so-called "write" buffers for that and the first empty "write"
* buffer is used as the target. If all of the "write" buffers are full, the
* thread has to wait (see the rules below). Otherwise, after placing the
* (compressed) contents of the work buffer into a "write" buffer, the main
* thread regards the work buffer as empty and starts to read more image pages
* from the kernel.
*
* The second thread (call it the "move" thread) encrypts the contents of the
* "write" buffers, one buffer at a time. It really encrypts individual pages
* and the encryption is not in-place, too. The encrypted pages of data are
* placed in yet another buffer (call it the "encrypt" buffer) until it's full,
* in which case the "move" thread has to wait. Of course, it also has to wait
* for data from the main thread if all of the "write" buffers are empty.
* After encrypting an entire "write" buffer, the "move" thread progresses to
* the next "write" buffer, in a round-robin manner.
*
* The synchronization between the main thread and the "move" thread is done
* with the help of two index variables, move_start and move_end. �The rule
* is that:
* (1) the main thread can only put data into write_buffers[move_start],
* (2) after putting data into write_buffers[move_start], the main thread
* increases move_start, modulo the number of "write" buffers, but
* move_start cannot be modified as long as the _next_ "write" buffer is
* write_buffers[move_end] (the thread has to wait if that happens),
* (3) the "move" thread can only read data from write_buffers[move_end] and
* only if move_end != move_start (it has to wait if that's not the case),
* (4) after reading data from write_buffers[move_end], the "move" thread
* increases move_end, modulo the number of "write" buffers.
* This way, move_end always "follows" move_start and the threads don't access
* the same buffer at any time.
*
* The third thread (call it the "save" thread) reads (encrypted) pages of data
* from the "encrypt" buffer and writes them out to the swap. This is done if
* there are some pages to write in the "encrypt" buffer, otherwise the "save"
* thread has to wait for the "move" thread to put more pages in there.
*
* The synchronization between the "move" thread and the "save" thread is done
* with the help of two pointers, save_start and save_end, where save_start
* points to the first empty page and save_end points to the last data page
* that hasn't been written out yet. Thus, the rule is:
* (1) the "move" thread can only put data into the page pointed to by
* save_start,
* (2) after putting data into the page pointed to by save_start, the "move"
* thread increases save_start, modulo the number of pages in the buffer,
* provided that the _next_ page is not the one pointed to by save_end (it
* has to wait if that happens),
* (3) the "save" thread can only read from the page pointed to by save_end,
* as long as save_end != save_start (it has to wait if the two pointers
* are equal),
* (4) after writing data from the page pointed to by save_end, the "save"
* thread increases save_end, modulo the number of pages in the buffer.
* IOW, the "encrypt" buffer is handled as a typical circular buffer with one
* producer (the "move" thread) and one consumer (the "save" thread).
*
* If encryption is not used, the "save" thread is not started and the "move"
* thread writes data to the swap directly out of the "write" buffers.
*/
static int save_ret;
static pthread_mutex_t finish_mutex;
static pthread_cond_t finish_cond;
static char *encrypt_buf;
static char *save_start, *save_end;
static pthread_mutex_t save_mutex;
static pthread_cond_t save_cond;
static pthread_t save_th;
struct write_buffer {
ssize_t size;
void *start;
};
static struct write_buffer write_buffers[WRITE_BUFFERS];
static int move_start, move_end;
static pthread_mutex_t move_mutex;
static pthread_cond_t move_cond;
static pthread_t move_th;
#define FORCE_EXIT 1
static char *save_inc(char *ptr)
{
return encrypt_buf +
(((ptr - encrypt_buf) + page_size) % encrypt_buf_size);
}
static int move_inc(int index)
{
return (index + 1) % WRITE_BUFFERS;
}
static int wait_for_finish(void)
{
pthread_mutex_lock(&finish_mutex);
while((save_end != save_start || move_start != move_end) && !save_ret)
pthread_cond_wait(&finish_cond, &finish_mutex);
pthread_mutex_unlock(&finish_mutex);
return save_ret;
}
static void *save_thread(void *arg)
{
struct swap_writer *handle = arg;
int error = 0;
for (;;) {
/* Wait until there is a buffer ready for processing. */
pthread_mutex_lock(&save_mutex);
while(save_end == save_start && !save_ret)
pthread_cond_wait(&save_cond, &save_mutex);
pthread_mutex_unlock(&save_mutex);
if (save_ret)
return NULL;
error = save_page(handle, save_end);
if (error) {
pthread_mutex_lock(&finish_mutex);
if (!save_ret)
save_ret = error;
pthread_mutex_unlock(&finish_mutex);
pthread_cond_signal(&move_cond);
pthread_cond_signal(&save_cond);
pthread_cond_signal(&finish_cond);
return NULL;
}
/* Go to the next page */
pthread_mutex_lock(&finish_mutex);
pthread_mutex_lock(&save_mutex);
save_end = save_inc(save_end);
pthread_mutex_unlock(&save_mutex);
pthread_mutex_unlock(&finish_mutex);
pthread_cond_signal(&save_cond);
pthread_cond_signal(&finish_cond);
}
return NULL;
}
#ifdef CONFIG_ENCRYPT
static void encrypt_and_save_buffer(void)
{
char *src;
ssize_t buf_size, moved_size;
/*
* The buffer to process is at write_buffers[move_end].start and the
* size of it is write_buffers[move_end].size .
*/
src = write_buffers[move_end].start;
buf_size = write_buffers[move_end].size;
moved_size = 0;
do {
int error;
void *next_start;
/* Encrypt page_size of data. */
error = gcry_cipher_encrypt(cipher_handle,
save_start, page_size,
src, page_size);
if (error) {
pthread_mutex_lock(&finish_mutex);
if (!save_ret)
save_ret = error;
pthread_mutex_unlock(&finish_mutex);
pthread_cond_signal(&move_cond);
pthread_cond_signal(&save_cond);
pthread_cond_signal(&finish_cond);
break;
}
moved_size += page_size;
src += page_size;
pthread_mutex_lock(&save_mutex);
next_start = save_inc(save_start);
while (next_start == save_end && !save_ret)
pthread_cond_wait(&save_cond, &save_mutex);
save_start = next_start;
pthread_mutex_unlock(&save_mutex);
pthread_cond_signal(&save_cond);
} while (moved_size < buf_size && !save_ret);
}
#else /* !CONFIG_ENCRYPT */
static inline void encrypt_and_save_buffer(void) {}
#endif /* !CONFIG_ENCRYPT */
static void save_buffer(struct swap_writer *handle)
{
void *src;
ssize_t size;
/*
* The buffer to process is at write_buffers[move_end].start and the
* size of it is write_buffers[move_end].size .
*/
src = write_buffers[move_end].start;
size = write_buffers[move_end].size;
while (size > 0) {
int error = save_page(handle, src);
if (error) {
pthread_mutex_lock(&finish_mutex);
if (!save_ret)
save_ret = error;
pthread_mutex_unlock(&finish_mutex);
pthread_cond_signal(&move_cond);
pthread_cond_signal(&finish_cond);
break;
}
src += page_size;
size -= page_size;
}
}
static void *move_thread(void *arg)
{
struct swap_writer *handle = arg;
for (;;) {
/* Wait until there is a buffer ready for processing. */
pthread_mutex_lock(&move_mutex);
while(move_end == move_start && !save_ret)
pthread_cond_wait(&move_cond, &move_mutex);
pthread_mutex_unlock(&move_mutex);
if (save_ret)
break;
if (do_encrypt)
encrypt_and_save_buffer();
else
save_buffer(handle);
if (save_ret)
break;
/* Tell the reader thread that we have processed the buffer */
pthread_mutex_lock(&finish_mutex);
pthread_mutex_lock(&move_mutex);
move_end = move_inc(move_end);
pthread_mutex_unlock(&move_mutex);
pthread_mutex_unlock(&finish_mutex);
pthread_cond_signal(&move_cond);
pthread_cond_signal(&finish_cond);
}
return NULL;
}
static inline void *current_write_buffer(void)
{
return write_buffers[move_start].start;
}
static int prepare_next_write_buffer(ssize_t size)
{
int next_start;
/* Move to the next buffer and signal that the current one is ready*/
write_buffers[move_start].size = size;
pthread_mutex_lock(&move_mutex);
next_start = move_inc(move_start);
while (next_start == move_end && !save_ret)
pthread_cond_wait(&move_cond, &move_mutex);
move_start = next_start;
pthread_mutex_unlock(&move_mutex);
pthread_cond_signal(&move_cond);
return save_ret;
}
static void start_threads(struct swap_writer *handle)
{
int error;
unsigned int write_buf_size;
char *write_buf;
int j;
encrypt_buf = handle->encrypt_buffer;
save_start = encrypt_buf;
save_end = save_start;
write_buf_size = do_compress ? compress_buf_size : buffer_size;
write_buf = handle->write_buffer;
for (j = 0; j < WRITE_BUFFERS; j++) {
write_buffers[j].start = write_buf;
write_buf += write_buf_size;
}
move_start = 0;
move_end = move_start;
if (do_encrypt) {
error = pthread_mutex_init(&save_mutex, NULL);
if (error) {
perror("pthread_mutex_init() failed:");
goto Error_exit;
}
error = pthread_cond_init(&save_cond, NULL);
if (error) {
perror("pthread_cond_init() failed:");
goto Destroy_save_mutex;
}
}
error = pthread_mutex_init(&move_mutex, NULL);
if (error) {
perror("pthread_mutex_init() failed:");
goto Destroy_save_cond;
}
error = pthread_cond_init(&move_cond, NULL);
if (error) {
perror("pthread_cond_init() failed:");
goto Destroy_move_mutex;
}
if (do_encrypt) {
error = pthread_create(&save_th, NULL, save_thread, handle);
if (error) {
perror("pthread_create() failed:");
goto Destroy_move_cond;
}
}
error = pthread_create(&move_th, NULL, move_thread, handle);
if (error) {
perror("pthread_create() failed:");
goto Stop_save_thread;
}
error = pthread_mutex_init(&finish_mutex, NULL);
if (error) {
perror("pthread_mutex_init() failed:");
goto Stop_move_thread;
}
error = pthread_cond_init(&finish_cond, NULL);
if (error) {
perror("pthread_cond_init() failed:");
goto Destroy_finish_mutex;
}
return;
Destroy_finish_mutex:
pthread_mutex_destroy(&finish_mutex);
Stop_move_thread:
save_ret = FORCE_EXIT;
pthread_cond_signal(&move_cond);
pthread_join(move_th, NULL);
Stop_save_thread:
if (do_encrypt) {
save_ret = FORCE_EXIT;
pthread_cond_signal(&save_cond);
pthread_join(save_th, NULL);
}
Destroy_move_cond:
pthread_cond_destroy(&move_cond);
Destroy_move_mutex:
pthread_mutex_destroy(&move_mutex);
Destroy_save_cond:
if (do_encrypt)
pthread_cond_destroy(&save_cond);
Destroy_save_mutex:
if (do_encrypt)
pthread_mutex_destroy(&save_mutex);
Error_exit:
use_threads = 0;
}
static void stop_threads(void)
{
pthread_mutex_lock(&finish_mutex);
if (!save_ret)
save_ret = FORCE_EXIT;
pthread_mutex_unlock(&finish_mutex);
pthread_cond_destroy(&finish_cond);
pthread_mutex_destroy(&finish_mutex);
pthread_cond_signal(&move_cond);
pthread_join(move_th, NULL);
if (do_encrypt) {
pthread_cond_signal(&save_cond);
pthread_join(save_th, NULL);
}
pthread_cond_destroy(&move_cond);
pthread_mutex_destroy(&move_mutex);
if (do_encrypt) {
pthread_cond_destroy(&save_cond);
pthread_mutex_destroy(&save_mutex);
}
}
#else /* !CONFIG_THREADS */
static inline int wait_for_finish(void) { return -ENOSYS; }
static inline void *current_write_buffer(void) { return NULL; }
static inline int prepare_next_write_buffer(ssize_t size)
{
(void)size;
return -ENOSYS;
}
static inline void start_threads(struct swap_writer *handle) { (void)handle; }
static inline void stop_threads(void) {}
#endif /* !CONFIG_THREADS */
/**
* encrypt_and_save_page - encrypt a page of data and write it to the swap
*/
static int encrypt_and_save_page(struct swap_writer *handle, void *src)
{
#ifdef CONFIG_ENCRYPT
if (do_encrypt) {
int error = gcry_cipher_encrypt(cipher_handle,
handle->encrypt_ptr, page_size, src, page_size);
if (error)
return error;
src = handle->encrypt_ptr;
handle->encrypt_ptr += page_size;
if (handle->encrypt_ptr - handle->encrypt_buffer
>= encrypt_buf_size)
handle->encrypt_ptr = handle->encrypt_buffer;
}
#endif
return save_page(handle, src);
}
/**
* flush_buffer - flush data stored in the buffer to the swap
*/
static int flush_buffer(struct swap_writer *handle)
{
ssize_t size;
char *src;
int error = 0;
/* Check if there is anything to do */
if (handle->page_ptr <= handle->buffer)
return 0;
size = handle->page_ptr - handle->buffer;
if (compute_checksum || verify_image)
md5_process_block(handle->buffer, size, &handle->ctx);
src = use_threads ? current_write_buffer() : handle->write_buffer;
/* Compress the buffer, if necessary */
if (do_compress) {
#ifdef CONFIG_COMPRESS
struct buf_block *block = (struct buf_block *)src;
lzo_uint cnt;
lzo1x_1_compress(handle->buffer, size,
(lzo_bytep)block->data, &cnt,
handle->lzo_work_buffer);
block->size = cnt;
size = cnt + sizeof(size_t);
#endif
} else if (use_threads) {
memcpy(src, handle->buffer, size);
}
if (use_threads)
return prepare_next_write_buffer(size);
/*
* If there's no compression and threads are not used, handle->buffer is
* equal to handle->write_buffer. In that case, the data are taken
* directly out of handle->buffer.
*/
while (size > 0) {
error = encrypt_and_save_page(handle, src);
if (error)
break;
src += page_size;
size -= page_size;
}
return error;
}
/**
* save_image - save the hibernation image data
*/
static int save_image(struct swap_writer *handle, unsigned int nr_pages)
{
unsigned int m, writeout_rate;
ssize_t ret;
struct termios newtrm, savedtrm;
int abort_possible, key, error = 0;
char message[SPLASH_GENERIC_MESSAGE_SIZE];
/* Switch the state of the terminal so that we can read the keyboard
* without blocking and with no echo.
*
* stdin must be attached to the terminal now.
*/
abort_possible = !splash.prepare_abort(&savedtrm, &newtrm);
sprintf(message, "Saving %u image data pages", nr_pages);
if (abort_possible)
strcat(message, " (press " ABORT_KEY_NAME " to abort) ");
strcat(message, "...");
printf("%s: %s ", my_name, message);
splash.set_caption(message);
if (use_threads)
start_threads(handle);
m = nr_pages / 100;
if (!m)
m = 1;
if (early_writeout)
writeout_rate = m;
else
writeout_rate = nr_pages + 1;
/* The buffer may be partially filled at this point */
for (nr_pages = 0; ; nr_pages++) {
ret = read(handle->input, handle->page_ptr, page_size);
if (ret < page_size) {
if (ret < 0) {
error = -EIO;
perror("\nError reading an image page");
} else if (ret > 0) {
error = -EFAULT;
perror("\nShort read from /dev/snapshot?");
}
break;
}
handle->page_ptr += page_size;
if (!(nr_pages % m)) {
printf("\b\b\b\b%3d%%", nr_pages / m);
splash.progress(20 + (nr_pages / m) * 0.75);
while ((key = splash.key_pressed()) > 0) {
switch (key) {
case ABORT_KEY_CODE:
if (abort_possible) {
printf(" aborted!\n");
error = -EINTR;
goto Exit;
}
break;
case REBOOT_KEY_CODE:
printf (" reboot enabled\b\b\b\b\b\b\b"
"\b\b\b\b\b\b\b\b");
splash.set_caption("Reboot enabled");
shutdown_method =
SHUTDOWN_METHOD_REBOOT;
break;
}
}
}
if (!((nr_pages + 1) % writeout_rate))
start_writeout(handle->fd);
if (handle->page_ptr - handle->buffer >= buffer_size) {
/* The buffer is full, flush it */
error = flush_buffer(handle);
if (error)
break;
handle->page_ptr = handle->buffer;
}
}
if (!error) {
/* Flush whatever's left in the buffer and save the extents */
error = flush_buffer(handle);
if (use_threads)
error = wait_for_finish();
if (!error)
error = save_extents(handle, 1);
if (!error)
printf(" done (%u pages)\n", nr_pages);
}
Exit:
if (use_threads)
stop_threads();
if (abort_possible)
splash.restore_abort(&savedtrm);
return error;
}
/**
* enough_swap - Make sure we have enough swap to save the image.
*
* Returns TRUE or FALSE after checking the total amount of swap
* space avaiable from the resume partition.
*/
static int enough_swap(struct swap_writer *handle)
{
loff_t free_swap = check_free_swap(handle->dev);
loff_t size = do_compress ?
handle->swap_needed / 2 : handle->swap_needed;
printf("%s: Free swap: %llu kilobytes\n", my_name,
(unsigned long long)free_swap / 1024);
return free_swap > size;
}
static struct swsusp_header swsusp_header;
static int mark_swap(int fd, loff_t start)
{
int error = 0;
unsigned int size = sizeof(struct swsusp_header);
off64_t shift = ((off64_t)resume_offset + 1) * page_size - size;
if (lseek64(fd, shift, SEEK_SET) != shift)
return -EIO;
if (read(fd, &swsusp_header, size) < size)
return -EIO;
if (!memcmp("SWAP-SPACE", swsusp_header.sig, 10) ||
!memcmp("SWAPSPACE2", swsusp_header.sig, 10)) {
memcpy(swsusp_header.orig_sig, swsusp_header.sig, 10);
memcpy(swsusp_header.sig, SWSUSP_SIG, 10);
swsusp_header.image = start;
if (lseek64(fd, shift, SEEK_SET) != shift)
return -EIO;
if (write(fd, &swsusp_header, size) < size)
error = -EIO;
} else {
error = -ENODEV;
}
return error;
}
/**
* write_image - Write entire image and metadata.
* @snapshot_fd: File handle of the snapshot device
* @resume_fd: File handle of the swap device used for image saving
* @test_fd: (Optional) File handle of a file to read the image from
*
* If @test_fd is not negative, the function works in the test mode in
* which the image is read from a regular file instead of the snapshot
* device.
*/
static int write_image(int snapshot_fd, int resume_fd, int test_fd)
{
static struct swap_writer handle;
struct image_header_info *header;
loff_t start;
loff_t image_size;
double real_size;
unsigned long nr_pages = 0;
int error, test_mode = (test_fd >= 0);
struct timeval begin;
printf("%s: System snapshot ready. Preparing to write\n", my_name);
/* Allocate a swap page for the additional "userland" header */
start = alloc_swap_page(snapshot_fd, 1);
if (!start)
return -ENOSPC;
header = getmem(page_size);
memset(header, 0, page_size);
error = init_swap_writer(&handle, snapshot_fd, resume_fd, test_fd);
if (error)
goto Exit;
image_size = test_mode ? test_image_size : get_image_size(snapshot_fd);
if (image_size > 0) {
nr_pages = (unsigned long)((image_size + page_size - 1) /
page_size);
} else {
/*
* The kernel doesn't allow us to get the image size via ioctl,
* so we need to read it from the image header.
*/
struct swsusp_info *image_header;
ssize_t ret;
/*
* Do it in such a way that save_image() will believe it has
* already read the header page.
*/
image_header = handle.page_ptr;
ret = read(snapshot_fd, image_header, page_size);
if (ret < page_size) {
error = ret < 0 ? ret : -EFAULT;
goto Free_writer;
}
handle.page_ptr += page_size;
image_size = image_header->size;
nr_pages = image_header->pages;
if (!nr_pages) {
error = -ENODATA;
goto Free_writer;
}
/* We have already read one page */
nr_pages--;
}
printf("%s: Image size: %lu kilobytes\n", my_name, (unsigned long) image_size / 1024);
real_size = image_size;
handle.swap_needed = image_size;
if (do_compress) {
/* This is necessary in case the image is not compressible */
handle.swap_needed += round_up_page_size(
(handle.swap_needed >> 4) + 67);
}
if (!enough_swap(&handle)) {
fprintf(stderr, "%s: Not enough free swap\n", my_name);
error = -ENOSPC;
goto Free_writer;
}
if (!preallocate_swap(&handle)) {
fprintf(stderr, "%s: Failed to allocate swap\n", my_name);
error = -ENOSPC;
goto Free_writer;
}
/* Shift handle.cur_offset for the first call to next_swap_page() */
handle.cur_offset -= page_size;
header->pages = nr_pages;
header->flags = 0;
header->map_start = handle.extents_spc;
if (compute_checksum)
header->flags |= IMAGE_CHECKSUM;
if (do_compress)
header->flags |= IMAGE_COMPRESSED;
#ifdef CONFIG_ENCRYPT
if (!do_encrypt)
goto Save_image;
if (use_RSA) {
error = gcry_cipher_setkey(cipher_handle, key_data.key,
KEY_SIZE);
if (error)
goto No_RSA;
error = gcry_cipher_setiv(cipher_handle, key_data.ivec,
CIPHER_BLOCK);
if (error)
goto No_RSA;
header->flags |= IMAGE_ENCRYPTED | IMAGE_USE_RSA;
memcpy(&header->rsa, &key_data.rsa, sizeof(struct RSA_data));
memcpy(&header->key, &key_data.encrypted_key,
sizeof(struct encrypted_key));
} else {
int j;
No_RSA:
encrypt_init(key_data.key, key_data.ivec, password);
splash.progress(20);
get_random_salt(header->salt, CIPHER_BLOCK);
for (j = 0; j < CIPHER_BLOCK; j++)
key_data.ivec[j] ^= header->salt[j];
error = gcry_cipher_setkey(cipher_handle, key_data.key,
KEY_SIZE);
if (!error)
error = gcry_cipher_setiv(cipher_handle, key_data.ivec,
CIPHER_BLOCK);
if (!error)
header->flags |= IMAGE_ENCRYPTED;
}
if (error) {
fprintf(stderr,"%s: libgcrypt error: %s\n", my_name,
gcry_strerror(error));
goto Free_writer;
}
Save_image:
#endif
gettimeofday(&begin, NULL);
error = save_image(&handle, nr_pages);
if (!error) {
struct timeval end;
fsync(resume_fd);
header->image_data_size = handle.written_data;
real_size = handle.written_data;
/*
* NOTICE: This needs to go after save_image(), because the
* user may modify the behavior.
*/
if (shutdown_method == SHUTDOWN_METHOD_PLATFORM)
header->flags |= PLATFORM_SUSPEND;
if (compute_checksum || verify_image)
md5_finish_ctx(&handle.ctx, header->checksum);
gettimeofday(&end, NULL);
timersub(&end, &begin, &end);
header->writeout_time = end.tv_usec / 1000000.0 + end.tv_sec;
header->resume_pause = resume_pause;
error = write_page(resume_fd, header, start);
fsync(resume_fd);
}
Free_writer:
free_swap_writer(&handle);
if (!error && (verify_image || test_mode)) {
splash.progress(0);
if (verify_image)
printf("%s: Image verification\n", my_name);
error = read_or_verify(snapshot_fd, resume_fd, header, start,
verify_image, test_mode);
if (verify_image)
printf(error ? "%s: Image verification failed\n" :
"%s: Image verified successfully\n",
my_name);
splash.progress(100);
}
if (!error) {
if (do_compress) {
printf("%s: Compression ratio %4.2lf\n", my_name,
real_size / image_size);
}
printf("S");
error = mark_swap(resume_fd, start);
if (!error) {
fsync(resume_fd);
printf( "|" );
}
printf("\n");
}
Exit:
freemem(header);
return error;
}
static int reset_signature(int fd)
{
int ret, error = 0;
unsigned int size = sizeof(struct swsusp_header);
off64_t shift = ((off64_t)resume_offset + 1) * page_size - size;
if (lseek64(fd, shift, SEEK_SET) != shift)
return -EIO;
memset(&swsusp_header, 0, size);
ret = read(fd, &swsusp_header, size);
if (ret == size) {
if (memcmp(SWSUSP_SIG, swsusp_header.sig, 10)) {
/* Impossible? We wrote signature and it is not there?! */
error = -EINVAL;
}
} else {
error = ret < 0 ? ret : -EIO;
}
if (!error) {
/* Reset swap signature now */
memcpy(swsusp_header.sig, swsusp_header.orig_sig, 10);
if (lseek64(fd, shift, SEEK_SET) == shift) {
ret = write(fd, &swsusp_header, size);
if (ret != size)
error = ret < 0 ? ret : -EIO;
} else {
error = -EIO;
}
}
fsync(fd);
if (error) {
fprintf(stderr, "%s: Error %d resetting the image.\n"
"There should be valid image on disk. "
"Powerdown and carry out normal resume.\n"
"Continuing with this booted system "
"will lead to data corruption.\n", my_name, error);
while(1)
sleep(10);
}
return error;
}
static void suspend_shutdown(int snapshot_fd)
{
splash.set_caption("Done.");
if (shutdown_method == SHUTDOWN_METHOD_REBOOT) {
reboot();
} else if (shutdown_method == SHUTDOWN_METHOD_PLATFORM) {
if (platform_enter(snapshot_fd))
suspend_error("Could not enter the hibernation state, "
"calling power_off.");
}
power_off();
/* Signature is on disk, it is very dangerous to continue now.
* We'd do resume with stale caches on next boot. */
fprintf(stderr,"Powerdown failed. That's impossible.\n");
while(1)
sleep (60);
}
int suspend_system(int snapshot_fd, int resume_fd, int test_fd)
{
loff_t avail_swap;
loff_t image_size;
int attempts, in_suspend, error = 0;
char message[SPLASH_GENERIC_MESSAGE_SIZE];
avail_swap = check_free_swap(snapshot_fd);
if (avail_swap > pref_image_size)
image_size = pref_image_size;
else
image_size = avail_swap;
if (!avail_swap) {
suspend_error("Not enough swap space for suspend");
return ENOSPC;
}
error = freeze(snapshot_fd);
/* This a hack for a bug in bootsplash. Apparently it will
* drop to 'verbose mode' after the freeze() call.
*/
splash.switch_to();
splash.progress(15);
if (error) {
suspend_error("Freeze failed.");
goto Unfreeze;
}
if (test_fd >= 0) {
printf("%s: Running in test mode\n", my_name);
error = write_image(snapshot_fd, resume_fd, test_fd);
if (error)
error = -error;
reset_signature(resume_fd);
free_swap_pages(snapshot_fd);
goto Unfreeze;
}
if (shutdown_method == SHUTDOWN_METHOD_PLATFORM) {
if (platform_prepare(snapshot_fd)) {
suspend_error("Unable to use platform hibernation "
"support, using shutdown mode.");
shutdown_method = SHUTDOWN_METHOD_SHUTDOWN;
}
}
sprintf(message, "Snapshotting system");
printf("%s: %s\n", my_name, message);
splash.set_caption(message);
attempts = 2;
do {
if (set_image_size(snapshot_fd, image_size)) {
error = errno;
break;
}
if (atomic_snapshot(snapshot_fd, &in_suspend)) {
error = errno;
break;
}
if (!in_suspend) {
/* first unblank the console, see console_codes(4) */
printf("\e[13]");
printf("%s: returned to userspace\n", my_name);
free_snapshot(snapshot_fd);
break;
}
error = write_image(snapshot_fd, resume_fd, -1);
if (error) {
free_swap_pages(snapshot_fd);
free_snapshot(snapshot_fd);
image_size = 0;
error = -error;
if (error != ENOSPC)
break;
} else {
splash.progress(100);
#ifdef CONFIG_BOTH
if (s2ram_kms || s2ram) {
/* If we die (and allow system to continue)
* between now and reset_signature(), very bad
* things will happen. */
error = suspend_to_ram(snapshot_fd);
if (error)
goto Shutdown;
reset_signature(resume_fd);
free_swap_pages(snapshot_fd);
free_snapshot(snapshot_fd);
if (!s2ram_kms)
s2ram_resume();
goto Unfreeze;
}
Shutdown:
#endif
close(resume_fd);
suspend_shutdown(snapshot_fd);
}
} while (--attempts);
Unfreeze:
/*
* We get here during the resume or when we failed to suspend.
* Remember, suspend_shutdown() never returns!
*/
unfreeze(snapshot_fd);
return error;
}
/**
* console_fd - get file descriptor for given file name and verify
* if that's a console descriptor (based on the code of openvt)
*/
static inline int console_fd(const char *fname)
{
int fd;
char arg;
fd = open(fname, O_RDONLY);
if (fd < 0 && errno == EACCES)
fd = open(fname, O_WRONLY);
if (fd >= 0 && (ioctl(fd, KDGKBTYPE, &arg)
|| (arg != KB_101 && arg != KB_84))) {
close(fd);
return -ENOTTY;
}
return fd;
}
#ifndef TIOCL_GETKMSGREDIRECT
#define TIOCL_GETKMSGREDIRECT 17
#endif
static int set_kmsg_redirect;
/**
* prepare_console - find a spare virtual terminal, open it and attach
* the standard streams to it. The number of the currently active
* virtual terminal is saved via @orig_vc
*/
static int prepare_console(int *orig_vc, int *new_vc)
{
int fd, error, vt = -1;
char vt_name[GENERIC_NAME_SIZE];
struct vt_stat vtstat;
char clear_vt, tiocl[2];
fd = console_fd("/dev/console");
if (fd < 0)
return fd;
tiocl[0] = TIOCL_GETKMSGREDIRECT;
if (!ioctl(fd, TIOCLINUX, tiocl)) {
if (tiocl[0] > 0)
vt = tiocl[0];
}
clear_vt = 0;
error = ioctl(fd, VT_GETSTATE, &vtstat);
if (!error) {
*orig_vc = vtstat.v_active;
if (vt < 0) {
clear_vt = 1;
error = ioctl(fd, VT_OPENQRY, &vt);
}
}
close(fd);
if (error || vt < 0)
return -1;
sprintf(vt_name, "/dev/tty%d", vt);
fd = open(vt_name, O_RDWR);
if (fd < 0)
return fd;
error = ioctl(fd, VT_ACTIVATE, vt);
if (error) {
suspend_error("Could not activate the VT %d.", vt);
fflush(stderr);
goto Close_fd;
}
error = ioctl(fd, VT_WAITACTIVE, vt);
if (error) {
suspend_error("VT %d activation failed.", vt);
fflush(stderr);
goto Close_fd;
}
if (clear_vt) {
char *msg = "\33[H\33[J";
write(fd, msg, strlen(msg));
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
*new_vc = vt;
set_kmsg_redirect = !tiocl[0];
if (set_kmsg_redirect) {
tiocl[0] = TIOCL_SETKMSGREDIRECT;
tiocl[1] = vt;
if (ioctl(fd, TIOCLINUX, tiocl)) {
suspend_error("Failed to redirect kernel messages "
"to VT %d.", vt);
fflush(stderr);
set_kmsg_redirect = 0;
}
}
return fd;
Close_fd:
close(fd);
return error;
}
/**
* restore_console - switch to the virtual console that was active before
* suspend
*/
static void restore_console(int fd, int orig_vc)
{
int error;
error = ioctl(fd, VT_ACTIVATE, orig_vc);
if (error) {
suspend_error("Could not activate the VT %d.", orig_vc);
fflush(stderr);
goto Close_fd;
}
error = ioctl(fd, VT_WAITACTIVE, orig_vc);
if (error) {
suspend_error("VT %d activation failed.", orig_vc);
fflush(stderr);
}
if (set_kmsg_redirect) {
char tiocl[2];
tiocl[0] = TIOCL_SETKMSGREDIRECT;
tiocl[1] = 0;
ioctl(fd, TIOCLINUX, tiocl);
}
Close_fd:
close(fd);
}
static FILE *swappiness_file;
static inline void open_swappiness(void)
{
swappiness_file = fopen("/proc/sys/vm/swappiness", "r+");
}
static inline int get_swappiness(void)
{
int swappiness = -1;
if (swappiness_file) {
rewind(swappiness_file);
fscanf(swappiness_file, "%d", &swappiness);
}
return swappiness;
}
static inline void set_swappiness(int swappiness)
{
if (swappiness_file) {
rewind(swappiness_file);
fprintf(swappiness_file, "%d\n", swappiness);
fflush(swappiness_file);
}
}
static inline void close_swappiness(void)
{
if (swappiness_file)
fclose(swappiness_file);
}
#ifdef CONFIG_ENCRYPT
static void generate_key(void)
{
gcry_ac_handle_t rsa_hd;
gcry_ac_data_t rsa_data_set, key_set;
gcry_ac_key_t rsa_pub;
gcry_mpi_t mpi;
size_t size;
int ret, fd, rnd_fd;
struct RSA_data *rsa;
unsigned char *buf;
int j;
fd = open(key_name, O_RDONLY);
if (fd < 0)
return;
rsa = &key_data.rsa;
if (read(fd, rsa, sizeof(struct RSA_data)) <= 0)
goto Close;
ret = gcry_ac_open(&rsa_hd, GCRY_AC_RSA, 0);
if (ret)
goto Close;
buf = rsa->data;
ret = gcry_ac_data_new(&rsa_data_set);
if (ret)
goto Free_rsa;
for (j = 0; j < RSA_FIELDS_PUB; j++) {
size_t s = rsa->size[j];
gcry_mpi_scan(&mpi, GCRYMPI_FMT_USG, buf, s, NULL);
ret = gcry_ac_data_set(rsa_data_set, GCRY_AC_FLAG_COPY,
rsa->field[j], mpi);
gcry_mpi_release(mpi);
if (ret)
break;
buf += s;
}
if (!ret)
ret = gcry_ac_key_init(&rsa_pub, rsa_hd, GCRY_AC_KEY_PUBLIC,
rsa_data_set);
if (ret)
goto Destroy_data_set;
ret = gcry_ac_data_new(&key_set);
if (ret)
goto Destroy_key;
rnd_fd = open("/dev/urandom", O_RDONLY);
if (rnd_fd <= 0)
goto Destroy_key_set;
size = KEY_SIZE + CIPHER_BLOCK;
for (;;) {
unsigned char *res;
size_t test;
int cmp;
if (read(rnd_fd, key_data.key, size) != size)
goto Close_urandom;
gcry_mpi_scan(&mpi, GCRYMPI_FMT_USG, key_data.key, size, NULL);
gcry_mpi_aprint(GCRYMPI_FMT_USG, &res, &test, mpi);
cmp = memcmp(key_data.key, res, size);
gcry_free(res);
if (test == size && !cmp)
break;
gcry_mpi_release(mpi);
}
ret = gcry_ac_data_encrypt(rsa_hd, 0, rsa_pub, mpi, &key_set);
gcry_mpi_release(mpi);
if (!ret) {
struct encrypted_key *key = &key_data.encrypted_key;
char *str;
size_t s;
gcry_ac_data_get_index(key_set, GCRY_AC_FLAG_COPY, 0,
(const char **)&str, &mpi);
gcry_free(str);
ret = gcry_mpi_print(GCRYMPI_FMT_USG, key->data, KEY_DATA_SIZE,
&s, mpi);
gcry_mpi_release(mpi);
if (!ret) {
key->size = s;
use_RSA = 'y';
}
}
Close_urandom:
close(rnd_fd);
Destroy_key_set:
gcry_ac_data_destroy(key_set);
Destroy_key:
gcry_ac_key_destroy(rsa_pub);
Destroy_data_set:
gcry_ac_data_destroy(rsa_data_set);
Free_rsa:
gcry_ac_close(rsa_hd);
Close:
close(fd);
}
#endif
static void unlock_vt(void)
{
ioctl(vfd, VT_SETMODE, &orig_vtm);
close(vfd);
}
static int lock_vt(void)
{
struct sigaction sa;
struct vt_mode vtm;
struct vt_stat vtstat;
char vt_name[GENERIC_NAME_SIZE];
int fd, error;
fd = console_fd("/dev/console");
if (fd < 0)
return fd;
error = ioctl(fd, VT_GETSTATE, &vtstat);
close(fd);
if (error < 0)
return error;
sprintf(vt_name, "/dev/tty%d", vtstat.v_active);
vfd = open(vt_name, O_RDWR);
if (vfd < 0)
return vfd;
error = ioctl(vfd, VT_GETMODE, &vtm);
if (error < 0)
return error;
/* Setting vt mode to VT_PROCESS means this process
* will handle vt switching requests.
* We just ignore all request by installing SIG_IGN.
*/
sigemptyset(&(sa.sa_mask));
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_IGN;
sigaction(SIGUSR1, &sa, NULL);
orig_vtm = vtm;
vtm.mode = VT_PROCESS;
vtm.relsig = SIGUSR1;
vtm.acqsig = SIGUSR1;
error = ioctl(vfd, VT_SETMODE, &vtm);
if (error < 0)
return error;
return 0;
}
/* 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'
},
{
"image_size\0\tdesired size of the image.",
required_argument, NULL, 's'
},
{
"parameter\0\toverride config file parameter.",
required_argument, NULL, 'P'
},
#ifdef CONFIG_BOTH
HACKS_LONG_OPTS
#endif
{ NULL, 0, NULL, 0 }
};
int i, error;
char *conf_name = CONFIG_FILE;
const char *optstring = "hVf:s: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 's':
pref_image_size = atoll(optarg);
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:
#ifdef CONFIG_BOTH
s2ram_add_flag(i, optarg);
break;
#else
usage(my_name, options, optstring);
return -EINVAL;
#endif
}
}
if (optind < argc)
strncpy(resume_dev_name, argv[optind], MAX_STR_LEN - 1);
#ifdef CONFIG_BOTH
s2ram_kms = !s2ram_check_kms();
if (s2ram_kms)
return 0;
s2ram = s2ram_is_supported();
/* s2ram_is_supported returns EINVAL if there was something wrong
* with the options that where added with s2ram_add_flag.
* On any other error (unsupported) we will just continue with s2disk.
*/
if (s2ram == EINVAL)
return -EINVAL;
s2ram = !s2ram;
#endif
return 0;
}
int main(int argc, char *argv[])
{
unsigned int mem_size;
struct stat stat_buf;
int resume_fd, snapshot_fd, vt_fd, orig_vc = -1, suspend_vc = -1;
int test_fd = -1;
dev_t resume_dev;
int orig_loglevel, orig_swappiness, ret;
struct rlimit rlim;
static char chroot_path[MAX_STR_LEN];
my_name = basename(argv[0]);
/* Make sure the 0, 1, 2 descriptors are open before opening the
* snapshot and resume devices
*/
do {
ret = open("/dev/null", O_RDWR);
if (ret < 0) {
perror(argv[0]);
return ret;
}
} while (ret < 3);
close(ret);
ret = get_config(argc, argv);
if (ret)
return -ret;
if (compute_checksum != 'y' && compute_checksum != 'Y')
compute_checksum = 0;
#ifdef CONFIG_COMPRESS
if (do_compress != 'y' && do_compress != 'Y') {
do_compress = 0;
} else if (lzo_init() != LZO_E_OK) {
suspend_error("Failed to initialize LZO. "
"Compression disabled.\n");
do_compress = 0;
}
#endif
#ifdef CONFIG_ENCRYPT
if (do_encrypt != 'y' && do_encrypt != 'Y')
do_encrypt = 0;
#endif
if (splash_param != 'y' && splash_param != 'Y')
splash_param = 0;
else
splash_param = SPL_SUSPEND;
if (early_writeout != 'n' && early_writeout != 'N')
early_writeout = 1;
if (!strcmp (shutdown_method_value, "shutdown")) {
shutdown_method = SHUTDOWN_METHOD_SHUTDOWN;
} else if (!strcmp (shutdown_method_value, "platform")) {
shutdown_method = SHUTDOWN_METHOD_PLATFORM;
} else if (!strcmp (shutdown_method_value, "reboot")) {
shutdown_method = SHUTDOWN_METHOD_REBOOT;
}
if (resume_pause > RESUME_PAUSE_MAX)
resume_pause = RESUME_PAUSE_MAX;
if (verify_image != 'y' && verify_image != 'Y')
verify_image = 0;
#ifdef CONFIG_THREADS
if (use_threads != 'y' && use_threads != 'Y')
use_threads = 0;
#endif
get_page_and_buffer_sizes();
mem_size = 2 * page_size + buffer_size;
#ifdef CONFIG_COMPRESS
if (do_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
#ifdef CONFIG_ENCRYPT
if (do_encrypt) {
printf("%s: libgcrypt version: %s\n", my_name,
gcry_check_version(NULL));
gcry_control(GCRYCTL_INIT_SECMEM, page_size, 0);
ret = gcry_cipher_open(&cipher_handle, IMAGE_CIPHER,
GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE);
if (ret) {
suspend_error("libgcrypt error %s", gcry_strerror(ret));
do_encrypt = 0;
} else {
encrypt_buf_size = ENCRYPT_BUF_PAGES * page_size;
mem_size += encrypt_buf_size;
}
}
#endif
if (use_threads) {
mem_size += (compress_buf_size > 0) ?
(WRITE_BUFFERS - 1) * compress_buf_size :
WRITE_BUFFERS * buffer_size;
if (!do_encrypt)
mem_size += WRITE_BUFFERS * buffer_size;
}
ret = init_memalloc(page_size, mem_size);
if (ret) {
suspend_error("Could not allocate memory.");
return ret;
}
#ifdef CONFIG_ENCRYPT
if (do_encrypt)
generate_key();
#endif
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
ret = errno;
suspend_error("Could not lock myself.");
return ret;
}
if (strlen(test_file_name) > 0) {
if (stat(test_file_name, &stat_buf)) {
ret = errno;
suspend_error("Unable to stat test image file %s",
test_file_name);
return ret;
}
test_image_size = round_down_page_size(stat_buf.st_size);
if (test_image_size < MIN_TEST_IMAGE_PAGES * page_size) {
suspend_error("Test image file %s is too small",
test_file_name);
return ENODATA;
}
test_fd = open(test_file_name, O_RDONLY);
if (test_fd < 0) {
ret = errno;
suspend_error("Unable to open test image file %s",
test_file_name);
return ret;
}
}
snprintf(chroot_path, MAX_STR_LEN, "/proc/%d", getpid());
if (mount("none", chroot_path, "tmpfs", 0, NULL)) {
ret = errno;
suspend_error("Could not mount tmpfs on %s.", chroot_path);
return ret;
}
ret = 0;
if (stat(resume_dev_name, &stat_buf)) {
suspend_error("Could not stat the resume device file.");
ret = ENODEV;
goto Umount;
}
if (!S_ISBLK(stat_buf.st_mode)) {
suspend_error("Invalid resume device.");
ret = EINVAL;
goto Umount;
}
if (chdir(chroot_path)) {
ret = errno;
suspend_error("Could not change directory to %s.",
chroot_path);
goto Umount;
}
resume_dev = stat_buf.st_rdev;
if (mknod("resume", S_IFBLK | 0600, resume_dev)) {
ret = errno;
suspend_error("Could not create %s/%s.", chroot_path, "resume");
goto Umount;
}
resume_fd = open("resume", O_RDWR);
if (resume_fd < 0) {
ret = errno;
suspend_error("Could not open the resume device.");
goto Umount;
}
if (stat(snapshot_dev_name, &stat_buf)) {
suspend_error("Could not stat the snapshot device file.");
ret = ENODEV;
goto Close_resume_fd;
}
if (!S_ISCHR(stat_buf.st_mode)) {
suspend_error("Invalid snapshot device.");
ret = EINVAL;
goto Close_resume_fd;
}
snapshot_fd = open(snapshot_dev_name, O_RDONLY);
if (snapshot_fd < 0) {
ret = errno;
suspend_error("Could not open the snapshot device.");
goto Close_resume_fd;
}
if (set_swap_file(snapshot_fd, resume_dev, resume_offset)) {
ret = errno;
suspend_error("Could not use the resume device "
"(try swapon -a).");
goto Close_snapshot_fd;
}
vt_fd = prepare_console(&orig_vc, &suspend_vc);
if (vt_fd < 0) {
ret = errno;
if (vt_fd == -ENOTTY)
suspend_error("No local tty. Remember to specify local " \
"console after the remote.");
else
suspend_error("Could not open a virtual terminal.");
goto Close_snapshot_fd;
}
splash_prepare(&splash, splash_param);
if (lock_vt() < 0) {
ret = errno;
suspend_error("Could not lock the terminal.");
goto Restore_console;
}
splash.progress(5);
#ifdef CONFIG_BOTH
/* If s2ram_hacks returns != 0, better not try to suspend to RAM */
if (s2ram)
s2ram = !s2ram_hacks();
#endif
#ifdef CONFIG_ENCRYPT
if (do_encrypt && ! use_RSA)
splash.read_password(password, 1);
#endif
open_printk();
orig_loglevel = get_kernel_console_loglevel();
set_kernel_console_loglevel(suspend_loglevel);
open_swappiness();
orig_swappiness = get_swappiness();
set_swappiness(suspend_swappiness);
sync();
splash.progress(10);
rlim.rlim_cur = 0;
rlim.rlim_max = 0;
setrlimit(RLIMIT_NOFILE, &rlim);
setrlimit(RLIMIT_NPROC, &rlim);
setrlimit(RLIMIT_CORE, &rlim);
ret = suspend_system(snapshot_fd, resume_fd, test_fd);
if (orig_loglevel >= 0)
set_kernel_console_loglevel(orig_loglevel);
close_printk();
if(orig_swappiness >= 0)
set_swappiness(orig_swappiness);
close_swappiness();
unlock_vt();
Restore_console:
splash.finish();
restore_console(vt_fd, orig_vc);
Close_snapshot_fd:
close(snapshot_fd);
Close_resume_fd:
close(resume_fd);
Umount:
if (chdir("/")) {
ret = errno;
suspend_error("Could not change directory to /");
} else {
umount(chroot_path);
}
if (test_fd >= 0)
close(test_fd);
#ifdef CONFIG_ENCRYPT
if (do_encrypt)
gcry_cipher_close(cipher_handle);
#endif
free_memalloc();
return ret;
}