allow OTA package to provide binary instead of script

Allow installation of OTA packages which do not contain an
update-script, but instead contain an update-binary.
diff --git a/install.c b/install.c
index e7db2a8..eff9312 100644
--- a/install.c
+++ b/install.c
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
+#include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
 
 #include "amend/amend.h"
 #include "common.h"
@@ -30,8 +33,10 @@
 #include "mtdutils/mtdutils.h"
 #include "roots.h"
 #include "verifier.h"
+#include "firmware.h"
 
 #define ASSUMED_UPDATE_SCRIPT_NAME  "META-INF/com/google/android/update-script"
+#define ASSUMED_UPDATE_BINARY_NAME  "META-INF/com/google/android/update-binary"
 #define PUBLIC_KEYS_FILE "/res/keys"
 
 static const ZipEntry *
@@ -95,7 +100,7 @@
     int ret = execCommandList((ExecContext *)1, commands);
     if (ret != 0) {
         int num = ret;
-        char *line, *next = script_data;
+        char *line = NULL, *next = script_data;
         while (next != NULL && ret-- > 0) {
             line = next;
             next = memchr(line, '\n', script_data + script_len - line);
@@ -109,6 +114,159 @@
     return INSTALL_SUCCESS;
 }
 
+// The update binary ask us to install a firmware file on reboot.  Set
+// that up.  Takes ownership of type and filename.
+static int
+handle_firmware_update(char* type, char* filename) {
+    struct stat st_data;
+    if (stat(filename, &st_data) < 0) {
+        LOGE("Error stat'ing %s: %s\n", filename, strerror(errno));
+        return INSTALL_ERROR;
+    }
+
+    LOGI("type is [%s]\n", type);
+
+    char* data = malloc(st_data.st_size);
+    if (data == NULL) {
+        LOGE("Can't allocate %d bytes for firmware data\n", st_data.st_size);
+        return INSTALL_ERROR;
+    }
+
+    FILE* f = fopen(filename, "rb");
+    if (f == NULL) {
+        LOGE("Failed to open %s: %s\n", filename, strerror(errno));
+        return INSTALL_ERROR;
+    }
+    if (fread(data, 1, st_data.st_size, f) != st_data.st_size) {
+        LOGE("Failed to read firmware data: %s\n", strerror(errno));
+        return INSTALL_ERROR;
+    }
+    fclose(f);
+
+    if (remember_firmware_update(type, data, st_data.st_size)) {
+        LOGE("Can't store %s image\n", type);
+        free(data);
+        return INSTALL_ERROR;
+    }
+    free(filename);
+
+    return INSTALL_SUCCESS;
+}
+
+// If the package contains an update binary, extract it and run it.
+static int
+try_update_binary(const char *path, ZipArchive *zip) {
+    const ZipEntry* binary_entry =
+            mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
+    if (binary_entry == NULL) {
+        return INSTALL_CORRUPT;
+    }
+
+    char* binary = "/tmp/update_binary";
+    unlink(binary);
+    int fd = creat(binary, 0755);
+    if (fd < 0) {
+        LOGE("Can't make %s\n", binary);
+        return 1;
+    }
+    bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
+    close(fd);
+
+    if (!ok) {
+        LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
+        return 1;
+    }
+
+    int pipefd[2];
+    pipe(pipefd);
+
+    // When executing the update binary contained in the package, the
+    // arguments passed are:
+    //
+    //   - the version number for this interface (currently 1)
+    //
+    //   - an fd to which the program can write in order to update the
+    //     progress bar.  The program can write single-line commands:
+    //
+    //        progress <frac> <secs>
+    //            fill up <frac> of the progress bar over <secs> seconds.
+    //
+    //        firmware <"hboot"|"radio"> <filename>
+    //            arrange to install the contents of <filename> in the
+    //            given partition on reboot.
+    //
+    //   - the name of the package zip file.
+    //
+
+    char** args = malloc(sizeof(char*) * 5);
+    args[0] = binary;
+    args[1] = "1";
+    args[2] = malloc(10);
+    sprintf(args[2], "%d", pipefd[1]);
+    args[3] = (char*)path;
+    args[4] = NULL;
+
+    pid_t pid = fork();
+    if (pid == 0) {
+        close(pipefd[0]);
+        execv(binary, args);
+        fprintf(stderr, "E:Can't run %s (%s)\n", binary, strerror(errno));
+        _exit(-1);
+    }
+    close(pipefd[1]);
+
+    char* firmware_type = NULL;
+    char* firmware_filename = NULL;
+
+    char buffer[81];
+    FILE* from_child = fdopen(pipefd[0], "r");
+    while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
+        LOGI("read: %s", buffer);
+
+        char* command = strtok(buffer, " \n");
+        if (command == NULL) {
+            continue;
+        } else if (strcmp(command, "progress") == 0) {
+            char* fraction_s = strtok(NULL, " \n");
+            char* seconds_s = strtok(NULL, " \n");
+
+            float fraction = strtof(fraction_s, NULL);
+            int seconds = strtol(seconds_s, NULL, 10);
+
+            ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION),
+                             seconds);
+        } else if (strcmp(command, "firmware") == 0) {
+            char* type = strtok(NULL, " \n");
+            char* filename = strtok(NULL, " \n");
+
+            if (type != NULL && filename != NULL) {
+                if (firmware_type != NULL) {
+                    LOGE("ignoring attempt to do multiple firmware updates");
+                } else {
+                    firmware_type = strdup(type);
+                    firmware_filename = strdup(filename);
+                }
+            }
+        } else {
+            LOGE("unknown command [%s]\n", command);
+        }
+    }
+    fclose(from_child);
+
+    int status;
+    waitpid(pid, &status, 0);
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        LOGE("Error in %s\n(Status %d)\n", path, status);
+        return INSTALL_ERROR;
+    }
+
+    if (firmware_type != NULL) {
+        return handle_firmware_update(firmware_type, firmware_filename);
+    } else {
+        return INSTALL_SUCCESS;
+    }
+}
+
 static int
 handle_update_package(const char *path, ZipArchive *zip,
                       const RSAPublicKey *keys, int numKeys)
@@ -127,6 +285,16 @@
     // Update should take the rest of the progress bar.
     ui_print("Installing update...\n");
 
+    int result = try_update_binary(path, zip);
+    if (result == INSTALL_SUCCESS || result == INSTALL_ERROR) {
+        register_package_root(NULL, NULL);  // Unregister package root
+        return result;
+    }
+
+    // if INSTALL_CORRUPT is returned, this package doesn't have an
+    // update binary.  Fall back to the older mechanism of looking for
+    // an update script.
+
     const ZipEntry *script_entry;
     script_entry = find_update_script(zip);
     if (script_entry == NULL) {