fixes to edify and updater script

A few more changes to edify:

  - fix write_raw_image(); my last change neglected to close the write
    context, so the written image was corrupt.

  - each expression tracks the span of the source code from which it
    was compiled, so that assert()'s error message can include the
    source of the expression that failed.

  - the 'cookie' argument to each Function is replaced with a State
    object, which contains the cookie, the source script (for use with
    the above spans), and the current error message (replacing the
    global variables that were used for this purpose).

  - in the recovery image, a new command "ui_print" can be sent back
    through the command pipe to cause text to appear on the screen.
    Add a new ui_print() function to print things from scripts.
    Rename existing "print" function to "stdout".
diff --git a/updater/install.c b/updater/install.c
index 2e965ce..616cb2c 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -32,13 +32,14 @@
 #include "mtdutils/mtdutils.h"
 #include "updater.h"
 
-char* ErrorAbort(void* cookie, char* format, ...) {
+char* ErrorAbort(State* state, char* format, ...) {
     char* buffer = malloc(4096);
     va_list v;
     va_start(v, format);
     vsnprintf(buffer, 4096, format, v);
     va_end(v);
-    SetError(buffer);
+    free(state->errmsg);
+    state->errmsg = buffer;
     return NULL;
 }
 
@@ -47,28 +48,28 @@
 //
 //   what:  type="MTD"   location="<partition>"            to mount a yaffs2 filesystem
 //          type="vfat"  location="/dev/block/<whatever>"  to mount a device
-char* MountFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* MountFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     if (argc != 3) {
-        return ErrorAbort(cookie, "%s() expects 3 args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 3 args, got %d", name, argc);
     }
     char* type;
     char* location;
     char* mount_point;
-    if (ReadArgs(cookie, argv, 3, &type, &location, &mount_point) < 0) {
+    if (ReadArgs(state, argv, 3, &type, &location, &mount_point) < 0) {
         return NULL;
     }
 
     if (strlen(type) == 0) {
-        ErrorAbort(cookie, "type argument to %s() can't be empty", name);
+        ErrorAbort(state, "type argument to %s() can't be empty", name);
         goto done;
     }
     if (strlen(location) == 0) {
-        ErrorAbort(cookie, "location argument to %s() can't be empty", name);
+        ErrorAbort(state, "location argument to %s() can't be empty", name);
         goto done;
     }
     if (strlen(mount_point) == 0) {
-        ErrorAbort(cookie, "mount_point argument to %s() can't be empty", name);
+        ErrorAbort(state, "mount_point argument to %s() can't be empty", name);
         goto done;
     }
 
@@ -109,17 +110,17 @@
 
 
 // is_mounted(mount_point)
-char* IsMountedFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     if (argc != 1) {
-        return ErrorAbort(cookie, "%s() expects 1 arg, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
     }
     char* mount_point;
-    if (ReadArgs(cookie, argv, 1, &mount_point) < 0) {
+    if (ReadArgs(state, argv, 1, &mount_point) < 0) {
         return NULL;
     }
     if (strlen(mount_point) == 0) {
-        ErrorAbort(cookie, "mount_point argument to unmount() can't be empty");
+        ErrorAbort(state, "mount_point argument to unmount() can't be empty");
         goto done;
     }
 
@@ -137,17 +138,17 @@
 }
 
 
-char* UnmountFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     if (argc != 1) {
-        return ErrorAbort(cookie, "%s() expects 1 arg, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
     }
     char* mount_point;
-    if (ReadArgs(cookie, argv, 1, &mount_point) < 0) {
+    if (ReadArgs(state, argv, 1, &mount_point) < 0) {
         return NULL;
     }
     if (strlen(mount_point) == 0) {
-        ErrorAbort(cookie, "mount_point argument to unmount() can't be empty");
+        ErrorAbort(state, "mount_point argument to unmount() can't be empty");
         goto done;
     }
 
@@ -170,23 +171,23 @@
 // format(type, location)
 //
 //    type="MTD"  location=partition
-char* FormatFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     if (argc != 2) {
-        return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
     }
     char* type;
     char* location;
-    if (ReadArgs(cookie, argv, 2, &type, &location) < 0) {
+    if (ReadArgs(state, argv, 2, &type, &location) < 0) {
         return NULL;
     }
 
     if (strlen(type) == 0) {
-        ErrorAbort(cookie, "type argument to %s() can't be empty", name);
+        ErrorAbort(state, "type argument to %s() can't be empty", name);
         goto done;
     }
     if (strlen(location) == 0) {
-        ErrorAbort(cookie, "location argument to %s() can't be empty", name);
+        ErrorAbort(state, "location argument to %s() can't be empty", name);
         goto done;
     }
 
@@ -228,11 +229,11 @@
 }
 
 
-char* DeleteFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) {
     char** paths = malloc(argc * sizeof(char*));
     int i;
     for (i = 0; i < argc; ++i) {
-        paths[i] = Evaluate(cookie, argv[i]);
+        paths[i] = Evaluate(state, argv[i]);
         if (paths[i] == NULL) {
             int j;
             for (j = 0; j < i; ++i) {
@@ -259,20 +260,20 @@
 }
 
 
-char* ShowProgressFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
     if (argc != 2) {
-        return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
     }
     char* frac_str;
     char* sec_str;
-    if (ReadArgs(cookie, argv, 2, &frac_str, &sec_str) < 0) {
+    if (ReadArgs(state, argv, 2, &frac_str, &sec_str) < 0) {
         return NULL;
     }
 
     double frac = strtod(frac_str, NULL);
     int sec = strtol(sec_str, NULL, 10);
 
-    UpdaterInfo* ui = (UpdaterInfo*)cookie;
+    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
     fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
 
     free(frac_str);
@@ -281,16 +282,16 @@
 }
 
 // package_extract_dir(package_path, destination_path)
-char* PackageExtractDirFn(const char* name, void* cookie,
+char* PackageExtractDirFn(const char* name, State* state,
                           int argc, Expr* argv[]) {
     if (argc != 2) {
-        return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
+        return ErrorAbort(state, "%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;
+    if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
 
-    ZipArchive* za = ((UpdaterInfo*)cookie)->package_zip;
+    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
 
     // To create a consistent system image, never use the clock for timestamps.
     struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
@@ -305,18 +306,18 @@
 
 
 // package_extract_file(package_path, destination_path)
-char* PackageExtractFileFn(const char* name, void* cookie,
+char* PackageExtractFileFn(const char* name, State* state,
                            int argc, Expr* argv[]) {
     if (argc != 2) {
-        return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
+        return ErrorAbort(state, "%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;
+    if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
 
     bool success = false;
 
-    ZipArchive* za = ((UpdaterInfo*)cookie)->package_zip;
+    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
     const ZipEntry* entry = mzFindZipEntry(za, zip_path);
     if (entry == NULL) {
         fprintf(stderr, "%s: no %s in package\n", name, zip_path);
@@ -340,15 +341,15 @@
 
 
 // symlink target src1 src2 ...
-char* SymlinkFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) {
     if (argc == 0) {
-        return ErrorAbort(cookie, "%s() expects 1+ args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 1+ args, got %d", name, argc);
     }
     char* target;
-    target = Evaluate(cookie, argv[0]);
+    target = Evaluate(state, argv[0]);
     if (target == NULL) return NULL;
 
-    char** srcs = ReadVarArgs(cookie, argc-1, argv+1);
+    char** srcs = ReadVarArgs(state, argc-1, argv+1);
     if (srcs == NULL) {
         free(target);
         return NULL;
@@ -364,16 +365,16 @@
 }
 
 
-char* SetPermFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     bool recursive = (strcmp(name, "set_perm_recursive") == 0);
 
     int min_args = 4 + (recursive ? 1 : 0);
     if (argc < min_args) {
-        return ErrorAbort(cookie, "%s() expects %d+ args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects %d+ args, got %d", name, argc);
     }
 
-    char** args = ReadVarArgs(cookie, argc, argv);
+    char** args = ReadVarArgs(state, argc, argv);
     if (args == NULL) return NULL;
 
     char* end;
@@ -381,26 +382,26 @@
 
     int uid = strtoul(args[0], &end, 0);
     if (*end != '\0' || args[0][0] == 0) {
-        ErrorAbort(cookie, "%s: \"%s\" not a valid uid", name, args[0]);
+        ErrorAbort(state, "%s: \"%s\" not a valid uid", name, args[0]);
         goto done;
     }
 
     int gid = strtoul(args[1], &end, 0);
     if (*end != '\0' || args[1][0] == 0) {
-        ErrorAbort(cookie, "%s: \"%s\" not a valid gid", name, args[1]);
+        ErrorAbort(state, "%s: \"%s\" not a valid gid", name, args[1]);
         goto done;
     }
 
     if (recursive) {
         int dir_mode = strtoul(args[2], &end, 0);
         if (*end != '\0' || args[2][0] == 0) {
-            ErrorAbort(cookie, "%s: \"%s\" not a valid dirmode", name, args[2]);
+            ErrorAbort(state, "%s: \"%s\" not a valid dirmode", name, args[2]);
             goto done;
         }
 
         int file_mode = strtoul(args[3], &end, 0);
         if (*end != '\0' || args[3][0] == 0) {
-            ErrorAbort(cookie, "%s: \"%s\" not a valid filemode",
+            ErrorAbort(state, "%s: \"%s\" not a valid filemode",
                        name, args[3]);
             goto done;
         }
@@ -411,7 +412,7 @@
     } else {
         int mode = strtoul(args[2], &end, 0);
         if (*end != '\0' || args[2][0] == 0) {
-            ErrorAbort(cookie, "%s: \"%s\" not a valid mode", name, args[2]);
+            ErrorAbort(state, "%s: \"%s\" not a valid mode", name, args[2]);
             goto done;
         }
 
@@ -432,12 +433,12 @@
 }
 
 
-char* GetPropFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
     if (argc != 1) {
-        return ErrorAbort(cookie, "%s() expects 1 arg, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
     }
     char* key;
-    key = Evaluate(cookie, argv[0]);
+    key = Evaluate(state, argv[0]);
     if (key == NULL) return NULL;
 
     char value[PROPERTY_VALUE_MAX];
@@ -457,21 +458,21 @@
 }
 
 // write_raw_image(file, partition)
-char* WriteRawImageFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
 
     char* partition;
     char* filename;
-    if (ReadArgs(cookie, argv, 2, &filename, &partition) < 0) {
+    if (ReadArgs(state, argv, 2, &filename, &partition) < 0) {
         return NULL;
     }
 
     if (strlen(partition) == 0) {
-        ErrorAbort(cookie, "partition argument to %s can't be empty", name);
+        ErrorAbort(state, "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);
+        ErrorAbort(state, "file argument to %s can't be empty", name);
         goto done;
     }
 
@@ -515,6 +516,13 @@
     free(buffer);
     fclose(f);
 
+    if (mtd_erase_blocks(ctx, -1) == -1) {
+        fprintf(stderr, "%s: error erasing blocks of %s\n", name, partition);
+    }
+    if (mtd_write_close(ctx) != 0) {
+        fprintf(stderr, "%s: error closing write of %s\n", name, partition);
+    }
+
     printf("%s %s partition from %s\n",
            success ? "wrote" : "failed to write", partition, filename);
 
@@ -532,26 +540,26 @@
 //    file is not used until after updater exits
 //
 // TODO: this should live in some HTC-specific library
-char* WriteFirmwareImageFn(const char* name, void* cookie,
+char* WriteFirmwareImageFn(const char* name, State* state,
                            int argc, Expr* argv[]) {
     char* result = NULL;
 
     char* partition;
     char* filename;
-    if (ReadArgs(cookie, argv, 2, &filename, &partition) < 0) {
+    if (ReadArgs(state, argv, 2, &filename, &partition) < 0) {
         return NULL;
     }
 
     if (strlen(partition) == 0) {
-        ErrorAbort(cookie, "partition argument to %s can't be empty", name);
+        ErrorAbort(state, "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);
+        ErrorAbort(state, "file argument to %s can't be empty", name);
         goto done;
     }
 
-    FILE* cmd = ((UpdaterInfo*)cookie)->cmd_pipe;
+    FILE* cmd = ((UpdaterInfo*)(state->cookie))->cmd_pipe;
     fprintf(cmd, "firmware %s %s\n", partition, filename);
 
     printf("will write %s firmware from %s\n", partition, filename);
@@ -569,7 +577,7 @@
 // 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[]) {
+char* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) {
     printf("in applypatchfn (%s)\n", name);
 
     char* prepend = NULL;
@@ -579,7 +587,7 @@
         prepend = "-s";
     }
 
-    char** args = ReadVarArgs(cookie, argc, argv);
+    char** args = ReadVarArgs(state, argc, argv);
     if (args == NULL) return NULL;
 
     // insert the "program name" argv[0] and a copy of the "prepend"
@@ -610,10 +618,42 @@
     switch (result) {
         case 0:   return strdup("t");
         case 1:   return strdup("");
-        default:  return ErrorAbort(cookie, "applypatch couldn't parse args");
+        default:  return ErrorAbort(state, "applypatch couldn't parse args");
     }
 }
 
+char* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char** args = ReadVarArgs(state, argc, argv);
+    if (args == NULL) {
+        return NULL;
+    }
+
+    int size = 0;
+    int i;
+    for (i = 0; i < argc; ++i) {
+        size += strlen(args[i]);
+    }
+    char* buffer = malloc(size+1);
+    size = 0;
+    for (i = 0; i < argc; ++i) {
+        strcpy(buffer+size, args[i]);
+        size += strlen(args[i]);
+        free(args[i]);
+    }
+    free(args);
+    buffer[size] = '\0';
+
+    char* line = strtok(buffer, "\n");
+    while (line) {
+        fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe,
+                "ui_print %s\n", line);
+        line = strtok(NULL, "\n");
+    }
+    fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "ui_print\n");
+
+    return buffer;
+}
+
 
 void RegisterInstallFunctions() {
     RegisterFunction("mount", MountFn);
@@ -636,4 +676,6 @@
     RegisterFunction("apply_patch", ApplyPatchFn);
     RegisterFunction("apply_patch_check", ApplyPatchFn);
     RegisterFunction("apply_patch_space", ApplyPatchFn);
+
+    RegisterFunction("ui_print", UIPrintFn);
 }
diff --git a/updater/updater.c b/updater/updater.c
index 0977625..5a2ed2c 100644
--- a/updater/updater.c
+++ b/updater/updater.c
@@ -94,12 +94,26 @@
     updater_info.cmd_pipe = cmd_pipe;
     updater_info.package_zip = &za;
 
-    char* result = Evaluate(&updater_info, root);
+    State state;
+    state.cookie = &updater_info;
+    state.script = script;
+    state.errmsg = NULL;
+
+    char* result = Evaluate(&state, root);
     if (result == NULL) {
-        const char* errmsg = GetError();
-        fprintf(stderr, "script aborted with error: %s\n",
-                errmsg == NULL ? "(none)" : errmsg);
-        ClearError();
+        if (state.errmsg == NULL) {
+            fprintf(stderr, "script aborted (no error message)\n");
+            fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
+        } else {
+            fprintf(stderr, "script aborted: %s\n", state.errmsg);
+            char* line = strtok(state.errmsg, "\n");
+            while (line) {
+                fprintf(cmd_pipe, "ui_print %s\n", line);
+                line = strtok(NULL, "\n");
+            }
+            fprintf(cmd_pipe, "ui_print\n");
+        }
+        free(state.errmsg);
         return 7;
     } else {
         fprintf(stderr, "script result was [%s]\n", result);
@@ -107,6 +121,7 @@
     }
 
     mzCloseZipArchive(&za);
+    free(script);
 
     return 0;
 }