edify extensions for OTA package installation, part 2

Adds more edify functions for OTAs:

  is_mounted getprop apply_patch apply_patch_check apply_patch_space
  write_raw_image write_firmware_image package_extract_file

This allows us to install radios, hboots, boot images, and install
incremental OTA packages.

Fixes a couple of dumb bugs in edify itself:

  - we were doubling the size of the function table each time it was
    *not* full, rather than each time it was full

  - "no such function" errors weren't visible to the parser, so they
    didn't prevent execution of the script.
diff --git a/edify/expr.c b/edify/expr.c
index 129fbd9..5470a2b 100644
--- a/edify/expr.c
+++ b/edify/expr.c
@@ -283,7 +283,7 @@
 NamedFunction* fn_table = NULL;
 
 void RegisterFunction(const char* name, Function fn) {
-  if (fn_entries <= fn_size) {
+  if (fn_entries >= fn_size) {
     fn_size = fn_size*2 + 1;
     fn_table = realloc(fn_table, fn_size * sizeof(NamedFunction));
   }
diff --git a/edify/main.c b/edify/main.c
index c959683..7da89e2 100644
--- a/edify/main.c
+++ b/edify/main.c
@@ -153,10 +153,11 @@
   buffer[size] = '\0';
 
   Expr* root;
+  int error_count = 0;
   yy_scan_bytes(buffer, size);
-  int error = yyparse(&root);
-  printf("parse returned %d\n", error);
-  if (error == 0) {
+  int error = yyparse(&root, &error_count);
+  printf("parse returned %d; %d errors encountered\n", error, error_count);
+  if (error == 0 || error_count > 0) {
     char* result = Evaluate(NULL, root);
     if (result == NULL) {
       char* errmsg = GetError();
diff --git a/edify/parser.y b/edify/parser.y
index 67a210f..cf163c0 100644
--- a/edify/parser.y
+++ b/edify/parser.y
@@ -25,8 +25,8 @@
 extern int gLine;
 extern int gColumn;
 
-void yyerror(Expr** root, const char* s);
-int yyparse(Expr** root);
+void yyerror(Expr** root, int* error_count, const char* s);
+int yyparse(Expr** root, int* error_count);
 
 %}
 
@@ -45,6 +45,7 @@
 %type <args> arglist
 
 %parse-param {Expr** root}
+%parse-param {int* error_count}
 %error-verbose
 
 /* declarations in increasing order of precedence */
@@ -86,7 +87,7 @@
     if ($$->fn == NULL) {
         char buffer[256];
         snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1);
-        yyerror(root, buffer);
+        yyerror(root, error_count, buffer);
         YYERROR;
     }
     $$->name = $1;
@@ -113,9 +114,10 @@
 
 %%
 
-void yyerror(Expr** root, const char* s) {
+void yyerror(Expr** root, int* error_count, const char* s) {
   if (strlen(s) == 0) {
     s = "syntax error";
   }
   printf("line %d col %d: %s\n", gLine, gColumn, s);
+  ++*error_count;
 }
diff --git a/install.c b/install.c
index 0b5c04d..cca9400 100644
--- a/install.c
+++ b/install.c
@@ -124,7 +124,8 @@
         return INSTALL_ERROR;
     }
 
-    LOGI("type is [%s]\n", type);
+    LOGI("type is %s; size is %d; file is %s\n",
+         type, (int)st_data.st_size, filename);
 
     char* data = malloc(st_data.st_size);
     if (data == NULL) {
diff --git a/updater/Android.mk b/updater/Android.mk
index 2716d48..1159d7b 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -13,7 +13,8 @@
 
 LOCAL_SRC_FILES := $(updater_src_files)
 
-LOCAL_STATIC_LIBRARIES := libedify libmtdutils libminzip libz
+LOCAL_STATIC_LIBRARIES := libapplypatch libedify libmtdutils libminzip libz
+LOCAL_STATIC_LIBRARIES += libmincrypt libbz
 LOCAL_STATIC_LIBRARIES += libcutils libstdc++ libc
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
 
diff --git a/updater/install.c b/updater/install.c
index 2336f61..2e965ce 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -24,6 +24,8 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "cutils/misc.h"
+#include "cutils/properties.h"
 #include "edify/expr.h"
 #include "minzip/DirUtil.h"
 #include "mtdutils/mounts.h"
@@ -40,6 +42,7 @@
     return NULL;
 }
 
+
 // mount(type, location, mount_point)
 //
 //   what:  type="MTD"   location="<partition>"            to mount a yaffs2 filesystem
@@ -104,6 +107,36 @@
     return result;
 }
 
+
+// is_mounted(mount_point)
+char* IsMountedFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 1) {
+        return ErrorAbort(cookie, "%s() expects 1 arg, got %d", name, argc);
+    }
+    char* mount_point;
+    if (ReadArgs(cookie, argv, 1, &mount_point) < 0) {
+        return NULL;
+    }
+    if (strlen(mount_point) == 0) {
+        ErrorAbort(cookie, "mount_point argument to unmount() can't be empty");
+        goto done;
+    }
+
+    scan_mounted_volumes();
+    const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point);
+    if (vol == NULL) {
+        result = strdup("");
+    } else {
+        result = mount_point;
+    }
+
+done:
+    if (result != mount_point) free(mount_point);
+    return result;
+}
+
+
 char* UnmountFn(const char* name, void* cookie, int argc, Expr* argv[]) {
     char* result = NULL;
     if (argc != 1) {
@@ -132,6 +165,8 @@
     if (result != mount_point) free(mount_point);
     return result;
 }
+
+
 // format(type, location)
 //
 //    type="MTD"  location=partition
@@ -192,6 +227,7 @@
     return result;
 }
 
+
 char* DeleteFn(const char* name, void* cookie, int argc, Expr* argv[]) {
     char** paths = malloc(argc * sizeof(char*));
     int i;
@@ -222,6 +258,7 @@
     return strdup(buffer);
 }
 
+
 char* ShowProgressFn(const char* name, void* cookie, int argc, Expr* argv[]) {
     if (argc != 2) {
         return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
@@ -243,8 +280,9 @@
     return strdup("");
 }
 
-// package_extract package_path destination_path
-char* PackageExtractFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+// package_extract_dir(package_path, destination_path)
+char* PackageExtractDirFn(const char* name, void* cookie,
+                          int argc, Expr* argv[]) {
     if (argc != 2) {
         return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
     }
@@ -265,6 +303,42 @@
     return strdup(success ? "t" : "");
 }
 
+
+// package_extract_file(package_path, destination_path)
+char* PackageExtractFileFn(const char* name, void* cookie,
+                           int argc, Expr* argv[]) {
+    if (argc != 2) {
+        return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
+    }
+    char* zip_path;
+    char* dest_path;
+    if (ReadArgs(cookie, argv, 2, &zip_path, &dest_path) < 0) return NULL;
+
+    bool success = false;
+
+    ZipArchive* za = ((UpdaterInfo*)cookie)->package_zip;
+    const ZipEntry* entry = mzFindZipEntry(za, zip_path);
+    if (entry == NULL) {
+        fprintf(stderr, "%s: no %s in package\n", name, zip_path);
+        goto done;
+    }
+
+    FILE* f = fopen(dest_path, "wb");
+    if (f == NULL) {
+        fprintf(stderr, "%s: can't open %s for write: %s\n",
+                name, dest_path, strerror(errno));
+        goto done;
+    }
+    success = mzExtractZipEntryToFile(za, entry, fileno(f));
+    fclose(f);
+
+  done:
+    free(zip_path);
+    free(dest_path);
+    return strdup(success ? "t" : "");
+}
+
+
 // symlink target src1 src2 ...
 char* SymlinkFn(const char* name, void* cookie, int argc, Expr* argv[]) {
     if (argc == 0) {
@@ -289,6 +363,7 @@
     return strdup("");
 }
 
+
 char* SetPermFn(const char* name, void* cookie, int argc, Expr* argv[]) {
     char* result = NULL;
     bool recursive = (strcmp(name, "set_perm_recursive") == 0);
@@ -356,15 +431,209 @@
     return result;
 }
 
+
+char* GetPropFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+    if (argc != 1) {
+        return ErrorAbort(cookie, "%s() expects 1 arg, got %d", name, argc);
+    }
+    char* key;
+    key = Evaluate(cookie, argv[0]);
+    if (key == NULL) return NULL;
+
+    char value[PROPERTY_VALUE_MAX];
+    property_get(key, value, "");
+    free(key);
+
+    return strdup(value);
+}
+
+
+static bool write_raw_image_cb(const unsigned char* data,
+                               int data_len, void* ctx) {
+    int r = mtd_write_data((MtdWriteContext*)ctx, (const char *)data, data_len);
+    if (r == data_len) return true;
+    fprintf(stderr, "%s\n", strerror(errno));
+    return false;
+}
+
+// write_raw_image(file, partition)
+char* WriteRawImageFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+    char* result = NULL;
+
+    char* partition;
+    char* filename;
+    if (ReadArgs(cookie, argv, 2, &filename, &partition) < 0) {
+        return NULL;
+    }
+
+    if (strlen(partition) == 0) {
+        ErrorAbort(cookie, "partition argument to %s can't be empty", name);
+        goto done;
+    }
+    if (strlen(filename) == 0) {
+        ErrorAbort(cookie, "file argument to %s can't be empty", name);
+        goto done;
+    }
+
+    mtd_scan_partitions();
+    const MtdPartition* mtd = mtd_find_partition_by_name(partition);
+    if (mtd == NULL) {
+        fprintf(stderr, "%s: no mtd partition named \"%s\"\n", name, partition);
+        result = strdup("");
+        goto done;
+    }
+
+    MtdWriteContext* ctx = mtd_write_partition(mtd);
+    if (ctx == NULL) {
+        fprintf(stderr, "%s: can't write mtd partition \"%s\"\n",
+                name, partition);
+        result = strdup("");
+        goto done;
+    }
+
+    bool success;
+
+    FILE* f = fopen(filename, "rb");
+    if (f == NULL) {
+        fprintf(stderr, "%s: can't open %s: %s\n",
+                name, filename, strerror(errno));
+        result = strdup("");
+        goto done;
+    }
+
+    success = true;
+    char* buffer = malloc(BUFSIZ);
+    int read;
+    while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) {
+        int wrote = mtd_write_data(ctx, buffer, read);
+        success = success && (wrote == read);
+        if (!success) {
+            fprintf(stderr, "mtd_write_data to %s failed: %s\n",
+                    partition, strerror(errno));
+        }
+    }
+    free(buffer);
+    fclose(f);
+
+    printf("%s %s partition from %s\n",
+           success ? "wrote" : "failed to write", partition, filename);
+
+    result = success ? partition : strdup("");
+
+done:
+    if (result != partition) free(partition);
+    free(filename);
+    return result;
+}
+
+// write_firmware_image(file, partition)
+//
+//    partition is "radio" or "hboot"
+//    file is not used until after updater exits
+//
+// TODO: this should live in some HTC-specific library
+char* WriteFirmwareImageFn(const char* name, void* cookie,
+                           int argc, Expr* argv[]) {
+    char* result = NULL;
+
+    char* partition;
+    char* filename;
+    if (ReadArgs(cookie, argv, 2, &filename, &partition) < 0) {
+        return NULL;
+    }
+
+    if (strlen(partition) == 0) {
+        ErrorAbort(cookie, "partition argument to %s can't be empty", name);
+        goto done;
+    }
+    if (strlen(filename) == 0) {
+        ErrorAbort(cookie, "file argument to %s can't be empty", name);
+        goto done;
+    }
+
+    FILE* cmd = ((UpdaterInfo*)cookie)->cmd_pipe;
+    fprintf(cmd, "firmware %s %s\n", partition, filename);
+
+    printf("will write %s firmware from %s\n", partition, filename);
+    result = partition;
+
+done:
+    if (result != partition) free(partition);
+    free(filename);
+    return result;
+}
+
+
+extern int applypatch(int argc, char** argv);
+
+// apply_patch(srcfile, tgtfile, tgtsha1, tgtsize, sha1:patch, ...)
+// apply_patch_check(file, sha1, ...)
+// apply_patch_space(bytes)
+char* ApplyPatchFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+    printf("in applypatchfn (%s)\n", name);
+
+    char* prepend = NULL;
+    if (strstr(name, "check") != NULL) {
+        prepend = "-c";
+    } else if (strstr(name, "space") != NULL) {
+        prepend = "-s";
+    }
+
+    char** args = ReadVarArgs(cookie, argc, argv);
+    if (args == NULL) return NULL;
+
+    // insert the "program name" argv[0] and a copy of the "prepend"
+    // string (if any) at the start of the args.
+
+    int extra = 1 + (prepend != NULL ? 1 : 0);
+    char** temp = malloc((argc+extra) * sizeof(char*));
+    memcpy(temp+extra, args, argc * sizeof(char*));
+    temp[0] = strdup("updater");
+    if (prepend) {
+        temp[1] = strdup(prepend);
+    }
+    free(args);
+    args = temp;
+    argc += extra;
+
+    printf("calling applypatch\n");
+    fflush(stdout);
+    int result = applypatch(argc, args);
+    printf("applypatch returned %d\n", result);
+
+    int i;
+    for (i = 0; i < argc; ++i) {
+        free(args[i]);
+    }
+    free(args);
+
+    switch (result) {
+        case 0:   return strdup("t");
+        case 1:   return strdup("");
+        default:  return ErrorAbort(cookie, "applypatch couldn't parse args");
+    }
+}
+
+
 void RegisterInstallFunctions() {
     RegisterFunction("mount", MountFn);
+    RegisterFunction("is_mounted", IsMountedFn);
     RegisterFunction("unmount", UnmountFn);
     RegisterFunction("format", FormatFn);
     RegisterFunction("show_progress", ShowProgressFn);
     RegisterFunction("delete", DeleteFn);
     RegisterFunction("delete_recursive", DeleteFn);
-    RegisterFunction("package_extract", PackageExtractFn);
+    RegisterFunction("package_extract_dir", PackageExtractDirFn);
+    RegisterFunction("package_extract_file", PackageExtractFileFn);
     RegisterFunction("symlink", SymlinkFn);
     RegisterFunction("set_perm", SetPermFn);
     RegisterFunction("set_perm_recursive", SetPermFn);
+
+    RegisterFunction("getprop", GetPropFn);
+    RegisterFunction("write_raw_image", WriteRawImageFn);
+    RegisterFunction("write_firmware_image", WriteFirmwareImageFn);
+
+    RegisterFunction("apply_patch", ApplyPatchFn);
+    RegisterFunction("apply_patch_check", ApplyPatchFn);
+    RegisterFunction("apply_patch_space", ApplyPatchFn);
 }
diff --git a/updater/updater.c b/updater/updater.c
index aa03803..0977625 100644
--- a/updater/updater.c
+++ b/updater/updater.c
@@ -80,10 +80,11 @@
     // Parse the script.
 
     Expr* root;
+    int error_count = 0;
     yy_scan_string(script);
-    int error = yyparse(&root);
-    if (error != 0) {
-        fprintf(stderr, "%d parse errors\n", error);
+    int error = yyparse(&root, &error_count);
+    if (error != 0 || error_count > 0) {
+        fprintf(stderr, "%d parse errors\n", error_count);
         return 6;
     }