edify extensions for OTA package installation, part 1

Adds the following edify functions:

  mount unmount format show_progress delete delete_recursive
  package_extract symlink set_perm set_perm_recursive

This set is enough to extract and install the system part of a (full)
OTA package.

Adds the updater binary that extracts an edify script from the OTA
package and then executes it.  Minor changes to the edify core (adds a
sleep() builtin for debugging, adds "." to the set of characters that
can appear in an unquoted string).
diff --git a/edify/Android.mk b/edify/Android.mk
index 803fba2..fac0ba7 100644
--- a/edify/Android.mk
+++ b/edify/Android.mk
@@ -26,15 +26,14 @@
 
 include $(BUILD_HOST_EXECUTABLE)
 
-# #
-# # Build the device-side library
-# #
-# include $(CLEAR_VARS)
+#
+# Build the device-side library
+#
+include $(CLEAR_VARS)
 
-# LOCAL_SRC_FILES := $(edify_src_files)
-# LOCAL_SRC_FILES += $(edify_test_files)
+LOCAL_SRC_FILES := $(edify_src_files)
 
-# LOCAL_CFLAGS := $(edify_cflags)
-# LOCAL_MODULE := libedify
+LOCAL_CFLAGS := $(edify_cflags)
+LOCAL_MODULE := libedify
 
-# include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_STATIC_LIBRARY)
diff --git a/edify/README b/edify/README
index 5ccb582..810455c 100644
--- a/edify/README
+++ b/edify/README
@@ -10,7 +10,7 @@
   understood, as are hexadecimal escapes like \x4a.
 
 - String literals consisting of only letters, numbers, colons,
-  underscores, and slashes don't need to be in double quotes.
+  underscores, slashes, and periods don't need to be in double quotes.
 
 - The following words are reserved:
 
diff --git a/edify/expr.c b/edify/expr.c
index b3b8927..129fbd9 100644
--- a/edify/expr.c
+++ b/edify/expr.c
@@ -19,6 +19,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
+#include <unistd.h>
 
 #include "expr.h"
 
@@ -92,6 +93,12 @@
 }
 
 char* AbortFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+  char* msg = NULL;
+  if (argc > 0) {
+    msg = Evaluate(cookie, argv[0]);
+  }
+  SetError(msg == NULL ? "called abort()" : msg);
+  free(msg);
   return NULL;
 }
 
@@ -105,12 +112,23 @@
     int b = BooleanString(v);
     free(v);
     if (!b) {
+      SetError("assert() failed");
       return NULL;
     }
   }
   return strdup("");
 }
 
+char* SleepFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+  char* val = Evaluate(cookie, argv[0]);
+  if (val == NULL) {
+    return NULL;
+  }
+  int v = strtol(val, NULL, 10);
+  sleep(v);
+  return val;
+}
+
 char* PrintFn(const char* name, void* cookie, int argc, Expr* argv[]) {
   int i;
   for (i = 0; i < argc; ++i) {
@@ -234,6 +252,32 @@
   return e;
 }
 
+// -----------------------------------------------------------------
+//   error reporting
+// -----------------------------------------------------------------
+
+static char* error_message = NULL;
+
+void SetError(const char* message) {
+  if (error_message) {
+    free(error_message);
+  }
+  error_message = strdup(message);
+}
+
+const char* GetError() {
+  return error_message;
+}
+
+void ClearError() {
+  free(error_message);
+  error_message = NULL;
+}
+
+// -----------------------------------------------------------------
+//   the function table
+// -----------------------------------------------------------------
+
 static int fn_entries = 0;
 static int fn_size = 0;
 NamedFunction* fn_table = NULL;
@@ -276,4 +320,55 @@
   RegisterFunction("concat", ConcatFn);
   RegisterFunction("is_substring", SubstringFn);
   RegisterFunction("print", PrintFn);
+  RegisterFunction("sleep", SleepFn);
+}
+
+
+// -----------------------------------------------------------------
+//   convenience methods for functions
+// -----------------------------------------------------------------
+
+// Evaluate the expressions in argv, giving 'count' char* (the ... is
+// zero or more char** to put them in).  If any expression evaluates
+// to NULL, free the rest and return -1.  Return 0 on success.
+int ReadArgs(void* cookie, Expr* argv[], int count, ...) {
+  char** args = malloc(count * sizeof(char*));
+  va_list v;
+  va_start(v, count);
+  int i;
+  for (i = 0; i < count; ++i) {
+    args[i] = Evaluate(cookie, argv[i]);
+    if (args[i] == NULL) {
+      va_end(v);
+      int j;
+      for (j = 0; j < i; ++j) {
+        free(args[j]);
+      }
+      return -1;
+    }
+    *(va_arg(v, char**)) = args[i];
+  }
+  va_end(v);
+  return 0;
+}
+
+// Evaluate the expressions in argv, returning an array of char*
+// results.  If any evaluate to NULL, free the rest and return NULL.
+// The caller is responsible for freeing the returned array and the
+// strings it contains.
+char** ReadVarArgs(void* cookie, int argc, Expr* argv[]) {
+  char** args = (char**)malloc(argc * sizeof(char*));
+  int i = 0;
+  for (i = 0; i < argc; ++i) {
+    args[i] = Evaluate(cookie, argv[i]);
+    if (args[i] == NULL) {
+      int j;
+      for (j = 0; j < i; ++j) {
+        free(args[j]);
+      }
+      free(args);
+      return NULL;
+    }
+  }
+  return args;
 }
diff --git a/edify/expr.h b/edify/expr.h
index ac5df18..cfbef90 100644
--- a/edify/expr.h
+++ b/edify/expr.h
@@ -57,6 +57,14 @@
 char* AssertFn(const char* name, void* cookie, int argc, Expr* argv[]);
 char* AbortFn(const char* name, void* cookie, int argc, Expr* argv[]);
 
+
+// For setting and getting the global error string (when returning
+// NULL from a function).
+void SetError(const char* message);  // makes a copy
+const char* GetError();              // retains ownership
+void ClearError();
+
+
 typedef struct {
   const char* name;
   Function fn;
@@ -77,4 +85,19 @@
 // exists.
 Function FindFunction(const char* name);
 
+
+// --- convenience functions for use in functions ---
+
+// Evaluate the expressions in argv, giving 'count' char* (the ... is
+// zero or more char** to put them in).  If any expression evaluates
+// to NULL, free the rest and return -1.  Return 0 on success.
+int ReadArgs(void* cookie, Expr* argv[], int count, ...);
+
+// Evaluate the expressions in argv, returning an array of char*
+// results.  If any evaluate to NULL, free the rest and return NULL.
+// The caller is responsible for freeing the returned array and the
+// strings it contains.
+char** ReadVarArgs(void* cookie, int argc, Expr* argv[]);
+
+
 #endif  // _EXPRESSION_H
diff --git a/edify/lexer.l b/edify/lexer.l
index 4faef5d..cb5eb31 100644
--- a/edify/lexer.l
+++ b/edify/lexer.l
@@ -77,7 +77,7 @@
 else              { gColumn += yyleng; return ELSE; }
 endif             { gColumn += yyleng; return ENDIF; }
 
-[a-zA-Z0-9_:/]+ {
+[a-zA-Z0-9_:/.]+ {
   gColumn += yyleng;
   yylval.str = strdup(yytext);
   return STRING;
diff --git a/edify/main.c b/edify/main.c
index 4d65da2..c959683 100644
--- a/edify/main.c
+++ b/edify/main.c
@@ -158,7 +158,14 @@
   printf("parse returned %d\n", error);
   if (error == 0) {
     char* result = Evaluate(NULL, root);
-    printf("result is [%s]\n", result == NULL ? "(NULL)" : result);
+    if (result == NULL) {
+      char* errmsg = GetError();
+      printf("result was NULL, message is: %s\n",
+             (errmsg == NULL ? "(NULL)" : errmsg));
+      ClearError();
+    } else {
+      printf("result is [%s]\n", result);
+    }
   }
   return 0;
 }