add file_getprop() to updater

Add a function to read a property from a ".prop"-formatted file
(key=value pairs, one per line, ignore # comment lines and blank
lines).  Move ErrorAbort to the core of edify; it's not specific to
updater now that errors aren't stored in the app cookie.
diff --git a/edify/expr.c b/edify/expr.c
index f1c5555..72e5100 100644
--- a/edify/expr.c
+++ b/edify/expr.c
@@ -417,3 +417,16 @@
     }
     return args;
 }
+
+// Use printf-style arguments to compose an error message to put into
+// *state.  Returns NULL.
+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);
+    free(state->errmsg);
+    state->errmsg = buffer;
+    return NULL;
+}
diff --git a/edify/expr.h b/edify/expr.h
index 671b499..d2e7392 100644
--- a/edify/expr.h
+++ b/edify/expr.h
@@ -118,5 +118,9 @@
 // strings it contains.
 char** ReadVarArgs(State* state, int argc, Expr* argv[]);
 
+// Use printf-style arguments to compose an error message to put into
+// *state.  Returns NULL.
+char* ErrorAbort(State* state, char* format, ...);
+
 
 #endif  // _EXPRESSION_H
diff --git a/updater/install.c b/updater/install.c
index 616cb2c..0bd0939 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -32,17 +32,6 @@
 #include "mtdutils/mtdutils.h"
 #include "updater.h"
 
-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);
-    free(state->errmsg);
-    state->errmsg = buffer;
-    return NULL;
-}
-
 
 // mount(type, location, mount_point)
 //
@@ -449,6 +438,105 @@
 }
 
 
+// file_getprop(file, key)
+//
+//   interprets 'file' as a getprop-style file (key=value pairs, one
+//   per line, # comment lines and blank lines okay), and returns the value
+//   for 'key' (or "" if it isn't defined).
+char* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    char* buffer = NULL;
+    char* filename;
+    char* key;
+    if (ReadArgs(state, argv, 2, &filename, &key) < 0) {
+        return NULL;
+    }
+
+    struct stat st;
+    if (stat(filename, &st) < 0) {
+        ErrorAbort(state, "%s: failed to stat \"%s\": %s",
+                   name, filename, strerror(errno));
+        goto done;
+    }
+
+#define MAX_FILE_GETPROP_SIZE    65536
+
+    if (st.st_size > MAX_FILE_GETPROP_SIZE) {
+        ErrorAbort(state, "%s too large for %s (max %d)",
+                   filename, name, MAX_FILE_GETPROP_SIZE);
+        goto done;
+    }
+
+    buffer = malloc(st.st_size+1);
+    if (buffer == NULL) {
+        ErrorAbort(state, "%s: failed to alloc %d bytes", name, st.st_size+1);
+        goto done;
+    }
+
+    FILE* f = fopen(filename, "rb");
+    if (f == NULL) {
+        ErrorAbort(state, "%s: failed to open %s: %s",
+                   name, filename, strerror(errno));
+        goto done;
+    }
+
+    if (fread(buffer, 1, st.st_size, f) != st.st_size) {
+        ErrorAbort(state, "%s: failed to read %d bytes from %s",
+                   name, st.st_size+1, filename);
+        fclose(f);
+        goto done;
+    }
+    buffer[st.st_size] = '\0';
+
+    fclose(f);
+
+    char* line = strtok(buffer, "\n");
+    do {
+        // skip whitespace at start of line
+        while (*line && isspace(*line)) ++line;
+
+        // comment or blank line: skip to next line
+        if (*line == '\0' || *line == '#') continue;
+
+        char* equal = strchr(line, '=');
+        if (equal == NULL) {
+            ErrorAbort(state, "%s: malformed line \"%s\": %s not a prop file?",
+                       name, line, filename);
+            goto done;
+        }
+
+        // trim whitespace between key and '='
+        char* key_end = equal-1;
+        while (key_end > line && isspace(*key_end)) --key_end;
+        key_end[1] = '\0';
+
+        // not the key we're looking for
+        if (strcmp(key, line) != 0) continue;
+
+        // skip whitespace after the '=' to the start of the value
+        char* val_start = equal+1;
+        while(*val_start && isspace(*val_start)) ++val_start;
+
+        // trim trailing whitespace
+        char* val_end = val_start + strlen(val_start)-1;
+        while (val_end > val_start && isspace(*val_end)) --val_end;
+        val_end[1] = '\0';
+
+        result = strdup(val_start);
+        break;
+
+    } while ((line = strtok(NULL, "\n")));
+
+    if (result == NULL) result = strdup("");
+
+  done:
+    free(filename);
+    free(key);
+    free(buffer);
+    return result;
+}
+
+
 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);
@@ -670,6 +758,7 @@
     RegisterFunction("set_perm_recursive", SetPermFn);
 
     RegisterFunction("getprop", GetPropFn);
+    RegisterFunction("file_getprop", FileGetPropFn);
     RegisterFunction("write_raw_image", WriteRawImageFn);
     RegisterFunction("write_firmware_image", WriteFirmwareImageFn);