add syspatch support to updater

Add the syspatch() function, which can apply xdelta3+xz patches using
the libsyspatch library.

Change-Id: Idc1921e449020923bcaf425a1983bec0833e47ed
diff --git a/updater/Android.mk b/updater/Android.mk
index 67e98ec..2e92504 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -35,6 +35,8 @@
 LOCAL_STATIC_LIBRARIES += libselinux
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
 
+LOCAL_STATIC_LIBRARIES += libsyspatch libxz libxdelta3
+
 # Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
 # named "Register_<libname>()".  Here we emit a little C function that
 # gets #included by updater.c.  It calls all those registration
diff --git a/updater/install.c b/updater/install.c
index aebd4f3..2cf00bf 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -45,11 +45,25 @@
 #include "mtdutils/mounts.h"
 #include "mtdutils/mtdutils.h"
 #include "updater.h"
+#include "syspatch.h"
 
 #ifdef USE_EXT4
 #include "make_ext4fs.h"
 #endif
 
+// Take a sha-1 digest and return it as a newly-allocated hex string.
+static char* PrintSha1(uint8_t* digest) {
+    char* buffer = malloc(SHA_DIGEST_SIZE*2 + 1);
+    int i;
+    const char* alphabet = "0123456789abcdef";
+    for (i = 0; i < SHA_DIGEST_SIZE; ++i) {
+        buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf];
+        buffer[i*2+1] = alphabet[digest[i] & 0xf];
+    }
+    buffer[i*2] = '\0';
+    return buffer;
+}
+
 // mount(fs_type, partition_type, location, mount_point)
 //
 //    fs_type="yaffs2" partition_type="MTD"     location=partition
@@ -1053,8 +1067,104 @@
     return StringValue(strdup(CacheSizeCheck(bytes) ? "" : "t"));
 }
 
+// syspatch(file, size, tgt_sha1, init_sha1, patch)
 
-// apply_patch(srcfile, tgtfile, tgtsha1, tgtsize, sha1_1, patch_1, ...)
+Value* SysPatchFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 5) {
+        return ErrorAbort(state, "%s(): expected 5 args, got %d", name, argc);
+    }
+
+    char* filename;
+    char* filename_size_str;
+    char* target_sha1;
+    char* init_sha1;
+    char* patch_filename;
+    uint8_t target_digest[SHA_DIGEST_SIZE];
+    uint8_t init_digest[SHA_DIGEST_SIZE];
+
+    if (ReadArgs(state, argv, 5, &filename, &filename_size_str,
+                 &target_sha1, &init_sha1, &patch_filename) < 0) {
+        return NULL;
+    }
+
+    if (ParseSha1(target_sha1, target_digest) != 0) {
+        printf("%s(): failed to parse '%s' as target SHA-1", name, target_sha1);
+        memset(target_digest, 0, SHA_DIGEST_SIZE);
+    }
+    if (ParseSha1(init_sha1, init_digest) != 0) {
+        printf("%s(): failed to parse '%s' as init SHA-1", name, init_sha1);
+        memset(init_digest, 0, SHA_DIGEST_SIZE);
+    }
+
+    size_t len = strtoull(filename_size_str, NULL, 0);
+
+    SHA_CTX ctx;
+    SHA_init(&ctx);
+    FILE* src = fopen(filename, "r");
+    size_t pos = 0;
+    unsigned char buffer[4096];
+    while (pos < len) {
+        size_t to_read = len - pos;
+        if (to_read > sizeof(buffer)) to_read = sizeof(buffer);
+        size_t read = fread(buffer, 1, to_read, src);
+        if (read <= 0) {
+            printf("%s(): short read after %zu bytes\n", name, pos);
+            break;
+        }
+        SHA_update(&ctx, buffer, read);
+        pos += read;
+    }
+    rewind(src);
+    uint8_t* digest = SHA_final(&ctx);
+
+    char* hexdigest = PrintSha1(digest);
+    printf("  system partition sha1 = %s\n", hexdigest);
+
+    if (memcmp(digest, target_digest, SHA_DIGEST_SIZE) == 0) {
+        printf("%s(): %s is already target\n", name, filename);
+        fclose(src);
+        goto done;
+    }
+
+    if (memcmp(digest, init_digest, SHA_DIGEST_SIZE) != 0) {
+        return ErrorAbort(state, "%s(): %s in unknown state\n", name, filename);
+    }
+
+    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
+
+    const ZipEntry* entry = mzFindZipEntry(za, patch_filename);
+    if (entry == NULL) {
+        return ErrorAbort(state, "%s(): no %s in package\n", name, patch_filename);
+    }
+
+    unsigned char* patch_data;
+    size_t patch_len;
+    if (!mzGetStoredEntry(za, entry, &patch_data, &patch_len)) {
+        return ErrorAbort(state, "%s(): failed to get %s entry\n", name, patch_filename);
+    }
+
+    FILE* tgt = fopen(filename, "r+");
+
+    int ret = syspatch(src, patch_data, patch_len, tgt);
+
+    fclose(src);
+    fclose(tgt);
+
+    if (ret != 0) {
+        return ErrorAbort(state, "%s(): patching failed\n", name);
+    }
+
+      done:
+    free(filename_size_str);
+    free(target_sha1);
+    free(init_sha1);
+    free(patch_filename);
+    return StringValue(filename);
+
+}
+
+// apply_patch(file, size, init_sha1, tgt_sha1, patch)
+
 Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) {
     if (argc < 6 || (argc % 2) == 1) {
         return ErrorAbort(state, "%s(): expected at least 6 args and an "
@@ -1239,19 +1349,6 @@
     return StringValue(strdup(buffer));
 }
 
-// Take a sha-1 digest and return it as a newly-allocated hex string.
-static char* PrintSha1(uint8_t* digest) {
-    char* buffer = malloc(SHA_DIGEST_SIZE*2 + 1);
-    int i;
-    const char* alphabet = "0123456789abcdef";
-    for (i = 0; i < SHA_DIGEST_SIZE; ++i) {
-        buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf];
-        buffer[i*2+1] = alphabet[digest[i] & 0xf];
-    }
-    buffer[i*2] = '\0';
-    return buffer;
-}
-
 // sha1_check(data)
 //    to return the sha1 of the data (given in the format returned by
 //    read_file).
@@ -1469,6 +1566,8 @@
     RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
     RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
 
+    RegisterFunction("syspatch", SysPatchFn);
+
     RegisterFunction("read_file", ReadFileFn);
     RegisterFunction("sha1_check", Sha1CheckFn);
     RegisterFunction("rename", RenameFn);