blob: 9584bbc91ae956eb06a82da0f1bbba186a1f3340 [file] [log] [blame]
/*
* load.c
*
* Image loading for s2disk/s2both and resume.
*
* Copyright (C) 2008 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 "md5.h"
#include "splash.h"
char *my_name;
static char verify_checksum;
#ifdef CONFIG_COMPRESS
unsigned int compress_buf_size;
static char do_decompress;
#else
#define do_decompress 0
#endif
#ifdef CONFIG_ENCRYPT
static char do_decrypt;
static char password[PASSBUF_SIZE];
#else
#define do_decrypt 0
#endif
/**
* read_page - Read data from a swap location
* @fd: File handle of the resume partition.
* @buf: Pointer to the area we're reading into.
* @offset: Swap offset of the place to read from.
*/
static int read_page(int fd, void *buf, loff_t offset)
{
int res = 0;
ssize_t cnt = 0;
if (!offset)
return 0;
if (lseek(fd, offset, SEEK_SET) == offset)
cnt = read(fd, buf, page_size);
if (cnt < (ssize_t)page_size)
res = -EIO;
return res;
}
/*
* The swap_reader 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
* must be all zeros and is the end marker.
*
* @cur_extent: The extent currently used as the source of swap pages.
*
* @cur_offset: The offset of the swap page that will be used next.
*
* @total_size: The amount of data to read.
*
* @buffer: Buffer used for storing image data pages.
*
* @read_buffer: If compression is used, the compressed contents of
* @buffer are stored here. Otherwise, it is equal to
* @buffer.
*
* @fd: File handle associated with the swap.
*
* @ctx: Used for checksum computing, if so configured.
*
* @lzo_work_buffer: Work buffer used for decompression.
*
* @decrypt_buffer: Buffer for storing encrypted pages (page_size bytes).
*/
struct swap_reader {
struct extent *extents;
struct extent *cur_extent;
loff_t cur_offset;
loff_t next_extents;
loff_t total_size;
void *buffer;
void *read_buffer;
int fd;
struct md5_ctx ctx;
void *lzo_work_buffer;
char *decrypt_buffer;
};
/**
* load_extents_page - load the array of extents
* handle: Structure holding the pointer to the array of extents etc.
*
* Read the table of extents from the swap location pointed to by
* @handle->next_extents and store it at the address in @handle->extents.
* Read the swap location of the next array of extents from the last
* element of the array and fill this element with zeros. Initialize
* @handle->cur_extent and @handle->cur_offset as appropriate.
*/
static int load_extents_page(struct swap_reader *handle)
{
int error, n;
error = read_page(handle->fd, handle->extents, handle->next_extents);
if (error)
return error;
n = page_size / sizeof(struct extent) - 1;
handle->next_extents = handle->extents[n].start;
memset(handle->extents + n, 0, sizeof(struct extent));
handle->cur_extent = handle->extents;
handle->cur_offset = handle->cur_extent->start;
if (posix_fadvise(handle->fd, handle->cur_offset,
handle->cur_extent->end - handle->cur_offset,
POSIX_FADV_NOREUSE))
perror("posix_fadvise");
return 0;
}
/**
* free_swap_reader - free memory allocated for loading the image
* @handle: Structure containing pointers to memory buffers to free.
*/
static void free_swap_reader(struct swap_reader *handle)
{
if (do_decompress) {
freemem(handle->lzo_work_buffer);
freemem(handle->read_buffer);
}
if (do_decrypt)
freemem(handle->decrypt_buffer);
freemem(handle->buffer);
freemem(handle->extents);
}
/**
* init_swap_reader - initialize the structure used for loading the image
* @handle: Structure to initialize.
* @fd: File descriptor associated with the swap.
* @start: Swap location (offset) of the first image page.
* @image_size: Total size of the image data.
*
* Initialize buffers and related fields of @handle and load the first
* array of extents.
*/
static int init_swap_reader(struct swap_reader *handle, int fd, loff_t start,
loff_t image_size)
{
int error;
if (!start)
return -EINVAL;
handle->fd = fd;
handle->total_size = image_size;
handle->extents = getmem(page_size);
handle->buffer = getmem(buffer_size);
handle->read_buffer = handle->buffer;
if (do_decrypt)
handle->decrypt_buffer = getmem(page_size);
if (do_decompress) {
handle->read_buffer = getmem(compress_buf_size);
handle->lzo_work_buffer = getmem(LZO1X_1_MEM_COMPRESS);
}
/* Read the table of extents */
handle->next_extents = start;
error = load_extents_page(handle);
if (error) {
free_swap_reader(handle);
return error;
}
if (verify_checksum)
md5_init_ctx(&handle->ctx);
return 0;
}
/**
* find_next_image_page - find the next swap location holding image data
*/
static void find_next_image_page(struct swap_reader *handle)
{
int error;
handle->cur_offset += page_size;
if (handle->cur_offset < handle->cur_extent->end)
return;
/* We have exhausted the current extent. Forward to the next one */
handle->cur_extent++;
if (handle->cur_extent->start < handle->cur_extent->end) {
handle->cur_offset = handle->cur_extent->start;
if (posix_fadvise(handle->fd, handle->cur_offset,
handle->cur_extent->end - handle->cur_offset,
POSIX_FADV_NOREUSE))
perror("posix_fadvise");
return;
}
/* No more extents. Load the next extents page. */
error = load_extents_page(handle);
if (error)
handle->cur_offset = 0;
}
/**
* load_and_decrypt_page - load a page of data from swap and decrypt it,
* if necessary.
*/
static int load_and_decrypt_page(struct swap_reader *handle, void *dst)
{
int error;
void *buf = dst;
if (!handle->cur_offset)
return -EINVAL;
if (do_decrypt)
buf = handle->decrypt_buffer;
error = read_page(handle->fd, buf, handle->cur_offset);
#ifdef CONFIG_ENCRYPT
if (!error && do_decrypt)
error = gcry_cipher_decrypt(cipher_handle, dst, page_size,
buf, page_size);
#endif
if (!error) {
handle->total_size -= page_size;
find_next_image_page(handle);
}
return error;
}
/**
* load_buffer - load (and decrypt, if necessary) a block od data from the
* swap, decompress it if necessary and store it in the
* image data buffer.
*/
static ssize_t load_buffer(struct swap_reader *handle)
{
void *dst;
ssize_t size;
int error;
#ifdef CONFIG_COMPRESS
if (do_decompress) {
struct buf_block *block = handle->read_buffer;
lzo_uint cnt;
/* Read the block size from the first block page. */
error = load_and_decrypt_page(handle, block);
if (error)
return 0;
size = page_size;
dst = block;
dst += page_size;
/* Load the rest of the block pages */
while (size < block->size + sizeof(size_t)) {
error = load_and_decrypt_page(handle, dst);
if (error)
return 0;
size += page_size;
dst += page_size;
}
/* Decompress block */
error = lzo1x_decompress((lzo_bytep)block->data, block->size,
handle->buffer, &cnt,
handle->lzo_work_buffer);
if (error)
return 0;
size = cnt;
goto Checksum;
}
#endif
dst = handle->buffer;
size = 0;
while (size < buffer_size && handle->total_size > 0) {
error = load_and_decrypt_page(handle, dst);
if (error)
return 0;
size += page_size;
dst += page_size;
}
Checksum:
if (verify_checksum)
md5_process_block(handle->buffer, size, &handle->ctx);
return size;
}
/**
* load_image - load a hibernation image
* @handle: Structure containing image information.
* @dev: Special device file to write image data pages to.
* @nr_pages: Number of image data pages.
*/
static int load_image(struct swap_reader *handle, int dev,
unsigned int nr_pages, int verify_only)
{
unsigned int m, n;
ssize_t buf_size;
ssize_t ret;
void *buf = 0;
int error = 0;
char message[SPLASH_GENERIC_MESSAGE_SIZE];
sprintf(message, "Loading image data pages (%u pages)...", nr_pages);
splash.set_caption(message);
printf("%s ", message);
m = nr_pages / 100;
if (!m)
m = 1;
n = 0;
buf_size = 0;
do {
if (buf_size <= 0) {
buf_size = load_buffer(handle);
if (buf_size <= 0) {
printf("\n");
return -EIO;
}
buf = handle->buffer;
}
ret = verify_only ? page_size : write(dev, buf, page_size);
if (ret < page_size) {
if (ret < 0)
perror("\nError while writing an image page");
else
printf("\n");
return -EIO;
}
buf += page_size;
buf_size -= page_size;
if (!(n % m)) {
printf("\b\b\b\b%3d%%", n / m);
if (n / m > 15)
splash.progress(n / m);
}
n++;
} while (n < nr_pages);
if (!error)
printf(" done\n");
return error;
}
static char *print_checksum(char * buf, unsigned char *checksum)
{
int j;
for (j = 0; j < 16; j++)
buf += sprintf(buf, "%02hhx ", checksum[j]);
return buf;
}
#ifdef CONFIG_ENCRYPT
static int decrypt_key(struct image_header_info *header, unsigned char *key,
unsigned char *ivec)
{
gcry_ac_handle_t rsa_hd;
gcry_ac_data_t rsa_data_set, key_set;
gcry_ac_key_t rsa_priv;
gcry_mpi_t mpi;
unsigned char *buf, *out, *key_buf, *ivec_buf;
struct md5_ctx ctx;
struct RSA_data *rsa;
gcry_cipher_hd_t sym_hd;
int j, ret;
rsa = &header->rsa;
ret = gcry_ac_open(&rsa_hd, GCRY_AC_RSA, 0);
if (ret)
return ret;
ret = gcry_cipher_open(&sym_hd, PK_CIPHER, GCRY_CIPHER_MODE_CFB,
GCRY_CIPHER_SECURE);
if (ret)
goto Free_rsa;
key_buf = getmem(PK_KEY_SIZE);
ivec_buf = getmem(PK_CIPHER_BLOCK);
out = getmem(KEY_TEST_SIZE);
do {
splash.read_password(password, 0);
memset(ivec_buf, 0, PK_CIPHER_BLOCK);
strncpy(ivec_buf, password, PK_CIPHER_BLOCK);
md5_init_ctx(&ctx);
md5_process_bytes(password, strlen(password), &ctx);
md5_finish_ctx(&ctx, key_buf);
ret = gcry_cipher_setkey(sym_hd, key_buf, PK_KEY_SIZE);
if (!ret)
ret = gcry_cipher_setiv(sym_hd, ivec_buf,
PK_CIPHER_BLOCK);
if (!ret)
ret = gcry_cipher_encrypt(sym_hd,
out, KEY_TEST_SIZE,
KEY_TEST_DATA, KEY_TEST_SIZE);
if (ret) {
fprintf(stderr, "%s: libgcrypt error: %s\n", my_name,
gcry_strerror(ret));
break;
}
ret = memcmp(out, rsa->key_test, KEY_TEST_SIZE);
if (ret)
printf("%s: Wrong passphrase, try again.\n", my_name);
} while (ret);
gcry_cipher_close(sym_hd);
if (!ret)
ret = gcry_cipher_open(&sym_hd, PK_CIPHER,
GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE);
if (ret)
goto Free_buffers;
ret = gcry_cipher_setkey(sym_hd, key_buf, PK_KEY_SIZE);
if (!ret)
ret = gcry_cipher_setiv(sym_hd, ivec_buf, PK_CIPHER_BLOCK);
if (!ret)
ret = gcry_ac_data_new(&rsa_data_set);
if (ret)
goto Close_cypher;
buf = rsa->data;
for (j = 0; j < RSA_FIELDS; j++) {
size_t s = rsa->size[j];
/* We need to decrypt some components */
if (j >= RSA_FIELDS_PUB) {
/* We use the in-place decryption */
ret = gcry_cipher_decrypt(sym_hd, buf, s, NULL, 0);
if (ret)
break;
}
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_priv, rsa_hd,
GCRY_AC_KEY_SECRET, rsa_data_set);
if (ret)
goto Destroy_data_set;
ret = gcry_ac_data_new(&key_set);
if (ret)
goto Destroy_key;
gcry_mpi_scan(&mpi, GCRYMPI_FMT_USG, header->key.data,
header->key.size, NULL);
ret = gcry_ac_data_set(key_set, GCRY_AC_FLAG_COPY, "a", mpi);
if (ret)
goto Destroy_key_set;
gcry_mpi_release(mpi);
ret = gcry_ac_data_decrypt(rsa_hd, 0, rsa_priv, &mpi, key_set);
if (!ret) {
unsigned char *res;
size_t s;
gcry_mpi_aprint(GCRYMPI_FMT_USG, &res, &s, mpi);
if (s == KEY_SIZE + CIPHER_BLOCK) {
memcpy(key, res, KEY_SIZE);
memcpy(ivec, res + KEY_SIZE, CIPHER_BLOCK);
} else {
ret = -ENODATA;
}
gcry_free(res);
}
Destroy_key_set:
gcry_mpi_release(mpi);
gcry_ac_data_destroy(key_set);
Destroy_key:
gcry_ac_key_destroy(rsa_priv);
Destroy_data_set:
gcry_ac_data_destroy(rsa_data_set);
Close_cypher:
gcry_cipher_close(sym_hd);
Free_buffers:
freemem(out);
freemem(ivec_buf);
freemem(key_buf);
Free_rsa:
gcry_ac_close(rsa_hd);
return ret;
}
static int restore_key(struct image_header_info *header)
{
static unsigned char key[KEY_SIZE], ivec[CIPHER_BLOCK];
int error;
if (header->flags & IMAGE_USE_RSA) {
error = decrypt_key(header, key, ivec);
} else {
int j;
splash.read_password(password, 0);
encrypt_init(key, ivec, password);
for (j = 0; j < CIPHER_BLOCK; j++)
ivec[j] ^= header->salt[j];
}
if (!error)
error = gcry_cipher_open(&cipher_handle,
IMAGE_CIPHER, GCRY_CIPHER_MODE_CFB,
GCRY_CIPHER_SECURE);
if (!error) {
error = gcry_cipher_setkey(cipher_handle, key, KEY_SIZE);
if (!error)
error = gcry_cipher_setiv(cipher_handle, ivec,
CIPHER_BLOCK);
if (error)
gcry_cipher_close(cipher_handle);
}
return error;
}
#endif
int read_or_verify(int dev, int fd, struct image_header_info *header,
loff_t start, int verify, int test)
{
static struct swap_reader handle;
static unsigned char orig_checksum[16], checksum[16];
static char csum_buf[48];
int error = 0, test_mode = (verify || test);
error = read_page(fd, header, start);
if (error)
return error;
if ((header->flags & IMAGE_CHECKSUM) || verify) {
memcpy(orig_checksum, header->checksum, 16);
print_checksum(csum_buf, orig_checksum);
printf("%s: MD5 checksum %s\n", my_name, csum_buf);
verify_checksum = 1;
}
splash.progress(10);
if (header->flags & IMAGE_COMPRESSED) {
printf("%s: Compressed image\n", my_name);
#ifdef CONFIG_COMPRESS
if (lzo_init() == LZO_E_OK) {
do_decompress = 1;
} else {
fprintf(stderr, "%s: Failed to initialize LZO\n",
my_name);
error = -EFAULT;
}
#else
fprintf(stderr, "%s: Compression not supported\n", my_name);
error = -EINVAL;
#endif
}
if (error)
return error;
if (header->flags & IMAGE_ENCRYPTED) {
#ifdef CONFIG_ENCRYPT
printf("%s: Encrypted image\n", my_name);
error = test_mode ?
gcry_cipher_setiv(cipher_handle, key_data.ivec,
CIPHER_BLOCK) :
restore_key(header);
if (error) {
fprintf(stderr, "%s: libgcrypt error: %s\n", my_name,
gcry_strerror(error));
} else {
do_decrypt = 1;
splash.progress(15);
}
#else
fprintf(stderr, "%s: Encryption not supported\n", my_name);
error = -EINVAL;
#endif
}
if (error)
goto Exit_encrypt;
error = init_swap_reader(&handle, fd, header->map_start,
header->image_data_size);
if (!error) {
struct timeval begin, end;
double delta, mb;
gettimeofday(&begin, NULL);
error = load_image(&handle, dev, header->pages, test_mode);
if (!error && verify_checksum) {
md5_finish_ctx(&handle.ctx, checksum);
if (memcmp(orig_checksum, checksum, 16)) {
fprintf(stderr,
"%s: MD5 checksum does not match\n",
my_name);
print_checksum(csum_buf, checksum);
fprintf(stderr,
"%s: Computed MD5 checksum %s\n",
my_name, csum_buf);
error = -EINVAL;
}
}
free_swap_reader(&handle);
if (error)
goto Exit_encrypt;
gettimeofday(&end, NULL);
timersub(&end, &begin, &end);
delta = end.tv_usec / 1000000.0 + end.tv_sec;
mb = (header->pages * (page_size / 1024.0)) / 1024.0;
printf("wrote %0.1lf MB in %0.1lf seconds (%0.1lf MB/s)\n",
mb, header->writeout_time, mb / header->writeout_time);
printf("read %0.1lf MB in %0.1lf seconds (%0.1lf MB/s)\n",
mb, delta, mb / delta);
mb *= 2.0;
delta += header->writeout_time;
printf("total image i/o: %0.1lf MB in %0.1lf seconds "
"(%0.1lf MB/s)\n", mb, delta, mb / delta);
if (do_decompress) {
double real_size = header->image_data_size;
printf("%s: Compression ratio %4.2lf\n", my_name,
real_size / (header->pages * page_size));
real_size /= (1024.0 * 1024.0);
delta -= header->writeout_time;
printf("wrote %0.1lf MB of compressed data in %0.1lf "
"seconds (%0.1lf MB/s)\n", real_size,
header->writeout_time,
real_size / header->writeout_time);
printf("read %0.1lf MB of compressed data in %0.1lf "
"seconds (%0.1lf MB/s)\n", real_size,
delta, real_size / delta);
real_size *= 2.0;
delta += header->writeout_time;
printf("total compressed data i/o: %0.1lf MB in %0.1lf "
"seconds (%0.1lf MB/s)\n", real_size, delta,
real_size / delta);
}
}
Exit_encrypt:
#ifdef CONFIG_ENCRYPT
if (do_decrypt && !test_mode)
gcry_cipher_close(cipher_handle);
#endif
return error;
}