/* * suspend.c * * A simple user space suspend handler for swsusp. * * Copyright (C) 2005 Rafael J. Wysocki * * This file is released under the GPLv2. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_THREADS #include #endif #ifdef CONFIG_COMPRESS #include #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 unsigned long 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; #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, unsigned int 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 = (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, 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 = (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; unsigned long 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) { /* 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); s2ram_resume(); goto Unfreeze; } Shutdown: #endif close(resume_fd); suspend_shutdown(snapshot_fd); } } while (--attempts); /* We get here during the resume or when we failed to suspend. * Remember, suspend_shutdown() never returns! */ if (shutdown_method == SHUTDOWN_METHOD_PLATFORM) platform_finish(snapshot_fd); Unfreeze: 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 = 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; }