summaryrefslogtreecommitdiff
path: root/syspatch.c
diff options
context:
space:
mode:
authorGeremy Condra <gcondra@google.com>2013-11-18 17:25:49 -0800
committerGeremy Condra <gcondra@google.com>2013-11-18 17:29:36 -0800
commita87a07926b6c472893a032989a55442bd819223e (patch)
treef555d6bdc558e0ed47607dfaa7582a37457d5019 /syspatch.c
parent50cf90ead3e4eaa183424759714a450a31931cc2 (diff)
downloadsyspatch-a87a07926b6c472893a032989a55442bd819223e.tar.gz
Initial commit for syspatch, a binary patching tool for large files.
Change-Id: I3519f6b1a40d95d36a0317e14d53bcbfc20eba6e
Diffstat (limited to 'syspatch.c')
-rw-r--r--syspatch.c566
1 files changed, 566 insertions, 0 deletions
diff --git a/syspatch.c b/syspatch.c
new file mode 100644
index 0000000..070dfd5
--- /dev/null
+++ b/syspatch.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "xdelta3.c"
+#include "xdelta3.h"
+#include "xz.h"
+
+// XZ_DICT_SIZE represents the size of the dictionary used for XZ decompression.
+// Higher values improve compression, but consume more memory.
+#define XZ_DICT_SIZE (1U<<26)
+// XZ_OUTPUT_SIZE tunes the amount of data that XZ buffers before handing it off
+// to the patcher. Settings lower than TARGET_WINDOW_SIZE or higher than
+// SOURCE_WINDOW_SIZE probably don't make any sense, but this is untested. The
+// primary tradeoff made here is between memory and extra calls to the decoder.
+#define XZ_OUTPUT_SIZE (1U<<23)
+// TARGET_WINDOW_SIZE determines how large writes to the target file are. There
+// isn't a lot of science behind this yet, excepting the notes about the write
+// queue below.
+#define TARGET_WINDOW_SIZE (1U<<23)
+// SOURCE_WINDOW_SIZE is one of the primary tunables. Higher values increase
+// compression ratios and improve decoder speed, but consume more memory.
+#define SOURCE_WINDOW_SIZE (1U<<26)
+// WRITE_QUEUE_LENGTH * TARGET_WINDOW_SIZE must be >= SOURCE_WINDOW_SIZE / 2.
+// This ensures that writes will always happen behind an area where a read
+// might occur. This is enforced by the READ_FRONTIER check.
+#define WRITE_QUEUE_LENGTH (4)
+// READ_CACHE_LENGTH is defined based on the acceptability of a time/memory
+// tradeoff. A value of at least one is necessary to not completely suck, and
+// two or higher is recommended. Higher than 8 will probably not yield
+// reasonable returns for realistic patches.
+#define READ_CACHE_LENGTH (2)
+
+static uint8_t in[XZ_OUTPUT_SIZE];
+static uint8_t out[XZ_OUTPUT_SIZE];
+
+typedef struct TargetWrite TargetWrite;
+struct TargetWrite {
+ size_t start;
+ size_t length;
+ uint8_t data[TARGET_WINDOW_SIZE];
+};
+
+typedef struct SourceRead SourceRead;
+struct SourceRead {
+ size_t blkno;
+ size_t length;
+ uint8_t data[SOURCE_WINDOW_SIZE];
+};
+
+static SourceRead* READ_CACHE[READ_CACHE_LENGTH];
+static size_t SOURCE_WINDOWS_CACHED;
+
+typedef struct XZContext XZContext;
+struct XZContext {
+ struct xz_buf b;
+ struct xz_dec *s;
+ enum xz_ret ret;
+ const char *msg;
+};
+
+static TargetWrite* WRITE_QUEUE[WRITE_QUEUE_LENGTH];
+static size_t TARGET_WINDOWS_WRITTEN;
+static size_t READ_FRONTIER;
+
+//============================================================================//
+// Source I/O //
+//============================================================================//
+
+static SourceRead *shuffle_cache(SourceRead *tmp, size_t index) {
+ SourceRead *current = READ_CACHE[index];
+ READ_CACHE[index] = tmp;
+ return current;
+}
+
+static int add_to_read_cache(SourceRead *source_read) {
+ int i = 0;
+ SourceRead *tmp = NULL;
+ for (i; i < READ_CACHE_LENGTH; i++) {
+ if (tmp == source_read)
+ break;
+ tmp = shuffle_cache(tmp, i);
+ }
+ if (tmp != source_read)
+ free(tmp);
+ READ_CACHE[0] = source_read;
+ return 0;
+}
+
+static size_t read_source_file(uint8_t *data, FILE *file) {
+ size_t size;
+ fflush(file);
+ size = fread((void*)data, 1, SOURCE_WINDOW_SIZE, file);
+ fflush(file);
+ return size;
+}
+
+static int check_read(size_t pos, size_t frontier) {
+ if (pos < frontier) {
+ fprintf(stderr, "Read past frontier: %zu > %zu\n", pos, frontier);
+ return -1;
+ }
+ return 0;
+}
+
+static SourceRead *get_source_window_from_file(xd3_source *source) {
+ size_t read_position = source->getblkno * source->blksize;
+ SourceRead *source_read = malloc(sizeof(SourceRead));
+ memset(source_read, 0, sizeof(SourceRead));
+ if (check_read(read_position, READ_FRONTIER) < 0)
+ return NULL;
+ if (fseek(source->ioh, read_position, SEEK_SET) != 0) {
+ fprintf(stderr, "Couldn't seek to %zu\n", read_position);
+ return NULL;
+ }
+ source_read->blkno = source->getblkno;
+ source_read->length = read_source_file(source_read->data, source->ioh);
+ return source_read;
+}
+
+static SourceRead *get_source_window_from_cache(size_t blkno) {
+ int i = 0;
+ for (i; i < READ_CACHE_LENGTH; i++) {
+ if (READ_CACHE[i]->blkno == blkno) {
+ return READ_CACHE[i];
+ }
+ }
+ return NULL;
+}
+
+static int read_to_source(SourceRead *source_read, xd3_source *source) {
+ source->onblk = source_read->length;
+ source->curblkno = source->getblkno;
+ source->curblk = source_read->data;
+ return 0;
+}
+
+static int process_source_data(xd3_source *source) {
+ SourceRead *source_read = get_source_window_from_cache(source->getblkno);
+ if (source_read == NULL) {
+ source_read = get_source_window_from_file(source);
+ }
+ if (source_read == NULL) {
+ return -1;
+ }
+ add_to_read_cache(source_read);
+ return read_to_source(source_read, source);
+}
+
+//============================================================================//
+// Patch I/O //
+//============================================================================//
+
+static int read_compressed_input(XZContext *context, FILE *infile) {
+ if (context->b.in_pos == context->b.in_size) {
+ context->b.in_size = fread(in, 1, XZ_OUTPUT_SIZE, infile);
+ context->b.in_pos = 0;
+ }
+ return 0;
+}
+
+//============================================================================//
+// Target I/O //
+//============================================================================//
+
+static int write_target(TargetWrite *tgt, FILE *target_file) {
+ if (tgt->length > 0) {
+ if (fseek(target_file, tgt->start, SEEK_SET) != 0)
+ return -1;
+ if (fwrite(tgt->data, 1, tgt->length, target_file) != tgt->length)
+ return -1;
+ if (fflush(target_file) != 0)
+ return -1;
+ }
+ READ_FRONTIER = ftell(target_file);
+ return 0;
+}
+
+static int stream_to_target_write(xd3_stream *stream, TargetWrite *tgt) {
+ if (stream->avail_out) {
+ tgt->start = TARGET_WINDOWS_WRITTEN * TARGET_WINDOW_SIZE;
+ tgt->length = stream->avail_out;
+ memcpy(tgt->data, stream->next_out, tgt->length);
+ xd3_consume_output(stream);
+ }
+ return 0;
+}
+
+static int advance_target_buffer(xd3_stream *stream, FILE *target_file) {
+ TargetWrite *tgt = WRITE_QUEUE[TARGET_WINDOWS_WRITTEN % WRITE_QUEUE_LENGTH];
+ if (write_target(tgt, target_file) != 0)
+ return -1;
+ if (stream_to_target_write(stream, tgt) != 0)
+ return -1;
+ TARGET_WINDOWS_WRITTEN += 1;
+ return 0;
+}
+
+static int process_target_data(xd3_stream *stream, FILE *target, int force) {
+ size_t iters = (force == 0) + (force * WRITE_QUEUE_LENGTH);
+ while (iters) {
+ if (advance_target_buffer(stream, target) < 0)
+ return -1;
+ iters -= 1;
+ }
+ return 0;
+}
+
+//============================================================================//
+// Read Cache Setup and Teardown //
+//============================================================================//
+
+static int setup_read_cache(FILE *source_file) {
+ int i = 0;
+ size_t length;
+ for (i; i < READ_CACHE_LENGTH; i++) {
+ READ_CACHE[i] = malloc(sizeof(SourceRead));
+ if (READ_CACHE[i] == NULL)
+ return -1;
+ READ_CACHE[i]->blkno = i;
+ length = read_source_file(READ_CACHE[i]->data, source_file);
+ READ_CACHE[i]->length = length;
+ }
+ SOURCE_WINDOWS_CACHED = READ_CACHE_LENGTH;
+ return 0;
+}
+
+static int teardown_read_cache() {
+ int i = 0;
+ for (i; i < READ_CACHE_LENGTH; i++) {
+ free(READ_CACHE[i]);
+ }
+ return 0;
+}
+
+//============================================================================//
+// Write Queue Setup and Teardown //
+//============================================================================//
+
+static int setup_write_queue() {
+ TARGET_WINDOWS_WRITTEN = 0;
+ int i = 0;
+ for (i; i < WRITE_QUEUE_LENGTH; i++) {
+ WRITE_QUEUE[i] = malloc(sizeof(TargetWrite));
+ if (WRITE_QUEUE[i] == NULL)
+ return -1;
+ WRITE_QUEUE[i]->start = 0;
+ WRITE_QUEUE[i]->length = 0;
+ memset(WRITE_QUEUE[i]->data, 0, TARGET_WINDOW_SIZE);
+ }
+ return 0;
+}
+
+static int teardown_write_queue() {
+ int i = 0;
+ for (i; i < WRITE_QUEUE_LENGTH; i++) {
+ free(WRITE_QUEUE[i]);
+ }
+ return 0;
+}
+
+//============================================================================//
+// Decompressor Setup and Teardown //
+//============================================================================//
+static int setup_xz_buf(struct xz_buf *b) {
+ b->in = in;
+ b->in_pos = 0;
+ b->in_size = 0;
+ b->out = out;
+ b->out_pos = 0;
+ b->out_size = XZ_OUTPUT_SIZE;
+ return 0;
+}
+
+static int setup_xz_dec(struct xz_dec **s) {
+ *s = xz_dec_init(XZ_DYNALLOC, XZ_DICT_SIZE);
+ if (*s == NULL)
+ return -1;
+ return 0;
+}
+
+static int setup_xz_context(XZContext *context) {
+ xz_crc32_init();
+ setup_xz_dec(&context->s);
+ if (setup_xz_buf(&context->b) < 0)
+ return -1;
+ return 0;
+}
+
+//============================================================================//
+// Patcher Setup and Teardown //
+//============================================================================//
+
+static int setup_xdelta_config(xd3_config *config, xd3_stream *stream) {
+ int ret;
+ memset(config, 0, sizeof(*config));
+ xd3_init_config(config, 0);
+ config->winsize = TARGET_WINDOW_SIZE;
+ config->getblk = NULL;
+ ret = xd3_config_stream(stream, config);
+ if (ret != 0) {
+ fprintf(stderr, "xd3_config_stream error: %s\n", xd3_strerror(ret));
+ return -1;
+ }
+ return 0;
+}
+
+static int setup_xdelta_source( xd3_source *source,
+ xd3_stream *stream,
+ FILE *source_file) {
+ int ret;
+ memset(source, 0, sizeof(*source));
+ source->name = "source";
+ source->ioh = source_file;
+ source->blksize = SOURCE_WINDOW_SIZE;
+ source->curblkno = 0;
+ source->curblk = NULL;
+ source->onblk = 0;
+ READ_FRONTIER = 0;
+
+ ret = xd3_set_source(stream, source);
+ if (ret != 0) {
+ fprintf(stderr, "xd3_set_source error: %s\n", xd3_strerror(ret));
+ return -1;
+ }
+ return 0;
+}
+
+static void teardown_xdelta_stream(xd3_stream *stream) {
+ xd3_close_stream(stream);
+ xd3_free_stream(stream);
+}
+
+//============================================================================//
+// Decompression Loop //
+//============================================================================//
+
+static int run_decompressor(XZContext *context, FILE *infile) {
+ context->ret = xz_dec_run(context->s, &context->b);
+ read_compressed_input(context, infile);
+ return context->b.out_pos == XZ_OUTPUT_SIZE;
+}
+
+static int decompress_into_buffer(
+ XZContext *context,
+ FILE *infile,
+ int *buffer_filled,
+ int *done) {
+
+ *buffer_filled = run_decompressor(context, infile);
+ if (context->ret == XZ_OK)
+ return 0;
+
+ switch (context->ret) {
+ case XZ_STREAM_END:
+ *buffer_filled = 1;
+ *done = 1;
+ return 0;
+ case XZ_MEM_ERROR:
+ case XZ_MEMLIMIT_ERROR:
+ case XZ_FORMAT_ERROR:
+ case XZ_OPTIONS_ERROR:
+ case XZ_DATA_ERROR:
+ case XZ_BUF_ERROR:
+ context->msg = "File is corrupt\n";
+ return -1;
+ default:
+ context->msg = "Bug!\n";
+ return -1;
+ }
+}
+
+static int decompress(XZContext *context, FILE *patch_file) {
+ int done = 0;
+ int filled = 0;
+ while (!filled) {
+ if (decompress_into_buffer(context, patch_file, &filled, &done) < 0)
+ return -1;
+ }
+ return done;
+}
+
+static int decompress_patch(XZContext *context,
+ FILE *patch_file,
+ xd3_stream *stream) {
+ int done = decompress(context, patch_file);
+ xd3_avail_input(stream, out, context->b.out_pos);
+ context->b.out_pos = 0;
+ return done;
+}
+
+//============================================================================//
+// Patching Loop //
+//============================================================================//
+
+static int patch(
+ XZContext *context,
+ xd3_stream *stream,
+ xd3_source *source,
+ FILE *patch_file,
+ FILE *target_file) {
+
+ int ret = 0;
+ int decompression_done;
+
+ fprintf(stderr, "Hello?\n");
+ source->curblk = READ_CACHE[0]->data;
+ source->onblk = READ_CACHE[0]->length;
+ source->curblkno = 0;
+
+ do {
+ decompression_done = decompress_patch(context, patch_file, stream);
+ if (decompression_done < 0)
+ goto err;
+ if (decompression_done)
+ xd3_set_flags(stream, XD3_FLUSH);
+process:
+ ret = xd3_decode_input(stream);
+ switch (ret) {
+ case XD3_INPUT:
+ continue;
+ case XD3_OUTPUT:
+ if (process_target_data(stream, target_file, 0) < 0)
+ goto err;
+ goto process;
+ case XD3_GETSRCBLK:
+ if (process_source_data(source) < 0)
+ goto err;
+ goto process;
+ case XD3_GOTHEADER:
+ goto process;
+ case XD3_WINSTART:
+ goto process;
+ case XD3_WINFINISH:
+ goto process;
+ default:
+ goto err;
+ }
+ } while (!decompression_done);
+
+ process_target_data(stream, target_file, 1);
+
+ ret = 0;
+ goto out;
+
+err:
+ fprintf(stderr, "error: %s (%s)\n", xd3_strerror(ret), stream->msg);
+ ret = -1;
+out:
+ xz_dec_end(context->s);
+ return ret;
+}
+
+//============================================================================//
+// Main Logic //
+//============================================================================//
+static int syspatch(FILE *source_file, FILE *patch_file, FILE *target_file) {
+ XZContext xz_context;
+ xd3_stream stream;
+ xd3_config config;
+ xd3_source source;
+
+ if (setup_read_cache(source_file) != 0)
+ return -1;
+
+ if (setup_write_queue() != 0)
+ return -1;
+
+ if (setup_xz_context(&xz_context) != 0)
+ return -1;
+
+ if (setup_xdelta_config(&config, &stream) != 0)
+ return -1;
+
+ if (setup_xdelta_source(&source, &stream, source_file) != 0)
+ return -1;
+
+ if (patch(&xz_context, &stream, &source, patch_file, target_file) != 0)
+ return -1;
+
+ teardown_read_cache();
+ teardown_write_queue();
+ teardown_xdelta_stream(&stream);
+ return 0;
+}
+
+//============================================================================//
+// Test Harness //
+//============================================================================//
+
+static int usage(char *progname) {
+ return fprintf(stderr, "%s: <source> <patch> <target>", progname);
+}
+
+static int parse_arguments(
+ int argc,
+ char **argv,
+ FILE **source_file,
+ FILE **patch_file,
+ FILE **target_file) {
+
+ if (argc < 4) {
+ usage(argv[1]);
+ return -1;
+ }
+
+ *source_file = fopen(argv[1], "r");
+ if (*source_file == NULL) {
+ fprintf(stderr, "Error opening source file: %s\n", strerror(errno));
+ return -1;
+ }
+
+ *patch_file = fopen(argv[2], "r");
+ if (*patch_file == NULL) {
+ fprintf(stderr, "Error opening patch file: %s\n", strerror(errno));
+ return -1;
+ }
+
+ *target_file = fopen(argv[3], "r+");
+ if (*target_file == NULL) {
+ fprintf(stderr, "Error opening target file: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int retval;
+ FILE *source_file;
+ FILE *patch_file;
+ FILE *target_file;
+
+ if (parse_arguments(argc, argv, &source_file, &patch_file, &target_file))
+ return 1;
+
+ retval = syspatch(source_file, patch_file, target_file);
+
+ fclose(source_file);
+ fclose(patch_file);
+ fclose(target_file);
+
+ return (retval != 0);
+}