Initial commit for syspatch, a binary patching tool for large files.

Change-Id: I3519f6b1a40d95d36a0317e14d53bcbfc20eba6e
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..63ab1f3
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,22 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := syspatch_host
+LOCAL_SRC_FILES := syspatch.c
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := libxz_host libxdelta3_host
+LOCAL_C_INCLUDES += external/xdelta
+LOCAL_C_INCLUDES += external/lzma/xz-embedded
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(CLEAR_VARS)
+#LOCAL_ADDRESS_SANITIZER := true
+LOCAL_MODULE := syspatch
+LOCAL_SRC_FILES := syspatch.c
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := libxz libxdelta3
+LOCAL_C_INCLUDES += external/xdelta
+LOCAL_C_INCLUDES += external/lzma/xz-embedded
+include $(BUILD_EXECUTABLE)
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);
+}
diff --git a/test/big_syspatch_test/test.sh b/test/big_syspatch_test/test.sh
new file mode 100755
index 0000000..a78878f
--- /dev/null
+++ b/test/big_syspatch_test/test.sh
@@ -0,0 +1,54 @@
+#! /bin/sh
+
+# initial cleanup
+rm -f a
+rm -f b
+rm -f c
+rm -f a_to_b.patch
+rm -f a_to_b.patch.xz
+
+# build the files
+#./make_test_data.py
+dd if=/dev/urandom of=b bs=1M count=100
+dd if=/dev/full of=a bs=1M count=100
+
+# build the patch
+xdelta3 -0 -B 67108864 -e -s a b a_to_b.patch
+/usr/bin/time -v xdelta3 -B 67108864 -d -s a a_to_b.patch c
+cmp b c
+if [ $? -ne 0 ]; then
+	echo "Couldn't verify patch"
+else
+	echo "Built patch"
+fi
+rm c
+
+# compress it and verify the compressed patch
+xz -zk --check=crc32 a_to_b.patch
+mv a_to_b.patch a_to_b.patch.orig
+xz -dk a_to_b.patch.xz
+cmp a_to_b.patch a_to_b.patch.orig
+if [ $? -ne 0 ]; then
+	echo "Couldn't verify compression"
+fi
+rm -f a_to_b.patch
+rm -f a_to_b.patch.orig
+
+# run the test
+cp a c
+$ANDROID_BUILD_TOP/out/host/linux-x86/bin/syspatch a a_to_b.patch.xz c
+cmp c b
+if [ $? -ne 0 ]; then
+	echo "Different file test failed"
+fi
+/usr/bin/time -v $ANDROID_BUILD_TOP/out/host/linux-x86/bin/syspatch a a_to_b.patch.xz a
+cmp a b
+if [ $? -ne 0 ]; then
+	echo "Same file test failed"
+fi
+
+# cleanup
+rm -f a
+rm -f b
+rm -f c
+rm -f a_to_b.patch.xz
diff --git a/test/real_syspatch_test/test.sh b/test/real_syspatch_test/test.sh
new file mode 100755
index 0000000..452bd82
--- /dev/null
+++ b/test/real_syspatch_test/test.sh
@@ -0,0 +1,54 @@
+#! /bin/sh
+
+rm temp.img
+rm to.img
+rm from.img
+rm *patch*
+
+cp from.orig from.img
+cp to.orig to.img
+
+# build the patch
+xdelta3 -0 -B 67108864 -e -s from.img to.img a_to_b.patch
+/usr/bin/time -v xdelta3 -B 67108864 -d -s from.img a_to_b.patch temp.img
+cmp to.img temp.img
+if [ $? -ne 0 ]; then
+	echo "Couldn't verify patch"
+else
+	echo "Built patch"
+fi
+
+# compress it and verify the compressed patch
+xz -zk --check=crc32 a_to_b.patch
+mv a_to_b.patch a_to_b.patch.orig
+xz -dk a_to_b.patch.xz
+cmp a_to_b.patch a_to_b.patch.orig
+if [ $? -ne 0 ]; then
+	echo "Couldn't verify compression"
+fi
+rm a_to_b.patch.orig
+
+# run the test
+STARTTIME=$(date +%s)
+$ANDROID_BUILD_TOP/out/host/linux-x86/bin/syspatch from.img a_to_b.patch.xz temp.img
+ENDTIME=$(date +%s)
+cmp to.img temp.img
+if [ $? -ne 0 ]; then
+	echo "Different file test failed"
+else
+	echo "Different file test passed, took $((ENDTIME - STARTTIME)) seconds"
+fi
+STARTTIME=$(date +%s)
+/usr/bin/time -v $ANDROID_BUILD_TOP/out/host/linux-x86/bin/syspatch from.img a_to_b.patch.xz from.img
+ENDTIME=$(date +%s)
+cmp from.img to.img
+if [ $? -ne 0 ]; then
+	echo "Same file test failed"
+else
+	echo "Same file test passed, took $((ENDTIME - STARTTIME)) seconds"
+fi
+
+rm temp.img
+rm to.img
+rm from.img
+rm *patch*
diff --git a/test/simple_syspatch_test/test.sh b/test/simple_syspatch_test/test.sh
new file mode 100755
index 0000000..08d91f1
--- /dev/null
+++ b/test/simple_syspatch_test/test.sh
@@ -0,0 +1,46 @@
+#! /bin/sh
+
+# preemptive cleanup
+rm a
+rm b
+rm a_to_b.patch
+rm a_to_b.patch.orig
+rm a_to_b.patch.xz
+
+# setup the files
+echo "hello world" > a
+echo "Hello, world" > b
+
+# make and verify the patch
+xdelta3 -e -s a b a_to_b.patch
+xdelta3 -d -s a a_to_b.patch c
+cmp b c
+if [ $? -ne 0 ]; then
+	echo "Couldn't verify patch"
+fi
+rm c
+
+# compress it and verify the compressed patch
+xz -zk -9 --check=crc32 a_to_b.patch
+mv a_to_b.patch a_to_b.patch.orig
+xz -dk a_to_b.patch.xz
+cmp a_to_b.patch a_to_b.patch.orig
+if [ $? -ne 0 ]; then
+	echo "Couldn't verify compression"
+fi
+rm a_to_b.patch
+rm a_to_b.patch.orig
+
+# run the test
+$ANDROID_BUILD_TOP/out/host/linux-x86/bin/syspatch a a_to_b.patch.xz a
+cmp a b
+if [ $? -ne 0 ]; then
+	echo "Test failed"
+fi
+
+# cleanup
+rm a
+rm b
+rm a_to_b.patch
+rm a_to_b.patch.orig
+rm a_to_b.patch.xz