Merge korg/donut into korg/master
diff --git a/Android.mk b/Android.mk
index 816d143..0367fef 100644
--- a/Android.mk
+++ b/Android.mk
@@ -22,6 +22,9 @@
 
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
+RECOVERY_API_VERSION := 2
+LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
+
 # This binary is in the recovery ramdisk, which is otherwise a copy of root.
 # It gets copied there in config/Makefile.  LOCAL_MODULE_TAGS suppresses
 # a (redundant) copy of the binary in /system/bin for user builds.
@@ -30,31 +33,20 @@
 LOCAL_MODULE_TAGS := eng
 
 LOCAL_STATIC_LIBRARIES := libminzip libunz libamend libmtdutils libmincrypt
-LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libcutils
+LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libpng libcutils
 LOCAL_STATIC_LIBRARIES += libstdc++ libc
 
-# Specify a C-includable file containing the OTA public keys.
-# This is built in config/Makefile.
-# *** THIS IS A TOTAL HACK; EXECUTABLES MUST NOT CHANGE BETWEEN DIFFERENT
-#     PRODUCTS/BUILD TYPES. ***
-# TODO: make recovery read the keys from an external file.
-RECOVERY_INSTALL_OTA_KEYS_INC := \
-	$(call intermediates-dir-for,PACKAGING,ota_keys_inc)/keys.inc
-# Let install.c say #include "keys.inc"
-LOCAL_C_INCLUDES += $(dir $(RECOVERY_INSTALL_OTA_KEYS_INC))
-
 include $(BUILD_EXECUTABLE)
 
-# Depend on the generated keys.inc containing the OTA public keys.
-$(intermediates)/install.o: $(RECOVERY_INSTALL_OTA_KEYS_INC)
-
 include $(commands_recovery_local_path)/minui/Android.mk
-
-endif   # TARGET_ARCH == arm
-endif	# !TARGET_SIMULATOR
-
 include $(commands_recovery_local_path)/amend/Android.mk
 include $(commands_recovery_local_path)/minzip/Android.mk
 include $(commands_recovery_local_path)/mtdutils/Android.mk
 include $(commands_recovery_local_path)/tools/Android.mk
+include $(commands_recovery_local_path)/edify/Android.mk
+include $(commands_recovery_local_path)/updater/Android.mk
 commands_recovery_local_path :=
+
+endif   # TARGET_ARCH == arm
+endif	# !TARGET_SIMULATOR
+
diff --git a/amend/Android.mk b/amend/Android.mk
index ae2d44a..c3b7c32 100644
--- a/amend/Android.mk
+++ b/amend/Android.mk
@@ -10,13 +10,11 @@
 	ast.c \
 	symtab.c \
 	commands.c \
-	permissions.c \
 	execute.c
 
 amend_test_files := \
 	test_symtab.c \
-	test_commands.c \
-	test_permissions.c
+	test_commands.c
 
 # "-x c" forces the lex/yacc files to be compiled as c;
 # the build system otherwise forces them to be c++.
diff --git a/amend/amend.c b/amend/amend.c
index 49cd64e..6f706d0 100644
--- a/amend/amend.c
+++ b/amend/amend.c
@@ -17,6 +17,7 @@
 #include <stdlib.h>
 #include "amend.h"
 #include "lexer.h"
+#include "parser.h"
 
 extern const AmCommandList *gCommands;
 
diff --git a/amend/commands.c b/amend/commands.c
index 75ff828..78121ad 100644
--- a/amend/commands.c
+++ b/amend/commands.c
@@ -152,37 +152,30 @@
 }
 
 static int
-callCommandInternal(CommandEntry *entry, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+callCommandInternal(CommandEntry *entry, int argc, const char *argv[])
 {
     if (entry != NULL && entry->argType == CMD_ARGS_WORDS &&
             (argc == 0 || (argc > 0 && argv != NULL)))
     {
-        if (permissions == NULL) {
-            int i;
-            for (i = 0; i < argc; i++) {
-                if (argv[i] == NULL) {
-                    goto bail;
-                }
+        int i;
+        for (i = 0; i < argc; i++) {
+            if (argv[i] == NULL) {
+                goto bail;
             }
         }
         TRACE("calling command %s\n", entry->name);
-        return entry->hook(entry->name, entry->cookie, argc, argv, permissions);
-//xxx if permissions, make sure the entry has added at least one element.
+        return entry->hook(entry->name, entry->cookie, argc, argv);
     }
 bail:
     return -1;
 }
 
 static int
-callBooleanCommandInternal(CommandEntry *entry, bool arg,
-        PermissionRequestList *permissions)
+callBooleanCommandInternal(CommandEntry *entry, bool arg)
 {
     if (entry != NULL && entry->argType == CMD_ARGS_BOOLEAN) {
         TRACE("calling boolean command %s\n", entry->name);
-        return entry->hook(entry->name, entry->cookie, arg ? 1 : 0, NULL,
-                permissions);
-//xxx if permissions, make sure the entry has added at least one element.
+        return entry->hook(entry->name, entry->cookie, arg ? 1 : 0, NULL);
     }
     return -1;
 }
@@ -190,63 +183,37 @@
 int
 callCommand(Command *cmd, int argc, const char *argv[])
 {
-    return callCommandInternal((CommandEntry *)cmd, argc, argv, NULL);
+    return callCommandInternal((CommandEntry *)cmd, argc, argv);
 }
 
 int
 callBooleanCommand(Command *cmd, bool arg)
 {
-    return callBooleanCommandInternal((CommandEntry *)cmd, arg, NULL);
-}
-
-int
-getCommandPermissions(Command *cmd, int argc, const char *argv[],
-        PermissionRequestList *permissions)
-{
-    if (permissions != NULL) {
-        return callCommandInternal((CommandEntry *)cmd, argc, argv,
-                permissions);
-    }
-    return -1;
-}
-
-int
-getBooleanCommandPermissions(Command *cmd, bool arg,
-        PermissionRequestList *permissions)
-{
-    if (permissions != NULL) {
-        return callBooleanCommandInternal((CommandEntry *)cmd, arg,
-                permissions);
-    }
-    return -1;
+    return callBooleanCommandInternal((CommandEntry *)cmd, arg);
 }
 
 int
 callFunctionInternal(CommandEntry *entry, int argc, const char *argv[],
-        char **result, size_t *resultLen, PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     if (entry != NULL && entry->argType == CMD_ARGS_WORDS &&
             (argc == 0 || (argc > 0 && argv != NULL)))
     {
-        if ((permissions == NULL && result != NULL) ||
-                (permissions != NULL && result == NULL))
+        if (result != NULL)
         {
-            if (permissions == NULL) {
-                /* This is the actual invocation of the function,
-                 * which means that none of the arguments are allowed
-                 * to be NULL.
-                 */
-                int i;
-                for (i = 0; i < argc; i++) {
-                    if (argv[i] == NULL) {
-                        goto bail;
-                    }
+            /* This is the actual invocation of the function,
+             * which means that none of the arguments are allowed
+             * to be NULL.
+             */
+            int i;
+            for (i = 0; i < argc; i++) {
+                if (argv[i] == NULL) {
+                    goto bail;
                 }
             }
             TRACE("calling function %s\n", entry->name);
             return ((FunctionHook)entry->hook)(entry->name, entry->cookie,
-                    argc, argv, result, resultLen, permissions);
-//xxx if permissions, make sure the entry has added at least one element.
+                    argc, argv, result, resultLen);
         }
     }
 bail:
@@ -258,16 +225,5 @@
         char **result, size_t *resultLen)
 {
     return callFunctionInternal((CommandEntry *)fn, argc, argv,
-            result, resultLen, NULL);
-}
-
-int
-getFunctionPermissions(Function *fn, int argc, const char *argv[],
-        PermissionRequestList *permissions)
-{
-    if (permissions != NULL) {
-        return callFunctionInternal((CommandEntry *)fn, argc, argv,
-                NULL, NULL, permissions);
-    }
-    return -1;
+            result, resultLen);
 }
diff --git a/amend/commands.h b/amend/commands.h
index 38931c0..6c97e55 100644
--- a/amend/commands.h
+++ b/amend/commands.h
@@ -14,31 +14,18 @@
  * limitations under the License.
  */
 
+#include <stdbool.h>
+
 #ifndef AMEND_COMMANDS_H_
 #define AMEND_COMMANDS_H_
 
-#include "permissions.h"
-
-/* Invoke or dry-run a command.  If "permissions" is non-NULL,
- * the hook should fill it out with the list of files and operations that
- * it would need to complete its operation.  If "permissions" is NULL,
- * the hook should do the actual work specified by its arguments.
- *
- * When a command is called with non-NULL "permissions", some arguments
- * may be NULL.  A NULL argument indicates that the argument is actually
- * the output of another function, so is not known at permissions time.
- * The permissions of leaf-node functions (those that have only literal
- * strings as arguments) will get appended to the permissions of the
- * functions that call them.  However, to be completely safe, functions
- * that receive a NULL argument should request the broadest-possible
- * permissions for the range of the input argument.
+/* Invoke a command.
  *
  * When a boolean command is called, "argc" is the boolean value and
  * "argv" is NULL.
  */
 typedef int (*CommandHook)(const char *name, void *cookie,
-                                int argc, const char *argv[],
-                                PermissionRequestList *permissions);
+                           int argc, const char *argv[]);
 
 int commandInit(void);
 void commandCleanup(void);
@@ -66,19 +53,13 @@
 int callCommand(Command *cmd, int argc, const char *argv[]);
 int callBooleanCommand(Command *cmd, bool arg);
 
-int getCommandPermissions(Command *cmd, int argc, const char *argv[],
-        PermissionRequestList *permissions);
-int getBooleanCommandPermissions(Command *cmd, bool arg,
-        PermissionRequestList *permissions);
-
 /*
  * Function management
  */
 
 typedef int (*FunctionHook)(const char *name, void *cookie,
                                 int argc, const char *argv[],
-                                char **result, size_t *resultLen,
-                                PermissionRequestList *permissions);
+                                char **result, size_t *resultLen);
 
 struct Function;
 typedef struct Function Function;
@@ -90,7 +71,4 @@
 int callFunction(Function *fn, int argc, const char *argv[],
         char **result, size_t *resultLen);
 
-int getFunctionPermissions(Function *fn, int argc, const char *argv[],
-        PermissionRequestList *permissions);
-
 #endif  // AMEND_COMMANDS_H_
diff --git a/amend/main.c b/amend/main.c
index 9bb0785..bc9e587 100644
--- a/amend/main.c
+++ b/amend/main.c
@@ -86,12 +86,6 @@
         fprintf(stderr, "test_cmd_fn() failed: %d\n", ret);
         exit(ret);
     }
-    extern int test_permissions(void);
-    ret = test_permissions();
-    if (ret != 0) {
-        fprintf(stderr, "test_permissions() failed: %d\n", ret);
-        exit(ret);
-    }
 #endif
 
     argc--;
diff --git a/amend/permissions.c b/amend/permissions.c
deleted file mode 100644
index a642d0b..0000000
--- a/amend/permissions.c
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include "permissions.h"
-
-int
-initPermissionRequestList(PermissionRequestList *list)
-{
-    if (list != NULL) {
-        list->requests = NULL;
-        list->numRequests = 0;
-        list->requestsAllocated = 0;
-        return 0;
-    }
-    return -1;
-}
-
-int
-addPermissionRequestToList(PermissionRequestList *list,
-        const char *path, bool recursive, unsigned int permissions)
-{
-    if (list == NULL || list->numRequests < 0 ||
-            list->requestsAllocated < list->numRequests || path == NULL)
-    {
-        return -1;
-    }
-
-    if (list->numRequests == list->requestsAllocated) {
-        int newSize;
-        PermissionRequest *newRequests;
-
-        newSize = list->requestsAllocated * 2;
-        if (newSize < 16) {
-            newSize = 16;
-        }
-        newRequests = (PermissionRequest *)realloc(list->requests,
-                newSize * sizeof(PermissionRequest));
-        if (newRequests == NULL) {
-            return -2;
-        }
-        list->requests = newRequests;
-        list->requestsAllocated = newSize;
-    }
-
-    PermissionRequest *req;
-    req = &list->requests[list->numRequests++];
-    req->path = strdup(path);
-    if (req->path == NULL) {
-        list->numRequests--;
-        return -3;
-    }
-    req->recursive = recursive;
-    req->requested = permissions;
-    req->allowed = 0;
-
-    return 0;
-}
-
-void
-freePermissionRequestListElements(PermissionRequestList *list)
-{
-    if (list != NULL && list->numRequests >= 0 &&
-            list->requestsAllocated >= list->numRequests)
-    {
-        int i;
-        for (i = 0; i < list->numRequests; i++) {
-            free((void *)list->requests[i].path);
-        }
-        free(list->requests);
-        initPermissionRequestList(list);
-    }
-}
-
-/*
- * Global permission table
- */
-
-static struct {
-    Permission *permissions;
-    int numPermissionEntries;
-    int allocatedPermissionEntries;
-    bool permissionStateInitialized;
-} gPermissionState = {
-#if 1
-    NULL, 0, 0, false
-#else
-    .permissions = NULL,
-    .numPermissionEntries = 0,
-    .allocatedPermissionEntries = 0,
-    .permissionStateInitialized = false
-#endif
-};
-
-int
-permissionInit()
-{
-    if (gPermissionState.permissionStateInitialized) {
-        return -1;
-    }
-    gPermissionState.permissions = NULL;
-    gPermissionState.numPermissionEntries = 0;
-    gPermissionState.allocatedPermissionEntries = 0;
-    gPermissionState.permissionStateInitialized = true;
-//xxx maybe add an "namespace root gets no permissions" fallback by default
-    return 0;
-}
-
-void
-permissionCleanup()
-{
-    if (gPermissionState.permissionStateInitialized) {
-        gPermissionState.permissionStateInitialized = false;
-        if (gPermissionState.permissions != NULL) {
-            int i;
-            for (i = 0; i < gPermissionState.numPermissionEntries; i++) {
-                free((void *)gPermissionState.permissions[i].path);
-            }
-            free(gPermissionState.permissions);
-        }
-    }
-}
-
-int
-getPermissionCount()
-{
-    if (gPermissionState.permissionStateInitialized) {
-        return gPermissionState.numPermissionEntries;
-    }
-    return -1;
-}
-
-const Permission *
-getPermissionAt(int index)
-{
-    if (!gPermissionState.permissionStateInitialized) {
-        return NULL;
-    }
-    if (index < 0 || index >= gPermissionState.numPermissionEntries) {
-        return NULL;
-    }
-    return &gPermissionState.permissions[index];
-}
-
-int
-getAllowedPermissions(const char *path, bool recursive,
-        unsigned int *outAllowed)
-{
-    if (!gPermissionState.permissionStateInitialized) {
-        return -2;
-    }
-    if (outAllowed == NULL) {
-        return -1;
-    }
-    *outAllowed = 0;
-    if (path == NULL) {
-        return -1;
-    }
-    //TODO: implement this for real.
-    recursive = false;
-    *outAllowed = PERMSET_ALL;
-    return 0;
-}
-
-int
-countPermissionConflicts(PermissionRequestList *requests, bool updateAllowed)
-{
-    if (!gPermissionState.permissionStateInitialized) {
-        return -2;
-    }
-    if (requests == NULL || requests->requests == NULL ||
-            requests->numRequests < 0 ||
-            requests->requestsAllocated < requests->numRequests)
-    {
-        return -1;
-    }
-    int conflicts = 0;
-    int i;
-    for (i = 0; i < requests->numRequests; i++) {
-        PermissionRequest *req;
-        unsigned int allowed;
-        int ret;
-
-        req = &requests->requests[i];
-        ret = getAllowedPermissions(req->path, req->recursive, &allowed);
-        if (ret < 0) {
-            return ret;
-        }
-        if ((req->requested & ~allowed) != 0) {
-            conflicts++;
-        }
-        if (updateAllowed) {
-            req->allowed = allowed;
-        }
-    }
-    return conflicts;
-}
-
-int
-registerPermissionSet(int count, Permission *set)
-{
-    if (!gPermissionState.permissionStateInitialized) {
-        return -2;
-    }
-    if (count < 0 || (count > 0 && set == NULL)) {
-        return -1;
-    }
-    if (count == 0) {
-        return 0;
-    }
-
-    if (gPermissionState.numPermissionEntries + count >=
-            gPermissionState.allocatedPermissionEntries)
-    {
-        Permission *newList;
-        int newSize;
-
-        newSize = (gPermissionState.allocatedPermissionEntries + count) * 2;
-        if (newSize < 16) {
-            newSize = 16;
-        }
-        newList = (Permission *)realloc(gPermissionState.permissions,
-                newSize * sizeof(Permission));
-        if (newList == NULL) {
-            return -3;
-        }
-        gPermissionState.permissions = newList;
-        gPermissionState.allocatedPermissionEntries = newSize;
-    }
-
-    Permission *p = &gPermissionState.permissions[
-                        gPermissionState.numPermissionEntries];
-    int i;
-    for (i = 0; i < count; i++) {
-        *p = set[i];
-        //TODO: cache the strlen of the path
-        //TODO: normalize; strip off trailing /
-        p->path = strdup(p->path);
-        if (p->path == NULL) {
-            /* If we can't add all of the entries, we don't
-             * add any of them.
-             */
-            Permission *pp = &gPermissionState.permissions[
-                                gPermissionState.numPermissionEntries];
-            while (pp != p) {
-                free((void *)pp->path);
-                pp++;
-            }
-            return -4;
-        }
-        p++;
-    }
-    gPermissionState.numPermissionEntries += count;
-
-    return 0;
-}
diff --git a/amend/permissions.h b/amend/permissions.h
deleted file mode 100644
index 5b1d14d..0000000
--- a/amend/permissions.h
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AMEND_PERMISSIONS_H_
-#define AMEND_PERMISSIONS_H_
-
-#include <stdbool.h>
-
-#define PERM_NONE   (0)
-#define PERM_STAT   (1<<0)
-#define PERM_READ   (1<<1)
-#define PERM_WRITE  (1<<2)  // including create, delete, mkdir, rmdir
-#define PERM_CHMOD  (1<<3)
-#define PERM_CHOWN  (1<<4)
-#define PERM_CHGRP  (1<<5)
-#define PERM_SETUID (1<<6)
-#define PERM_SETGID (1<<7)
-
-#define PERMSET_READ (PERM_STAT | PERM_READ)
-#define PERMSET_WRITE (PERMSET_READ | PERM_WRITE)
-
-#define PERMSET_ALL \
-    (PERM_STAT | PERM_READ | PERM_WRITE | PERM_CHMOD | \
-    PERM_CHOWN | PERM_CHGRP | PERM_SETUID | PERM_SETGID)
-
-typedef struct {
-    unsigned int requested;
-    unsigned int allowed;
-    const char *path;
-    bool recursive;
-} PermissionRequest;
-
-typedef struct {
-    PermissionRequest *requests;
-    int numRequests;
-    int requestsAllocated;
-} PermissionRequestList;
-
-/* Properly clear out a PermissionRequestList.
- *
- * @return 0 if list is non-NULL, negative otherwise.
- */
-int initPermissionRequestList(PermissionRequestList *list);
-
-/* Add a permission request to the list, allocating more space
- * if necessary.
- *
- * @return 0 on success or a negative value on failure.
- */
-int addPermissionRequestToList(PermissionRequestList *list,
-        const char *path, bool recursive, unsigned int permissions);
-
-/* Free anything allocated by addPermissionRequestToList().  The caller
- * is responsible for freeing the actual PermissionRequestList.
- */
-void freePermissionRequestListElements(PermissionRequestList *list);
-
-
-/*
- * Global permission table
- */
-
-typedef struct {
-    const char *path;
-    unsigned int allowed;
-} Permission;
-
-int permissionInit(void);
-void permissionCleanup(void);
-
-/* Returns the allowed permissions for the path in "outAllowed".
- * Returns 0 if successful, negative if a parameter or global state
- * is bad.
- */
-int getAllowedPermissions(const char *path, bool recursive,
-        unsigned int *outAllowed);
-
-/* More-recently-registered permissions override older permissions.
- */
-int registerPermissionSet(int count, Permission *set);
-
-/* Check to make sure that each request is allowed.
- *
- * @param requests The list of permission requests
- * @param updateAllowed If true, update the "allowed" field in each
- *                      element of the list
- * @return the number of requests that were denied, or negative if
- *         an error occurred.
- */
-int countPermissionConflicts(PermissionRequestList *requests,
-        bool updateAllowed);
-
-/* Inspection/testing/debugging functions
- */
-int getPermissionCount(void);
-const Permission *getPermissionAt(int index);
-
-#endif  // AMEND_PERMISSIONS_H_
diff --git a/amend/register.c b/amend/register.c
index 167dd32..0f44b74 100644
--- a/amend/register.c
+++ b/amend/register.c
@@ -39,40 +39,15 @@
         if (argc < 0) return -1; \
         assert(argc == 0 || argv != NULL); \
         if (argc != 0 && argv == NULL) return -1; \
-        if (permissions != NULL) { \
-            int CW_I_; \
-            for (CW_I_ = 0; CW_I_ < argc; CW_I_++) { \
-                assert(argv[CW_I_] != NULL); \
-                if (argv[CW_I_] == NULL) return -1; \
-            } \
-        } \
     } while (false)
 
 #define CHECK_FN() \
     do { \
         CHECK_WORDS(); \
-        if (permissions != NULL) { \
-            assert(result == NULL); \
-            if (result != NULL) return -1; \
-        } else { \
-            assert(result != NULL); \
-            if (result == NULL) return -1; \
-        } \
+        assert(result != NULL);            \
+        if (result == NULL) return -1;     \
     } while (false)
 
-#define NO_PERMS(perms) \
-    do { \
-        PermissionRequestList *NP_PRL_ = (perms); \
-        if (NP_PRL_ != NULL) { \
-            int NP_RET_ = addPermissionRequestToList(NP_PRL_, \
-                    "", false, PERM_NONE); \
-            if (NP_RET_ < 0) { \
-                /* Returns from the calling function. \
-                 */ \
-                return NP_RET_; \
-            } \
-        } \
-    } while (false)
 
 /*
  * Command definitions
@@ -81,13 +56,11 @@
 /* assert <boolexpr>
  */
 static int
-cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_assert(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_BOOL();
-    NO_PERMS(permissions);
 
     /* If our argument is false, return non-zero (failure)
      * If our argument is true, return zero (success)
@@ -102,8 +75,7 @@
 /* format <root>
  */
 static int
-cmd_format(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_format(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(name);
     UNUSED(cookie);
@@ -115,8 +87,7 @@
 /* copy_dir <srcdir> <dstdir>
  */
 static int
-cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(name);
     UNUSED(cookie);
@@ -128,8 +99,7 @@
 /* mark <resource> dirty|clean
  */
 static int
-cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_mark(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(name);
     UNUSED(cookie);
@@ -144,8 +114,7 @@
 /* done
  */
 static int
-cmd_done(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_done(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(name);
     UNUSED(cookie);
@@ -174,11 +143,6 @@
     ret = registerCommand("done", CMD_ARGS_WORDS, cmd_done, NULL);
     if (ret < 0) return ret;
 
-//xxx some way to fix permissions
-//xxx could have "installperms" commands that build the fs_config list
-//xxx along with a "commitperms", and any copy_dir etc. needs to see
-//    a commitperms before it will work
-
     return 0;
 }
 
@@ -194,13 +158,11 @@
  */
 static int
 fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     if (argc != 0) {
         fprintf(stderr, "%s: wrong number of arguments (%d)\n",
@@ -228,13 +190,11 @@
  */
 static int
 fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     if (argc != 1) {
         fprintf(stderr, "%s: wrong number of arguments (%d)\n",
@@ -255,8 +215,7 @@
  */
 static int
 fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     int ret = -1;
 
@@ -273,23 +232,12 @@
         dir = argv[0];
     }
 
-    if (permissions != NULL) {
-        if (dir == NULL) {
-            /* The argument is the result of another function.
-             * Assume the worst case, where the function returns
-             * the root.
-             */
-            dir = "/";
-        }
-        ret = addPermissionRequestToList(permissions, dir, true, PERM_READ);
-    } else {
 //xxx build and return the string
-        *result = strdup("hashvalue");
-        if (resultLen != NULL) {
-            *resultLen = strlen(*result);
-        }
-        ret = 0;
+    *result = strdup("hashvalue");
+    if (resultLen != NULL) {
+      *resultLen = strlen(*result);
     }
+    ret = 0;
 
     return ret;
 }
@@ -302,13 +250,11 @@
  */
 static int
 fn_matches(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     if (argc < 2) {
         fprintf(stderr, "%s: not enough arguments (%d < 2)\n",
@@ -339,13 +285,11 @@
  */
 static int
 fn_concat(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     size_t totalLen = 0;
     int i;
diff --git a/amend/test_commands.c b/amend/test_commands.c
index be938ac..452f808 100644
--- a/amend/test_commands.c
+++ b/amend/test_commands.c
@@ -27,34 +27,30 @@
     void *cookie;
     int argc;
     const char **argv;
-    PermissionRequestList *permissions;
     int returnValue;
     char *functionResult;
 } gTestCommandState;
 
 static int
-testCommand(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+testCommand(const char *name, void *cookie, int argc, const char *argv[])
 {
     gTestCommandState.called = true;
     gTestCommandState.name = name;
     gTestCommandState.cookie = cookie;
     gTestCommandState.argc = argc;
     gTestCommandState.argv = argv;
-    gTestCommandState.permissions = permissions;
     return gTestCommandState.returnValue;
 }
 
 static int
 testFunction(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen, PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     gTestCommandState.called = true;
     gTestCommandState.name = name;
     gTestCommandState.cookie = cookie;
     gTestCommandState.argc = argc;
     gTestCommandState.argv = argv;
-    gTestCommandState.permissions = permissions;
     if (result != NULL) {
         *result = gTestCommandState.functionResult;
         if (resultLen != NULL) {
@@ -187,7 +183,6 @@
     memset(&gTestCommandState, 0, sizeof(gTestCommandState));
     gTestCommandState.called = false;
     gTestCommandState.returnValue = 25;
-    gTestCommandState.permissions = (PermissionRequestList *)1;
     ret = callCommand(cmd, argc, argv);
 //xxx also try calling with a null argv element (should fail)
     assert(ret == 25);
@@ -196,7 +191,6 @@
     assert(gTestCommandState.cookie == &gTestCommandState);
     assert(gTestCommandState.argc == argc);
     assert(gTestCommandState.argv == argv);
-    assert(gTestCommandState.permissions == NULL);
 
     /* Make a boolean call and make sure that it occurred.
      */
@@ -206,7 +200,6 @@
     memset(&gTestCommandState, 0, sizeof(gTestCommandState));
     gTestCommandState.called = false;
     gTestCommandState.returnValue = 12;
-    gTestCommandState.permissions = (PermissionRequestList *)1;
     ret = callBooleanCommand(cmd, false);
     assert(ret == 12);
     assert(gTestCommandState.called);
@@ -214,12 +207,10 @@
     assert(gTestCommandState.cookie == &gTestCommandState);
     assert(gTestCommandState.argc == 0);
     assert(gTestCommandState.argv == NULL);
-    assert(gTestCommandState.permissions == NULL);
 
     memset(&gTestCommandState, 0, sizeof(gTestCommandState));
     gTestCommandState.called = false;
     gTestCommandState.returnValue = 13;
-    gTestCommandState.permissions = (PermissionRequestList *)1;
     ret = callBooleanCommand(cmd, true);
     assert(ret == 13);
     assert(gTestCommandState.called);
@@ -227,45 +218,6 @@
     assert(gTestCommandState.cookie == &gTestCommandState);
     assert(gTestCommandState.argc == 1);
     assert(gTestCommandState.argv == NULL);
-    assert(gTestCommandState.permissions == NULL);
-
-    /* Try looking up permissions.
-     */
-    PermissionRequestList permissions;
-    cmd = findCommand("one");
-    assert(cmd != NULL);
-    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
-    gTestCommandState.called = false;
-    gTestCommandState.returnValue = 27;
-    gTestCommandState.permissions = (PermissionRequestList *)1;
-    argv[1] = NULL; // null out an arg, which should be ok
-    ret = getCommandPermissions(cmd, argc, argv, &permissions);
-    assert(ret == 27);
-    assert(gTestCommandState.called);
-    assert(strcmp(gTestCommandState.name, "one") == 0);
-    assert(gTestCommandState.cookie == &gTestCommandState);
-    assert(gTestCommandState.argc == argc);
-    assert(gTestCommandState.argv == argv);
-    assert(gTestCommandState.permissions == &permissions);
-
-    /* Boolean command permissions
-     */
-    cmd = findCommand("bool");
-    assert(cmd != NULL);
-    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
-    gTestCommandState.called = false;
-    gTestCommandState.returnValue = 55;
-    gTestCommandState.permissions = (PermissionRequestList *)1;
-    // argv[1] is still NULL
-    ret = getBooleanCommandPermissions(cmd, true, &permissions);
-    assert(ret == 55);
-    assert(gTestCommandState.called);
-    assert(strcmp(gTestCommandState.name, "bool") == 0);
-    assert(gTestCommandState.cookie == &gTestCommandState);
-    assert(gTestCommandState.argc == 1);
-    assert(gTestCommandState.argv == NULL);
-    assert(gTestCommandState.permissions == &permissions);
-
 
     /* Smoke test commandCleanup().
      */
@@ -365,7 +317,6 @@
     gTestCommandState.called = false;
     gTestCommandState.returnValue = 25;
     gTestCommandState.functionResult = "1234";
-    gTestCommandState.permissions = (PermissionRequestList *)1;
     functionResult = NULL;
     functionResultLen = 55;
     ret = callFunction(fn, argc, argv,
@@ -378,29 +329,9 @@
     assert(gTestCommandState.cookie == &gTestCommandState);
     assert(gTestCommandState.argc == argc);
     assert(gTestCommandState.argv == argv);
-    assert(gTestCommandState.permissions == NULL);
     assert(strcmp(functionResult, "1234") == 0);
     assert(functionResultLen == strlen(functionResult));
 
-    /* Try looking up permissions.
-     */
-    PermissionRequestList permissions;
-    fn = findFunction("one");
-    assert(fn != NULL);
-    memset(&gTestCommandState, 0, sizeof(gTestCommandState));
-    gTestCommandState.called = false;
-    gTestCommandState.returnValue = 27;
-    gTestCommandState.permissions = (PermissionRequestList *)1;
-    argv[1] = NULL; // null out an arg, which should be ok
-    ret = getFunctionPermissions(fn, argc, argv, &permissions);
-    assert(ret == 27);
-    assert(gTestCommandState.called);
-    assert(strcmp(gTestCommandState.name, "one") == 0);
-    assert(gTestCommandState.cookie == &gTestCommandState);
-    assert(gTestCommandState.argc == argc);
-    assert(gTestCommandState.argv == argv);
-    assert(gTestCommandState.permissions == &permissions);
-
     /* Smoke test commandCleanup().
      */
     commandCleanup();
@@ -470,7 +401,6 @@
     memset(&gTestCommandState, 0, sizeof(gTestCommandState));
     gTestCommandState.called = false;
     gTestCommandState.returnValue = 123;
-    gTestCommandState.permissions = (PermissionRequestList *)1;
     ret = callCommand(cmd, argc, argv);
     assert(ret == 123);
     assert(gTestCommandState.called);
@@ -478,7 +408,6 @@
     assert((int)gTestCommandState.cookie == 0xc1);
     assert(gTestCommandState.argc == argc);
     assert(gTestCommandState.argv == argv);
-    assert(gTestCommandState.permissions == NULL);
 
     /* Call the overlapping function and make sure that the cookie is correct.
      */
@@ -490,7 +419,6 @@
     gTestCommandState.called = false;
     gTestCommandState.returnValue = 125;
     gTestCommandState.functionResult = "5678";
-    gTestCommandState.permissions = (PermissionRequestList *)2;
     functionResult = NULL;
     functionResultLen = 66;
     ret = callFunction(fn, argc, argv, &functionResult, &functionResultLen);
@@ -500,7 +428,6 @@
     assert((int)gTestCommandState.cookie == 0xf1);
     assert(gTestCommandState.argc == argc);
     assert(gTestCommandState.argv == argv);
-    assert(gTestCommandState.permissions == NULL);
     assert(strcmp(functionResult, "5678") == 0);
     assert(functionResultLen == strlen(functionResult));
 
diff --git a/amend/test_permissions.c b/amend/test_permissions.c
deleted file mode 100644
index c389456..0000000
--- a/amend/test_permissions.c
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#undef NDEBUG
-#include <assert.h>
-#include "permissions.h"
-
-static int
-test_permission_list()
-{
-    PermissionRequestList list;
-    int ret;
-    int numRequests;
-
-    /* Bad parameter
-     */
-    ret = initPermissionRequestList(NULL);
-    assert(ret < 0);
-
-    /* Good parameter
-     */
-    ret = initPermissionRequestList(&list);
-    assert(ret == 0);
-
-    /* Bad parameters
-     */
-    ret = addPermissionRequestToList(NULL, NULL, false, 0);
-    assert(ret < 0);
-
-    ret = addPermissionRequestToList(&list, NULL, false, 0);
-    assert(ret < 0);
-
-    /* Good parameters
-     */
-    numRequests = 0;
-
-    ret = addPermissionRequestToList(&list, "one", false, 1);
-    assert(ret == 0);
-    numRequests++;
-
-    ret = addPermissionRequestToList(&list, "two", false, 2);
-    assert(ret == 0);
-    numRequests++;
-
-    ret = addPermissionRequestToList(&list, "three", false, 3);
-    assert(ret == 0);
-    numRequests++;
-
-    ret = addPermissionRequestToList(&list, "recursive", true, 55);
-    assert(ret == 0);
-    numRequests++;
-
-    /* Validate the list
-     */
-    assert(list.requests != NULL);
-    assert(list.numRequests == numRequests);
-    assert(list.numRequests <= list.requestsAllocated);
-    bool sawOne = false;
-    bool sawTwo = false;
-    bool sawThree = false;
-    bool sawRecursive = false;
-    int i;
-    for (i = 0; i < list.numRequests; i++) {
-        PermissionRequest *req = &list.requests[i];
-        assert(req->allowed == 0);
-
-        /* Order isn't guaranteed, so we have to switch every time.
-         */
-        if (strcmp(req->path, "one") == 0) {
-            assert(!sawOne);
-            assert(req->requested == 1);
-            assert(!req->recursive);
-            sawOne = true;
-        } else if (strcmp(req->path, "two") == 0) {
-            assert(!sawTwo);
-            assert(req->requested == 2);
-            assert(!req->recursive);
-            sawTwo = true;
-        } else if (strcmp(req->path, "three") == 0) {
-            assert(!sawThree);
-            assert(req->requested == 3);
-            assert(!req->recursive);
-            sawThree = true;
-        } else if (strcmp(req->path, "recursive") == 0) {
-            assert(!sawRecursive);
-            assert(req->requested == 55);
-            assert(req->recursive);
-            sawRecursive = true;
-        } else {
-            assert(false);
-        }
-    }
-    assert(sawOne);
-    assert(sawTwo);
-    assert(sawThree);
-    assert(sawRecursive);
-
-    /* Smoke test the teardown
-     */
-    freePermissionRequestListElements(&list);
-
-    return 0;
-}
-
-static int
-test_permission_table()
-{
-    int ret;
-
-    /* Test the global permissions table.
-     * Try calling functions without initializing first.
-     */
-    ret = registerPermissionSet(0, NULL);
-    assert(ret < 0);
-
-    ret = countPermissionConflicts((PermissionRequestList *)16, false);
-    assert(ret < 0);
-
-    ret = getPermissionCount();
-    assert(ret < 0);
-
-    const Permission *p;
-    p = getPermissionAt(0);
-    assert(p == NULL);
-
-    /* Initialize.
-     */
-    ret = permissionInit();
-    assert(ret == 0);
-
-    /* Make sure we can't initialize twice.
-     */
-    ret = permissionInit();
-    assert(ret < 0);
-
-    /* Test the inspection functions.
-     */
-    ret = getPermissionCount();
-    assert(ret == 0);
-
-    p = getPermissionAt(-1);
-    assert(p == NULL);
-
-    p = getPermissionAt(0);
-    assert(p == NULL);
-
-    p = getPermissionAt(1);
-    assert(p == NULL);
-
-    /* Test registerPermissionSet().
-     * Try some bad parameter values.
-     */
-    ret = registerPermissionSet(-1, NULL);
-    assert(ret < 0);
-
-    ret = registerPermissionSet(1, NULL);
-    assert(ret < 0);
-
-    /* Register some permissions.
-     */
-    Permission p1;
-    p1.path = "one";
-    p1.allowed = 1;
-    ret = registerPermissionSet(1, &p1);
-    assert(ret == 0);
-    ret = getPermissionCount();
-    assert(ret == 1);
-
-    Permission p2[2];
-    p2[0].path = "two";
-    p2[0].allowed = 2;
-    p2[1].path = "three";
-    p2[1].allowed = 3;
-    ret = registerPermissionSet(2, p2);
-    assert(ret == 0);
-    ret = getPermissionCount();
-    assert(ret == 3);
-
-    ret = registerPermissionSet(0, NULL);
-    assert(ret == 0);
-    ret = getPermissionCount();
-    assert(ret == 3);
-
-    p1.path = "four";
-    p1.allowed = 4;
-    ret = registerPermissionSet(1, &p1);
-    assert(ret == 0);
-
-    /* Make sure the table looks correct.
-     * Order is important;  more-recent additions
-     * should appear at higher indices.
-     */
-    ret = getPermissionCount();
-    assert(ret == 4);
-
-    int i;
-    for (i = 0; i < ret; i++) {
-        const Permission *p;
-        p = getPermissionAt(i);
-        assert(p != NULL);
-        assert(p->allowed == (unsigned int)(i + 1));
-        switch (i) {
-        case 0:
-            assert(strcmp(p->path, "one") == 0);
-            break;
-        case 1:
-            assert(strcmp(p->path, "two") == 0);
-            break;
-        case 2:
-            assert(strcmp(p->path, "three") == 0);
-            break;
-        case 3:
-            assert(strcmp(p->path, "four") == 0);
-            break;
-        default:
-            assert(!"internal error");
-            break;
-        }
-    }
-    p = getPermissionAt(ret);
-    assert(p == NULL);
-
-    /* Smoke test the teardown
-     */
-    permissionCleanup();
-
-    return 0;
-}
-
-static int
-test_allowed_permissions()
-{
-    int ret;
-    int numPerms;
-
-    /* Make sure these fail before initialization.
-     */
-    ret = countPermissionConflicts((PermissionRequestList *)1, false);
-    assert(ret < 0);
-
-    ret = getAllowedPermissions((const char *)1, false, (unsigned int *)1);
-    assert(ret < 0);
-
-    /* Initialize.
-     */
-    ret = permissionInit();
-    assert(ret == 0);
-
-    /* Make sure countPermissionConflicts() fails with bad parameters.
-     */
-    ret = countPermissionConflicts(NULL, false);
-    assert(ret < 0);
-
-    /* Register a set of permissions.
-     */
-    Permission perms[] = {
-        { "/", PERM_NONE },
-        { "/stat", PERM_STAT },
-        { "/read", PERMSET_READ },
-        { "/write", PERMSET_WRITE },
-        { "/.stat", PERM_STAT },
-        { "/.stat/.read", PERMSET_READ },
-        { "/.stat/.read/.write", PERMSET_WRITE },
-        { "/.stat/.write", PERMSET_WRITE },
-    };
-    numPerms = sizeof(perms) / sizeof(perms[0]);
-    ret = registerPermissionSet(numPerms, perms);
-    assert(ret == 0);
-
-    /* Build a permission request list.
-     */
-    PermissionRequestList list;
-    ret = initPermissionRequestList(&list);
-    assert(ret == 0);
-
-    ret = addPermissionRequestToList(&list, "/stat", false, PERM_STAT);
-    assert(ret == 0);
-
-    ret = addPermissionRequestToList(&list, "/read", false, PERM_READ);
-    assert(ret == 0);
-
-    ret = addPermissionRequestToList(&list, "/write", false, PERM_WRITE);
-    assert(ret == 0);
-
-    //TODO: cover more cases once the permission stuff has been implemented
-
-    /* All of the requests in the list should be allowed.
-     */
-    ret = countPermissionConflicts(&list, false);
-    assert(ret == 0);
-
-    /* Add a request that will be denied.
-     */
-    ret = addPermissionRequestToList(&list, "/stat", false, 1<<31 | PERM_STAT);
-    assert(ret == 0);
-
-    ret = countPermissionConflicts(&list, false);
-    assert(ret == 1);
-
-    //TODO: more tests
-
-    permissionCleanup();
-
-    return 0;
-}
-
-int
-test_permissions()
-{
-    int ret;
-
-    ret = test_permission_list();
-    if (ret != 0) {
-        fprintf(stderr, "test_permission_list() failed: %d\n", ret);
-        return ret;
-    }
-
-    ret = test_permission_table();
-    if (ret != 0) {
-        fprintf(stderr, "test_permission_table() failed: %d\n", ret);
-        return ret;
-    }
-
-    ret = test_allowed_permissions();
-    if (ret != 0) {
-        fprintf(stderr, "test_permission_table() failed: %d\n", ret);
-        return ret;
-    }
-
-    return 0;
-}
diff --git a/commands.c b/commands.c
index 23ad91c..b4678ba 100644
--- a/commands.c
+++ b/commands.c
@@ -57,39 +57,13 @@
         if (argc < 0) return -1; \
         assert(argc == 0 || argv != NULL); \
         if (argc != 0 && argv == NULL) return -1; \
-        if (permissions != NULL) { \
-            int CW_I_; \
-            for (CW_I_ = 0; CW_I_ < argc; CW_I_++) { \
-                assert(argv[CW_I_] != NULL); \
-                if (argv[CW_I_] == NULL) return -1; \
-            } \
-        } \
     } while (false)
 
 #define CHECK_FN() \
     do { \
         CHECK_WORDS(); \
-        if (permissions != NULL) { \
-            assert(result == NULL); \
-            if (result != NULL) return -1; \
-        } else { \
-            assert(result != NULL); \
-            if (result == NULL) return -1; \
-        } \
-    } while (false)
-
-#define NO_PERMS(perms) \
-    do { \
-        PermissionRequestList *NP_PRL_ = (perms); \
-        if (NP_PRL_ != NULL) { \
-            int NP_RET_ = addPermissionRequestToList(NP_PRL_, \
-                    "", false, PERM_NONE); \
-            if (NP_RET_ < 0) { \
-                /* Returns from the calling function. \
-                 */ \
-                return NP_RET_; \
-            } \
-        } \
+        assert(result != NULL); \
+        if (result == NULL) return -1; \
     } while (false)
 
 /*
@@ -99,13 +73,11 @@
 /* assert <boolexpr>
  */
 static int
-cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_assert(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_BOOL();
-    NO_PERMS(permissions);
 
     /* If our argument is false, return non-zero (failure)
      * If our argument is true, return zero (success)
@@ -120,8 +92,7 @@
 /* format <root>
  */
 static int
-cmd_format(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_format(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(name);
     UNUSED(cookie);
@@ -151,8 +122,7 @@
  * give up early.
  */
 static int
-cmd_delete(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_delete(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(cookie);
     CHECK_WORDS();
@@ -166,7 +136,6 @@
 
     recurse = (strcmp(name, "delete_recursive") == 0);
     ui_print("Deleting files...\n");
-//xxx permissions
 
     int i;
     for (i = 0; i < argc; i++) {
@@ -233,13 +202,11 @@
  * or a fixed default timestamp will be supplied otherwise.
  */
 static int
-cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_WORDS();
-//xxx permissions
 
     // To create a consistent system image, never use the clock for timestamps.
     struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
@@ -331,8 +298,7 @@
  * Run an external program included in the update package.
  */
 static int
-cmd_run_program(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_run_program(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(cookie);
     CHECK_WORDS();
@@ -407,8 +373,7 @@
  * User, group, and modes must all be integer values (hex or octal OK).
  */
 static int
-cmd_set_perm(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_set_perm(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(cookie);
     CHECK_WORDS();
@@ -461,8 +426,7 @@
  * if the actual rate of progress can be determined).
  */
 static int
-cmd_show_progress(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_show_progress(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(cookie);
     CHECK_WORDS();
@@ -499,8 +463,7 @@
  * for the target filesystem (and may be relative).
  */
 static int
-cmd_symlink(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_symlink(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(cookie);
     CHECK_WORDS();
@@ -554,7 +517,7 @@
  */
 static int
 cmd_write_firmware_image(const char *name, void *cookie,
-        int argc, const char *argv[], PermissionRequestList *permissions)
+        int argc, const char *argv[])
 {
     UNUSED(cookie);
     CHECK_WORDS();
@@ -634,11 +597,10 @@
  */
 static int
 cmd_write_raw_image(const char *name, void *cookie,
-        int argc, const char *argv[], PermissionRequestList *permissions)
+        int argc, const char *argv[])
 {
     UNUSED(cookie);
     CHECK_WORDS();
-//xxx permissions
 
     if (argc != 2) {
         LOGE("Command %s requires exactly two arguments\n", name);
@@ -726,8 +688,7 @@
 /* mark <resource> dirty|clean
  */
 static int
-cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_mark(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(name);
     UNUSED(cookie);
@@ -742,8 +703,7 @@
 /* done
  */
 static int
-cmd_done(const char *name, void *cookie, int argc, const char *argv[],
-        PermissionRequestList *permissions)
+cmd_done(const char *name, void *cookie, int argc, const char *argv[])
 {
     UNUSED(name);
     UNUSED(cookie);
@@ -764,13 +724,11 @@
  */
 static int
 fn_compatible_with(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     if (argc != 1) {
         fprintf(stderr, "%s: wrong number of arguments (%d)\n",
@@ -796,13 +754,11 @@
  */
 static int
 fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     if (argc != 0) {
         fprintf(stderr, "%s: wrong number of arguments (%d)\n",
@@ -830,13 +786,11 @@
  */
 static int
 fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     if (argc != 1) {
         fprintf(stderr, "%s: wrong number of arguments (%d)\n",
@@ -857,8 +811,7 @@
  */
 static int
 fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     int ret = -1;
 
@@ -875,24 +828,6 @@
         dir = argv[0];
     }
 
-    if (permissions != NULL) {
-        if (dir == NULL) {
-            /* The argument is the result of another function.
-             * Assume the worst case, where the function returns
-             * the root.
-             */
-            dir = "/";
-        }
-        ret = addPermissionRequestToList(permissions, dir, true, PERM_READ);
-    } else {
-//xxx build and return the string
-        *result = strdup("hashvalue");
-        if (resultLen != NULL) {
-            *resultLen = strlen(*result);
-        }
-        ret = 0;
-    }
-
     return ret;
 }
 
@@ -904,13 +839,11 @@
  */
 static int
 fn_matches(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     if (argc < 2) {
         fprintf(stderr, "%s: not enough arguments (%d < 2)\n",
@@ -941,13 +874,11 @@
  */
 static int
 fn_concat(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(name);
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     size_t totalLen = 0;
     int i;
@@ -977,12 +908,10 @@
  */
 static int
 fn_getprop(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     if (argc != 1) {
         LOGE("Command %s requires exactly one argument\n", name);
@@ -1005,12 +934,10 @@
  */
 static int
 fn_file_contains(const char *name, void *cookie, int argc, const char *argv[],
-        char **result, size_t *resultLen,
-        PermissionRequestList *permissions)
+        char **result, size_t *resultLen)
 {
     UNUSED(cookie);
     CHECK_FN();
-    NO_PERMS(permissions);
 
     if (argc != 2) {
         LOGE("Command %s requires exactly two arguments\n", name);
diff --git a/common.h b/common.h
index e17f76a..6761159 100644
--- a/common.h
+++ b/common.h
@@ -47,7 +47,6 @@
 // Set the icon (normally the only thing visible besides the progress bar).
 enum {
   BACKGROUND_ICON_NONE,
-  BACKGROUND_ICON_UNPACKING,
   BACKGROUND_ICON_INSTALLING,
   BACKGROUND_ICON_ERROR,
   BACKGROUND_ICON_FIRMWARE_INSTALLING,
@@ -91,4 +90,7 @@
 #define LOGD(...) do {} while (0)
 #endif
 
+#define STRINGIFY(x) #x
+#define EXPAND(x) STRINGIFY(x)
+
 #endif  // RECOVERY_COMMON_H
diff --git a/edify/Android.mk b/edify/Android.mk
new file mode 100644
index 0000000..fac0ba7
--- /dev/null
+++ b/edify/Android.mk
@@ -0,0 +1,39 @@
+# Copyright 2009 The Android Open Source Project
+
+LOCAL_PATH := $(call my-dir)
+
+edify_src_files := \
+	lexer.l \
+	parser.y \
+	expr.c
+
+# "-x c" forces the lex/yacc files to be compiled as c;
+# the build system otherwise forces them to be c++.
+edify_cflags := -x c
+
+#
+# Build the host-side command line tool
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+		$(edify_src_files) \
+		main.c
+
+LOCAL_CFLAGS := $(edify_cflags) -g -O0
+LOCAL_MODULE := edify
+LOCAL_YACCFLAGS := -v
+
+include $(BUILD_HOST_EXECUTABLE)
+
+#
+# Build the device-side library
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(edify_src_files)
+
+LOCAL_CFLAGS := $(edify_cflags)
+LOCAL_MODULE := libedify
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/edify/README b/edify/README
new file mode 100644
index 0000000..810455c
--- /dev/null
+++ b/edify/README
@@ -0,0 +1,108 @@
+Update scripts (from donut onwards) are written in a new little
+scripting language ("edify") that is superficially somewhat similar to
+the old one ("amend").  This is a brief overview of the new language.
+
+- The entire script is a single expression.
+
+- All expressions are string-valued.
+
+- String literals appear in double quotes.  \n, \t, \", and \\ are
+  understood, as are hexadecimal escapes like \x4a.
+
+- String literals consisting of only letters, numbers, colons,
+  underscores, slashes, and periods don't need to be in double quotes.
+
+- The following words are reserved:
+
+       if    then    else   endif
+
+  They have special meaning when unquoted.  (In quotes, they are just
+  string literals.)
+
+- When used as a boolean, the empty string is "false" and all other
+  strings are "true".
+
+- All functions are actually macros (in the Lisp sense); the body of
+  the function can control which (if any) of the arguments are
+  evaluated.  This means that functions can act as control
+  structures.
+
+- Operators (like "&&" and "||") are just syntactic sugar for builtin
+  functions, so they can act as control structures as well.
+
+- ";" is a binary operator; evaluating it just means to first evaluate
+  the left side, then the right.  It can also appear after any
+  expression.
+
+- Comments start with "#" and run to the end of the line.
+
+
+
+Some examples:
+
+- There's no distinction between quoted and unquoted strings; the
+  quotes are only needed if you want characters like whitespace to
+  appear in the string.  The following expressions all evaluate to the
+  same string.
+
+     "a b"
+     a + " " + b
+     "a" + " " + "b"
+     "a\x20b"
+     a + "\x20b"
+     concat(a, " ", "b")
+     "concat"(a, " ", "b")
+
+  As shown in the last example, function names are just strings,
+  too.  They must be string *literals*, however.  This is not legal:
+
+     ("con" + "cat")(a, " ", b)         # syntax error!
+
+
+- The ifelse() builtin takes three arguments:  it evaluates exactly
+  one of the second and third, depending on whether the first one is
+  true.  There is also some syntactic sugar to make expressions that
+  look like if/else statements:
+
+     # these are all equivalent
+     ifelse(something(), "yes", "no")
+     if something() then yes else no endif
+     if something() then "yes" else "no" endif
+
+  The else part is optional.
+
+     if something() then "yes" endif    # if something() is false,
+                                        # evaluates to false
+
+     ifelse(condition(), "", abort())   # abort() only called if
+                                        # condition() is false
+
+  The last example is equivalent to:
+
+     assert(condition())
+
+
+- The && and || operators can be used similarly; they evaluate their
+  second argument only if it's needed to determine the truth of the
+  expression.  Their value is the value of the last-evaluated
+  argument:
+
+     file_exists("/data/system/bad") && delete("/data/system/bad")
+
+     file_exists("/data/system/missing") || create("/data/system/missing")
+
+     get_it() || "xxx"     # returns value of get_it() if that value is
+                           # true, otherwise returns "xxx"
+
+
+- The purpose of ";" is to simulate imperative statements, of course,
+  but the operator can be used anywhere.  Its value is the value of
+  its right side:
+
+     concat(a;b;c, d, e;f)     # evaluates to "cdf"
+
+  A more useful example might be something like:
+
+     ifelse(condition(),
+            (first_step(); second_step();),   # second ; is optional
+            alternative_procedure())
diff --git a/edify/expr.c b/edify/expr.c
new file mode 100644
index 0000000..72e5100
--- /dev/null
+++ b/edify/expr.c
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+#include "expr.h"
+
+// Functions should:
+//
+//    - return a malloc()'d string
+//    - if Evaluate() on any argument returns NULL, return NULL.
+
+int BooleanString(const char* s) {
+    return s[0] != '\0';
+}
+
+char* Evaluate(State* state, Expr* expr) {
+    return expr->fn(expr->name, state, expr->argc, expr->argv);
+}
+
+char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc == 0) {
+        return strdup("");
+    }
+    char** strings = malloc(argc * sizeof(char*));
+    int i;
+    for (i = 0; i < argc; ++i) {
+        strings[i] = NULL;
+    }
+    char* result = NULL;
+    int length = 0;
+    for (i = 0; i < argc; ++i) {
+        strings[i] = Evaluate(state, argv[i]);
+        if (strings[i] == NULL) {
+            goto done;
+        }
+        length += strlen(strings[i]);
+    }
+
+    result = malloc(length+1);
+    int p = 0;
+    for (i = 0; i < argc; ++i) {
+        strcpy(result+p, strings[i]);
+        p += strlen(strings[i]);
+    }
+    result[p] = '\0';
+
+  done:
+    for (i = 0; i < argc; ++i) {
+        free(strings[i]);
+    }
+    return result;
+}
+
+char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 2 && argc != 3) {
+        free(state->errmsg);
+        state->errmsg = strdup("ifelse expects 2 or 3 arguments");
+        return NULL;
+    }
+    char* cond = Evaluate(state, argv[0]);
+    if (cond == NULL) {
+        return NULL;
+    }
+
+    if (BooleanString(cond) == true) {
+        free(cond);
+        return Evaluate(state, argv[1]);
+    } else {
+        if (argc == 3) {
+            free(cond);
+            return Evaluate(state, argv[2]);
+        } else {
+            return cond;
+        }
+    }
+}
+
+char* AbortFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* msg = NULL;
+    if (argc > 0) {
+        msg = Evaluate(state, argv[0]);
+    }
+    free(state->errmsg);
+    if (msg) {
+        state->errmsg = msg;
+    } else {
+        state->errmsg = strdup("called abort()");
+    }
+    return NULL;
+}
+
+char* AssertFn(const char* name, State* state, int argc, Expr* argv[]) {
+    int i;
+    for (i = 0; i < argc; ++i) {
+        char* v = Evaluate(state, argv[i]);
+        if (v == NULL) {
+            return NULL;
+        }
+        int b = BooleanString(v);
+        free(v);
+        if (!b) {
+            int prefix_len;
+            int len = argv[i]->end - argv[i]->start;
+            char* err_src = malloc(len + 20);
+            strcpy(err_src, "assert failed: ");
+            prefix_len = strlen(err_src);
+            memcpy(err_src + prefix_len, state->script + argv[i]->start, len);
+            err_src[prefix_len + len] = '\0';
+            free(state->errmsg);
+            state->errmsg = err_src;
+            return NULL;
+        }
+    }
+    return strdup("");
+}
+
+char* SleepFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* val = Evaluate(state, argv[0]);
+    if (val == NULL) {
+        return NULL;
+    }
+    int v = strtol(val, NULL, 10);
+    sleep(v);
+    return val;
+}
+
+char* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) {
+    int i;
+    for (i = 0; i < argc; ++i) {
+        char* v = Evaluate(state, argv[i]);
+        if (v == NULL) {
+            return NULL;
+        }
+        fputs(v, stdout);
+        free(v);
+    }
+    return strdup("");
+}
+
+char* LogicalAndFn(const char* name, State* state,
+                   int argc, Expr* argv[]) {
+    char* left = Evaluate(state, argv[0]);
+    if (left == NULL) return NULL;
+    if (BooleanString(left) == true) {
+        free(left);
+        return Evaluate(state, argv[1]);
+    } else {
+        return left;
+    }
+}
+
+char* LogicalOrFn(const char* name, State* state,
+                  int argc, Expr* argv[]) {
+    char* left = Evaluate(state, argv[0]);
+    if (left == NULL) return NULL;
+    if (BooleanString(left) == false) {
+        free(left);
+        return Evaluate(state, argv[1]);
+    } else {
+        return left;
+    }
+}
+
+char* LogicalNotFn(const char* name, State* state,
+                   int argc, Expr* argv[]) {
+    char* val = Evaluate(state, argv[0]);
+    if (val == NULL) return NULL;
+    bool bv = BooleanString(val);
+    free(val);
+    if (bv) {
+        return strdup("");
+    } else {
+        return strdup("t");
+    }
+}
+
+char* SubstringFn(const char* name, State* state,
+                  int argc, Expr* argv[]) {
+    char* needle = Evaluate(state, argv[0]);
+    if (needle == NULL) return NULL;
+    char* haystack = Evaluate(state, argv[1]);
+    if (haystack == NULL) {
+        free(needle);
+        return NULL;
+    }
+
+    char* result = strdup(strstr(haystack, needle) ? "t" : "");
+    free(needle);
+    free(haystack);
+    return result;
+}
+
+char* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* left = Evaluate(state, argv[0]);
+    if (left == NULL) return NULL;
+    char* right = Evaluate(state, argv[1]);
+    if (right == NULL) {
+        free(left);
+        return NULL;
+    }
+
+    char* result = strdup(strcmp(left, right) == 0 ? "t" : "");
+    free(left);
+    free(right);
+    return result;
+}
+
+char* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* left = Evaluate(state, argv[0]);
+    if (left == NULL) return NULL;
+    char* right = Evaluate(state, argv[1]);
+    if (right == NULL) {
+        free(left);
+        return NULL;
+    }
+
+    char* result = strdup(strcmp(left, right) != 0 ? "t" : "");
+    free(left);
+    free(right);
+    return result;
+}
+
+char* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* left = Evaluate(state, argv[0]);
+    if (left == NULL) return NULL;
+    free(left);
+    return Evaluate(state, argv[1]);
+}
+
+char* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 2) {
+        free(state->errmsg);
+        state->errmsg = strdup("less_than_int expects 2 arguments");
+        return NULL;
+    }
+
+    char* left;
+    char* right;
+    if (ReadArgs(state, argv, 2, &left, &right) < 0) return NULL;
+
+    bool result = false;
+    char* end;
+
+    long l_int = strtol(left, &end, 10);
+    if (left[0] == '\0' || *end != '\0') {
+        fprintf(stderr, "[%s] is not an int\n", left);
+        goto done;
+    }
+
+    long r_int = strtol(right, &end, 10);
+    if (right[0] == '\0' || *end != '\0') {
+        fprintf(stderr, "[%s] is not an int\n", right);
+        goto done;
+    }
+
+    result = l_int < r_int;
+
+  done:
+    free(left);
+    free(right);
+    return strdup(result ? "t" : "");
+}
+
+char* GreaterThanIntFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 2) {
+        free(state->errmsg);
+        state->errmsg = strdup("greater_than_int expects 2 arguments");
+        return NULL;
+    }
+
+    Expr* temp[2];
+    temp[0] = argv[1];
+    temp[1] = argv[0];
+
+    return LessThanIntFn(name, state, 2, temp);
+}
+
+char* Literal(const char* name, State* state, int argc, Expr* argv[]) {
+    return strdup(name);
+}
+
+Expr* Build(Function fn, YYLTYPE loc, int count, ...) {
+    va_list v;
+    va_start(v, count);
+    Expr* e = malloc(sizeof(Expr));
+    e->fn = fn;
+    e->name = "(operator)";
+    e->argc = count;
+    e->argv = malloc(count * sizeof(Expr*));
+    int i;
+    for (i = 0; i < count; ++i) {
+        e->argv[i] = va_arg(v, Expr*);
+    }
+    va_end(v);
+    e->start = loc.start;
+    e->end = loc.end;
+    return e;
+}
+
+// -----------------------------------------------------------------
+//   the function table
+// -----------------------------------------------------------------
+
+static int fn_entries = 0;
+static int fn_size = 0;
+NamedFunction* fn_table = NULL;
+
+void RegisterFunction(const char* name, Function fn) {
+    if (fn_entries >= fn_size) {
+        fn_size = fn_size*2 + 1;
+        fn_table = realloc(fn_table, fn_size * sizeof(NamedFunction));
+    }
+    fn_table[fn_entries].name = name;
+    fn_table[fn_entries].fn = fn;
+    ++fn_entries;
+}
+
+static int fn_entry_compare(const void* a, const void* b) {
+    const char* na = ((const NamedFunction*)a)->name;
+    const char* nb = ((const NamedFunction*)b)->name;
+    return strcmp(na, nb);
+}
+
+void FinishRegistration() {
+    qsort(fn_table, fn_entries, sizeof(NamedFunction), fn_entry_compare);
+}
+
+Function FindFunction(const char* name) {
+    NamedFunction key;
+    key.name = name;
+    NamedFunction* nf = bsearch(&key, fn_table, fn_entries,
+                                sizeof(NamedFunction), fn_entry_compare);
+    if (nf == NULL) {
+        return NULL;
+    }
+    return nf->fn;
+}
+
+void RegisterBuiltins() {
+    RegisterFunction("ifelse", IfElseFn);
+    RegisterFunction("abort", AbortFn);
+    RegisterFunction("assert", AssertFn);
+    RegisterFunction("concat", ConcatFn);
+    RegisterFunction("is_substring", SubstringFn);
+    RegisterFunction("stdout", StdoutFn);
+    RegisterFunction("sleep", SleepFn);
+
+    RegisterFunction("less_than_int", LessThanIntFn);
+    RegisterFunction("greater_than_int", GreaterThanIntFn);
+}
+
+
+// -----------------------------------------------------------------
+//   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(State* state, 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(state, 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(State* state, int argc, Expr* argv[]) {
+    char** args = (char**)malloc(argc * sizeof(char*));
+    int i = 0;
+    for (i = 0; i < argc; ++i) {
+        args[i] = Evaluate(state, argv[i]);
+        if (args[i] == NULL) {
+            int j;
+            for (j = 0; j < i; ++j) {
+                free(args[j]);
+            }
+            free(args);
+            return NULL;
+        }
+    }
+    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
new file mode 100644
index 0000000..d2e7392
--- /dev/null
+++ b/edify/expr.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _EXPRESSION_H
+#define _EXPRESSION_H
+
+#include "yydefs.h"
+
+#define MAX_STRING_LEN 1024
+
+typedef struct Expr Expr;
+
+typedef struct {
+    // Optional pointer to app-specific data; the core of edify never
+    // uses this value.
+    void* cookie;
+
+    // The source of the original script.  Must be NULL-terminated,
+    // and in writable memory (Evaluate may make temporary changes to
+    // it but will restore it when done).
+    char* script;
+
+    // The error message (if any) returned if the evaluation aborts.
+    // Should be NULL initially, will be either NULL or a malloc'd
+    // pointer after Evaluate() returns.
+    char* errmsg;
+} State;
+
+typedef char* (*Function)(const char* name, State* state,
+                          int argc, Expr* argv[]);
+
+struct Expr {
+    Function fn;
+    char* name;
+    int argc;
+    Expr** argv;
+    int start, end;
+};
+
+char* Evaluate(State* state, Expr* expr);
+
+// Glue to make an Expr out of a literal.
+char* Literal(const char* name, State* state, int argc, Expr* argv[]);
+
+// Functions corresponding to various syntactic sugar operators.
+// ("concat" is also available as a builtin function, to concatenate
+// more than two strings.)
+char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]);
+char* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]);
+char* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]);
+char* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]);
+char* SubstringFn(const char* name, State* state, int argc, Expr* argv[]);
+char* EqualityFn(const char* name, State* state, int argc, Expr* argv[]);
+char* InequalityFn(const char* name, State* state, int argc, Expr* argv[]);
+char* SequenceFn(const char* name, State* state, int argc, Expr* argv[]);
+
+// Convenience function for building expressions with a fixed number
+// of arguments.
+Expr* Build(Function fn, YYLTYPE loc, int count, ...);
+
+// Global builtins, registered by RegisterBuiltins().
+char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]);
+char* AssertFn(const char* name, State* state, int argc, Expr* argv[]);
+char* AbortFn(const char* name, State* state, 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;
+} NamedFunction;
+
+// Register a new function.  The same Function may be registered under
+// multiple names, but a given name should only be used once.
+void RegisterFunction(const char* name, Function fn);
+
+// Register all the builtins.
+void RegisterBuiltins();
+
+// Call this after all calls to RegisterFunction() but before parsing
+// any scripts to finish building the function table.
+void FinishRegistration();
+
+// Find the Function for a given name; return NULL if no such function
+// 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(State* state, 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(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/edify/lexer.l b/edify/lexer.l
new file mode 100644
index 0000000..2c4489c
--- /dev/null
+++ b/edify/lexer.l
@@ -0,0 +1,110 @@
+%{
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "expr.h"
+#include "yydefs.h"
+#include "parser.h"
+
+int gLine = 1;
+int gColumn = 1;
+int gPos = 0;
+
+// TODO: enforce MAX_STRING_LEN during lexing
+char string_buffer[MAX_STRING_LEN];
+char* string_pos;
+
+#define ADVANCE do {yylloc.start=gPos; yylloc.end=gPos+yyleng; \
+                    gColumn+=yyleng; gPos+=yyleng;} while(0)
+
+%}
+
+%x STR
+
+%option noyywrap
+
+%%
+
+
+\" {
+    BEGIN(STR);
+    string_pos = string_buffer;
+    yylloc.start = gPos;
+    ++gColumn;
+    ++gPos;
+}
+
+<STR>{
+  \" {
+      ++gColumn;
+      ++gPos;
+      BEGIN(INITIAL);
+      *string_pos = '\0';
+      yylval.str = strdup(string_buffer);
+      yylloc.end = gPos;
+      return STRING;
+  }
+
+  \\n   { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\n'; }
+  \\t   { gColumn += yyleng; gPos += yyleng;  *string_pos++ = '\t'; }
+  \\\"  { gColumn += yyleng; gPos += yyleng;  *string_pos++ = '\"'; }
+  \\\\  { gColumn += yyleng; gPos += yyleng;  *string_pos++ = '\\'; }
+
+  \\x[0-9a-fA-F]{2} {
+      gColumn += yyleng;
+      gPos += yyleng;
+      int val;
+      sscanf(yytext+2, "%x", &val);
+      *string_pos++ = val;
+  }
+
+  \n {
+      ++gLine;
+      ++gPos;
+      gColumn = 1;
+      *string_pos++ = yytext[0];
+  }
+
+  . {
+      ++gColumn;
+      ++gPos;
+      *string_pos++ = yytext[0];
+  }
+}
+
+if                ADVANCE; return IF;
+then              ADVANCE; return THEN;
+else              ADVANCE; return ELSE;
+endif             ADVANCE; return ENDIF;
+
+[a-zA-Z0-9_:/.]+ {
+  ADVANCE;
+  yylval.str = strdup(yytext);
+  return STRING;
+}
+
+\&\&              ADVANCE; return AND;
+\|\|              ADVANCE; return OR;
+==                ADVANCE; return EQ;
+!=                ADVANCE; return NE;
+
+[+(),!;]          ADVANCE; return yytext[0];
+
+[ \t]+            ADVANCE;
+
+(#.*)?\n          gPos += yyleng; ++gLine; gColumn = 1;
+
+.                 return BAD;
diff --git a/edify/main.c b/edify/main.c
new file mode 100644
index 0000000..0e36108
--- /dev/null
+++ b/edify/main.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "expr.h"
+#include "parser.h"
+
+extern int yyparse(Expr** root, int* error_count);
+
+int expect(const char* expr_str, const char* expected, int* errors) {
+    Expr* e;
+    int error;
+    char* result;
+
+    printf(".");
+
+    yy_scan_string(expr_str);
+    int error_count = 0;
+    error = yyparse(&e, &error_count);
+    if (error > 0 || error_count > 0) {
+        fprintf(stderr, "error parsing \"%s\" (%d errors)\n",
+                expr_str, error_count);
+        ++*errors;
+        return 0;
+    }
+
+    State state;
+    state.cookie = NULL;
+    state.script = expr_str;
+    state.errmsg = NULL;
+
+    result = Evaluate(&state, e);
+    free(state.errmsg);
+    if (result == NULL && expected != NULL) {
+        fprintf(stderr, "error evaluating \"%s\"\n", expr_str);
+        ++*errors;
+        return 0;
+    }
+
+    if (result == NULL && expected == NULL) {
+        return 1;
+    }
+
+    if (strcmp(result, expected) != 0) {
+        fprintf(stderr, "evaluating \"%s\": expected \"%s\", got \"%s\"\n",
+                expr_str, expected, result);
+        ++*errors;
+        free(result);
+        return 0;
+    }
+
+    free(result);
+    return 1;
+}
+
+int test() {
+    int errors = 0;
+
+    expect("a", "a", &errors);
+    expect("\"a\"", "a", &errors);
+    expect("\"\\x61\"", "a", &errors);
+    expect("# this is a comment\n"
+           "  a\n"
+           "   \n",
+           "a", &errors);
+
+
+    // sequence operator
+    expect("a; b; c", "c", &errors);
+
+    // string concat operator
+    expect("a + b", "ab", &errors);
+    expect("a + \n \"b\"", "ab", &errors);
+    expect("a + b +\nc\n", "abc", &errors);
+
+    // string concat function
+    expect("concat(a, b)", "ab", &errors);
+    expect("concat(a,\n \"b\")", "ab", &errors);
+    expect("concat(a + b,\nc,\"d\")", "abcd", &errors);
+    expect("\"concat\"(a + b,\nc,\"d\")", "abcd", &errors);
+
+    // logical and
+    expect("a && b", "b", &errors);
+    expect("a && \"\"", "", &errors);
+    expect("\"\" && b", "", &errors);
+    expect("\"\" && \"\"", "", &errors);
+    expect("\"\" && abort()", "", &errors);   // test short-circuiting
+    expect("t && abort()", NULL, &errors);
+
+    // logical or
+    expect("a || b", "a", &errors);
+    expect("a || \"\"", "a", &errors);
+    expect("\"\" || b", "b", &errors);
+    expect("\"\" || \"\"", "", &errors);
+    expect("a || abort()", "a", &errors);     // test short-circuiting
+    expect("\"\" || abort()", NULL, &errors);
+
+    // logical not
+    expect("!a", "", &errors);
+    expect("! \"\"", "t", &errors);
+    expect("!!a", "t", &errors);
+
+    // precedence
+    expect("\"\" == \"\" && b", "b", &errors);
+    expect("a + b == ab", "t", &errors);
+    expect("ab == a + b", "t", &errors);
+    expect("a + (b == ab)", "a", &errors);
+    expect("(ab == a) + b", "b", &errors);
+
+    // substring function
+    expect("is_substring(cad, abracadabra)", "t", &errors);
+    expect("is_substring(abrac, abracadabra)", "t", &errors);
+    expect("is_substring(dabra, abracadabra)", "t", &errors);
+    expect("is_substring(cad, abracxadabra)", "", &errors);
+    expect("is_substring(abrac, axbracadabra)", "", &errors);
+    expect("is_substring(dabra, abracadabrxa)", "", &errors);
+
+    // ifelse function
+    expect("ifelse(t, yes, no)", "yes", &errors);
+    expect("ifelse(!t, yes, no)", "no", &errors);
+    expect("ifelse(t, yes, abort())", "yes", &errors);
+    expect("ifelse(!t, abort(), no)", "no", &errors);
+
+    // if "statements"
+    expect("if t then yes else no endif", "yes", &errors);
+    expect("if \"\" then yes else no endif", "no", &errors);
+    expect("if \"\" then yes endif", "", &errors);
+    expect("if \"\"; t then yes endif", "yes", &errors);
+
+    // numeric comparisons
+    expect("less_than_int(3, 14)", "t", &errors);
+    expect("less_than_int(14, 3)", "", &errors);
+    expect("less_than_int(x, 3)", "", &errors);
+    expect("less_than_int(3, x)", "", &errors);
+    expect("greater_than_int(3, 14)", "", &errors);
+    expect("greater_than_int(14, 3)", "t", &errors);
+    expect("greater_than_int(x, 3)", "", &errors);
+    expect("greater_than_int(3, x)", "", &errors);
+
+    printf("\n");
+
+    return errors;
+}
+
+void ExprDump(int depth, Expr* n, char* script) {
+    printf("%*s", depth*2, "");
+    char temp = script[n->end];
+    script[n->end] = '\0';
+    printf("%s %p (%d-%d) \"%s\"\n",
+           n->name == NULL ? "(NULL)" : n->name, n->fn, n->start, n->end,
+           script+n->start);
+    script[n->end] = temp;
+    int i;
+    for (i = 0; i < n->argc; ++i) {
+        ExprDump(depth+1, n->argv[i], script);
+    }
+}
+
+int main(int argc, char** argv) {
+    RegisterBuiltins();
+    FinishRegistration();
+
+    if (argc == 1) {
+        return test() != 0;
+    }
+
+    FILE* f = fopen(argv[1], "r");
+    char buffer[8192];
+    int size = fread(buffer, 1, 8191, f);
+    fclose(f);
+    buffer[size] = '\0';
+
+    Expr* root;
+    int error_count = 0;
+    yy_scan_bytes(buffer, size);
+    int error = yyparse(&root, &error_count);
+    printf("parse returned %d; %d errors encountered\n", error, error_count);
+    if (error == 0 || error_count > 0) {
+
+        ExprDump(0, root, buffer);
+
+        State state;
+        state.cookie = NULL;
+        state.script = buffer;
+        state.errmsg = NULL;
+
+        char* result = Evaluate(&state, root);
+        if (result == NULL) {
+            printf("result was NULL, message is: %s\n",
+                   (state.errmsg == NULL ? "(NULL)" : state.errmsg));
+            free(state.errmsg);
+        } else {
+            printf("result is [%s]\n", result);
+        }
+    }
+    return 0;
+}
diff --git a/edify/parser.y b/edify/parser.y
new file mode 100644
index 0000000..3f9ade1
--- /dev/null
+++ b/edify/parser.y
@@ -0,0 +1,130 @@
+%{
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "expr.h"
+#include "yydefs.h"
+#include "parser.h"
+
+extern int gLine;
+extern int gColumn;
+
+void yyerror(Expr** root, int* error_count, const char* s);
+int yyparse(Expr** root, int* error_count);
+
+%}
+
+%locations
+
+%union {
+    char* str;
+    Expr* expr;
+    struct {
+        int argc;
+        Expr** argv;
+    } args;
+}
+
+%token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF
+%token <str> STRING BAD
+%type <expr> expr
+%type <args> arglist
+
+%parse-param {Expr** root}
+%parse-param {int* error_count}
+%error-verbose
+
+/* declarations in increasing order of precedence */
+%left ';'
+%left ','
+%left OR
+%left AND
+%left EQ NE
+%left '+'
+%right '!'
+
+%%
+
+input:  expr           { *root = $1; }
+;
+
+expr:  STRING {
+    $$ = malloc(sizeof(Expr));
+    $$->fn = Literal;
+    $$->name = $1;
+    $$->argc = 0;
+    $$->argv = NULL;
+    $$->start = @$.start;
+    $$->end = @$.end;
+}
+|  '(' expr ')'                      { $$ = $2; $$->start=@$.start; $$->end=@$.end; }
+|  expr ';'                          { $$ = $1; $$->start=@1.start; $$->end=@1.end; }
+|  expr ';' expr                     { $$ = Build(SequenceFn, @$, 2, $1, $3); }
+|  error ';' expr                    { $$ = $3; $$->start=@$.start; $$->end=@$.end; }
+|  expr '+' expr                     { $$ = Build(ConcatFn, @$, 2, $1, $3); }
+|  expr EQ expr                      { $$ = Build(EqualityFn, @$, 2, $1, $3); }
+|  expr NE expr                      { $$ = Build(InequalityFn, @$, 2, $1, $3); }
+|  expr AND expr                     { $$ = Build(LogicalAndFn, @$, 2, $1, $3); }
+|  expr OR expr                      { $$ = Build(LogicalOrFn, @$, 2, $1, $3); }
+|  '!' expr                          { $$ = Build(LogicalNotFn, @$, 1, $2); }
+|  IF expr THEN expr ENDIF           { $$ = Build(IfElseFn, @$, 2, $2, $4); }
+|  IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); }
+| STRING '(' arglist ')' {
+    $$ = malloc(sizeof(Expr));
+    $$->fn = FindFunction($1);
+    if ($$->fn == NULL) {
+        char buffer[256];
+        snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1);
+        yyerror(root, error_count, buffer);
+        YYERROR;
+    }
+    $$->name = $1;
+    $$->argc = $3.argc;
+    $$->argv = $3.argv;
+    $$->start = @$.start;
+    $$->end = @$.end;
+}
+;
+
+arglist:    /* empty */ {
+    $$.argc = 0;
+    $$.argv = NULL;
+}
+| expr {
+    $$.argc = 1;
+    $$.argv = malloc(sizeof(Expr*));
+    $$.argv[0] = $1;
+}
+| arglist ',' expr {
+    $$.argc = $1.argc + 1;
+    $$.argv = realloc($$.argv, $$.argc * sizeof(Expr*));
+    $$.argv[$$.argc-1] = $3;
+}
+;
+
+%%
+
+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/edify/yydefs.h b/edify/yydefs.h
new file mode 100644
index 0000000..6257862
--- /dev/null
+++ b/edify/yydefs.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _YYDEFS_H_
+#define _YYDEFS_H_
+
+#define YYLTYPE YYLTYPE
+typedef struct {
+    int start, end;
+} YYLTYPE;
+
+#define YYLLOC_DEFAULT(Current, Rhs, N) \
+    do { \
+        if (N) { \
+            (Current).start = YYRHSLOC(Rhs, 1).start; \
+            (Current).end = YYRHSLOC(Rhs, N).end; \
+        } else { \
+            (Current).start = YYRHSLOC(Rhs, 0).start; \
+            (Current).end = YYRHSLOC(Rhs, 0).end; \
+        } \
+    } while (0)
+
+#endif
diff --git a/firmware.c b/firmware.c
index 34b2918..e2e4fe6 100644
--- a/firmware.c
+++ b/firmware.c
@@ -39,6 +39,10 @@
     return 0;
 }
 
+// Return true if there is a firmware update pending.
+int firmware_update_pending() {
+  return update_data != NULL && update_length > 0;
+}
 
 /* Bootloader / Recovery Flow
  *
diff --git a/firmware.h b/firmware.h
index f3f7aab..aeb8f97 100644
--- a/firmware.h
+++ b/firmware.h
@@ -23,6 +23,9 @@
  */
 int remember_firmware_update(const char *type, const char *data, int length);
 
+/* Returns true if a firmware update has been saved. */
+int firmware_update_pending();
+
 /* If an update was saved, reboot into the bootloader now to install it.
  * Returns 0 if no radio image was defined, nonzero on error,
  * doesn't return at all on success...
diff --git a/install.c b/install.c
index 0691120..ab19478 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,13 +33,11 @@
 #include "mtdutils/mtdutils.h"
 #include "roots.h"
 #include "verifier.h"
-
-/* List of public keys */
-static const RSAPublicKey keys[] = {
-#include "keys.inc"
-};
+#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 *
 find_update_script(ZipArchive *zip)
@@ -99,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,12 +110,211 @@
         return INSTALL_ERROR;
     }
 
-    ui_print("Installation complete.\n");
+    LOGI("Installation complete.\n");
     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_update_package(const char *path, ZipArchive *zip)
+handle_firmware_update(char* type, char* filename, ZipArchive* zip) {
+    unsigned int data_size;
+    const ZipEntry* entry = NULL;
+
+    if (strncmp(filename, "PACKAGE:", 8) == 0) {
+        entry = mzFindZipEntry(zip, filename+8);
+        if (entry == NULL) {
+            LOGE("Failed to find \"%s\" in package", filename+8);
+            return INSTALL_ERROR;
+        }
+        data_size = entry->uncompLen;
+    } else {
+        struct stat st_data;
+        if (stat(filename, &st_data) < 0) {
+            LOGE("Error stat'ing %s: %s\n", filename, strerror(errno));
+            return INSTALL_ERROR;
+        }
+        data_size = st_data.st_size;
+    }
+
+    LOGI("type is %s; size is %d; file is %s\n",
+         type, data_size, filename);
+
+    char* data = malloc(data_size);
+    if (data == NULL) {
+        LOGI("Can't allocate %d bytes for firmware data\n", data_size);
+        return INSTALL_ERROR;
+    }
+
+    if (entry) {
+        if (mzReadZipEntry(zip, entry, data, data_size) == false) {
+            LOGE("Failed to read \"%s\" from package", filename+8);
+            return INSTALL_ERROR;
+        }
+    } else {
+        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, data_size, f) != data_size) {
+            LOGE("Failed to read firmware data: %s\n", strerror(errno));
+            return INSTALL_ERROR;
+        }
+        fclose(f);
+    }
+
+    if (remember_firmware_update(type, data, data_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
+    //
+    //   - 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 the next <frac> part of of the progress bar
+    //            over <secs> seconds.  If <secs> is zero, use
+    //            set_progress commands to manually control the
+    //            progress of this segment of the bar
+    //
+    //        set_progress <frac>
+    //            <frac> should be between 0.0 and 1.0; sets the
+    //            progress bar within the segment defined by the most
+    //            recent progress command.
+    //
+    //        firmware <"hboot"|"radio"> <filename>
+    //            arrange to install the contents of <filename> in the
+    //            given partition on reboot.  (API v2: <filename> may
+    //            start with "PACKAGE:" to indicate taking a file from
+    //            the OTA package.)
+    //
+    //        ui_print <string>
+    //            display <string> on the screen.
+    //
+    //   - the name of the package zip file.
+    //
+
+    char** args = malloc(sizeof(char*) * 5);
+    args[0] = binary;
+    args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk
+    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, "set_progress") == 0) {
+            char* fraction_s = strtok(NULL, " \n");
+            float fraction = strtof(fraction_s, NULL);
+            ui_set_progress(fraction);
+        } 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 if (strcmp(command, "ui_print") == 0) {
+            char* str = strtok(NULL, "\n");
+            if (str) {
+                ui_print(str);
+            } else {
+                ui_print("\n");
+            }
+        } 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, WEXITSTATUS(status));
+        return INSTALL_ERROR;
+    }
+
+    if (firmware_type != NULL) {
+        return handle_firmware_update(firmware_type, firmware_filename, zip);
+    } else {
+        return INSTALL_SUCCESS;
+    }
+}
+
+static int
+handle_update_package(const char *path, ZipArchive *zip,
+                      const RSAPublicKey *keys, int numKeys)
 {
     // Give verification half the progress bar...
     ui_print("Verifying update package...\n");
@@ -122,7 +322,7 @@
             VERIFICATION_PROGRESS_FRACTION,
             VERIFICATION_PROGRESS_TIME);
 
-    if (!verify_jar_signature(zip, keys, sizeof(keys) / sizeof(keys[0]))) {
+    if (!verify_jar_signature(zip, keys, numKeys)) {
         LOGE("Verification failed\n");
         return INSTALL_CORRUPT;
     }
@@ -130,6 +330,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) {
@@ -147,6 +357,80 @@
     return ret;
 }
 
+// Reads a file containing one or more public keys as produced by
+// DumpPublicKey:  this is an RSAPublicKey struct as it would appear
+// as a C source literal, eg:
+//
+//  "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
+//
+// (Note that the braces and commas in this example are actual
+// characters the parser expects to find in the file; the ellipses
+// indicate more numbers omitted from this example.)
+//
+// The file may contain multiple keys in this format, separated by
+// commas.  The last key must not be followed by a comma.
+//
+// Returns NULL if the file failed to parse, or if it contain zero keys.
+static RSAPublicKey*
+load_keys(const char* filename, int* numKeys) {
+    RSAPublicKey* out = NULL;
+    *numKeys = 0;
+
+    FILE* f = fopen(filename, "r");
+    if (f == NULL) {
+        LOGE("opening %s: %s\n", filename, strerror(errno));
+        goto exit;
+    }
+
+    int i;
+    bool done = false;
+    while (!done) {
+        ++*numKeys;
+        out = realloc(out, *numKeys * sizeof(RSAPublicKey));
+        RSAPublicKey* key = out + (*numKeys - 1);
+        if (fscanf(f, " { %i , %i , { %i",
+                   &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
+            goto exit;
+        }
+        if (key->len != RSANUMWORDS) {
+            LOGE("key length (%d) does not match expected size\n", key->len);
+            goto exit;
+        }
+        for (i = 1; i < key->len; ++i) {
+            if (fscanf(f, " , %i", &(key->n[i])) != 1) goto exit;
+        }
+        if (fscanf(f, " } , { %i", &(key->rr[0])) != 1) goto exit;
+        for (i = 1; i < key->len; ++i) {
+            if (fscanf(f, " , %i", &(key->rr[i])) != 1) goto exit;
+        }
+        fscanf(f, " } } ");
+
+        // if the line ends in a comma, this file has more keys.
+        switch (fgetc(f)) {
+            case ',':
+                // more keys to come.
+                break;
+
+            case EOF:
+                done = true;
+                break;
+
+            default:
+                LOGE("unexpected character between keys\n");
+                goto exit;
+        }
+    }
+
+    fclose(f);
+    return out;
+
+exit:
+    if (f) fclose(f);
+    free(out);
+    *numKeys = 0;
+    return NULL;
+}
+
 int
 install_package(const char *root_path)
 {
@@ -169,6 +453,14 @@
     ui_print("Opening update package...\n");
     LOGI("Update file path: %s\n", path);
 
+    int numKeys;
+    RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
+    if (loadedKeys == NULL) {
+        LOGE("Failed to load keys\n");
+        return INSTALL_CORRUPT;
+    }
+    LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);
+
     /* Try to open the package.
      */
     ZipArchive zip;
@@ -180,7 +472,8 @@
 
     /* Verify and install the contents of the package.
      */
-    int status = handle_update_package(path, &zip);
+    int status = handle_update_package(path, &zip, loadedKeys, numKeys);
     mzCloseZipArchive(&zip);
+    free(loadedKeys);
     return status;
 }
diff --git a/minui/resources.c b/minui/resources.c
index 5beb6a6..7ecfeef 100644
--- a/minui/resources.c
+++ b/minui/resources.c
@@ -29,97 +29,84 @@
 
 #include <pixelflinger/pixelflinger.h>
 
+#include <png.h>
+
 #include "minui.h"
 
-// File signature for BMP files.
-// The letters 'BM' as a little-endian unsigned short.
-
-#define BMP_SIGNATURE 0x4d42
-
-typedef struct {
-    // constant, value should equal BMP_SIGNATURE
-    unsigned short  bfType;
-    // size of the file in bytes.
-    unsigned long   bfSize;
-    // must always be set to zero.
-    unsigned short  bfReserved1;
-    // must always be set to zero.
-    unsigned short  bfReserved2;
-    // offset from the beginning of the file to the bitmap data.
-    unsigned long   bfOffBits;
-
-    // The BITMAPINFOHEADER:
-    // size of the BITMAPINFOHEADER structure, in bytes.
-    unsigned long   biSize;
-    // width of the image, in pixels.
-    unsigned long   biWidth;
-    // height of the image, in pixels.
-    unsigned long   biHeight;
-    // number of planes of the target device, must be set to 1.
-    unsigned short  biPlanes;
-    // number of bits per pixel.
-    unsigned short  biBitCount;
-    // type of compression, zero means no compression.
-    unsigned long   biCompression;
-    // size of the image data, in bytes. If there is no compression,
-    // it is valid to set this member to zero.
-    unsigned long   biSizeImage;
-    // horizontal pixels per meter on the designated targer device,
-    // usually set to zero.
-    unsigned long   biXPelsPerMeter;
-    // vertical pixels per meter on the designated targer device,
-    // usually set to zero.
-    unsigned long   biYPelsPerMeter;
-    // number of colors used in the bitmap, if set to zero the
-    // number of colors is calculated using the biBitCount member.
-    unsigned long   biClrUsed;
-    // number of color that are 'important' for the bitmap,
-    // if set to zero, all colors are important.
-    unsigned long   biClrImportant;
-} __attribute__((packed)) BitMapFileHeader;
+// libpng gives "undefined reference to 'pow'" errors, and I have no
+// idea how to convince the build system to link with -lm.  We don't
+// need this functionality (it's used for gamma adjustment) so provide
+// a dummy implementation to satisfy the linker.
+double pow(double x, double y) {
+    return x;
+}
 
 int res_create_surface(const char* name, gr_surface* pSurface) {
     char resPath[256];
-    BitMapFileHeader header;
     GGLSurface* surface = NULL;
     int result = 0;
-    
-    snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.bmp", name);
+    unsigned char header[8];
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+
+    snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
     resPath[sizeof(resPath)-1] = '\0';
-    int fd = open(resPath, O_RDONLY);
-    if (fd == -1) {
+    FILE* fp = fopen(resPath, "rb");
+    if (fp == NULL) {
         result = -1;
         goto exit;
     }
-    size_t bytesRead = read(fd, &header, sizeof(header));
+
+    size_t bytesRead = fread(header, 1, sizeof(header), fp);
     if (bytesRead != sizeof(header)) {
         result = -2;
         goto exit;
     }
-    if (header.bfType != BMP_SIGNATURE) {
-        result = -3; // Not a legal header
+
+    if (png_sig_cmp(header, 0, sizeof(header))) {
+        result = -3;
         goto exit;
     }
-    if (header.biPlanes != 1) {
+
+    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (!png_ptr) {
         result = -4;
         goto exit;
     }
-    if (!(header.biBitCount == 24 || header.biBitCount == 32)) {
+
+    info_ptr = png_create_info_struct(png_ptr);
+    if (!info_ptr) {
         result = -5;
         goto exit;
     }
-    if (header.biCompression != 0) {
+
+    if (setjmp(png_jmpbuf(png_ptr))) {
         result = -6;
         goto exit;
     }
-    size_t width = header.biWidth;
-    size_t height = header.biHeight;
+
+    png_init_io(png_ptr, fp);
+    png_set_sig_bytes(png_ptr, sizeof(header));
+    png_read_info(png_ptr, info_ptr);
+
+    size_t width = info_ptr->width;
+    size_t height = info_ptr->height;
     size_t stride = 4 * width;
     size_t pixelSize = stride * height;
-    
+
+    int color_type = info_ptr->color_type;
+    int bit_depth = info_ptr->bit_depth;
+    int channels = info_ptr->channels;
+    if (bit_depth != 8 || (channels != 3 && channels != 4) ||
+        (color_type != PNG_COLOR_TYPE_RGB &&
+         color_type != PNG_COLOR_TYPE_RGBA)) {
+        return -7;
+        goto exit;
+    }
+
     surface = malloc(sizeof(GGLSurface) + pixelSize);
     if (surface == NULL) {
-        result = -7;
+        result = -8;
         goto exit;
     }
     unsigned char* pData = (unsigned char*) (surface + 1);
@@ -128,63 +115,43 @@
     surface->height = height;
     surface->stride = width; /* Yes, pixels, not bytes */
     surface->data = pData;
-    surface->format = (header.biBitCount == 24) ?
+    surface->format = (channels == 3) ?
             GGL_PIXEL_FORMAT_RGBX_8888 : GGL_PIXEL_FORMAT_RGBA_8888;
 
-    // Source pixel bytes are stored B G R {A}
-    
-    lseek(fd, header.bfOffBits, SEEK_SET);
-    size_t y;
-    if (header.biBitCount == 24) { // RGB
-        size_t inputStride = (((3 * width + 3) >> 2) << 2);
-        for (y = 0; y < height; y++) {
-            unsigned char* pRow = pData + (height - (y + 1)) * stride;
-            bytesRead = read(fd,  pRow, inputStride);
-            if (bytesRead != inputStride) {
-                result = -8;
-                goto exit;
-            }
+    int y;
+    if (channels == 3) {
+        for (y = 0; y < height; ++y) {
+            unsigned char* pRow = pData + y * stride;
+            png_read_row(png_ptr, pRow, NULL);
+
             int x;
             for(x = width - 1; x >= 0; x--) {
                 int sx = x * 3;
                 int dx = x * 4;
-                unsigned char b = pRow[sx];
+                unsigned char r = pRow[sx];
                 unsigned char g = pRow[sx + 1];
-                unsigned char r = pRow[sx + 2];
+                unsigned char b = pRow[sx + 2];
                 unsigned char a = 0xff;
                 pRow[dx    ] = r; // r
                 pRow[dx + 1] = g; // g
-                pRow[dx + 2] = b; // b;
+                pRow[dx + 2] = b; // b
                 pRow[dx + 3] = a;
             }
         }
-    } else { // RGBA
-        for (y = 0; y < height; y++) {
-            unsigned char* pRow = pData + (height - (y + 1)) * stride;
-            bytesRead = read(fd,  pRow, stride);
-            if (bytesRead != stride) {
-                result = -9;
-                goto exit;
-            }
-            size_t x;
-            for(x = 0; x < width; x++) {
-                size_t xx = x * 4;
-                unsigned char b = pRow[xx];
-                unsigned char g = pRow[xx + 1];
-                unsigned char r = pRow[xx + 2];
-                unsigned char a = pRow[xx + 3];
-                pRow[xx    ] = r;
-                pRow[xx + 1] = g;
-                pRow[xx + 2] = b;
-                pRow[xx + 3] = a;
-            }
+    } else {
+        for (y = 0; y < height; ++y) {
+            unsigned char* pRow = pData + y * stride;
+            png_read_row(png_ptr, pRow, NULL);
         }
     }
+
     *pSurface = (gr_surface) surface;
 
 exit:
-    if (fd >= 0) {
-        close(fd);
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+
+    if (fp != NULL) {
+        fclose(fp);
     }
     if (result < 0) {
         if (surface) {
diff --git a/minzip/Zip.c b/minzip/Zip.c
index 100c833..8cdb898 100644
--- a/minzip/Zip.c
+++ b/minzip/Zip.c
@@ -41,7 +41,7 @@
     CENSIZ = 20,
     CENLEN = 24,
     CENNAM = 28,
-    CENEXT = 30, 
+    CENEXT = 30,
     CENCOM = 32,
     CENDSK = 34,
     CENATT = 36,
@@ -66,13 +66,13 @@
 
     LOCSIG = 0x04034b50,      // PK34
     LOCHDR = 30,
-        
+
     LOCVER =  4,
     LOCFLG =  6,
     LOCHOW =  8,
     LOCTIM = 10,
     LOCCRC = 14,
-    LOCSIZ = 18, 
+    LOCSIZ = 18,
     LOCLEN = 22,
     LOCNAM = 26,
     LOCEXT = 28,
@@ -757,7 +757,7 @@
 {
     CopyProcessArgs args;
     bool ret;
-    
+
     args.buf = buf;
     args.bufLen = bufLen;
     ret = mzProcessZipEntryContents(pArchive, pEntry, copyProcessFunction,
@@ -770,15 +770,29 @@
 }
 
 static bool writeProcessFunction(const unsigned char *data, int dataLen,
-        void *fd)
+                                 void *cookie)
 {
-    ssize_t n = write((int)fd, data, dataLen);
-    if (n != dataLen) {
-        LOGE("Can't write %d bytes (only %ld) from zip file: %s\n",
-                dataLen, n, strerror(errno));
-        return false;
+    int fd = (int)cookie;
+
+    ssize_t soFar = 0;
+    while (true) {
+        ssize_t n = write(fd, data+soFar, dataLen-soFar);
+        if (n <= 0) {
+            LOGE("Error writing %ld bytes from zip file from %p: %s\n",
+                 dataLen-soFar, data+soFar, strerror(errno));
+            if (errno != EINTR) {
+              return false;
+            }
+        } else if (n > 0) {
+            soFar += n;
+            if (soFar == dataLen) return true;
+            if (soFar > dataLen) {
+                LOGE("write overrun?  (%ld bytes instead of %d)\n",
+                     soFar, dataLen);
+                return false;
+            }
+        }
     }
-    return true;
 }
 
 /*
@@ -788,7 +802,7 @@
     const ZipEntry *pEntry, int fd)
 {
     bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction,
-            (void *)fd);
+                                         (void*)fd);
     if (!ret) {
         LOGE("Can't extract entry to file.\n");
         return false;
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
index 2b0106f..fc06766 100644
--- a/mtdutils/mtdutils.c
+++ b/mtdutils/mtdutils.c
@@ -297,7 +297,14 @@
                     after.corrected - before.corrected,
                     after.failed - before.failed, pos);
         } else {
-            return 0;  // Success!
+            int i;
+            for (i = 0; i < size; ++i) {
+                if (data[i] != 0) {
+                    return 0;  // Success!
+                }
+            }
+            fprintf(stderr, "mtd: read all-zero block at 0x%08lx; skipping\n",
+                    pos);
         }
 
         pos += partition->erase_size;
@@ -326,6 +333,10 @@
             read += ctx->partition->erase_size;
         }
 
+        if (read >= len) {
+            return read;
+        }
+
         // Read the next block into the buffer
         if (ctx->consumed == ctx->partition->erase_size && read < (int) len) {
             if (read_block(ctx->partition, ctx->fd, ctx->buffer)) return -1;
diff --git a/recovery.c b/recovery.c
index 221ee29..5ccd38f 100644
--- a/recovery.c
+++ b/recovery.c
@@ -265,7 +265,6 @@
 {
     extern int test_symtab(void);
     extern int test_cmd_fn(void);
-    extern int test_permissions(void);
     int ret;
     LOGD("Testing symtab...\n");
     ret = test_symtab();
@@ -273,9 +272,6 @@
     LOGD("Testing cmd_fn...\n");
     ret = test_cmd_fn();
     LOGD("  returned %d\n", ret);
-    LOGD("Testing permissions...\n");
-    ret = test_permissions();
-    LOGD("  returned %d\n", ret);
 }
 #endif  // TEST_AMEND
 
@@ -291,7 +287,8 @@
 static void
 prompt_and_wait()
 {
-    char* headers[] = { "Android system recovery utility",
+    char* headers[] = { "Android system recovery <"
+                          EXPAND(RECOVERY_API_VERSION) ">",
                         "",
                         "Use trackball to highlight;",
                         "click to select.",
@@ -302,9 +299,11 @@
 #define ITEM_REBOOT        0
 #define ITEM_APPLY_SDCARD  1
 #define ITEM_WIPE_DATA     2
+#define ITEM_WIPE_CACHE    3
     char* items[] = { "reboot system now [Home+Back]",
                       "apply sdcard:update.zip [Alt+S]",
                       "wipe data/factory reset [Alt+W]",
+                      "wipe cache partition",
                       NULL };
 
     ui_start_menu(headers, items);
@@ -357,6 +356,13 @@
                     if (!ui_text_visible()) return;
                     break;
 
+                case ITEM_WIPE_CACHE:
+                    ui_print("\n-- Wiping cache...\n");
+                    erase_root("CACHE:");
+                    ui_print("Cache wipe complete.\n");
+                    if (!ui_text_visible()) return;
+                    break;
+
                 case ITEM_APPLY_SDCARD:
                     ui_print("\n-- Install from sdcard...\n");
                     int status = install_package(SDCARD_PACKAGE_FILE);
@@ -366,7 +372,12 @@
                     } else if (!ui_text_visible()) {
                         return;  // reboot if logs aren't visible
                     } else {
-                      ui_print("Install from sdcard complete.\n");
+                      if (firmware_update_pending()) {
+                        ui_print("\nReboot via home+back or menu\n"
+                                 "to complete installation.\n");
+                      } else {
+                        ui_print("\nInstall from sdcard complete.\n");
+                      }
                     }
                     break;
             }
diff --git a/res/images/icon_error.bmp b/res/images/icon_error.bmp
deleted file mode 100644
index 7eb2bbc..0000000
--- a/res/images/icon_error.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_error.png b/res/images/icon_error.png
new file mode 100644
index 0000000..7064c2e
--- /dev/null
+++ b/res/images/icon_error.png
Binary files differ
diff --git a/res/images/icon_firmware_error.bmp b/res/images/icon_firmware_error.bmp
deleted file mode 100644
index 5b8649f..0000000
--- a/res/images/icon_firmware_error.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_firmware_error.png b/res/images/icon_firmware_error.png
new file mode 100644
index 0000000..0c32c9e
--- /dev/null
+++ b/res/images/icon_firmware_error.png
Binary files differ
diff --git a/res/images/icon_firmware_install.bmp b/res/images/icon_firmware_install.bmp
deleted file mode 100644
index b0f5f95..0000000
--- a/res/images/icon_firmware_install.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_firmware_install.png b/res/images/icon_firmware_install.png
new file mode 100644
index 0000000..ee2afac
--- /dev/null
+++ b/res/images/icon_firmware_install.png
Binary files differ
diff --git a/res/images/icon_installing.bmp b/res/images/icon_installing.bmp
deleted file mode 100644
index fff99fd..0000000
--- a/res/images/icon_installing.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing.png b/res/images/icon_installing.png
new file mode 100644
index 0000000..f24f2e3
--- /dev/null
+++ b/res/images/icon_installing.png
Binary files differ
diff --git a/res/images/icon_unpacking.bmp b/res/images/icon_unpacking.bmp
deleted file mode 100644
index ab6548c..0000000
--- a/res/images/icon_unpacking.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate1.bmp b/res/images/indeterminate1.bmp
deleted file mode 100644
index 716c925..0000000
--- a/res/images/indeterminate1.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate1.png b/res/images/indeterminate1.png
new file mode 100644
index 0000000..264bf27
--- /dev/null
+++ b/res/images/indeterminate1.png
Binary files differ
diff --git a/res/images/indeterminate2.bmp b/res/images/indeterminate2.bmp
deleted file mode 100644
index 223cd3c..0000000
--- a/res/images/indeterminate2.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate2.png b/res/images/indeterminate2.png
new file mode 100644
index 0000000..c30c049
--- /dev/null
+++ b/res/images/indeterminate2.png
Binary files differ
diff --git a/res/images/indeterminate3.bmp b/res/images/indeterminate3.bmp
deleted file mode 100644
index fd9086a..0000000
--- a/res/images/indeterminate3.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate3.png b/res/images/indeterminate3.png
new file mode 100644
index 0000000..891a000
--- /dev/null
+++ b/res/images/indeterminate3.png
Binary files differ
diff --git a/res/images/indeterminate4.bmp b/res/images/indeterminate4.bmp
deleted file mode 100644
index 87b2640..0000000
--- a/res/images/indeterminate4.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate4.png b/res/images/indeterminate4.png
new file mode 100644
index 0000000..7a64151
--- /dev/null
+++ b/res/images/indeterminate4.png
Binary files differ
diff --git a/res/images/indeterminate5.bmp b/res/images/indeterminate5.bmp
deleted file mode 100644
index e16efb0..0000000
--- a/res/images/indeterminate5.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate5.png b/res/images/indeterminate5.png
new file mode 100644
index 0000000..cd6ab20
--- /dev/null
+++ b/res/images/indeterminate5.png
Binary files differ
diff --git a/res/images/indeterminate6.bmp b/res/images/indeterminate6.bmp
deleted file mode 100644
index 085ad95..0000000
--- a/res/images/indeterminate6.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate6.png b/res/images/indeterminate6.png
new file mode 100644
index 0000000..ddd9e73
--- /dev/null
+++ b/res/images/indeterminate6.png
Binary files differ
diff --git a/res/images/progress_bar_empty.bmp b/res/images/progress_bar_empty.bmp
deleted file mode 100644
index 8e512fd..0000000
--- a/res/images/progress_bar_empty.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/progress_bar_empty.png b/res/images/progress_bar_empty.png
new file mode 100644
index 0000000..9013f04
--- /dev/null
+++ b/res/images/progress_bar_empty.png
Binary files differ
diff --git a/res/images/progress_bar_empty_left_round.bmp b/res/images/progress_bar_empty_left_round.bmp
deleted file mode 100644
index c4e2f44..0000000
--- a/res/images/progress_bar_empty_left_round.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/progress_bar_empty_left_round.png b/res/images/progress_bar_empty_left_round.png
new file mode 100644
index 0000000..dae7d5d
--- /dev/null
+++ b/res/images/progress_bar_empty_left_round.png
Binary files differ
diff --git a/res/images/progress_bar_empty_right_round.bmp b/res/images/progress_bar_empty_right_round.bmp
deleted file mode 100644
index 1906f62..0000000
--- a/res/images/progress_bar_empty_right_round.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/progress_bar_empty_right_round.png b/res/images/progress_bar_empty_right_round.png
new file mode 100644
index 0000000..5427088
--- /dev/null
+++ b/res/images/progress_bar_empty_right_round.png
Binary files differ
diff --git a/res/images/progress_bar_fill.bmp b/res/images/progress_bar_fill.bmp
deleted file mode 100644
index 8d57d81..0000000
--- a/res/images/progress_bar_fill.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/progress_bar_fill.png b/res/images/progress_bar_fill.png
new file mode 100644
index 0000000..37c04b4
--- /dev/null
+++ b/res/images/progress_bar_fill.png
Binary files differ
diff --git a/res/images/progress_bar_left_round.bmp b/res/images/progress_bar_left_round.bmp
deleted file mode 100644
index 6d2df8d..0000000
--- a/res/images/progress_bar_left_round.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/progress_bar_left_round.png b/res/images/progress_bar_left_round.png
new file mode 100644
index 0000000..e72af47
--- /dev/null
+++ b/res/images/progress_bar_left_round.png
Binary files differ
diff --git a/res/images/progress_bar_right_round.bmp b/res/images/progress_bar_right_round.bmp
deleted file mode 100644
index 68bb6fe..0000000
--- a/res/images/progress_bar_right_round.bmp
+++ /dev/null
Binary files differ
diff --git a/res/images/progress_bar_right_round.png b/res/images/progress_bar_right_round.png
new file mode 100644
index 0000000..d04c980
--- /dev/null
+++ b/res/images/progress_bar_right_round.png
Binary files differ
diff --git a/ui.c b/ui.c
index 5d06561..b84f172 100644
--- a/ui.c
+++ b/ui.c
@@ -46,7 +46,6 @@
 static gr_surface gProgressBarFill[NUM_SIDES];
 
 static const struct { gr_surface* surface; const char *name; } BITMAPS[] = {
-    { &gBackgroundIcon[BACKGROUND_ICON_UNPACKING],  "icon_unpacking" },
     { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" },
     { &gBackgroundIcon[BACKGROUND_ICON_ERROR],      "icon_error" },
     { &gBackgroundIcon[BACKGROUND_ICON_FIRMWARE_INSTALLING],
diff --git a/updater/Android.mk b/updater/Android.mk
new file mode 100644
index 0000000..897b9d7
--- /dev/null
+++ b/updater/Android.mk
@@ -0,0 +1,30 @@
+# Copyright 2009 The Android Open Source Project
+
+LOCAL_PATH := $(call my-dir)
+
+updater_src_files := \
+	install.c \
+	updater.c
+
+#
+# Build a statically-linked binary to include in OTA packages
+#
+include $(CLEAR_VARS)
+
+# Build only in eng, so we don't end up with a copy of this in /system
+# on user builds.  (TODO: find a better way to build device binaries
+# needed only for OTA packages.)
+LOCAL_MODULE_TAGS := eng
+
+LOCAL_SRC_FILES := $(updater_src_files)
+
+LOCAL_STATIC_LIBRARIES := libapplypatch libedify libmtdutils libminzip libz
+LOCAL_STATIC_LIBRARIES += libmincrypt libbz
+LOCAL_STATIC_LIBRARIES += libcutils libstdc++ libc
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
+
+LOCAL_MODULE := updater
+
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+
+include $(BUILD_EXECUTABLE)
diff --git a/updater/install.c b/updater/install.c
new file mode 100644
index 0000000..c4f5e03
--- /dev/null
+++ b/updater/install.c
@@ -0,0 +1,788 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#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"
+#include "mtdutils/mtdutils.h"
+#include "updater.h"
+
+
+// mount(type, location, mount_point)
+//
+//   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, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 3) {
+        return ErrorAbort(state, "%s() expects 3 args, got %d", name, argc);
+    }
+    char* type;
+    char* location;
+    char* mount_point;
+    if (ReadArgs(state, argv, 3, &type, &location, &mount_point) < 0) {
+        return NULL;
+    }
+
+    if (strlen(type) == 0) {
+        ErrorAbort(state, "type argument to %s() can't be empty", name);
+        goto done;
+    }
+    if (strlen(location) == 0) {
+        ErrorAbort(state, "location argument to %s() can't be empty", name);
+        goto done;
+    }
+    if (strlen(mount_point) == 0) {
+        ErrorAbort(state, "mount_point argument to %s() can't be empty", name);
+        goto done;
+    }
+
+    mkdir(mount_point, 0755);
+
+    if (strcmp(type, "MTD") == 0) {
+        mtd_scan_partitions();
+        const MtdPartition* mtd;
+        mtd = mtd_find_partition_by_name(location);
+        if (mtd == NULL) {
+            fprintf(stderr, "%s: no mtd partition named \"%s\"",
+                    name, location);
+            result = strdup("");
+            goto done;
+        }
+        if (mtd_mount_partition(mtd, mount_point, "yaffs2", 0 /* rw */) != 0) {
+            fprintf(stderr, "mtd mount of %s failed: %s\n",
+                    location, strerror(errno));
+            result = strdup("");
+            goto done;
+        }
+        result = mount_point;
+    } else {
+        if (mount(location, mount_point, type,
+                  MS_NOATIME | MS_NODEV | MS_NODIRATIME, "") < 0) {
+            result = strdup("");
+        } else {
+            result = mount_point;
+        }
+    }
+
+done:
+    free(type);
+    free(location);
+    if (result != mount_point) free(mount_point);
+    return result;
+}
+
+
+// is_mounted(mount_point)
+char* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
+    }
+    char* mount_point;
+    if (ReadArgs(state, argv, 1, &mount_point) < 0) {
+        return NULL;
+    }
+    if (strlen(mount_point) == 0) {
+        ErrorAbort(state, "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, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
+    }
+    char* mount_point;
+    if (ReadArgs(state, argv, 1, &mount_point) < 0) {
+        return NULL;
+    }
+    if (strlen(mount_point) == 0) {
+        ErrorAbort(state, "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) {
+        fprintf(stderr, "unmount of %s failed; no such volume\n", mount_point);
+        result = strdup("");
+    } else {
+        unmount_mounted_volume(vol);
+        result = mount_point;
+    }
+
+done:
+    if (result != mount_point) free(mount_point);
+    return result;
+}
+
+
+// format(type, location)
+//
+//    type="MTD"  location=partition
+char* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+    if (argc != 2) {
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    }
+    char* type;
+    char* location;
+    if (ReadArgs(state, argv, 2, &type, &location) < 0) {
+        return NULL;
+    }
+
+    if (strlen(type) == 0) {
+        ErrorAbort(state, "type argument to %s() can't be empty", name);
+        goto done;
+    }
+    if (strlen(location) == 0) {
+        ErrorAbort(state, "location argument to %s() can't be empty", name);
+        goto done;
+    }
+
+    if (strcmp(type, "MTD") == 0) {
+        mtd_scan_partitions();
+        const MtdPartition* mtd = mtd_find_partition_by_name(location);
+        if (mtd == NULL) {
+            fprintf(stderr, "%s: no mtd partition named \"%s\"",
+                    name, location);
+            result = strdup("");
+            goto done;
+        }
+        MtdWriteContext* ctx = mtd_write_partition(mtd);
+        if (ctx == NULL) {
+            fprintf(stderr, "%s: can't write \"%s\"", name, location);
+            result = strdup("");
+            goto done;
+        }
+        if (mtd_erase_blocks(ctx, -1) == -1) {
+            mtd_write_close(ctx);
+            fprintf(stderr, "%s: failed to erase \"%s\"", name, location);
+            result = strdup("");
+            goto done;
+        }
+        if (mtd_write_close(ctx) != 0) {
+            fprintf(stderr, "%s: failed to close \"%s\"", name, location);
+            result = strdup("");
+            goto done;
+        }
+        result = location;
+    } else {
+        fprintf(stderr, "%s: unsupported type \"%s\"", name, type);
+    }
+
+done:
+    free(type);
+    if (result != location) free(location);
+    return result;
+}
+
+
+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(state, argv[i]);
+        if (paths[i] == NULL) {
+            int j;
+            for (j = 0; j < i; ++i) {
+                free(paths[j]);
+            }
+            free(paths);
+            return NULL;
+        }
+    }
+
+    bool recursive = (strcmp(name, "delete_recursive") == 0);
+
+    int success = 0;
+    for (i = 0; i < argc; ++i) {
+        if ((recursive ? dirUnlinkHierarchy(paths[i]) : unlink(paths[i])) == 0)
+            ++success;
+        free(paths[i]);
+    }
+    free(paths);
+
+    char buffer[10];
+    sprintf(buffer, "%d", success);
+    return strdup(buffer);
+}
+
+
+char* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 2) {
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    }
+    char* frac_str;
+    char* sec_str;
+    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*)(state->cookie);
+    fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
+
+    free(sec_str);
+    return frac_str;
+}
+
+char* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
+    }
+    char* frac_str;
+    if (ReadArgs(state, argv, 1, &frac_str) < 0) {
+        return NULL;
+    }
+
+    double frac = strtod(frac_str, NULL);
+
+    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
+    fprintf(ui->cmd_pipe, "set_progress %f\n", frac);
+
+    return frac_str;
+}
+
+// package_extract_dir(package_path, destination_path)
+char* PackageExtractDirFn(const char* name, State* state,
+                          int argc, Expr* argv[]) {
+    if (argc != 2) {
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    }
+    char* zip_path;
+    char* dest_path;
+    if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
+
+    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
+
+    bool success = mzExtractRecursive(za, zip_path, dest_path,
+                                      MZ_EXTRACT_FILES_ONLY, &timestamp,
+                                      NULL, NULL);
+    free(zip_path);
+    free(dest_path);
+    return strdup(success ? "t" : "");
+}
+
+
+// package_extract_file(package_path, destination_path)
+char* PackageExtractFileFn(const char* name, State* state,
+                           int argc, Expr* argv[]) {
+    if (argc != 2) {
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    }
+    char* zip_path;
+    char* dest_path;
+    if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
+
+    bool success = false;
+
+    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);
+        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, State* state, int argc, Expr* argv[]) {
+    if (argc == 0) {
+        return ErrorAbort(state, "%s() expects 1+ args, got %d", name, argc);
+    }
+    char* target;
+    target = Evaluate(state, argv[0]);
+    if (target == NULL) return NULL;
+
+    char** srcs = ReadVarArgs(state, argc-1, argv+1);
+    if (srcs == NULL) {
+        free(target);
+        return NULL;
+    }
+
+    int i;
+    for (i = 0; i < argc-1; ++i) {
+        symlink(target, srcs[i]);
+        free(srcs[i]);
+    }
+    free(srcs);
+    return strdup("");
+}
+
+
+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(state, "%s() expects %d+ args, got %d", name, argc);
+    }
+
+    char** args = ReadVarArgs(state, argc, argv);
+    if (args == NULL) return NULL;
+
+    char* end;
+    int i;
+
+    int uid = strtoul(args[0], &end, 0);
+    if (*end != '\0' || args[0][0] == 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(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(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(state, "%s: \"%s\" not a valid filemode",
+                       name, args[3]);
+            goto done;
+        }
+
+        for (i = 4; i < argc; ++i) {
+            dirSetHierarchyPermissions(args[i], uid, gid, dir_mode, file_mode);
+        }
+    } else {
+        int mode = strtoul(args[2], &end, 0);
+        if (*end != '\0' || args[2][0] == 0) {
+            ErrorAbort(state, "%s: \"%s\" not a valid mode", name, args[2]);
+            goto done;
+        }
+
+        for (i = 3; i < argc; ++i) {
+            chown(args[i], uid, gid);
+            chmod(args[i], mode);
+        }
+    }
+    result = strdup("");
+
+done:
+    for (i = 0; i < argc; ++i) {
+        free(args[i]);
+    }
+    free(args);
+
+    return result;
+}
+
+
+char* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
+    }
+    char* key;
+    key = Evaluate(state, argv[0]);
+    if (key == NULL) return NULL;
+
+    char value[PROPERTY_VALUE_MAX];
+    property_get(key, value, "");
+    free(key);
+
+    return strdup(value);
+}
+
+
+// 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);
+    if (r == data_len) return true;
+    fprintf(stderr, "%s\n", strerror(errno));
+    return false;
+}
+
+// write_raw_image(file, partition)
+char* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* result = NULL;
+
+    char* partition;
+    char* filename;
+    if (ReadArgs(state, argv, 2, &filename, &partition) < 0) {
+        return NULL;
+    }
+
+    if (strlen(partition) == 0) {
+        ErrorAbort(state, "partition argument to %s can't be empty", name);
+        goto done;
+    }
+    if (strlen(filename) == 0) {
+        ErrorAbort(state, "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);
+
+    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);
+
+    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, State* state,
+                           int argc, Expr* argv[]) {
+    char* result = NULL;
+
+    char* partition;
+    char* filename;
+    if (ReadArgs(state, argv, 2, &filename, &partition) < 0) {
+        return NULL;
+    }
+
+    if (strlen(partition) == 0) {
+        ErrorAbort(state, "partition argument to %s can't be empty", name);
+        goto done;
+    }
+    if (strlen(filename) == 0) {
+        ErrorAbort(state, "file argument to %s can't be empty", name);
+        goto done;
+    }
+
+    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);
+    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, State* state, 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(state, 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(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);
+    RegisterFunction("is_mounted", IsMountedFn);
+    RegisterFunction("unmount", UnmountFn);
+    RegisterFunction("format", FormatFn);
+    RegisterFunction("show_progress", ShowProgressFn);
+    RegisterFunction("set_progress", SetProgressFn);
+    RegisterFunction("delete", DeleteFn);
+    RegisterFunction("delete_recursive", DeleteFn);
+    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("file_getprop", FileGetPropFn);
+    RegisterFunction("write_raw_image", WriteRawImageFn);
+    RegisterFunction("write_firmware_image", WriteFirmwareImageFn);
+
+    RegisterFunction("apply_patch", ApplyPatchFn);
+    RegisterFunction("apply_patch_check", ApplyPatchFn);
+    RegisterFunction("apply_patch_space", ApplyPatchFn);
+
+    RegisterFunction("ui_print", UIPrintFn);
+}
diff --git a/updater/install.h b/updater/install.h
new file mode 100644
index 0000000..94f344f
--- /dev/null
+++ b/updater/install.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UPDATER_INSTALL_H_
+#define _UPDATER_INSTALL_H_
+
+void RegisterInstallFunctions();
+
+#endif
diff --git a/updater/updater.c b/updater/updater.c
new file mode 100644
index 0000000..31d93ae
--- /dev/null
+++ b/updater/updater.c
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "edify/expr.h"
+#include "updater.h"
+#include "install.h"
+#include "minzip/Zip.h"
+
+// Where in the package we expect to find the edify script to execute.
+// (Note it's "updateR-script", not the older "update-script".)
+#define SCRIPT_NAME "META-INF/com/google/android/updater-script"
+
+int main(int argc, char** argv) {
+    if (argc != 4) {
+        fprintf(stderr, "unexpected number of arguments (%d)\n", argc);
+        return 1;
+    }
+
+    char* version = argv[1];
+    if ((version[0] != '1' && version[0] != '2') || version[1] != '\0') {
+        // We support version "1" or "2".
+        fprintf(stderr, "wrong updater binary API; expected 1 or 2, got %s\n",
+                argv[1]);
+        return 2;
+    }
+
+    // Set up the pipe for sending commands back to the parent process.
+
+    int fd = atoi(argv[2]);
+    FILE* cmd_pipe = fdopen(fd, "wb");
+    setlinebuf(cmd_pipe);
+
+    // Extract the script from the package.
+
+    char* package_data = argv[3];
+    ZipArchive za;
+    int err;
+    err = mzOpenZipArchive(package_data, &za);
+    if (err != 0) {
+        fprintf(stderr, "failed to open package %s: %s\n",
+                package_data, strerror(err));
+        return 3;
+    }
+
+    const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
+    if (script_entry == NULL) {
+        fprintf(stderr, "failed to find %s in %s\n", SCRIPT_NAME, package_data);
+        return 4;
+    }
+
+    char* script = malloc(script_entry->uncompLen+1);
+    if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
+        fprintf(stderr, "failed to read script from package\n");
+        return 5;
+    }
+    script[script_entry->uncompLen] = '\0';
+
+    // Configure edify's functions.
+
+    RegisterBuiltins();
+    RegisterInstallFunctions();
+    FinishRegistration();
+
+    // Parse the script.
+
+    Expr* root;
+    int error_count = 0;
+    yy_scan_string(script);
+    int error = yyparse(&root, &error_count);
+    if (error != 0 || error_count > 0) {
+        fprintf(stderr, "%d parse errors\n", error_count);
+        return 6;
+    }
+
+    // Evaluate the parsed script.
+
+    UpdaterInfo updater_info;
+    updater_info.cmd_pipe = cmd_pipe;
+    updater_info.package_zip = &za;
+
+    State state;
+    state.cookie = &updater_info;
+    state.script = script;
+    state.errmsg = NULL;
+
+    char* result = Evaluate(&state, root);
+    if (result == NULL) {
+        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);
+        free(result);
+    }
+
+    mzCloseZipArchive(&za);
+    free(script);
+
+    return 0;
+}
diff --git a/updater/updater.h b/updater/updater.h
new file mode 100644
index 0000000..22fbfd2
--- /dev/null
+++ b/updater/updater.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UPDATER_UPDATER_H_
+#define _UPDATER_UPDATER_H_
+
+#include <stdio.h>
+#include "minzip/Zip.h"
+
+typedef struct {
+    FILE* cmd_pipe;
+    ZipArchive* package_zip;
+} UpdaterInfo;
+
+#endif