Merge in lollipop and attempt to fix merge conflicts

This will probably not compile and may need additional work.
For tracking purposes so we know what might still need looking at
as none of this has been compiled and tested, here is a list of
the merge conflicts that I attempted to fix before pushing this
set of changes:

git pull aosp lollipop-release
remote: Finding sources: 100% (992/992)
remote: Total 992 (delta 473), reused 992 (delta 473)
Receiving objects: 100% (992/992), 1.51 MiB | 516.00 KiB/s, done.
Resolving deltas: 100% (473/473), completed with 42 local objects.
From https://android.googlesource.com/platform/bootable/recovery
 * branch            lollipop-release -> FETCH_HEAD
 * [new branch]      lollipop-release -> aosp/lollipop-release
Auto-merging verifier_test.cpp
CONFLICT (content): Merge conflict in verifier_test.cpp
Auto-merging verifier.h
CONFLICT (content): Merge conflict in verifier.h
Auto-merging verifier.cpp
CONFLICT (content): Merge conflict in verifier.cpp
Auto-merging updater/updater.c
Auto-merging updater/install.c
CONFLICT (content): Merge conflict in updater/install.c
Auto-merging updater/Android.mk
CONFLICT (content): Merge conflict in updater/Android.mk
Auto-merging uncrypt/Android.mk
CONFLICT (content): Merge conflict in uncrypt/Android.mk
Auto-merging ui.cpp
CONFLICT (content): Merge conflict in ui.cpp
Auto-merging screen_ui.cpp
Auto-merging roots.cpp
CONFLICT (content): Merge conflict in roots.cpp
CONFLICT (rename/delete): res-hdpi/images/progress_fill.png deleted
in HEAD and renamed in cddb68b5eafbeba696d5276bda1f1a9f70bbde42.
Version cddb68b5eafbeba696d5276bda1f1a9f70bbde42 of
res-hdpi/images/progress_fill.png left in tree.
CONFLICT (rename/delete): res-hdpi/images/progress_empty.png deleted
in HEAD and renamed in cddb68b5eafbeba696d5276bda1f1a9f70bbde42.
Version cddb68b5eafbeba696d5276bda1f1a9f70bbde42 of
res-hdpi/images/progress_empty.png left in tree.
CONFLICT (rename/delete): res-hdpi/images/icon_error.png deleted
in HEAD and renamed in cddb68b5eafbeba696d5276bda1f1a9f70bbde42.
Version cddb68b5eafbeba696d5276bda1f1a9f70bbde42 of
res-hdpi/images/icon_error.png left in tree.
Auto-merging recovery.cpp
CONFLICT (content): Merge conflict in recovery.cpp
Auto-merging minui/resources.c
CONFLICT (content): Merge conflict in minui/resources.c
Auto-merging minui/minui.h
CONFLICT (content): Merge conflict in minui/minui.h
Auto-merging minui/graphics.c
CONFLICT (content): Merge conflict in minui/graphics.c
Auto-merging minui/Android.mk
CONFLICT (content): Merge conflict in minui/Android.mk
Removing minelf/Retouch.h
Removing minelf/Retouch.c
Auto-merging minadbd/usb_linux_client.c
CONFLICT (content): Merge conflict in minadbd/usb_linux_client.c
Auto-merging minadbd/adb.h
CONFLICT (content): Merge conflict in minadbd/adb.h
Auto-merging minadbd/adb.c
CONFLICT (content): Merge conflict in minadbd/adb.c
Auto-merging minadbd/Android.mk
CONFLICT (content): Merge conflict in minadbd/Android.mk
Removing make-overlay.py
Auto-merging install.h
CONFLICT (content): Merge conflict in install.h
Auto-merging etc/init.rc
CONFLICT (content): Merge conflict in etc/init.rc
Auto-merging bootloader.h
Auto-merging applypatch/applypatch.c
Auto-merging applypatch/Android.mk
CONFLICT (content): Merge conflict in applypatch/Android.mk
Auto-merging adb_install.cpp
CONFLICT (content): Merge conflict in adb_install.cpp
Auto-merging Android.mk
CONFLICT (content): Merge conflict in Android.mk
Automatic merge failed; fix conflicts and then commit the result.

Change-Id: I3e0e03e48ad8550912111c7a5c9a140ed0267e2c
diff --git a/Android.mk b/Android.mk
index dfdff7d..835bb71 100644
--- a/Android.mk
+++ b/Android.mk
@@ -52,13 +52,32 @@
   LOCAL_SRC_FILES += $(TARGET_RECOVERY_REBOOT_SRC)
 endif
 
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    recovery.cpp \
+    bootloader.cpp \
+    install.cpp \
+    roots.cpp \
+    ui.cpp \
+    screen_ui.cpp \
+    asn1_decoder.cpp \
+    verifier.cpp \
+    adb_install.cpp \
+    fuse_sdcard_provider.c
+
 LOCAL_MODULE := recovery
 
 #LOCAL_FORCE_STATIC_EXECUTABLE := true
 
+#ifeq ($(HOST_OS),linux)
+#LOCAL_REQUIRED_MODULES := mkfs.f2fs
+#endif
+
 RECOVERY_API_VERSION := 3
 RECOVERY_FSTAB_VERSION := 2
 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
+LOCAL_CFLAGS += -Wno-unused-parameter
 
 #LOCAL_STATIC_LIBRARIES := \
 #    libext4_utils_static \
@@ -411,18 +430,44 @@
 RECOVERY_BUSYBOX_SYMLINKS :=
 endif # !TW_USE_TOOLBOX
 
+# All the APIs for testing
+include $(CLEAR_VARS)
+LOCAL_MODULE := libverifier
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := \
+    asn1_decoder.cpp
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := fuse_sideload.c
+
+LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter
+LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
+
+LOCAL_MODULE := libfusesideload
+
+LOCAL_SHARED_LIBRARIES := libcutils libc libmincrypttwrp
+include $(BUILD_SHARED_LIBRARY)
+
 include $(CLEAR_VARS)
 LOCAL_MODULE := verifier_test
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 LOCAL_MODULE_TAGS := tests
+
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes
+
+LOCAL_CFLAGS += -DNO_RECOVERY_MOUNT
+LOCAL_CFLAGS += -Wno-unused-parameter
+
 LOCAL_SRC_FILES := \
     verifier_test.cpp \
+    asn1_decoder.cpp \
     verifier.cpp \
     ui.cpp
 LOCAL_STATIC_LIBRARIES := \
     libmincrypttwrp \
     libminui \
+    libminzip \
     libcutils \
     libstdc++ \
     libc
@@ -434,7 +479,7 @@
 LOCAL_MODULE_TAGS := eng optional
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/libmincrypt/includes
 LOCAL_SRC_FILES = adb_install.cpp bootloader.cpp verifier.cpp mtdutils/mtdutils.c legacy_property_service.c
-LOCAL_SHARED_LIBRARIES += libc liblog libcutils libmtdutils
+LOCAL_SHARED_LIBRARIES += libc liblog libcutils libmtdutils libfusesideload
 LOCAL_STATIC_LIBRARIES += libmincrypttwrp
 
 ifneq ($(BOARD_RECOVERY_BLDRMSG_OFFSET),)
@@ -445,10 +490,14 @@
 
 commands_recovery_local_path := $(LOCAL_PATH)
 include $(LOCAL_PATH)/minui/Android.mk \
-    $(LOCAL_PATH)/minelf/Android.mk \
     $(LOCAL_PATH)/minadbd/Android.mk \
+    $(LOCAL_PATH)/minzip/Android.mk \
+    $(LOCAL_PATH)/minadbd/Android.mk \
+    $(LOCAL_PATH)/mtdutils/Android.mk \
+    $(LOCAL_PATH)/tests/Android.mk \
     $(LOCAL_PATH)/tools/Android.mk \
     $(LOCAL_PATH)/edify/Android.mk \
+    $(LOCAL_PATH)/uncrypt/Android.mk \
     $(LOCAL_PATH)/updater/Android.mk \
     $(LOCAL_PATH)/applypatch/Android.mk
 
diff --git a/CleanSpec.mk b/CleanSpec.mk
index ecf89ae..e2d97d4 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -48,3 +48,4 @@
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes)
diff --git a/adb_install.cpp b/adb_install.cpp
index c18126d..e10cb4a 100644
--- a/adb_install.cpp
+++ b/adb_install.cpp
@@ -30,7 +30,8 @@
 #include "cutils/properties.h"
 #include "adb_install.h"
 extern "C" {
-#include "minadbd/adb.h"
+#include "minadbd/fuse_adb_provider.h"
+#include "fuse_sideload.h"
 }
 
 static RecoveryUI* ui = NULL;
@@ -78,6 +79,10 @@
     }
 }
 
+// How long (in seconds) we wait for the host to start sending us a
+// package, before timing out.
+#define ADB_INSTALL_TIMEOUT 300
+
 int
 apply_from_adb(const char* install_file) {
 
@@ -92,22 +97,62 @@
         execl("/sbin/recovery", "recovery", "--adbd", install_file, NULL);
         _exit(-1);
     }
+
 	char child_prop[PROPERTY_VALUE_MAX];
 	sprintf(child_prop, "%i", child);
 	property_set("tw_child_pid", child_prop);
+
+    // FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host
+    // connects and starts serving a package.  Poll for its
+    // appearance.  (Note that inotify doesn't work with FUSE.)
+    int result;
     int status;
-    // TODO(dougz): there should be a way to cancel waiting for a
-    // package (by pushing some button combo on the device).  For now
-    // you just have to 'adb sideload' a file that's not a valid
-    // package, like "/dev/null".
-    waitpid(child, &status, 0);
+    bool waited = false;
+    struct stat st;
+    for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {
+        if (waitpid(child, &status, WNOHANG) != 0) {
+            result = INSTALL_ERROR;
+            waited = true;
+            break;
+        }
+
+        if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {
+            if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) {
+                sleep(1);
+                continue;
+            } else {
+                ui->Print("\nTimed out waiting for package.\n\n", strerror(errno));
+                result = INSTALL_ERROR;
+                kill(child, SIGKILL);
+                break;
+            }
+        }
+        result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false);
+        break;
+    }
+
+    if (!waited) {
+        // Calling stat() on this magic filename signals the minadbd
+        // subprocess to shut down.
+        stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
+
+        // TODO(dougz): there should be a way to cancel waiting for a
+        // package (by pushing some button combo on the device).  For now
+        // you just have to 'adb sideload' a file that's not a valid
+        // package, like "/dev/null".
+        waitpid(child, &status, 0);
+    }
+
     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-        printf("status %d\n", WEXITSTATUS(status));
+        if (WEXITSTATUS(status) == 3) {
+            printf("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n");
+        } else if (!WIFSIGNALED(status)) {
+            printf("status %d\n", WEXITSTATUS(status));
+        }
     }
     set_usb_driver(false);
     maybe_restart_adbd();
 
-    struct stat st;
     if (stat(install_file, &st) != 0) {
         if (errno == ENOENT) {
             printf("No package received.\n");
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
index 04ddfd8..2cce81f 100644
--- a/applypatch/Android.mk
+++ b/applypatch/Android.mk
@@ -40,7 +40,7 @@
 LOCAL_MODULE := applypatch
 LOCAL_MODULE_TAGS := optional
 LOCAL_C_INCLUDES += $(commands_recovery_local_path)
-LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypttwrp libbz libminelf
+LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypttwrp libbz
 LOCAL_SHARED_LIBRARIES += libz libcutils libstdc++ libc
 
 include $(BUILD_EXECUTABLE)
@@ -50,9 +50,9 @@
 LOCAL_SRC_FILES := main.c
 LOCAL_MODULE := applypatch_static
 LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_TAGS := optional eng
 LOCAL_C_INCLUDES += $(commands_recovery_local_path)
-LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypttwrp libbz libminelf
+LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypttwrp libbz
 LOCAL_STATIC_LIBRARIES += libz libcutils libstdc++ libc
 
 include $(BUILD_EXECUTABLE)
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
index b5ff12c..73195d9 100644
--- a/applypatch/applypatch.c
+++ b/applypatch/applypatch.c
@@ -24,6 +24,7 @@
 #include <sys/types.h>
 #include <fcntl.h>
 #include <unistd.h>
+#include <stdbool.h>
 
 #include "mincrypt/sha.h"
 #include "applypatch.h"
@@ -32,7 +33,7 @@
 #include "edify/expr.h"
 
 static int LoadPartitionContents(const char* filename, FileContents* file);
-static ssize_t FileSink(unsigned char* data, ssize_t len, void* token);
+static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token);
 static int GenerateTarget(FileContents* source_file,
                           const Value* source_patch_value,
                           FileContents* copy_file,
@@ -45,14 +46,11 @@
 
 static int mtd_partitions_scanned = 0;
 
-// Read a file into memory; optionally (retouch_flag == RETOUCH_DO_MASK) mask
-// the retouched entries back to their original value (such that SHA-1 checks
-// don't fail due to randomization); store the file contents and associated
+// Read a file into memory; store the file contents and associated
 // metadata in *file.
 //
 // Return 0 on success.
-int LoadFileContents(const char* filename, FileContents* file,
-                     int retouch_flag) {
+int LoadFileContents(const char* filename, FileContents* file) {
     file->data = NULL;
 
     // A special 'filename' beginning with "MTD:" or "EMMC:" means to
@@ -89,20 +87,6 @@
     }
     fclose(f);
 
-    // apply_patch[_check] functions are blind to randomization. Randomization
-    // is taken care of in [Undo]RetouchBinariesFn. If there is a mismatch
-    // within a file, this means the file is assumed "corrupt" for simplicity.
-    if (retouch_flag) {
-        int32_t desired_offset = 0;
-        if (retouch_mask_data(file->data, file->size,
-                              &desired_offset, NULL) != RETOUCH_DATA_MATCHED) {
-            printf("error trying to mask retouch entries\n");
-            free(file->data);
-            file->data = NULL;
-            return -1;
-        }
-    }
-
     SHA_hash(file->data, file->size, file->sha1);
     return 0;
 }
@@ -259,7 +243,7 @@
                     break;
             }
             if (next != read) {
-                printf("short read (%d bytes of %d) for partition \"%s\"\n",
+                printf("short read (%zu bytes of %zu) for partition \"%s\"\n",
                        read, next, partition);
                 free(file->data);
                 file->data = NULL;
@@ -286,7 +270,7 @@
         if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_SIZE) == 0) {
             // we have a match.  stop reading the partition; we'll return
             // the data we've read so far.
-            printf("partition read matched size %d sha %s\n",
+            printf("partition read matched size %zu sha %s\n",
                    size[index[i]], sha1sum[index[i]]);
             break;
         }
@@ -337,7 +321,7 @@
 // Save the contents of the given FileContents object under the given
 // filename.  Return 0 on success.
 int SaveFileContents(const char* filename, const FileContents* file) {
-    int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+    int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR);
     if (fd < 0) {
         printf("failed to open \"%s\" for write: %s\n",
                filename, strerror(errno));
@@ -352,8 +336,14 @@
         close(fd);
         return -1;
     }
-    fsync(fd);
-    close(fd);
+    if (fsync(fd) != 0) {
+        printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno));
+        return -1;
+    }
+    if (close(fd) != 0) {
+        printf("close of \"%s\" failed: %s\n", filename, strerror(errno));
+        return -1;
+    }
 
     if (chmod(filename, file->st.st_mode) != 0) {
         printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno));
@@ -433,7 +423,7 @@
 
             size_t written = mtd_write_data(ctx, (char*)data, len);
             if (written != len) {
-                printf("only wrote %d of %d bytes to MTD %s\n",
+                printf("only wrote %zu of %zu bytes to MTD %s\n",
                        written, len, partition);
                 mtd_write_close(ctx);
                 return -1;
@@ -462,13 +452,11 @@
             }
             int attempt;
 
-            for (attempt = 0; attempt < 10; ++attempt) {
-                size_t next_sync = start + (1<<20);
-                printf("raw O_SYNC write %s attempt %d start at %d\n", partition, attempt+1, start);
+            for (attempt = 0; attempt < 2; ++attempt) {
                 lseek(fd, start, SEEK_SET);
                 while (start < len) {
                     size_t to_write = len - start;
-                    if (to_write > 4096) to_write = 4096;
+                    if (to_write > 1<<20) to_write = 1<<20;
 
                     ssize_t written = write(fd, data+start, to_write);
                     if (written < 0) {
@@ -481,12 +469,23 @@
                         }
                     }
                     start += written;
-                    if (start >= next_sync) {
-                        fsync(fd);
-                        next_sync = start + (1<<20);
-                    }
                 }
-                fsync(fd);
+                if (fsync(fd) != 0) {
+                   printf("failed to sync to %s (%s)\n",
+                          partition, strerror(errno));
+                   return -1;
+                }
+                if (close(fd) != 0) {
+                   printf("failed to close %s (%s)\n",
+                          partition, strerror(errno));
+                   return -1;
+                }
+                fd = open(partition, O_RDONLY);
+                if (fd < 0) {
+                   printf("failed to reopen %s for verify (%s)\n",
+                          partition, strerror(errno));
+                   return -1;
+                }
 
                 // drop caches so our subsequent verification read
                 // won't just be reading the cache.
@@ -513,20 +512,20 @@
                             if (errno == EINTR) {
                                 read_count = 0;
                             } else {
-                                printf("verify read error %s at %d: %s\n",
+                                printf("verify read error %s at %zu: %s\n",
                                        partition, p, strerror(errno));
                                 return -1;
                             }
                         }
                         if ((size_t)read_count < to_read) {
-                            printf("short verify read %s at %d: %d %d %s\n",
+                            printf("short verify read %s at %zu: %zd %zu %s\n",
                                    partition, p, read_count, to_read, strerror(errno));
                         }
                         so_far += read_count;
                     }
 
                     if (memcmp(buffer, data+p, to_read)) {
-                        printf("verification failed starting at %d\n", p);
+                        printf("verification failed starting at %zu\n", p);
                         start = p;
                         break;
                     }
@@ -537,8 +536,6 @@
                     success = true;
                     break;
                 }
-
-                sleep(2);
             }
 
             if (!success) {
@@ -550,11 +547,7 @@
                 printf("error closing %s (%s)\n", partition, strerror(errno));
                 return -1;
             }
-            // hack: sync and sleep after closing in hopes of getting
-            // the data actually onto flash.
-            printf("sleeping after close\n");
             sync();
-            sleep(5);
             break;
         }
     }
@@ -622,7 +615,7 @@
     // LoadFileContents is successful.  (Useful for reading
     // partitions, where the filename encodes the sha1s; no need to
     // check them twice.)
-    if (LoadFileContents(filename, &file, RETOUCH_DO_MASK) != 0 ||
+    if (LoadFileContents(filename, &file) != 0 ||
         (num_patches > 0 &&
          FindMatchingPatch(file.sha1, patch_sha1_str, num_patches) < 0)) {
         printf("file \"%s\" doesn't have any of expected "
@@ -637,7 +630,7 @@
         // exists and matches the sha1 we're looking for, the check still
         // passes.
 
-        if (LoadFileContents(CACHE_TEMP_SOURCE, &file, RETOUCH_DO_MASK) != 0) {
+        if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) {
             printf("failed to load cache file\n");
             return 1;
         }
@@ -658,7 +651,7 @@
     return 0;
 }
 
-ssize_t FileSink(unsigned char* data, ssize_t len, void* token) {
+ssize_t FileSink(const unsigned char* data, ssize_t len, void* token) {
     int fd = *(int *)token;
     ssize_t done = 0;
     ssize_t wrote;
@@ -679,7 +672,7 @@
     ssize_t pos;
 } MemorySinkInfo;
 
-ssize_t MemorySink(unsigned char* data, ssize_t len, void* token) {
+ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) {
     MemorySinkInfo* msi = (MemorySinkInfo*)token;
     if (msi->size - msi->pos < len) {
         return -1;
@@ -773,8 +766,7 @@
     const Value* copy_patch_value = NULL;
 
     // We try to load the target file into the source_file object.
-    if (LoadFileContents(target_filename, &source_file,
-                         RETOUCH_DO_MASK) == 0) {
+    if (LoadFileContents(target_filename, &source_file) == 0) {
         if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) {
             // The early-exit case:  the patch was already applied, this file
             // has the desired hash, nothing for us to do.
@@ -793,8 +785,7 @@
         // target file, or we did but it's different from the source file.
         free(source_file.data);
         source_file.data = NULL;
-        LoadFileContents(source_filename, &source_file,
-                         RETOUCH_DO_MASK);
+        LoadFileContents(source_filename, &source_file);
     }
 
     if (source_file.data != NULL) {
@@ -810,8 +801,7 @@
         source_file.data = NULL;
         printf("source file is bad; trying copy\n");
 
-        if (LoadFileContents(CACHE_TEMP_SOURCE, &copy_file,
-                             RETOUCH_DO_MASK) < 0) {
+        if (LoadFileContents(CACHE_TEMP_SOURCE, &copy_file) < 0) {
             // fail.
             printf("failed to read copy file\n");
             return 1;
@@ -984,7 +974,8 @@
             strcpy(outname, target_filename);
             strcat(outname, ".patch");
 
-            output = open(outname, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+            output = open(outname, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC,
+                S_IRUSR | S_IWUSR);
             if (output < 0) {
                 printf("failed to open output file %s: %s\n",
                        outname, strerror(errno));
@@ -1015,8 +1006,14 @@
         }
 
         if (output >= 0) {
-            fsync(output);
-            close(output);
+            if (fsync(output) != 0) {
+                printf("failed to fsync file \"%s\" (%s)\n", outname, strerror(errno));
+                result = 1;
+            }
+            if (close(output) != 0) {
+                printf("failed to close file \"%s\" (%s)\n", outname, strerror(errno));
+                result = 1;
+            }
         }
 
         if (result != 0) {
diff --git a/applypatch/applypatch.h b/applypatch/applypatch.h
index f1f13a1..edec848 100644
--- a/applypatch/applypatch.h
+++ b/applypatch/applypatch.h
@@ -19,7 +19,6 @@
 
 #include <sys/stat.h>
 #include "mincrypt/sha.h"
-#include "minelf/Retouch.h"
 #include "edify/expr.h"
 
 typedef struct _Patch {
@@ -41,7 +40,7 @@
 // and use it as the source instead.
 #define CACHE_TEMP_SOURCE "/cache/saved.file"
 
-typedef ssize_t (*SinkFn)(unsigned char*, ssize_t, void*);
+typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*);
 
 // applypatch.c
 int ShowLicenses();
@@ -61,8 +60,7 @@
                      int num_patches,
                      char** const patch_sha1_str);
 
-int LoadFileContents(const char* filename, FileContents* file,
-                     int retouch_flag);
+int LoadFileContents(const char* filename, FileContents* file);
 int SaveFileContents(const char* filename, const FileContents* file);
 void FreeFileContents(FileContents* file);
 int FindMatchingPatch(uint8_t* sha1, char* const * const patch_sha1_str,
diff --git a/applypatch/bspatch.c b/applypatch/bspatch.c
index 2e80f81..b34ec2a 100644
--- a/applypatch/bspatch.c
+++ b/applypatch/bspatch.c
@@ -112,9 +112,7 @@
         printf("short write of output: %d (%s)\n", errno, strerror(errno));
         return 1;
     }
-    if (ctx) {
-        SHA_update(ctx, new_data, new_size);
-    }
+    if (ctx) SHA_update(ctx, new_data, new_size);
     free(new_data);
 
     return 0;
@@ -205,6 +203,11 @@
         ctrl[1] = offtin(buf+8);
         ctrl[2] = offtin(buf+16);
 
+        if (ctrl[0] < 0 || ctrl[1] < 0) {
+            printf("corrupt patch (negative byte counts)\n");
+            return 1;
+        }
+
         // Sanity check
         if (newpos + ctrl[0] > *new_size) {
             printf("corrupt patch (new file overrun)\n");
diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c
index 3a1df38..33c4487 100644
--- a/applypatch/imgpatch.c
+++ b/applypatch/imgpatch.c
@@ -18,6 +18,7 @@
 // format.
 
 #include <stdio.h>
+#include <sys/cdefs.h>
 #include <sys/stat.h>
 #include <errno.h>
 #include <unistd.h>
@@ -35,7 +36,7 @@
  * file, and update the SHA context with the output data as well.
  * Return 0 on success.
  */
-int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
+int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size __unused,
                     const Value* patch,
                     SinkFn sink, void* token, SHA_CTX* ctx,
                     const Value* bonus_data) {
@@ -94,7 +95,7 @@
                 printf("failed to read chunk %d raw data\n", i);
                 return -1;
             }
-            SHA_update(ctx, patch->data + pos, data_len);
+            if (ctx) SHA_update(ctx, patch->data + pos, data_len);
             if (sink((unsigned char*)patch->data + pos,
                      data_len, token) != data_len) {
                 printf("failed to write chunk %d raw data\n", i);
@@ -132,7 +133,7 @@
 
             unsigned char* expanded_source = malloc(expanded_len);
             if (expanded_source == NULL) {
-                printf("failed to allocate %d bytes for expanded_source\n",
+                printf("failed to allocate %zu bytes for expanded_source\n",
                        expanded_len);
                 return -1;
             }
@@ -163,7 +164,7 @@
             // We should have filled the output buffer exactly, except
             // for the bonus_size.
             if (strm.avail_out != bonus_size) {
-                printf("source inflation short by %d bytes\n", strm.avail_out-bonus_size);
+                printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size);
                 return -1;
             }
             inflateEnd(&strm);
@@ -216,7 +217,7 @@
                            (long)have);
                     return -1;
                 }
-                SHA_update(ctx, temp_data, have);
+                if (ctx) SHA_update(ctx, temp_data, have);
             } while (ret != Z_STREAM_END);
             deflateEnd(&strm);
 
diff --git a/applypatch/main.c b/applypatch/main.c
index f61db5d..8e9fe80 100644
--- a/applypatch/main.c
+++ b/applypatch/main.c
@@ -74,7 +74,7 @@
             (*patches)[i] = NULL;
         } else {
             FileContents fc;
-            if (LoadFileContents(colon, &fc, RETOUCH_DONT_MASK) != 0) {
+            if (LoadFileContents(colon, &fc) != 0) {
                 goto abort;
             }
             (*patches)[i] = malloc(sizeof(Value));
@@ -103,7 +103,7 @@
     Value* bonus = NULL;
     if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
         FileContents fc;
-        if (LoadFileContents(argv[2], &fc, RETOUCH_DONT_MASK) != 0) {
+        if (LoadFileContents(argv[2], &fc) != 0) {
             printf("failed to load bonus file %s\n", argv[2]);
             return 1;
         }
diff --git a/asn1_decoder.cpp b/asn1_decoder.cpp
new file mode 100644
index 0000000..7280f74
--- /dev/null
+++ b/asn1_decoder.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 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 <stdint.h>
+#include <string.h>
+
+#include "asn1_decoder.h"
+
+
+typedef struct asn1_context {
+    size_t length;
+    uint8_t* p;
+    int app_type;
+} asn1_context_t;
+
+
+static const int kMaskConstructed = 0xE0;
+static const int kMaskTag = 0x7F;
+static const int kMaskAppType = 0x1F;
+
+static const int kTagOctetString = 0x04;
+static const int kTagOid = 0x06;
+static const int kTagSequence = 0x30;
+static const int kTagSet = 0x31;
+static const int kTagConstructed = 0xA0;
+
+asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length) {
+    asn1_context_t* ctx = (asn1_context_t*) calloc(1, sizeof(asn1_context_t));
+    if (ctx == NULL) {
+        return NULL;
+    }
+    ctx->p = buffer;
+    ctx->length = length;
+    return ctx;
+}
+
+void asn1_context_free(asn1_context_t* ctx) {
+    free(ctx);
+}
+
+static inline int peek_byte(asn1_context_t* ctx) {
+    if (ctx->length <= 0) {
+        return -1;
+    }
+    return *ctx->p;
+}
+
+static inline int get_byte(asn1_context_t* ctx) {
+    if (ctx->length <= 0) {
+        return -1;
+    }
+    int byte = *ctx->p;
+    ctx->p++;
+    ctx->length--;
+    return byte;
+}
+
+static inline bool skip_bytes(asn1_context_t* ctx, size_t num_skip) {
+    if (ctx->length < num_skip) {
+        return false;
+    }
+    ctx->p += num_skip;
+    ctx->length -= num_skip;
+    return true;
+}
+
+static bool decode_length(asn1_context_t* ctx, size_t* out_len) {
+    int num_octets = get_byte(ctx);
+    if (num_octets == -1) {
+        return false;
+    }
+    if ((num_octets & 0x80) == 0x00) {
+        *out_len = num_octets;
+        return 1;
+    }
+    num_octets &= kMaskTag;
+    if ((size_t)num_octets >= sizeof(size_t)) {
+        return false;
+    }
+    size_t length = 0;
+    for (int i = 0; i < num_octets; ++i) {
+        int byte = get_byte(ctx);
+        if (byte == -1) {
+            return false;
+        }
+        length <<= 8;
+        length += byte;
+    }
+    *out_len = length;
+    return true;
+}
+
+/**
+ * Returns the constructed type and advances the pointer. E.g. A0 -> 0
+ */
+asn1_context_t* asn1_constructed_get(asn1_context_t* ctx) {
+    int type = get_byte(ctx);
+    if (type == -1 || (type & kMaskConstructed) != kTagConstructed) {
+        return NULL;
+    }
+    size_t length;
+    if (!decode_length(ctx, &length) || length > ctx->length) {
+        return NULL;
+    }
+    asn1_context_t* app_ctx = asn1_context_new(ctx->p, length);
+    app_ctx->app_type = type & kMaskAppType;
+    return app_ctx;
+}
+
+bool asn1_constructed_skip_all(asn1_context_t* ctx) {
+    int byte = peek_byte(ctx);
+    while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) {
+        skip_bytes(ctx, 1);
+        size_t length;
+        if (!decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
+            return false;
+        }
+        byte = peek_byte(ctx);
+    }
+    return byte != -1;
+}
+
+int asn1_constructed_type(asn1_context_t* ctx) {
+    return ctx->app_type;
+}
+
+asn1_context_t* asn1_sequence_get(asn1_context_t* ctx) {
+    if ((get_byte(ctx) & kMaskTag) != kTagSequence) {
+        return NULL;
+    }
+    size_t length;
+    if (!decode_length(ctx, &length) || length > ctx->length) {
+        return NULL;
+    }
+    return asn1_context_new(ctx->p, length);
+}
+
+asn1_context_t* asn1_set_get(asn1_context_t* ctx) {
+    if ((get_byte(ctx) & kMaskTag) != kTagSet) {
+        return NULL;
+    }
+    size_t length;
+    if (!decode_length(ctx, &length) || length > ctx->length) {
+        return NULL;
+    }
+    return asn1_context_new(ctx->p, length);
+}
+
+bool asn1_sequence_next(asn1_context_t* ctx) {
+    size_t length;
+    if (get_byte(ctx) == -1 || !decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
+        return false;
+    }
+    return true;
+}
+
+bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length) {
+    if (get_byte(ctx) != kTagOid) {
+        return false;
+    }
+    if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
+        return false;
+    }
+    *oid = ctx->p;
+    return true;
+}
+
+bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length) {
+    if (get_byte(ctx) != kTagOctetString) {
+        return false;
+    }
+    if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
+        return false;
+    }
+    *octet_string = ctx->p;
+    return true;
+}
diff --git a/asn1_decoder.h b/asn1_decoder.h
new file mode 100644
index 0000000..b17141c
--- /dev/null
+++ b/asn1_decoder.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 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 ASN1_DECODER_H_
+#define ASN1_DECODER_H_
+
+#include <stdint.h>
+
+typedef struct asn1_context asn1_context_t;
+
+asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length);
+void asn1_context_free(asn1_context_t* ctx);
+asn1_context_t* asn1_constructed_get(asn1_context_t* ctx);
+bool asn1_constructed_skip_all(asn1_context_t* ctx);
+int asn1_constructed_type(asn1_context_t* ctx);
+asn1_context_t* asn1_sequence_get(asn1_context_t* ctx);
+asn1_context_t* asn1_set_get(asn1_context_t* ctx);
+bool asn1_sequence_next(asn1_context_t* seq);
+bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length);
+bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length);
+
+#endif /* ASN1_DECODER_H_ */
diff --git a/bootloader.h b/bootloader.h
index 0a682b2..15e0a5c 100644
--- a/bootloader.h
+++ b/bootloader.h
@@ -38,11 +38,24 @@
  * The recovery field is only written by linux and used
  * for the system to send a message to recovery or the
  * other way around.
+ *
+ * The stage field is written by packages which restart themselves
+ * multiple times, so that the UI can reflect which invocation of the
+ * package it is.  If the value is of the format "#/#" (eg, "1/3"),
+ * the UI will add a simple indicator of that status.
  */
 struct bootloader_message {
     char command[32];
     char status[32];
-    char recovery[1024];
+    char recovery[768];
+
+    // The 'recovery' field used to be 1024 bytes.  It has only ever
+    // been used to store the recovery command line, so 768 bytes
+    // should be plenty.  We carve off the last 256 bytes to store the
+    // stage string (for multistage packages) and possible future
+    // expansion.
+    char stage[32];
+    char reserved[224];
 };
 
 /* Read and write the bootloader command from the "misc" partition.
diff --git a/default_device.cpp b/default_device.cpp
index 648eaec..97806ac 100644
--- a/default_device.cpp
+++ b/default_device.cpp
@@ -29,22 +29,15 @@
                                "apply update from ADB",
                                "wipe data/factory reset",
                                "wipe cache partition",
+                               "reboot to bootloader",
+                               "power down",
+                               "view recovery logs",
                                NULL };
 
-class DefaultUI : public ScreenRecoveryUI {
-  public:
-    virtual KeyAction CheckKey(int key) {
-        if (key == KEY_HOME) {
-            return TOGGLE;
-        }
-        return ENQUEUE;
-    }
-};
-
 class DefaultDevice : public Device {
   public:
     DefaultDevice() :
-        ui(new DefaultUI) {
+        ui(new ScreenRecoveryUI) {
     }
 
     RecoveryUI* GetUI() { return ui; }
@@ -61,6 +54,7 @@
                 return kHighlightUp;
 
               case KEY_ENTER:
+              case KEY_POWER:
                 return kInvokeItem;
             }
         }
@@ -74,6 +68,9 @@
           case 1: return APPLY_ADB_SIDELOAD;
           case 2: return WIPE_DATA;
           case 3: return WIPE_CACHE;
+          case 4: return REBOOT_BOOTLOADER;
+          case 5: return SHUTDOWN;
+          case 6: return READ_RECOVERY_LASTLOG;
           default: return NO_ACTION;
         }
     }
diff --git a/device.h b/device.h
index 583de75..8ff4ec0 100644
--- a/device.h
+++ b/device.h
@@ -65,8 +65,10 @@
     //   - invoke a specific action (a menu position: any non-negative number)
     virtual int HandleMenuKey(int key, int visible) = 0;
 
-    enum BuiltinAction { NO_ACTION, REBOOT, APPLY_EXT, APPLY_CACHE,
-                         APPLY_ADB_SIDELOAD, WIPE_DATA, WIPE_CACHE };
+    enum BuiltinAction { NO_ACTION, REBOOT, APPLY_EXT,
+                         APPLY_CACHE,   // APPLY_CACHE is deprecated; has no effect
+                         APPLY_ADB_SIDELOAD, WIPE_DATA, WIPE_CACHE,
+                         REBOOT_BOOTLOADER, SHUTDOWN, READ_RECOVERY_LASTLOG };
 
     // Perform a recovery action selected from the menu.
     // 'menu_position' will be the item number of the selected menu
diff --git a/edify/Android.mk b/edify/Android.mk
index fac0ba7..61ed6fa 100644
--- a/edify/Android.mk
+++ b/edify/Android.mk
@@ -23,6 +23,7 @@
 LOCAL_CFLAGS := $(edify_cflags) -g -O0
 LOCAL_MODULE := edify
 LOCAL_YACCFLAGS := -v
+LOCAL_CFLAGS += -Wno-unused-parameter
 
 include $(BUILD_HOST_EXECUTABLE)
 
@@ -34,6 +35,7 @@
 LOCAL_SRC_FILES := $(edify_src_files)
 
 LOCAL_CFLAGS := $(edify_cflags)
+LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_MODULE := libedify
 
 include $(BUILD_STATIC_LIBRARY)
diff --git a/edify/expr.c b/edify/expr.c
index a2f1f99..79f6282 100644
--- a/edify/expr.c
+++ b/edify/expr.c
@@ -287,13 +287,11 @@
 
     long l_int = strtol(left, &end, 10);
     if (left[0] == '\0' || *end != '\0') {
-        printf("[%s] is not an int\n", left);
         goto done;
     }
 
     long r_int = strtol(right, &end, 10);
     if (right[0] == '\0' || *end != '\0') {
-        printf("[%s] is not an int\n", right);
         goto done;
     }
 
diff --git a/edify/expr.h b/edify/expr.h
index 0d8ed8f..a9ed2f9 100644
--- a/edify/expr.h
+++ b/edify/expr.h
@@ -164,6 +164,8 @@
 // Free a Value object.
 void FreeValue(Value* v);
 
+int parse_string(const char* str, Expr** root, int* error_count);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
diff --git a/edify/main.c b/edify/main.c
index 9e6bab7..b3fad53 100644
--- a/edify/main.c
+++ b/edify/main.c
@@ -30,9 +30,7 @@
 
     printf(".");
 
-    yy_scan_string(expr_str);
-    int error_count = 0;
-    error = yyparse(&e, &error_count);
+    int error_count = parse_string(expr_str, &e, &error_count);
     if (error > 0 || error_count > 0) {
         printf("error parsing \"%s\" (%d errors)\n",
                expr_str, error_count);
@@ -193,8 +191,7 @@
 
     Expr* root;
     int error_count = 0;
-    yy_scan_bytes(buffer, size);
-    int error = yyparse(&root, &error_count);
+    int error = parse_string(buffer, &root, &error_count);
     printf("parse returned %d; %d errors encountered\n", error, error_count);
     if (error == 0 || error_count > 0) {
 
diff --git a/edify/parser.y b/edify/parser.y
index 3f9ade1..f8fb2d1 100644
--- a/edify/parser.y
+++ b/edify/parser.y
@@ -29,6 +29,10 @@
 void yyerror(Expr** root, int* error_count, const char* s);
 int yyparse(Expr** root, int* error_count);
 
+struct yy_buffer_state;
+void yy_switch_to_buffer(struct yy_buffer_state* new_buffer);
+struct yy_buffer_state* yy_scan_string(const char* yystr);
+
 %}
 
 %locations
@@ -128,3 +132,8 @@
   printf("line %d col %d: %s\n", gLine, gColumn, s);
   ++*error_count;
 }
+
+int parse_string(const char* str, Expr** root, int* error_count) {
+    yy_switch_to_buffer(yy_scan_string(str));
+    return yyparse(root, error_count);
+}
diff --git a/etc/init.rc b/etc/init.rc
index 8b79766..67b6a6f 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -1,6 +1,13 @@
 import /init.recovery.${ro.hardware}.rc
 
 on early-init
+    # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls.
+    write /sys/fs/selinux/checkreqprot 0
+
+    # Set the security context for the init process.
+    # This should occur before anything else (e.g. ueventd) is started.
+    setcon u:r:init:s0
+
     start ueventd
     start healthd
 
@@ -16,11 +23,14 @@
     mkdir /system
     mkdir /data
     mkdir /cache
+    mkdir /sideload
     mount tmpfs tmpfs /tmp
 
     chown root shell /tmp
     chmod 0775 /tmp
 
+    write /proc/sys/kernel/panic_on_oops 1
+
 on fs
     mkdir /dev/usb-ffs 0770 shell shell
     mkdir /dev/usb-ffs/adb 0770 shell shell
@@ -37,13 +47,31 @@
 
 
 on boot
-
     ifup lo
     hostname localhost
     domainname localdomain
 
     class_start default
 
+# Load properties from /system/ + /factory after fs mount.
+on load_all_props_action
+    load_all_props
+
+# Mount filesystems and start core system services.
+on late-init
+    trigger early-fs
+    trigger fs
+    trigger post-fs
+    trigger post-fs-data
+
+    # Load properties from /system/ + /factory after fs mount. Place
+    # this in another action so that the load will be scheduled after the prior
+    # issued fs triggers have completed.
+    trigger load_all_props_action
+
+    trigger early-boot
+    trigger boot
+
 on property:sys.powerctl=*
    powerctl ${sys.powerctl}
 
@@ -78,15 +106,19 @@
 
 service ueventd /sbin/ueventd
     critical
+    seclabel u:r:ueventd:s0
 
-service healthd /sbin/healthd -n
+service healthd /sbin/healthd -r
     critical
+    seclabel u:r:healthd:s0
 
 service recovery /sbin/recovery
+    seclabel u:r:recovery:s0
 
-service adbd /sbin/adbd recovery
+service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery
     disabled
     socket adbd stream 660 system system
+    seclabel u:r:adbd:s0
 
 # Always start adbd on userdebug and eng builds
 on property:ro.debuggable=1
diff --git a/fuse_sdcard_provider.c b/fuse_sdcard_provider.c
new file mode 100644
index 0000000..19fb52d
--- /dev/null
+++ b/fuse_sdcard_provider.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2014 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 <errno.h>
+#include <pthread.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "fuse_sideload.h"
+
+struct file_data {
+    int fd;  // the underlying sdcard file
+
+    uint64_t file_size;
+    uint32_t block_size;
+};
+
+static int read_block_file(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
+    struct file_data* fd = (struct file_data*)cookie;
+
+    if (lseek(fd->fd, block * fd->block_size, SEEK_SET) < 0) {
+        printf("seek on sdcard failed: %s\n", strerror(errno));
+        return -EIO;
+    }
+
+    while (fetch_size > 0) {
+        ssize_t r = read(fd->fd, buffer, fetch_size);
+        if (r < 0) {
+            if (r != -EINTR) {
+                printf("read on sdcard failed: %s\n", strerror(errno));
+                return -EIO;
+            }
+            r = 0;
+        }
+        fetch_size -= r;
+        buffer += r;
+    }
+
+    return 0;
+}
+
+static void close_file(void* cookie) {
+    struct file_data* fd = (struct file_data*)cookie;
+    close(fd->fd);
+}
+
+struct token {
+    pthread_t th;
+    const char* path;
+    int result;
+};
+
+static void* run_sdcard_fuse(void* cookie) {
+    struct token* t = (struct token*)cookie;
+
+    struct stat sb;
+    if (stat(t->path, &sb) < 0) {
+        fprintf(stderr, "failed to stat %s: %s\n", t->path, strerror(errno));
+        t->result = -1;
+        return NULL;
+    }
+
+    struct file_data fd;
+    struct provider_vtab vtab;
+
+    fd.fd = open(t->path, O_RDONLY);
+    if (fd.fd < 0) {
+        fprintf(stderr, "failed to open %s: %s\n", t->path, strerror(errno));
+        t->result = -1;
+        return NULL;
+    }
+    fd.file_size = sb.st_size;
+    fd.block_size = 65536;
+
+    vtab.read_block = read_block_file;
+    vtab.close = close_file;
+
+    t->result = run_fuse_sideload(&vtab, &fd, fd.file_size, fd.block_size);
+    return NULL;
+}
+
+// How long (in seconds) we wait for the fuse-provided package file to
+// appear, before timing out.
+#define SDCARD_INSTALL_TIMEOUT 10
+
+void* start_sdcard_fuse(const char* path) {
+    struct token* t = malloc(sizeof(struct token));
+
+    t->path = path;
+    pthread_create(&(t->th), NULL, run_sdcard_fuse, t);
+
+    struct stat st;
+    int i;
+    for (i = 0; i < SDCARD_INSTALL_TIMEOUT; ++i) {
+        if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {
+            if (errno == ENOENT && i < SDCARD_INSTALL_TIMEOUT-1) {
+                sleep(1);
+                continue;
+            } else {
+                return NULL;
+            }
+        }
+    }
+
+    // The installation process expects to find the sdcard unmounted.
+    // Unmount it with MNT_DETACH so that our open file continues to
+    // work but new references see it as unmounted.
+    umount2("/sdcard", MNT_DETACH);
+
+    return t;
+}
+
+void finish_sdcard_fuse(void* cookie) {
+    if (cookie == NULL) return;
+    struct token* t = (struct token*)cookie;
+
+    // Calling stat() on this magic filename signals the fuse
+    // filesystem to shut down.
+    struct stat st;
+    stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
+
+    pthread_join(t->th, NULL);
+    free(t);
+}
diff --git a/fuse_sdcard_provider.h b/fuse_sdcard_provider.h
new file mode 100644
index 0000000..dc2982c
--- /dev/null
+++ b/fuse_sdcard_provider.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 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 __FUSE_SDCARD_PROVIDER_H
+#define __FUSE_SDCARD_PROVIDER_H
+
+void* start_sdcard_fuse(const char* path);
+void finish_sdcard_fuse(void* token);
+
+#endif
diff --git a/fuse_sideload.c b/fuse_sideload.c
new file mode 100644
index 0000000..ab91def
--- /dev/null
+++ b/fuse_sideload.c
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+// This module creates a special filesystem containing two files.
+//
+// "/sideload/package.zip" appears to be a normal file, but reading
+// from it causes data to be fetched from the adb host.  We can use
+// this to sideload packages over an adb connection without having to
+// store the entire package in RAM on the device.
+//
+// Because we may not trust the adb host, this filesystem maintains
+// the following invariant: each read of a given position returns the
+// same data as the first read at that position.  That is, once a
+// section of the file is read, future reads of that section return
+// the same data.  (Otherwise, a malicious adb host process could
+// return one set of bits when the package is read for signature
+// verification, and then different bits for when the package is
+// accessed by the installer.)  If the adb host returns something
+// different than it did on the first read, the reader of the file
+// will see their read fail with EINVAL.
+//
+// The other file, "/sideload/exit", is used to control the subprocess
+// that creates this filesystem.  Calling stat() on the exit file
+// causes the filesystem to be unmounted and the adb process on the
+// device shut down.
+//
+// Note that only the minimal set of file operations needed for these
+// two files is implemented.  In particular, you can't opendir() or
+// readdir() on the "/sideload" directory; ls on it won't work.
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/fuse.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/mount.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "mincrypt/sha256.h"
+#include "fuse_sideload.h"
+
+#define PACKAGE_FILE_ID   (FUSE_ROOT_ID+1)
+#define EXIT_FLAG_ID      (FUSE_ROOT_ID+2)
+
+#define NO_STATUS         1
+#define NO_STATUS_EXIT    2
+
+struct fuse_data {
+    int ffd;   // file descriptor for the fuse socket
+
+    struct provider_vtab* vtab;
+    void* cookie;
+
+    uint64_t file_size;     // bytes
+
+    uint32_t block_size;    // block size that the adb host is using to send the file to us
+    uint32_t file_blocks;   // file size in block_size blocks
+
+    uid_t uid;
+    gid_t gid;
+
+    uint32_t curr_block;    // cache the block most recently read from the host
+    uint8_t* block_data;
+
+    uint8_t* extra_block;   // another block of storage for reads that
+                            // span two blocks
+
+    uint8_t* hashes;        // SHA-256 hash of each block (all zeros
+                            // if block hasn't been read yet)
+};
+
+static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, size_t len)
+{
+    struct fuse_out_header hdr;
+    struct iovec vec[2];
+    int res;
+
+    hdr.len = len + sizeof(hdr);
+    hdr.error = 0;
+    hdr.unique = unique;
+
+    vec[0].iov_base = &hdr;
+    vec[0].iov_len = sizeof(hdr);
+    vec[1].iov_base = data;
+    vec[1].iov_len = len;
+
+    res = writev(fd->ffd, vec, 2);
+    if (res < 0) {
+        printf("*** REPLY FAILED *** %d\n", errno);
+    }
+}
+
+static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    const struct fuse_init_in* req = data;
+    struct fuse_init_out out;
+
+    out.major = FUSE_KERNEL_VERSION;
+    out.minor = FUSE_KERNEL_MINOR_VERSION;
+    out.max_readahead = req->max_readahead;
+    out.flags = 0;
+    out.max_background = 32;
+    out.congestion_threshold = 32;
+    out.max_write = 4096;
+    fuse_reply(fd, hdr->unique, &out, sizeof(out));
+
+    return NO_STATUS;
+}
+
+static void fill_attr(struct fuse_attr* attr, struct fuse_data* fd,
+                      uint64_t nodeid, uint64_t size, uint32_t mode) {
+    memset(attr, 0, sizeof(*attr));
+    attr->nlink = 1;
+    attr->uid = fd->uid;
+    attr->gid = fd->gid;
+    attr->blksize = 4096;
+
+    attr->ino = nodeid;
+    attr->size = size;
+    attr->blocks = (size == 0) ? 0 : (((size-1) / attr->blksize) + 1);
+    attr->mode = mode;
+}
+
+static int handle_getattr(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    const struct fuse_getattr_in* req = data;
+    struct fuse_attr_out out;
+    memset(&out, 0, sizeof(out));
+    out.attr_valid = 10;
+
+    if (hdr->nodeid == FUSE_ROOT_ID) {
+        fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555);
+    } else if (hdr->nodeid == PACKAGE_FILE_ID) {
+        fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
+    } else if (hdr->nodeid == EXIT_FLAG_ID) {
+        fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
+    } else {
+        return -ENOENT;
+    }
+
+    fuse_reply(fd, hdr->unique, &out, sizeof(out));
+    return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
+}
+
+static int handle_lookup(void* data, struct fuse_data* fd,
+                         const struct fuse_in_header* hdr) {
+    struct fuse_entry_out out;
+    memset(&out, 0, sizeof(out));
+    out.entry_valid = 10;
+    out.attr_valid = 10;
+
+    if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, data,
+                sizeof(FUSE_SIDELOAD_HOST_FILENAME)) == 0) {
+        out.nodeid = PACKAGE_FILE_ID;
+        out.generation = PACKAGE_FILE_ID;
+        fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444);
+    } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, data,
+                       sizeof(FUSE_SIDELOAD_HOST_EXIT_FLAG)) == 0) {
+        out.nodeid = EXIT_FLAG_ID;
+        out.generation = EXIT_FLAG_ID;
+        fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0);
+    } else {
+        return -ENOENT;
+    }
+
+    fuse_reply(fd, hdr->unique, &out, sizeof(out));
+    return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS;
+}
+
+static int handle_open(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    const struct fuse_open_in* req = data;
+
+    if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM;
+    if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
+
+    struct fuse_open_out out;
+    memset(&out, 0, sizeof(out));
+    out.fh = 10;  // an arbitrary number; we always use the same handle
+    fuse_reply(fd, hdr->unique, &out, sizeof(out));
+    return NO_STATUS;
+}
+
+static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    return 0;
+}
+
+static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    return 0;
+}
+
+// Fetch a block from the host into fd->curr_block and fd->block_data.
+// Returns 0 on successful fetch, negative otherwise.
+static int fetch_block(struct fuse_data* fd, uint32_t block) {
+    if (block == fd->curr_block) {
+        return 0;
+    }
+
+    if (block >= fd->file_blocks) {
+        memset(fd->block_data, 0, fd->block_size);
+        fd->curr_block = block;
+        return 0;
+    }
+
+    size_t fetch_size = fd->block_size;
+    if (block * fd->block_size + fetch_size > fd->file_size) {
+        // If we're reading the last (partial) block of the file,
+        // expect a shorter response from the host, and pad the rest
+        // of the block with zeroes.
+        fetch_size = fd->file_size - (block * fd->block_size);
+        memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size);
+    }
+
+    int result = fd->vtab->read_block(fd->cookie, block, fd->block_data, fetch_size);
+    if (result < 0) return result;
+
+    fd->curr_block = block;
+
+    // Verify the hash of the block we just got from the host.
+    //
+    // - If the hash of the just-received data matches the stored hash
+    //   for the block, accept it.
+    // - If the stored hash is all zeroes, store the new hash and
+    //   accept the block (this is the first time we've read this
+    //   block).
+    // - Otherwise, return -EINVAL for the read.
+
+    uint8_t hash[SHA256_DIGEST_SIZE];
+    SHA256_hash(fd->block_data, fd->block_size, hash);
+    uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_SIZE;
+    if (memcmp(hash, blockhash, SHA256_DIGEST_SIZE) == 0) {
+        return 0;
+    }
+
+    int i;
+    for (i = 0; i < SHA256_DIGEST_SIZE; ++i) {
+        if (blockhash[i] != 0) {
+            fd->curr_block = -1;
+            return -EIO;
+        }
+    }
+
+    memcpy(blockhash, hash, SHA256_DIGEST_SIZE);
+    return 0;
+}
+
+static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) {
+    const struct fuse_read_in* req = data;
+    struct fuse_out_header outhdr;
+    struct iovec vec[3];
+    int vec_used;
+    int result;
+
+    if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT;
+
+    uint64_t offset = req->offset;
+    uint32_t size = req->size;
+
+    // The docs on the fuse kernel interface are vague about what to
+    // do when a read request extends past the end of the file.  We
+    // can return a short read -- the return structure does include a
+    // length field -- but in testing that caused the program using
+    // the file to segfault.  (I speculate that this is due to the
+    // reading program accessing it via mmap; maybe mmap dislikes when
+    // you return something short of a whole page?)  To fix this we
+    // zero-pad reads that extend past the end of the file so we're
+    // always returning exactly as many bytes as were requested.
+    // (Users of the mapped file have to know its real length anyway.)
+
+    outhdr.len = sizeof(outhdr) + size;
+    outhdr.error = 0;
+    outhdr.unique = hdr->unique;
+    vec[0].iov_base = &outhdr;
+    vec[0].iov_len = sizeof(outhdr);
+
+    uint32_t block = offset / fd->block_size;
+    result = fetch_block(fd, block);
+    if (result != 0) return result;
+
+    // Two cases:
+    //
+    //   - the read request is entirely within this block.  In this
+    //     case we can reply immediately.
+    //
+    //   - the read request goes over into the next block.  Note that
+    //     since we mount the filesystem with max_read=block_size, a
+    //     read can never span more than two blocks.  In this case we
+    //     copy the block to extra_block and issue a fetch for the
+    //     following block.
+
+    uint32_t block_offset = offset - (block * fd->block_size);
+
+    if (size + block_offset <= fd->block_size) {
+        // First case: the read fits entirely in the first block.
+
+        vec[1].iov_base = fd->block_data + block_offset;
+        vec[1].iov_len = size;
+        vec_used = 2;
+    } else {
+        // Second case: the read spills over into the next block.
+
+        memcpy(fd->extra_block, fd->block_data + block_offset,
+               fd->block_size - block_offset);
+        vec[1].iov_base = fd->extra_block;
+        vec[1].iov_len = fd->block_size - block_offset;
+
+        result = fetch_block(fd, block+1);
+        if (result != 0) return result;
+        vec[2].iov_base = fd->block_data;
+        vec[2].iov_len = size - vec[1].iov_len;
+        vec_used = 3;
+    }
+
+    if (writev(fd->ffd, vec, vec_used) < 0) {
+        printf("*** READ REPLY FAILED: %s ***\n", strerror(errno));
+    }
+    return NO_STATUS;
+}
+
+int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
+                      uint64_t file_size, uint32_t block_size)
+{
+    int result;
+
+    // If something's already mounted on our mountpoint, try to remove
+    // it.  (Mostly in case of a previous abnormal exit.)
+    umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE);
+
+    if (block_size < 1024) {
+        fprintf(stderr, "block size (%u) is too small\n", block_size);
+        return -1;
+    }
+    if (block_size > (1<<22)) {   // 4 MiB
+        fprintf(stderr, "block size (%u) is too large\n", block_size);
+        return -1;
+    }
+
+    struct fuse_data fd;
+    memset(&fd, 0, sizeof(fd));
+    fd.vtab = vtab;
+    fd.cookie = cookie;
+    fd.file_size = file_size;
+    fd.block_size = block_size;
+    fd.file_blocks = (file_size == 0) ? 0 : (((file_size-1) / block_size) + 1);
+
+    if (fd.file_blocks > (1<<18)) {
+        fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks);
+        result = -1;
+        goto done;
+    }
+
+    fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_SIZE);
+    if (fd.hashes == NULL) {
+        fprintf(stderr, "failed to allocate %d bites for hashes\n",
+                fd.file_blocks * SHA256_DIGEST_SIZE);
+        result = -1;
+        goto done;
+    }
+
+    fd.uid = getuid();
+    fd.gid = getgid();
+
+    fd.curr_block = -1;
+    fd.block_data = (uint8_t*)malloc(block_size);
+    if (fd.block_data == NULL) {
+        fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size);
+        result = -1;
+        goto done;
+    }
+    fd.extra_block = (uint8_t*)malloc(block_size);
+    if (fd.extra_block == NULL) {
+        fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size);
+        result = -1;
+        goto done;
+    }
+
+    fd.ffd = open("/dev/fuse", O_RDWR);
+    if (fd.ffd < 0) {
+        perror("open /dev/fuse");
+        result = -1;
+        goto done;
+    }
+
+    char opts[256];
+    snprintf(opts, sizeof(opts),
+             ("fd=%d,user_id=%d,group_id=%d,max_read=%zu,"
+              "allow_other,rootmode=040000"),
+             fd.ffd, fd.uid, fd.gid, block_size);
+
+    result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT,
+                   "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts);
+    if (result < 0) {
+        perror("mount");
+        goto done;
+    }
+    uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX*8];
+    for (;;) {
+        ssize_t len = read(fd.ffd, request_buffer, sizeof(request_buffer));
+        if (len < 0) {
+            if (errno != EINTR) {
+                perror("read request");
+                if (errno == ENODEV) {
+                    result = -1;
+                    break;
+                }
+            }
+            continue;
+        }
+
+        if ((size_t)len < sizeof(struct fuse_in_header)) {
+            fprintf(stderr, "request too short: len=%zu\n", (size_t)len);
+            continue;
+        }
+
+        struct fuse_in_header* hdr = (struct fuse_in_header*) request_buffer;
+        void* data = request_buffer + sizeof(struct fuse_in_header);
+
+        result = -ENOSYS;
+
+        switch (hdr->opcode) {
+             case FUSE_INIT:
+                result = handle_init(data, &fd, hdr);
+                break;
+
+             case FUSE_LOOKUP:
+                result = handle_lookup(data, &fd, hdr);
+                break;
+
+            case FUSE_GETATTR:
+                result = handle_getattr(data, &fd, hdr);
+                break;
+
+            case FUSE_OPEN:
+                result = handle_open(data, &fd, hdr);
+                break;
+
+            case FUSE_READ:
+                result = handle_read(data, &fd, hdr);
+                break;
+
+            case FUSE_FLUSH:
+                result = handle_flush(data, &fd, hdr);
+                break;
+
+            case FUSE_RELEASE:
+                result = handle_release(data, &fd, hdr);
+                break;
+
+            default:
+                fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode);
+                break;
+        }
+
+        if (result == NO_STATUS_EXIT) {
+            result = 0;
+            break;
+        }
+
+        if (result != NO_STATUS) {
+            struct fuse_out_header outhdr;
+            outhdr.len = sizeof(outhdr);
+            outhdr.error = result;
+            outhdr.unique = hdr->unique;
+            write(fd.ffd, &outhdr, sizeof(outhdr));
+        }
+    }
+
+  done:
+    fd.vtab->close(fd.cookie);
+
+    result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH);
+    if (result < 0) {
+        printf("fuse_sideload umount failed: %s\n", strerror(errno));
+    }
+
+    if (fd.ffd) close(fd.ffd);
+    free(fd.hashes);
+    free(fd.block_data);
+    free(fd.extra_block);
+
+    return result;
+}
diff --git a/fuse_sideload.h b/fuse_sideload.h
new file mode 100644
index 0000000..c0b16ef
--- /dev/null
+++ b/fuse_sideload.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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 __FUSE_SIDELOAD_H
+#define __FUSE_SIDELOAD_H
+
+// define the filenames created by the sideload FUSE filesystem
+#define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload"
+#define FUSE_SIDELOAD_HOST_FILENAME "package.zip"
+#define FUSE_SIDELOAD_HOST_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_FILENAME)
+#define FUSE_SIDELOAD_HOST_EXIT_FLAG "exit"
+#define FUSE_SIDELOAD_HOST_EXIT_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_EXIT_FLAG)
+
+struct provider_vtab {
+    // read a block
+    int (*read_block)(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size);
+
+    // close down
+    void (*close)(void* cookie);
+};
+
+int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
+                      uint64_t file_size, uint32_t block_size);
+
+#endif
diff --git a/install.cpp b/install.cpp
index 797a525..9db5640 100644
--- a/install.cpp
+++ b/install.cpp
@@ -120,6 +120,7 @@
 
     pid_t pid = fork();
     if (pid == 0) {
+        umask(022);
         close(pipefd[0]);
         execv(binary, (char* const*)args);
         fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
@@ -159,6 +160,11 @@
             *wipe_cache = 1;
         } else if (strcmp(command, "clear_display") == 0) {
             ui->SetBackground(RecoveryUI::NONE);
+        } else if (strcmp(command, "enable_reboot") == 0) {
+            // packages can explicitly request that they want the user
+            // to be able to reboot during installation (useful for
+            // debugging packages that don't exit).
+            ui->SetEnableReboot(true);
         } else {
             LOGE("unknown command [%s]\n", command);
         }
@@ -176,7 +182,7 @@
 }
 
 static int
-really_install_package(const char *path, int* wipe_cache)
+really_install_package(const char *path, int* wipe_cache, bool needs_mount)
 {
     ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
     ui->Print("Finding update package...\n");
@@ -185,12 +191,22 @@
     ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
     LOGI("Update location: %s\n", path);
 
-    if (ensure_path_mounted(path) != 0) {
-        LOGE("Can't mount %s\n", path);
-        return INSTALL_CORRUPT;
+    // Map the update package into memory.
+    ui->Print("Opening update package...\n");
+
+    if (path && needs_mount) {
+        if (path[0] == '@') {
+            ensure_path_mounted(path+1);
+        } else {
+            ensure_path_mounted(path);
+        }
     }
 
-    ui->Print("Opening update package...\n");
+    MemMapping map;
+    if (sysMapFile(path, &map) != 0) {
+        LOGE("failed to map file\n");
+        return INSTALL_CORRUPT;
+    }
 
     int numKeys;
     Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
@@ -203,31 +219,41 @@
     ui->Print("Verifying update package...\n");
 
     int err;
-    err = verify_file(path, loadedKeys, numKeys);
+    err = verify_file(map.addr, map.length, loadedKeys, numKeys);
     free(loadedKeys);
     LOGI("verify_file returned %d\n", err);
     if (err != VERIFY_SUCCESS) {
         LOGE("signature verification failed\n");
+        sysReleaseMap(&map);
         return INSTALL_CORRUPT;
     }
 
     /* Try to open the package.
      */
     ZipArchive zip;
-    err = mzOpenZipArchive(path, &zip);
+    err = mzOpenZipArchive(map.addr, map.length, &zip);
     if (err != 0) {
         LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
+        sysReleaseMap(&map);
         return INSTALL_CORRUPT;
     }
 
     /* Verify and install the contents of the package.
      */
     ui->Print("Installing update...\n");
-    return try_update_binary(path, &zip, wipe_cache);
+    ui->SetEnableReboot(false);
+    int result = try_update_binary(path, &zip, wipe_cache);
+    ui->SetEnableReboot(true);
+    ui->Print("\n");
+
+    sysReleaseMap(&map);
+
+    return result;
 }
 
 int
-install_package(const char* path, int* wipe_cache, const char* install_file)
+install_package(const char* path, int* wipe_cache, const char* install_file,
+                bool needs_mount)
 {
     FILE* install_log = fopen_path(install_file, "w");
     if (install_log) {
@@ -241,7 +267,7 @@
         LOGE("failed to set up expected mounts for install; aborting\n");
         result = INSTALL_ERROR;
     } else {
-        result = really_install_package(path, wipe_cache);
+        result = really_install_package(path, wipe_cache, needs_mount);
     }
     if (install_log) {
         fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
diff --git a/install.h b/install.h
index 043ce13..b5ac410 100644
--- a/install.h
+++ b/install.h
@@ -29,7 +29,7 @@
 // returned and *wipe_cache is true on exit, caller should wipe the
 // cache partition.
 int install_package(const char *root_path, int* wipe_cache,
-                    const char* install_file);
+                    const char* install_file, bool needs_mount);
 RSAPublicKey* load_keys(const char* filename, int* numKeys);
 
 #ifdef __cplusplus
diff --git a/interlace-frames.py b/interlace-frames.py
new file mode 100644
index 0000000..243e565
--- /dev/null
+++ b/interlace-frames.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2014 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.
+
+"""Script to take a set of frames (PNG files) for a recovery animation
+and turn it into a single output image which contains the input frames
+interlaced by row.  Run with the names of all the input frames on the
+command line, in order, followed by the name of the output file."""
+
+import sys
+try:
+  import Image
+  import PngImagePlugin
+except ImportError:
+  print "This script requires the Python Imaging Library to be installed."
+  sys.exit(1)
+
+frames = [Image.open(fn).convert("RGB") for fn in sys.argv[1:-1]]
+assert len(frames) > 0, "Must have at least one input frame."
+sizes = set()
+for fr in frames:
+  sizes.add(fr.size)
+
+assert len(sizes) == 1, "All input images must have the same size."
+w, h = sizes.pop()
+N = len(frames)
+
+out = Image.new("RGB", (w, h*N))
+for j in range(h):
+  for i in range(w):
+    for fn, f in enumerate(frames):
+      out.putpixel((i, j*N+fn), f.getpixel((i, j)))
+
+# When loading this image, the graphics library expects to find a text
+# chunk that specifies how many frames this animation represents.  If
+# you post-process the output of this script with some kind of
+# optimizer tool (eg pngcrush or zopflipng) make sure that your
+# optimizer preserves this text chunk.
+
+meta = PngImagePlugin.PngInfo()
+meta.add_text("Frames", str(N))
+
+out.save(sys.argv[-1], pnginfo=meta)
diff --git a/make-overlay.py b/make-overlay.py
deleted file mode 100644
index 7f931b3..0000000
--- a/make-overlay.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright (C) 2011 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.
-
-"""Script to take a set of frames (PNG files) for a recovery
-"installing" icon animation and turn it into a base image plus a set
-of overlays, as needed by the recovery UI code.  Run with the names of
-all the input frames on the command line, in order."""
-
-import sys
-try:
-  import Image
-except ImportError:
-  print "This script requires the Python Imaging Library to be installed."
-  sys.exit(1)
-
-# Find the smallest box that contains all the pixels which change
-# between images.
-
-print "reading", sys.argv[1]
-base = Image.open(sys.argv[1])
-
-minmini = base.size[0]-1
-maxmaxi = 0
-minminj = base.size[1]-1
-maxmaxj = 0
-
-for top_name in sys.argv[2:]:
-  print "reading", top_name
-  top = Image.open(top_name)
-
-  assert base.size == top.size
-
-  mini = base.size[0]-1
-  maxi = 0
-  minj = base.size[1]-1
-  maxj = 0
-
-  h, w = base.size
-  for j in range(w):
-    for i in range(h):
-      b = base.getpixel((i,j))
-      t = top.getpixel((i,j))
-      if b != t:
-        if i < mini: mini = i
-        if i > maxi: maxi = i
-        if j < minj: minj = j
-        if j > maxj: maxj = j
-
-  minmini = min(minmini, mini)
-  maxmaxi = max(maxmaxi, maxi)
-  minminj = min(minminj, minj)
-  maxmaxj = max(maxmaxj, maxj)
-
-w = maxmaxi - minmini + 1
-h = maxmaxj - minminj + 1
-
-# Now write out an image containing just that box, for each frame.
-
-for num, top_name in enumerate(sys.argv[1:]):
-  top = Image.open(top_name)
-
-  out = Image.new("RGB", (w, h))
-  for i in range(w):
-    for j in range(h):
-      t = top.getpixel((i+minmini, j+minminj))
-      out.putpixel((i, j), t)
-
-  fn = "icon_installing_overlay%02d.png" % (num+1,)
-  out.save(fn)
-  print "saved", fn
-
-# Write out the base icon, which is the first frame with that box
-# blacked out (just to make the file smaller, since it's always
-# displayed with one of the overlays on top of it).
-
-for i in range(w):
-  for j in range(h):
-    base.putpixel((i+minmini, j+minminj), (0, 0, 0))
-fn = "icon_installing.png"
-base.save(fn)
-print "saved", fn
-
-# The device_ui_init() function needs to tell the recovery UI the
-# position of the overlay box.
-
-print
-print "add this to your device_ui_init() function:"
-print "-" * 40
-print "  ui_parameters->install_overlay_offset_x = %d;" % (minmini,)
-print "  ui_parameters->install_overlay_offset_y = %d;" % (minminj,)
-print "-" * 40
diff --git a/minadbd/Android.mk b/minadbd/Android.mk
index c08fd46..4430a2b 100644
--- a/minadbd/Android.mk
+++ b/minadbd/Android.mk
@@ -13,6 +13,7 @@
 LOCAL_SRC_FILES := \
 	adb.c \
 	fdevent.c \
+	fuse_adb_provider.c \
 	transport.c \
 	transport_usb.c \
 	sockets.c \
@@ -26,8 +27,5 @@
 LOCAL_MODULE_TAGS := eng
 LOCAL_MODULE := libminadbd
 
-LOCAL_SHARED_LIBRARIES := libcutils libc
+LOCAL_SHARED_LIBRARIES := libfusesideload libcutils libc
 include $(BUILD_SHARED_LIBRARY)
-
-
-
diff --git a/minadbd/adb.c b/minadbd/adb.c
index 4626bd3..c35e830 100644
--- a/minadbd/adb.c
+++ b/minadbd/adb.c
@@ -407,6 +407,7 @@
 
     fprintf(stderr, "userid is %d\n", getuid());
 */
+
     D("Event loop starting\n");
 
     fdevent_loop();
diff --git a/minadbd/adb.h b/minadbd/adb.h
index c899b58..08ee989 100644
--- a/minadbd/adb.h
+++ b/minadbd/adb.h
@@ -244,15 +244,11 @@
 #if ADB_HOST
 int get_available_local_transport_index();
 #endif
-int  init_socket_transport(atransport *t, int s, int port, int local);
 void init_usb_transport(atransport *t, usb_handle *usb, int state);
 
 /* for MacOS X cleanup */
 void close_usb_devices();
 
-/* cause new transports to be init'd and added to the list */
-void register_socket_transport(int s, const char *serial, int port, int local);
-
 /* these should only be used for the "adb disconnect" command */
 void unregister_transport(atransport *t);
 void unregister_all_tcp_transports();
@@ -404,6 +400,7 @@
 #define CS_RECOVERY   4
 #define CS_NOPERM     5 /* Insufficient permissions to communicate with the device */
 #define CS_SIDELOAD   6
+#define CS_UNAUTHORIZED 7
 
 extern int HOST;
 extern int SHELL_EXIT_NOTIFY_FD;
diff --git a/minadbd/fuse_adb_provider.c b/minadbd/fuse_adb_provider.c
new file mode 100644
index 0000000..f80533a
--- /dev/null
+++ b/minadbd/fuse_adb_provider.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 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 <errno.h>
+
+#include "adb.h"
+#include "fuse_sideload.h"
+
+struct adb_data {
+    int sfd;  // file descriptor for the adb channel
+
+    uint64_t file_size;
+    uint32_t block_size;
+};
+
+static int read_block_adb(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
+    struct adb_data* ad = (struct adb_data*)cookie;
+
+    char buf[10];
+    snprintf(buf, sizeof(buf), "%08u", block);
+    if (writex(ad->sfd, buf, 8) < 0) {
+        fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno));
+        return -EIO;
+    }
+
+    if (readx(ad->sfd, buffer, fetch_size) < 0) {
+        fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno));
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static void close_adb(void* cookie) {
+    struct adb_data* ad = (struct adb_data*)cookie;
+
+    writex(ad->sfd, "DONEDONE", 8);
+}
+
+int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size) {
+    struct adb_data ad;
+    struct provider_vtab vtab;
+
+    ad.sfd = sfd;
+    ad.file_size = file_size;
+    ad.block_size = block_size;
+
+    vtab.read_block = read_block_adb;
+    vtab.close = close_adb;
+
+    return run_fuse_sideload(&vtab, &ad, file_size, block_size);
+}
diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h
new file mode 100644
index 0000000..0eb1f79
--- /dev/null
+++ b/minadbd/fuse_adb_provider.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2014 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 __FUSE_ADB_PROVIDER_H
+#define __FUSE_ADB_PROVIDER_H
+
+int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size);
+
+#endif
diff --git a/minadbd/services.c b/minadbd/services.c
index aef37f7..218b84a 100644
--- a/minadbd/services.c
+++ b/minadbd/services.c
@@ -22,6 +22,7 @@
 
 #include "sysdeps.h"
 #include "fdevent.h"
+#include "fuse_adb_provider.h"
 
 #define  TRACE_TAG  TRACE_SERVICES
 #include "adb.h"
@@ -43,44 +44,23 @@
     return 0;
 }
 
-static void sideload_service(int s, void *cookie)
+static void sideload_host_service(int sfd, void* cookie)
 {
-    unsigned char buf[4096];
-    unsigned count = (unsigned) cookie;
-    int fd;
+    char* saveptr;
+    const char* s = strtok_r(cookie, ":", &saveptr);
+    uint64_t file_size = strtoull(s, NULL, 10);
+    s = strtok_r(NULL, ":", &saveptr);
+    uint32_t block_size = strtoul(s, NULL, 10);
 
-    fprintf(stderr, "sideload_service invoked\n");
+    printf("sideload-host file size %llu block size %lu\n", file_size, block_size);
 
-    fd = adb_creat(ADB_SIDELOAD_FILENAME, 0644);
-    if(fd < 0) {
-        fprintf(stderr, "failed to create %s\n", ADB_SIDELOAD_FILENAME);
-        adb_close(s);
-        return;
-    }
+    int result = run_adb_fuse(sfd, file_size, block_size);
 
-    while(count > 0) {
-        unsigned xfer = (count > 4096) ? 4096 : count;
-        if(readx(s, buf, xfer)) break;
-        if(writex(fd, buf, xfer)) break;
-        count -= xfer;
-    }
-
-    if(count == 0) {
-        writex(s, "OKAY", 4);
-    } else {
-        writex(s, "FAIL", 4);
-    }
-    adb_close(fd);
-    adb_close(s);
-
-    if (count == 0) {
-        fprintf(stderr, "adbd exiting after successful sideload\n");
-        sleep(1);
-        exit(0);
-    }
+    printf("sideload_host finished\n");
+    sleep(1);
+    exit(result == 0 ? 0 : 1);
 }
 
-
 #if 0
 static void echo_service(int fd, void *cookie)
 {
@@ -149,7 +129,12 @@
     int ret = -1;
 
     if (!strncmp(name, "sideload:", 9)) {
-        ret = create_service_thread(sideload_service, (void*) atoi(name + 9));
+        // this exit status causes recovery to print a special error
+        // message saying to use a newer adb (that supports
+        // sideload-host).
+        exit(3);
+    } else if (!strncmp(name, "sideload-host:", 14)) {
+        ret = create_service_thread(sideload_host_service, (void*)(name + 14));
 #if 0
     } else if(!strncmp(name, "echo:", 5)){
         ret = create_service_thread(echo_service, 0);
diff --git a/minadbd/sockets.c b/minadbd/sockets.c
index 2dd6461..817410d 100644
--- a/minadbd/sockets.c
+++ b/minadbd/sockets.c
@@ -319,7 +319,8 @@
 
         while(avail > 0) {
             r = adb_read(fd, x, avail);
-            D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%d\n", s->id, s->fd, r, r<0?errno:0, avail);
+            D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%zu\n",
+              s->id, s->fd, r, r<0?errno:0, avail);
             if(r > 0) {
                 avail -= r;
                 x += r;
diff --git a/minadbd/transport.c b/minadbd/transport.c
index ff20049..92679f5 100644
--- a/minadbd/transport.c
+++ b/minadbd/transport.c
@@ -678,27 +678,6 @@
     return result;
 }
 
-void register_socket_transport(int s, const char *serial, int port, int local)
-{
-    atransport *t = calloc(1, sizeof(atransport));
-    char buff[32];
-
-    if (!serial) {
-        snprintf(buff, sizeof buff, "T-%p", t);
-        serial = buff;
-    }
-    D("transport: %s init'ing for socket %d, on port %d\n", serial, s, port);
-    if ( init_socket_transport(t, s, port, local) < 0 ) {
-        adb_close(s);
-        free(t);
-        return;
-    }
-    if(serial) {
-        t->serial = strdup(serial);
-    }
-    register_transport(t);
-}
-
 void register_usb_transport(usb_handle *usb, const char *serial, unsigned writeable)
 {
     atransport *t = calloc(1, sizeof(atransport));
@@ -734,7 +713,7 @@
     char *p = ptr;
     int r;
 #if ADB_TRACE
-    int  len0 = len;
+    size_t len0 = len;
 #endif
     D("readx: fd=%d wanted=%d\n", fd, (int)len);
     while(len > 0) {
@@ -755,7 +734,7 @@
     }
 
 #if ADB_TRACE
-    D("readx: fd=%d wanted=%d got=%d\n", fd, len0, len0 - len);
+    D("readx: fd=%d wanted=%zu got=%zu\n", fd, len0, len0 - len);
     dump_hex( ptr, len0 );
 #endif
     return 0;
diff --git a/minadbd/usb_linux_client.c b/minadbd/usb_linux_client.c
index c135d63..29bab15 100644
--- a/minadbd/usb_linux_client.c
+++ b/minadbd/usb_linux_client.c
@@ -388,7 +388,7 @@
         ret = adb_read(bulk_out, buf + count, length - count);
         if (ret < 0) {
             if (errno != EINTR) {
-                D("[ bulk_read failed fd=%d length=%d count=%d ]\n",
+                D("[ bulk_read failed fd=%d length=%zu count=%zu ]\n",
                                            bulk_out, length, count);
                 return ret;
             }
diff --git a/minelf/Retouch.c b/minelf/Retouch.c
deleted file mode 100644
index d75eec1..0000000
--- a/minelf/Retouch.c
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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 <errno.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <strings.h>
-#include "Retouch.h"
-#include "applypatch/applypatch.h"
-
-typedef struct {
-    int32_t mmap_addr;
-    char tag[4]; /* 'P', 'R', 'E', ' ' */
-} prelink_info_t __attribute__((packed));
-
-#define false 0
-#define true 1
-
-static int32_t offs_prev;
-static uint32_t cont_prev;
-
-static void init_compression_state(void) {
-    offs_prev = 0;
-    cont_prev = 0;
-}
-
-// For details on the encoding used for relocation lists, please
-// refer to build/tools/retouch/retouch-prepare.c. The intent is to
-// save space by removing most of the inherent redundancy.
-
-static void decode_bytes(uint8_t *encoded_bytes, int encoded_size,
-                         int32_t *dst_offset, uint32_t *dst_contents) {
-    if (encoded_size == 2) {
-        *dst_offset = offs_prev + (((encoded_bytes[0]&0x60)>>5)+1)*4;
-
-        // if the original was negative, we need to 1-pad before applying delta
-        int32_t tmp = (((encoded_bytes[0] & 0x0000001f) << 8) |
-                       encoded_bytes[1]);
-        if (tmp & 0x1000) tmp = 0xffffe000 | tmp;
-        *dst_contents = cont_prev + tmp;
-    } else if (encoded_size == 3) {
-        *dst_offset = offs_prev + (((encoded_bytes[0]&0x30)>>4)+1)*4;
-
-        // if the original was negative, we need to 1-pad before applying delta
-        int32_t tmp = (((encoded_bytes[0] & 0x0000000f) << 16) |
-                       (encoded_bytes[1] << 8) |
-                       encoded_bytes[2]);
-        if (tmp & 0x80000) tmp = 0xfff00000 | tmp;
-        *dst_contents = cont_prev + tmp;
-    } else {
-        *dst_offset =
-          (encoded_bytes[0]<<24) |
-          (encoded_bytes[1]<<16) |
-          (encoded_bytes[2]<<8) |
-          encoded_bytes[3];
-        if (*dst_offset == 0x3fffffff) *dst_offset = -1;
-        *dst_contents =
-          (encoded_bytes[4]<<24) |
-          (encoded_bytes[5]<<16) |
-          (encoded_bytes[6]<<8) |
-          encoded_bytes[7];
-    }
-}
-
-static uint8_t *decode_in_memory(uint8_t *encoded_bytes,
-                                 int32_t *offset, uint32_t *contents) {
-    int input_size, charIx;
-    uint8_t input[8];
-
-    input[0] = *(encoded_bytes++);
-    if (input[0] & 0x80)
-        input_size = 2;
-    else if (input[0] & 0x40)
-        input_size = 3;
-    else
-        input_size = 8;
-
-    // we already read one byte..
-    charIx = 1;
-    while (charIx < input_size) {
-        input[charIx++] = *(encoded_bytes++);
-    }
-
-    // depends on the decoder state!
-    decode_bytes(input, input_size, offset, contents);
-
-    offs_prev = *offset;
-    cont_prev = *contents;
-
-    return encoded_bytes;
-}
-
-int retouch_mask_data(uint8_t *binary_object,
-                      int32_t binary_size,
-                      int32_t *desired_offset,
-                      int32_t *retouch_offset) {
-    retouch_info_t *r_info;
-    prelink_info_t *p_info;
-
-    int32_t target_offset = 0;
-    if (desired_offset) target_offset = *desired_offset;
-
-    int32_t p_offs = binary_size-sizeof(prelink_info_t); // prelink_info_t
-    int32_t r_offs = p_offs-sizeof(retouch_info_t); // retouch_info_t
-    int32_t b_offs; // retouch data blob
-
-    // If not retouched, we say it was a match. This might get invoked on
-    // non-retouched binaries, so that's why we need to do this.
-    if (retouch_offset != NULL) *retouch_offset = target_offset;
-    if (r_offs < 0) return (desired_offset == NULL) ?
-                      RETOUCH_DATA_NOTAPPLICABLE : RETOUCH_DATA_MATCHED;
-    p_info = (prelink_info_t *)(binary_object+p_offs);
-    r_info = (retouch_info_t *)(binary_object+r_offs);
-    if (strncmp(p_info->tag, "PRE ", 4) ||
-        strncmp(r_info->tag, "RETOUCH ", 8))
-        return (desired_offset == NULL) ?
-          RETOUCH_DATA_NOTAPPLICABLE : RETOUCH_DATA_MATCHED;
-
-    b_offs = r_offs-r_info->blob_size;
-    if (b_offs < 0) {
-        printf("negative binary offset: %d = %d - %d\n",
-               b_offs, r_offs, r_info->blob_size);
-        return RETOUCH_DATA_ERROR;
-    }
-    uint8_t *b_ptr = binary_object+b_offs;
-
-    // Retouched: let's go through the work then.
-    int32_t offset_candidate = target_offset;
-    bool offset_set = false, offset_mismatch = false;
-    init_compression_state();
-    while (b_ptr < (uint8_t *)r_info) {
-        int32_t retouch_entry_offset;
-        uint32_t *retouch_entry;
-        uint32_t retouch_original_value;
-
-        b_ptr = decode_in_memory(b_ptr,
-                                 &retouch_entry_offset,
-                                 &retouch_original_value);
-        if (retouch_entry_offset < (-1) ||
-            retouch_entry_offset >= b_offs) {
-            printf("bad retouch_entry_offset: %d", retouch_entry_offset);
-            return RETOUCH_DATA_ERROR;
-        }
-
-        // "-1" means this is the value in prelink_info_t, which also gets
-        // randomized.
-        if (retouch_entry_offset == -1)
-            retouch_entry = (uint32_t *)&(p_info->mmap_addr);
-        else
-            retouch_entry = (uint32_t *)(binary_object+retouch_entry_offset);
-
-        if (desired_offset)
-            *retouch_entry = retouch_original_value + target_offset;
-
-        // Infer the randomization shift, compare to previously inferred.
-        int32_t offset_of_this_entry = (int32_t)(*retouch_entry-
-                                                 retouch_original_value);
-        if (!offset_set) {
-            offset_candidate = offset_of_this_entry;
-            offset_set = true;
-        } else {
-            if (offset_candidate != offset_of_this_entry) {
-                offset_mismatch = true;
-                printf("offset is mismatched: %d, this entry is %d,"
-                       " original 0x%x @ 0x%x",
-                       offset_candidate, offset_of_this_entry,
-                       retouch_original_value, retouch_entry_offset);
-            }
-        }
-    }
-    if (b_ptr > (uint8_t *)r_info) {
-        printf("b_ptr went too far: %p, while r_info is %p",
-               b_ptr, r_info);
-        return RETOUCH_DATA_ERROR;
-    }
-
-    if (offset_mismatch) return RETOUCH_DATA_MISMATCHED;
-    if (retouch_offset != NULL) *retouch_offset = offset_candidate;
-    return RETOUCH_DATA_MATCHED;
-}
diff --git a/minelf/Retouch.h b/minelf/Retouch.h
deleted file mode 100644
index 13bacd5..0000000
--- a/minelf/Retouch.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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 _MINELF_RETOUCH
-#define _MINELF_RETOUCH
-
-#include <stdbool.h>
-#include <sys/types.h>
-
-typedef struct {
-  char tag[8];        /* "RETOUCH ", not zero-terminated */
-  uint32_t blob_size; /* in bytes, located right before this struct */
-} retouch_info_t __attribute__((packed));
-
-#define RETOUCH_DONT_MASK           0
-#define RETOUCH_DO_MASK             1
-
-#define RETOUCH_DATA_ERROR          0 // This is bad. Should not happen.
-#define RETOUCH_DATA_MATCHED        1 // Up to an uniform random offset.
-#define RETOUCH_DATA_MISMATCHED     2 // Partially randomized, or total mess.
-#define RETOUCH_DATA_NOTAPPLICABLE  3 // Not retouched. Only when inferring.
-
-// Mask retouching in-memory. Used before apply_patch[_check].
-// Also used to determine status of retouching after a crash.
-//
-// If desired_offset is not NULL, then apply retouching instead,
-// and return that in retouch_offset.
-int retouch_mask_data(uint8_t *binary_object,
-                      int32_t binary_size,
-                      int32_t *desired_offset,
-                      int32_t *retouch_offset);
-#endif
diff --git a/minui/Android.mk b/minui/Android.mk
index 54eb061..7b40156 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -1,7 +1,7 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := graphics_overlay.c events.c resources.c
+LOCAL_SRC_FILES := graphics_adf.c graphics_fbdev.c graphics_overlay.c events.c resources.c
 ifneq ($(BOARD_CUSTOM_GRAPHICS),)
   LOCAL_SRC_FILES += $(BOARD_CUSTOM_GRAPHICS)
 else
@@ -25,6 +25,8 @@
 endif
 
 LOCAL_STATIC_LIBRARY := libpng
+LOCAL_WHOLE_STATIC_LIBRARIES += libadf
+
 LOCAL_MODULE := libminui
 
 # This used to compare against values in double-quotes (which are just
@@ -60,7 +62,7 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := graphics_overlay.c events.c resources.c
+LOCAL_SRC_FILES := graphics_adf.c graphics_fbdev.c graphics_overlay.c events.c resources.c
 ifneq ($(BOARD_CUSTOM_GRAPHICS),)
   LOCAL_SRC_FILES += $(BOARD_CUSTOM_GRAPHICS)
 else
@@ -87,6 +89,7 @@
 
 LOCAL_ARM_MODE:= arm
 LOCAL_SHARED_LIBRARIES := libpng libpixelflinger
+LOCAL_WHOLE_STATIC_LIBRARIES += libadf
 # This used to compare against values in double-quotes (which are just
 # ordinary characters in this context).  Strip double-quotes from the
 # value so that either will work.
diff --git a/minui/events.c b/minui/events.c
index 2918afa..df7dad4 100644
--- a/minui/events.c
+++ b/minui/events.c
@@ -18,7 +18,7 @@
 #include <stdlib.h>
 #include <fcntl.h>
 #include <dirent.h>
-#include <sys/poll.h>
+#include <sys/epoll.h>
 
 #include <linux/input.h>
 
@@ -34,11 +34,15 @@
     ((array)[(bit)/BITS_PER_LONG] & (1 << ((bit) % BITS_PER_LONG)))
 
 struct fd_info {
+    int fd;
     ev_callback cb;
     void *data;
 };
 
-static struct pollfd ev_fds[MAX_DEVICES + MAX_MISC_FDS];
+static int epollfd;
+static struct epoll_event polledevents[MAX_DEVICES + MAX_MISC_FDS];
+static int npolledevents;
+
 static struct fd_info ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS];
 
 static unsigned ev_count = 0;
@@ -50,6 +54,12 @@
     DIR *dir;
     struct dirent *de;
     int fd;
+    struct epoll_event ev;
+    bool epollctlfail = false;
+
+    epollfd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
+    if (epollfd == -1)
+        return -1;
 
     dir = opendir("/dev/input");
     if(dir != 0) {
@@ -74,8 +84,15 @@
                 continue;
             }
 
-            ev_fds[ev_count].fd = fd;
-            ev_fds[ev_count].events = POLLIN;
+            ev.events = EPOLLIN | EPOLLWAKEUP;
+            ev.data.ptr = (void *)&ev_fdinfo[ev_count];
+            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev)) {
+                close(fd);
+                epollctlfail = true;
+                continue;
+            }
+
+            ev_fdinfo[ev_count].fd = fd;
             ev_fdinfo[ev_count].cb = input_cb;
             ev_fdinfo[ev_count].data = data;
             ev_count++;
@@ -84,59 +101,78 @@
         }
     }
 
+    if (epollctlfail && !ev_count) {
+        close(epollfd);
+        epollfd = -1;
+        return -1;
+    }
+
     return 0;
 }
 
 int ev_add_fd(int fd, ev_callback cb, void *data)
 {
+    struct epoll_event ev;
+    int ret;
+
     if (ev_misc_count == MAX_MISC_FDS || cb == NULL)
         return -1;
 
-    ev_fds[ev_count].fd = fd;
-    ev_fds[ev_count].events = POLLIN;
-    ev_fdinfo[ev_count].cb = cb;
-    ev_fdinfo[ev_count].data = data;
-    ev_count++;
-    ev_misc_count++;
-    return 0;
+    ev.events = EPOLLIN | EPOLLWAKEUP;
+    ev.data.ptr = (void *)&ev_fdinfo[ev_count];
+    ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
+    if (!ret) {
+        ev_fdinfo[ev_count].fd = fd;
+        ev_fdinfo[ev_count].cb = cb;
+        ev_fdinfo[ev_count].data = data;
+        ev_count++;
+        ev_misc_count++;
+    }
+
+    return ret;
+}
+
+int ev_get_epollfd(void)
+{
+    return epollfd;
 }
 
 void ev_exit(void)
 {
     while (ev_count > 0) {
-        close(ev_fds[--ev_count].fd);
+        close(ev_fdinfo[--ev_count].fd);
     }
     ev_misc_count = 0;
     ev_dev_count = 0;
+    close(epollfd);
 }
 
 int ev_wait(int timeout)
 {
-    int r;
-
-    r = poll(ev_fds, ev_count, timeout);
-    if (r <= 0)
+    npolledevents = epoll_wait(epollfd, polledevents, ev_count, timeout);
+    if (npolledevents <= 0)
         return -1;
     return 0;
 }
 
 void ev_dispatch(void)
 {
-    unsigned n;
+    int n;
     int ret;
 
-    for (n = 0; n < ev_count; n++) {
-        ev_callback cb = ev_fdinfo[n].cb;
-        if (cb && (ev_fds[n].revents & ev_fds[n].events))
-            cb(ev_fds[n].fd, ev_fds[n].revents, ev_fdinfo[n].data);
+    for (n = 0; n < npolledevents; n++) {
+        struct fd_info *fdi = polledevents[n].data.ptr;
+        ev_callback cb = fdi->cb;
+        if (cb)
+            cb(fdi->fd, polledevents[n].events, fdi->data);
     }
 }
 
-int ev_get_input(int fd, short revents, struct input_event *ev)
+int ev_get_input(int fd, uint32_t epevents, struct input_event *ev)
 {
     int r;
 
-    if (revents & POLLIN) {
+    if (epevents & EPOLLIN) {
         r = read(fd, ev, sizeof(*ev));
         if (r == sizeof(*ev))
             return 0;
@@ -157,11 +193,11 @@
         memset(key_bits, 0, sizeof(key_bits));
         memset(ev_bits, 0, sizeof(ev_bits));
 
-        ret = ioctl(ev_fds[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits);
+        ret = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits);
         if (ret < 0 || !test_bit(EV_KEY, ev_bits))
             continue;
 
-        ret = ioctl(ev_fds[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits);
+        ret = ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits);
         if (ret < 0)
             continue;
 
diff --git a/minui/font_10x18.h b/minui/font_10x18.h
index 7f96465..29d7053 100644
--- a/minui/font_10x18.h
+++ b/minui/font_10x18.h
@@ -3,7 +3,7 @@
   unsigned height;
   unsigned cwidth;
   unsigned cheight;
-  unsigned char rundata[];
+  unsigned char rundata[2973];
 } font = {
   .width = 960,
   .height = 18,
diff --git a/minui/graphics.c b/minui/graphics.c
index 8f95175..470e735 100644
--- a/minui/graphics.c
+++ b/minui/graphics.c
@@ -29,44 +29,34 @@
 #include <linux/fb.h>
 #include <linux/kd.h>
 
-#include <pixelflinger/pixelflinger.h>
+#include <time.h>
 
 #include "font_10x18.h"
 #include "minui.h"
-
-#if defined(RECOVERY_BGRA)
-#define PIXEL_FORMAT GGL_PIXEL_FORMAT_BGRA_8888
-#define PIXEL_SIZE   4
-#elif defined(RECOVERY_RGBX)
-#define PIXEL_FORMAT GGL_PIXEL_FORMAT_RGBX_8888
-#define PIXEL_SIZE   4
-#else
-#define PIXEL_FORMAT GGL_PIXEL_FORMAT_RGB_565
-#define PIXEL_SIZE   2
-#endif
-
-#define NUM_BUFFERS 2
+#include "graphics.h"
 
 typedef struct {
-    GGLSurface* texture;
-    unsigned cwidth;
-    unsigned cheight;
+    GRSurface* texture;
+    int cwidth;
+    int cheight;
 } GRFont;
 
-static GRFont *gr_font = 0;
-static GGLContext *gr_context = 0;
-static GGLSurface gr_font_texture;
-static GGLSurface gr_framebuffer[NUM_BUFFERS];
-static GGLSurface gr_mem_surface;
-static unsigned gr_active_fb = 0;
-static unsigned double_buffering = 0;
+static GRFont* gr_font = NULL;
+static minui_backend* gr_backend = NULL;
+
 static int overscan_percent = OVERSCAN_PERCENT;
 static int overscan_offset_x = 0;
 static int overscan_offset_y = 0;
 
-static int gr_fb_fd = -1;
 static int gr_vt_fd = -1;
 
+static unsigned char gr_current_r = 255;
+static unsigned char gr_current_g = 255;
+static unsigned char gr_current_b = 255;
+static unsigned char gr_current_a = 255;
+
+static GRSurface* gr_draw = NULL;
+
 static struct fb_var_screeninfo vi;
 static struct fb_fix_screeninfo fi;
 
@@ -167,8 +157,6 @@
     if (vi.yres * fi.line_length * 2 > fi.smem_len)
         return fd;
 
-    double_buffering = 1;
-
     fb->version = sizeof(*fb);
     fb->width = vi.xres;
     fb->height = vi.yres;
@@ -233,6 +221,11 @@
     gl->color4xv(gl, color);
 }
 
+static bool outside(int x, int y)
+{
+    return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height;
+}
+
 int gr_measure(const char *s)
 {
     return gr_font->cwidth * strlen(s);
@@ -249,58 +242,118 @@
     return gr_text_impl(x, y, s, 0);
 }
 
+static void text_blend(unsigned char* src_p, int src_row_bytes,
+                       unsigned char* dst_p, int dst_row_bytes,
+                       int width, int height)
+{
+    int i, j;
+    for (j = 0; j < height; ++j) {
+        unsigned char* sx = src_p;
+        unsigned char* px = dst_p;
+        for (i = 0; i < width; ++i) {
+            unsigned char a = *sx++;
+            if (gr_current_a < 255) a = ((int)a * gr_current_a) / 255;
+            if (a == 255) {
+                *px++ = gr_current_r;
+                *px++ = gr_current_g;
+                *px++ = gr_current_b;
+                px++;
+            } else if (a > 0) {
+                *px = (*px * (255-a) + gr_current_r * a) / 255;
+                ++px;
+                *px = (*px * (255-a) + gr_current_g * a) / 255;
+                ++px;
+                *px = (*px * (255-a) + gr_current_b * a) / 255;
+                ++px;
+                ++px;
+            } else {
+                px += 4;
+            }
+        }
+        src_p += src_row_bytes;
+        dst_p += dst_row_bytes;
+    }
+}
+
+
 int gr_text_impl(int x, int y, const char *s, int bold)
 {
-    GGLContext *gl = gr_context;
     GRFont *font = gr_font;
     unsigned off;
 
-    if (!font->texture) return x;
+    if (!font->texture) return;
+    if (gr_current_a == 0) return;
 
     bold = bold && (font->texture->height != font->cheight);
 
     x += overscan_offset_x;
     y += overscan_offset_y;
 
-    gl->bindTexture(gl, font->texture);
-    gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
-    gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
-    gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
-    gl->enable(gl, GGL_TEXTURE_2D);
-
     while((off = *s++)) {
         off -= 32;
+        if (outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) break;
         if (off < 96) {
-            gl->texCoord2i(gl, (off * font->cwidth) - x,
-                           (bold ? font->cheight : 0) - y);
-            gl->recti(gl, x, y, x + font->cwidth, y + font->cheight);
+
+            unsigned char* src_p = font->texture->data + (off * font->cwidth) +
+                (bold ? font->cheight * font->texture->row_bytes : 0);
+            unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
+
+            text_blend(src_p, font->texture->row_bytes,
+                       dst_p, gr_draw->row_bytes,
+                       font->cwidth, font->cheight);
+
         }
         x += font->cwidth;
     }
-
-    return x;
 }
 
-void gr_texticon(int x, int y, gr_surface icon) {
-    if (gr_context == NULL || icon == NULL) {
+void gr_texticon(int x, int y, GRSurface* icon) {
+    if (icon == NULL) return;
+
+    if (icon->pixel_bytes != 1) {
+        printf("gr_texticon: source has wrong format\n");
         return;
     }
-    GGLContext* gl = gr_context;
 
     x += overscan_offset_x;
     y += overscan_offset_y;
 
-    gl->bindTexture(gl, (GGLSurface*) icon);
-    gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
-    gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
-    gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
-    gl->enable(gl, GGL_TEXTURE_2D);
+    if (outside(x, y) || outside(x+icon->width-1, y+icon->height-1)) return;
 
-    int w = gr_get_width(icon);
-    int h = gr_get_height(icon);
+    unsigned char* src_p = icon->data;
+    unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
 
-    gl->texCoord2i(gl, -x, -y);
-    gl->recti(gl, x, y, x+gr_get_width(icon), y+gr_get_height(icon));
+    text_blend(src_p, icon->row_bytes,
+               dst_p, gr_draw->row_bytes,
+               icon->width, icon->height);
+}
+
+void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+    gr_current_r = r;
+    gr_current_g = g;
+    gr_current_b = b;
+    gr_current_a = a;
+}
+
+void gr_clear()
+{
+    if (gr_current_r == gr_current_g &&
+        gr_current_r == gr_current_b) {
+        memset(gr_draw->data, gr_current_r, gr_draw->height * gr_draw->row_bytes);
+    } else {
+        int x, y;
+        unsigned char* px = gr_draw->data;
+        for (y = 0; y < gr_draw->height; ++y) {
+            for (x = 0; x < gr_draw->width; ++x) {
+                *px++ = gr_current_r;
+                *px++ = gr_current_g;
+                *px++ = gr_current_b;
+                px++;
+            }
+            px += gr_draw->row_bytes - (gr_draw->width * gr_draw->pixel_bytes);
+        }
+    }
 }
 
 void gr_fill(int x1, int y1, int x2, int y2)
@@ -311,48 +364,82 @@
     x2 += overscan_offset_x;
     y2 += overscan_offset_y;
 
-    GGLContext *gl = gr_context;
-    gl->disable(gl, GGL_TEXTURE_2D);
-    gl->recti(gl, x1, y1, x2, y2);
+    if (outside(x1, y1) || outside(x2-1, y2-1)) return;
+
+    unsigned char* p = gr_draw->data + y1 * gr_draw->row_bytes + x1 * gr_draw->pixel_bytes;
+    if (gr_current_a == 255) {
+        int x, y;
+        for (y = y1; y < y2; ++y) {
+            unsigned char* px = p;
+            for (x = x1; x < x2; ++x) {
+                *px++ = gr_current_r;
+                *px++ = gr_current_g;
+                *px++ = gr_current_b;
+                px++;
+            }
+            p += gr_draw->row_bytes;
+        }
+    } else if (gr_current_a > 0) {
+        int x, y;
+        for (y = y1; y < y2; ++y) {
+            unsigned char* px = p;
+            for (x = x1; x < x2; ++x) {
+                *px = (*px * (255-gr_current_a) + gr_current_r * gr_current_a) / 255;
+                ++px;
+                *px = (*px * (255-gr_current_a) + gr_current_g * gr_current_a) / 255;
+                ++px;
+                *px = (*px * (255-gr_current_a) + gr_current_b * gr_current_a) / 255;
+                ++px;
+                ++px;
+            }
+            p += gr_draw->row_bytes;
+        }
+    }
 }
 
-void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy) {
-    if (gr_context == NULL || source == NULL) {
+void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
+    if (source == NULL) return;
+
+    if (gr_draw->pixel_bytes != source->pixel_bytes) {
+        printf("gr_blit: source has wrong format\n");
         return;
     }
-    GGLContext *gl = gr_context;
 
     dx += overscan_offset_x;
     dy += overscan_offset_y;
 
-    gl->bindTexture(gl, (GGLSurface*) source);
-    gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
-    gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
-    gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
-    gl->enable(gl, GGL_TEXTURE_2D);
-    gl->texCoord2i(gl, sx - dx, sy - dy);
-    gl->recti(gl, dx, dy, dx + w, dy + h);
+    if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return;
+
+    unsigned char* src_p = source->data + sy*source->row_bytes + sx*source->pixel_bytes;
+    unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes;
+
+    int i;
+    for (i = 0; i < h; ++i) {
+        memcpy(dst_p, src_p, w * source->pixel_bytes);
+        src_p += source->row_bytes;
+        dst_p += gr_draw->row_bytes;
+    }
 }
 
-unsigned int gr_get_width(gr_surface surface) {
+unsigned int gr_get_width(GRSurface* surface) {
     if (surface == NULL) {
         return 0;
     }
-    return ((GGLSurface*) surface)->width;
+    return surface->width;
 }
 
-unsigned int gr_get_height(gr_surface surface) {
+unsigned int gr_get_height(GRSurface* surface) {
     if (surface == NULL) {
         return 0;
     }
-    return ((GGLSurface*) surface)->height;
+    return surface->height;
 }
 
 static void gr_init_font(void)
 {
     gr_font = calloc(sizeof(*gr_font), 1);
 
-    int res = res_create_surface("font", (void**)&(gr_font->texture));
+    int res = res_create_alpha_surface("font", &(gr_font->texture));
     if (res == 0) {
         // The font image should be a 96x2 array of character images.  The
         // columns are the printable ASCII characters 0x20 - 0x7f.  The
@@ -366,7 +453,8 @@
         gr_font->texture = malloc(sizeof(*gr_font->texture));
         gr_font->texture->width = font.width;
         gr_font->texture->height = font.height;
-        gr_font->texture->stride = font.width;
+        gr_font->texture->row_bytes = font.width;
+        gr_font->texture->pixel_bytes = 1;
 
         unsigned char* bits = malloc(font.width * font.height);
         gr_font->texture->data = (void*) bits;
@@ -381,17 +469,65 @@
         gr_font->cwidth = font.cwidth;
         gr_font->cheight = font.cheight;
     }
+}
 
-    // interpret the grayscale as alpha
-    gr_font->texture->format = GGL_PIXEL_FORMAT_A_8;
+#if 0
+// Exercises many of the gr_*() functions; useful for testing.
+static void gr_test() {
+    GRSurface** images;
+    int frames;
+    int result = res_create_multi_surface("icon_installing", &frames, &images);
+    if (result < 0) {
+        printf("create surface %d\n", result);
+        gr_exit();
+        return;
+    }
+
+    time_t start = time(NULL);
+    int x;
+    for (x = 0; x <= 1200; ++x) {
+        if (x < 400) {
+            gr_color(0, 0, 0, 255);
+        } else {
+            gr_color(0, (x-400)%128, 0, 255);
+        }
+        gr_clear();
+
+        gr_color(255, 0, 0, 255);
+        gr_surface frame = images[x%frames];
+        gr_blit(frame, 0, 0, frame->width, frame->height, x, 0);
+
+        gr_color(255, 0, 0, 128);
+        gr_fill(400, 150, 600, 350);
+
+        gr_color(255, 255, 255, 255);
+        gr_text(500, 225, "hello, world!", 0);
+        gr_color(255, 255, 0, 128);
+        gr_text(300+x, 275, "pack my box with five dozen liquor jugs", 1);
+
+        gr_color(0, 0, 255, 128);
+        gr_fill(gr_draw->width - 200 - x, 300, gr_draw->width - x, 500);
+
+        gr_draw = gr_backend->flip(gr_backend);
+    }
+    printf("getting end time\n");
+    time_t end = time(NULL);
+    printf("got end time\n");
+    printf("start %ld end %ld\n", (long)start, (long)end);
+    if (end > start) {
+        printf("%.2f fps\n", ((double)x) / (end-start));
+    }
+}
+#endif
+
+void gr_flip() {
+    gr_draw = gr_backend->flip(gr_backend);
 }
 
 int gr_init(void)
 {
-    gglInit(&gr_context);
-    GGLContext *gl = gr_context;
-
     gr_init_font();
+
     gr_vt_fd = open("/dev/tty0", O_RDWR | O_SYNC);
     if (gr_vt_fd < 0) {
         // This is non-fatal; post-Cupcake kernels don't have tty0.
@@ -403,10 +539,12 @@
         return -1;
     }
 
-    gr_fb_fd = get_framebuffer(gr_framebuffer);
-    if (gr_fb_fd < 0) {
-        gr_exit();
-        return -1;
+    gr_backend = open_adf();
+    if (gr_backend) {
+        gr_draw = gr_backend->init(gr_backend);
+        if (!gr_draw) {
+            gr_backend->exit(gr_backend);
+        }
     }
 
     get_memory_surface(&gr_mem_surface);
@@ -420,12 +558,19 @@
         set_active_framebuffer(0);
     gl->colorBuffer(gl, &gr_mem_surface);
 
-    gl->activeTexture(gl, 0);
-    gl->enable(gl, GGL_BLEND);
-    gl->blendFunc(gl, GGL_SRC_ALPHA, GGL_ONE_MINUS_SRC_ALPHA);
+    if (!gr_draw) {
+        gr_backend = open_fbdev();
+        gr_draw = gr_backend->init(gr_backend);
+        if (gr_draw == NULL) {
+            return -1;
+        }
+    }
 
-    gr_fb_blank(true);
-    gr_fb_blank(false);
+    overscan_offset_x = gr_draw->width * overscan_percent / 100;
+    overscan_offset_y = gr_draw->height * overscan_percent / 100;
+
+    gr_flip();
+    gr_flip();
 
     if (!alloc_ion_mem(fi.line_length * vi.yres))
         allocate_overlay(gr_fb_fd, gr_framebuffer);
@@ -443,6 +588,8 @@
 
     free(gr_mem_surface.data);
 
+    gr_backend->exit(gr_backend);
+
     ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT);
     close(gr_vt_fd);
     gr_vt_fd = -1;
@@ -450,17 +597,12 @@
 
 int gr_fb_width(void)
 {
-    return gr_framebuffer[0].width - 2*overscan_offset_x;
+    return gr_draw->width - 2*overscan_offset_x;
 }
 
 int gr_fb_height(void)
 {
-    return gr_framebuffer[0].height - 2*overscan_offset_y;
-}
-
-gr_pixel *gr_fb_data(void)
-{
-    return (unsigned short *) gr_mem_surface.data;
+    return gr_draw->height - 2*overscan_offset_y;
 }
 
 void gr_fb_blank(bool blank)
@@ -478,14 +620,11 @@
     write(fd, blank ? "000" : brightness, 3);
     close(fd);
 #else
-    int ret;
+    gr_backend->blank(gr_backend, blank);
+
     if (blank)
         free_overlay(gr_fb_fd);
 
-    ret = ioctl(gr_fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
-    if (ret < 0)
-        perror("ioctl(): blank");
-
     if (!blank)
         allocate_overlay(gr_fb_fd, gr_framebuffer);
 #endif
diff --git a/minui/graphics.h b/minui/graphics.h
new file mode 100644
index 0000000..993e986
--- /dev/null
+++ b/minui/graphics.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 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 _GRAPHICS_H_
+#define _GRAPHICS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include "minui.h"
+
+typedef struct minui_backend {
+    // Initializes the backend and returns a gr_surface to draw into.
+    gr_surface (*init)(struct minui_backend*);
+
+    // Causes the current drawing surface (returned by the most recent
+    // call to flip() or init()) to be displayed, and returns a new
+    // drawing surface.
+    gr_surface (*flip)(struct minui_backend*);
+
+    // Blank (or unblank) the screen.
+    void (*blank)(struct minui_backend*, bool);
+
+    // Device cleanup when drawing is done.
+    void (*exit)(struct minui_backend*);
+} minui_backend;
+
+minui_backend* open_fbdev();
+minui_backend* open_adf();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/minui/graphics_adf.c b/minui/graphics_adf.c
new file mode 100644
index 0000000..ac6d64e
--- /dev/null
+++ b/minui/graphics_adf.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2014 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 <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/cdefs.h>
+#include <sys/mman.h>
+
+#include <adf/adf.h>
+
+#include "graphics.h"
+
+struct adf_surface_pdata {
+    GRSurface base;
+    int fd;
+    __u32 offset;
+    __u32 pitch;
+};
+
+struct adf_pdata {
+    minui_backend base;
+    int intf_fd;
+    adf_id_t eng_id;
+    __u32 format;
+
+    unsigned int current_surface;
+    unsigned int n_surfaces;
+    struct adf_surface_pdata surfaces[2];
+};
+
+static gr_surface adf_flip(struct minui_backend *backend);
+static void adf_blank(struct minui_backend *backend, bool blank);
+
+static int adf_surface_init(struct adf_pdata *pdata,
+        struct drm_mode_modeinfo *mode, struct adf_surface_pdata *surf)
+{
+    memset(surf, 0, sizeof(*surf));
+
+    surf->fd = adf_interface_simple_buffer_alloc(pdata->intf_fd, mode->hdisplay,
+            mode->vdisplay, pdata->format, &surf->offset, &surf->pitch);
+    if (surf->fd < 0)
+        return surf->fd;
+
+    surf->base.width = mode->hdisplay;
+    surf->base.height = mode->vdisplay;
+    surf->base.row_bytes = surf->pitch;
+    surf->base.pixel_bytes = (pdata->format == DRM_FORMAT_RGB565) ? 2 : 4;
+
+    surf->base.data = mmap(NULL, surf->pitch * surf->base.height, PROT_WRITE,
+            MAP_SHARED, surf->fd, surf->offset);
+    if (surf->base.data == MAP_FAILED) {
+        close(surf->fd);
+        return -errno;
+    }
+
+    return 0;
+}
+
+static int adf_interface_init(struct adf_pdata *pdata)
+{
+    struct adf_interface_data intf_data;
+    int ret = 0;
+    int err;
+
+    err = adf_get_interface_data(pdata->intf_fd, &intf_data);
+    if (err < 0)
+        return err;
+
+    err = adf_surface_init(pdata, &intf_data.current_mode, &pdata->surfaces[0]);
+    if (err < 0) {
+        fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err));
+        ret = err;
+        goto done;
+    }
+
+    err = adf_surface_init(pdata, &intf_data.current_mode,
+            &pdata->surfaces[1]);
+    if (err < 0) {
+        fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err));
+        memset(&pdata->surfaces[1], 0, sizeof(pdata->surfaces[1]));
+        pdata->n_surfaces = 1;
+    } else {
+        pdata->n_surfaces = 2;
+    }
+
+done:
+    adf_free_interface_data(&intf_data);
+    return ret;
+}
+
+static int adf_device_init(struct adf_pdata *pdata, struct adf_device *dev)
+{
+    adf_id_t intf_id;
+    int intf_fd;
+    int err;
+
+    err = adf_find_simple_post_configuration(dev, &pdata->format, 1, &intf_id,
+            &pdata->eng_id);
+    if (err < 0)
+        return err;
+
+    err = adf_device_attach(dev, pdata->eng_id, intf_id);
+    if (err < 0 && err != -EALREADY)
+        return err;
+
+    pdata->intf_fd = adf_interface_open(dev, intf_id, O_RDWR);
+    if (pdata->intf_fd < 0)
+        return pdata->intf_fd;
+
+    err = adf_interface_init(pdata);
+    if (err < 0) {
+        close(pdata->intf_fd);
+        pdata->intf_fd = -1;
+    }
+
+    return err;
+}
+
+static gr_surface adf_init(minui_backend *backend)
+{
+    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    adf_id_t *dev_ids = NULL;
+    ssize_t n_dev_ids, i;
+    gr_surface ret;
+
+#if defined(RECOVERY_BGRA)
+    pdata->format = DRM_FORMAT_BGRA8888;
+#elif defined(RECOVERY_RGBX)
+    pdata->format = DRM_FORMAT_RGBX8888;
+#else
+    pdata->format = DRM_FORMAT_RGB565;
+#endif
+
+    n_dev_ids = adf_devices(&dev_ids);
+    if (n_dev_ids == 0) {
+        return NULL;
+    } else if (n_dev_ids < 0) {
+        fprintf(stderr, "enumerating adf devices failed: %s\n",
+                strerror(-n_dev_ids));
+        return NULL;
+    }
+
+    pdata->intf_fd = -1;
+
+    for (i = 0; i < n_dev_ids && pdata->intf_fd < 0; i++) {
+        struct adf_device dev;
+
+        int err = adf_device_open(dev_ids[i], O_RDWR, &dev);
+        if (err < 0) {
+            fprintf(stderr, "opening adf device %u failed: %s\n", dev_ids[i],
+                    strerror(-err));
+            continue;
+        }
+
+        err = adf_device_init(pdata, &dev);
+        if (err < 0)
+            fprintf(stderr, "initializing adf device %u failed: %s\n",
+                    dev_ids[i], strerror(-err));
+
+        adf_device_close(&dev);
+    }
+
+    free(dev_ids);
+
+    if (pdata->intf_fd < 0)
+        return NULL;
+
+    ret = adf_flip(backend);
+
+    adf_blank(backend, true);
+    adf_blank(backend, false);
+
+    return ret;
+}
+
+static gr_surface adf_flip(struct minui_backend *backend)
+{
+    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    struct adf_surface_pdata *surf = &pdata->surfaces[pdata->current_surface];
+
+    int fence_fd = adf_interface_simple_post(pdata->intf_fd, pdata->eng_id,
+            surf->base.width, surf->base.height, pdata->format, surf->fd,
+            surf->offset, surf->pitch, -1);
+    if (fence_fd >= 0)
+        close(fence_fd);
+
+    pdata->current_surface = (pdata->current_surface + 1) % pdata->n_surfaces;
+    return &pdata->surfaces[pdata->current_surface].base;
+}
+
+static void adf_blank(struct minui_backend *backend, bool blank)
+{
+    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    adf_interface_blank(pdata->intf_fd,
+            blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON);
+}
+
+static void adf_surface_destroy(struct adf_surface_pdata *surf)
+{
+    munmap(surf->base.data, surf->pitch * surf->base.height);
+    close(surf->fd);
+}
+
+static void adf_exit(struct minui_backend *backend)
+{
+    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    unsigned int i;
+
+    for (i = 0; i < pdata->n_surfaces; i++)
+        adf_surface_destroy(&pdata->surfaces[i]);
+    if (pdata->intf_fd >= 0)
+        close(pdata->intf_fd);
+    free(pdata);
+}
+
+minui_backend *open_adf()
+{
+    struct adf_pdata *pdata = calloc(1, sizeof(*pdata));
+    if (!pdata) {
+        perror("allocating adf backend failed");
+        return NULL;
+    }
+
+    pdata->base.init = adf_init;
+    pdata->base.flip = adf_flip;
+    pdata->base.blank = adf_blank;
+    pdata->base.exit = adf_exit;
+    return &pdata->base;
+}
diff --git a/minui/graphics_fbdev.c b/minui/graphics_fbdev.c
new file mode 100644
index 0000000..c0c1bcb
--- /dev/null
+++ b/minui/graphics_fbdev.c
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2014 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 <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include "minui.h"
+#include "graphics.h"
+
+static gr_surface fbdev_init(minui_backend*);
+static gr_surface fbdev_flip(minui_backend*);
+static void fbdev_blank(minui_backend*, bool);
+static void fbdev_exit(minui_backend*);
+
+static GRSurface gr_framebuffer[2];
+static bool double_buffered;
+static GRSurface* gr_draw = NULL;
+static int displayed_buffer;
+
+static struct fb_var_screeninfo vi;
+static int fb_fd = -1;
+
+static minui_backend my_backend = {
+    .init = fbdev_init,
+    .flip = fbdev_flip,
+    .blank = fbdev_blank,
+    .exit = fbdev_exit,
+};
+
+minui_backend* open_fbdev() {
+    return &my_backend;
+}
+
+static void fbdev_blank(minui_backend* backend __unused, bool blank)
+{
+    int ret;
+
+    ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
+    if (ret < 0)
+        perror("ioctl(): blank");
+}
+
+static void set_displayed_framebuffer(unsigned n)
+{
+    if (n > 1 || !double_buffered) return;
+
+    vi.yres_virtual = gr_framebuffer[0].height * 2;
+    vi.yoffset = n * gr_framebuffer[0].height;
+    vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
+    if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
+        perror("active fb swap failed");
+    }
+    displayed_buffer = n;
+}
+
+static gr_surface fbdev_init(minui_backend* backend) {
+    int fd;
+    void *bits;
+
+    struct fb_fix_screeninfo fi;
+
+    fd = open("/dev/graphics/fb0", O_RDWR);
+    if (fd < 0) {
+        perror("cannot open fb0");
+        return NULL;
+    }
+
+    if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
+        perror("failed to get fb0 info");
+        close(fd);
+        return NULL;
+    }
+
+    if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
+        perror("failed to get fb0 info");
+        close(fd);
+        return NULL;
+    }
+
+    // We print this out for informational purposes only, but
+    // throughout we assume that the framebuffer device uses an RGBX
+    // pixel format.  This is the case for every development device I
+    // have access to.  For some of those devices (eg, hammerhead aka
+    // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a
+    // different format (XBGR) but actually produces the correct
+    // results on the display when you write RGBX.
+    //
+    // If you have a device that actually *needs* another pixel format
+    // (ie, BGRX, or 565), patches welcome...
+
+    printf("fb0 reports (possibly inaccurate):\n"
+           "  vi.bits_per_pixel = %d\n"
+           "  vi.red.offset   = %3d   .length = %3d\n"
+           "  vi.green.offset = %3d   .length = %3d\n"
+           "  vi.blue.offset  = %3d   .length = %3d\n",
+           vi.bits_per_pixel,
+           vi.red.offset, vi.red.length,
+           vi.green.offset, vi.green.length,
+           vi.blue.offset, vi.blue.length);
+
+    bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (bits == MAP_FAILED) {
+        perror("failed to mmap framebuffer");
+        close(fd);
+        return NULL;
+    }
+
+    memset(bits, 0, fi.smem_len);
+
+    gr_framebuffer[0].width = vi.xres;
+    gr_framebuffer[0].height = vi.yres;
+    gr_framebuffer[0].row_bytes = fi.line_length;
+    gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
+    gr_framebuffer[0].data = bits;
+    memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
+
+    /* check if we can use double buffering */
+    if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
+        double_buffered = true;
+
+        memcpy(gr_framebuffer+1, gr_framebuffer, sizeof(GRSurface));
+        gr_framebuffer[1].data = gr_framebuffer[0].data +
+            gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
+
+        gr_draw = gr_framebuffer+1;
+
+    } else {
+        double_buffered = false;
+
+        // Without double-buffering, we allocate RAM for a buffer to
+        // draw in, and then "flipping" the buffer consists of a
+        // memcpy from the buffer we allocated to the framebuffer.
+
+        gr_draw = (GRSurface*) malloc(sizeof(GRSurface));
+        memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
+        gr_draw->data = (unsigned char*) malloc(gr_draw->height * gr_draw->row_bytes);
+        if (!gr_draw->data) {
+            perror("failed to allocate in-memory surface");
+            return NULL;
+        }
+    }
+
+    memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes);
+    fb_fd = fd;
+    set_displayed_framebuffer(0);
+
+    printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
+
+    fbdev_blank(backend, true);
+    fbdev_blank(backend, false);
+
+    return gr_draw;
+}
+
+static gr_surface fbdev_flip(minui_backend* backend __unused) {
+    if (double_buffered) {
+        // Change gr_draw to point to the buffer currently displayed,
+        // then flip the driver so we're displaying the other buffer
+        // instead.
+        gr_draw = gr_framebuffer + displayed_buffer;
+        set_displayed_framebuffer(1-displayed_buffer);
+    } else {
+        // Copy from the in-memory surface to the framebuffer.
+
+#if defined(RECOVERY_BGRA)
+        unsigned int idx;
+        unsigned char* ucfb_vaddr = (unsigned char*)gr_framebuffer[0].data;
+        unsigned char* ucbuffer_vaddr = (unsigned char*)gr_draw->data;
+        for (idx = 0 ; idx < (gr_draw->height * gr_draw->row_bytes); idx += 4) {
+            ucfb_vaddr[idx    ] = ucbuffer_vaddr[idx + 2];
+            ucfb_vaddr[idx + 1] = ucbuffer_vaddr[idx + 1];
+            ucfb_vaddr[idx + 2] = ucbuffer_vaddr[idx    ];
+            ucfb_vaddr[idx + 3] = ucbuffer_vaddr[idx + 3];
+        }
+#else
+        memcpy(gr_framebuffer[0].data, gr_draw->data,
+               gr_draw->height * gr_draw->row_bytes);
+#endif
+    }
+    return gr_draw;
+}
+
+static void fbdev_exit(minui_backend* backend __unused) {
+    close(fb_fd);
+    fb_fd = -1;
+
+    if (!double_buffered && gr_draw) {
+        free(gr_draw->data);
+        free(gr_draw);
+    }
+    gr_draw = NULL;
+}
diff --git a/minui/minui.h b/minui/minui.h
index 103318a..8228248 100644
--- a/minui/minui.h
+++ b/minui/minui.h
@@ -17,24 +17,34 @@
 #ifndef _MINUI_H_
 #define _MINUI_H_
 
+#include <sys/types.h>
+
 #include <stdbool.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-typedef void* gr_surface;
-typedef unsigned short gr_pixel;
+typedef struct {
+    int width;
+    int height;
+    int row_bytes;
+    int pixel_bytes;
+    unsigned char* data;
+} GRSurface;
+
+typedef GRSurface* gr_surface;
 
 int gr_init(void);
 void gr_exit(void);
 
 int gr_fb_width(void);
 int gr_fb_height(void);
-gr_pixel *gr_fb_data(void);
+
 void gr_flip(void);
 void gr_fb_blank(bool blank);
 
+void gr_clear();  // clear entire surface to current color
 void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
 void gr_fill(int x1, int y1, int x2, int y2);
 
@@ -43,7 +53,7 @@
 int gr_text(int x, int y, const char *s, ...);
 int gr_text_impl(int x, int y, const char *s, int bold);
 
- void gr_texticon(int x, int y, gr_surface icon);
+void gr_texticon(int x, int y, gr_surface icon);
 int gr_measure(const char *s);
 void gr_font_size(int *x, int *y);
 void gr_get_memory_surface(gr_surface);
@@ -56,7 +66,7 @@
 // see http://www.mjmwired.net/kernel/Documentation/input/ for info.
 struct input_event;
 
-typedef int (*ev_callback)(int fd, short revents, void *data);
+typedef int (*ev_callback)(int fd, uint32_t epevents, void *data);
 typedef int (*ev_set_key_callback)(int code, int value, void *data);
 
 int ev_init(ev_callback input_cb, void *data);
@@ -71,14 +81,46 @@
  */
 int ev_wait(int timeout);
 
-int ev_get_input(int fd, short revents, struct input_event *ev);
+int ev_get_input(int fd, uint32_t epevents, struct input_event *ev);
 void ev_dispatch(void);
+int ev_get_epollfd(void);
 
 // Resources
 
-// Returns 0 if no error, else negative.
-int res_create_surface(const char* name, gr_surface* pSurface);
-int res_create_localized_surface(const char* name, gr_surface* pSurface);
+// res_create_*_surface() functions return 0 if no error, else
+// negative.
+//
+// A "display" surface is one that is intended to be drawn to the
+// screen with gr_blit().  An "alpha" surface is a grayscale image
+// interpreted as an alpha mask used to render text in the current
+// color (with gr_text() or gr_texticon()).
+//
+// All these functions load PNG images from "/res/images/${name}.png".
+
+// Load a single display surface from a PNG image.
+int res_create_display_surface(const char* name, gr_surface* pSurface);
+
+// Load an array of display surfaces from a single PNG image.  The PNG
+// should have a 'Frames' text chunk whose value is the number of
+// frames this image represents.  The pixel data itself is interlaced
+// by row.
+int res_create_multi_display_surface(const char* name,
+                                     int* frames, gr_surface** pSurface);
+
+// Load a single alpha surface from a grayscale PNG image.
+int res_create_alpha_surface(const char* name, gr_surface* pSurface);
+
+// Load part of a grayscale PNG image that is the first match for the
+// given locale.  The image is expected to be a composite of multiple
+// translations of the same text, with special added rows that encode
+// the subimages' size and intended locale in the pixel data.  See
+// development/tools/recovery_l10n for an app that will generate these
+// specialized images from Android resources.
+int res_create_localized_alpha_surface(const char* name, const char* locale,
+                                       gr_surface* pSurface);
+
+// Free a surface allocated by any of the res_create_*_surface()
+// functions.
 void res_free_surface(gr_surface surface);
 static inline int res_create_display_surface(const char* name, gr_surface* pSurface) {
     return res_create_surface(name, pSurface);
diff --git a/minui/resources.c b/minui/resources.c
index 39f83c7..92a3d31 100644
--- a/minui/resources.c
+++ b/minui/resources.c
@@ -27,8 +27,6 @@
 #include <linux/fb.h>
 #include <linux/kd.h>
 
-#include <pixelflinger/pixelflinger.h>
-
 #include <png.h>
 
 #include "minui.h"
@@ -39,23 +37,22 @@
 extern char* locale;
 #endif
 
-// 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 * y;
+#define SURFACE_DATA_ALIGNMENT 8
+
+static gr_surface malloc_surface(size_t data_size) {
+    unsigned char* temp = malloc(sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT);
+    if (temp == NULL) return NULL;
+    gr_surface surface = (gr_surface) temp;
+    surface->data = temp + sizeof(GRSurface) +
+        (SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT));
+    return surface;
 }
 
-int res_create_surface(const char* name, gr_surface* pSurface) {
+static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr,
+                    png_uint_32* width, png_uint_32* height, png_byte* channels) {
     char resPath[256];
-    GGLSurface* surface = NULL;
-    int result = 0;
     unsigned char header[8];
-    png_structp png_ptr = NULL;
-    png_infop info_ptr = NULL;
-
-    *pSurface = NULL;
+    int result = 0;
 
     snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
     resPath[sizeof(resPath)-1] = '\0';
@@ -76,114 +73,286 @@
         goto exit;
     }
 
-    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-    if (!png_ptr) {
+    *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (!*png_ptr) {
         result = -4;
         goto exit;
     }
 
-    info_ptr = png_create_info_struct(png_ptr);
-    if (!info_ptr) {
+    *info_ptr = png_create_info_struct(*png_ptr);
+    if (!*info_ptr) {
         result = -5;
         goto exit;
     }
 
-    if (setjmp(png_jmpbuf(png_ptr))) {
+    if (setjmp(png_jmpbuf(*png_ptr))) {
         result = -6;
         goto exit;
     }
 
-    png_init_io(png_ptr, fp);
-    png_set_sig_bytes(png_ptr, sizeof(header));
-    png_read_info(png_ptr, info_ptr);
+    png_init_io(*png_ptr, fp);
+    png_set_sig_bytes(*png_ptr, sizeof(header));
+    png_read_info(*png_ptr, *info_ptr);
 
-    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 && color_type == PNG_COLOR_TYPE_RGB) ||
-           (channels == 4 && color_type == PNG_COLOR_TYPE_RGBA) ||
-           (channels == 1 && (color_type == PNG_COLOR_TYPE_PALETTE ||
-                              color_type == PNG_COLOR_TYPE_GRAY))))) {
-        return -7;
+    int color_type, bit_depth;
+    png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth,
+            &color_type, NULL, NULL, NULL);
+
+    *channels = png_get_channels(*png_ptr, *info_ptr);
+
+    if (bit_depth == 8 && *channels == 3 && color_type == PNG_COLOR_TYPE_RGB) {
+        // 8-bit RGB images: great, nothing to do.
+    } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_GRAY) {
+        // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
+        png_set_expand_gray_1_2_4_to_8(*png_ptr);
+    } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE) {
+        // paletted images: expand to 8-bit RGB.  Note that we DON'T
+        // currently expand the tRNS chunk (if any) to an alpha
+        // channel, because minui doesn't support alpha channels in
+        // general.
+        png_set_palette_to_rgb(*png_ptr);
+        *channels = 3;
+    } else {
+        fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n",
+                bit_depth, *channels, color_type);
+        result = -7;
         goto exit;
     }
 
-    size_t width = info_ptr->width;
-    size_t height = info_ptr->height;
-    size_t stride = (color_type == PNG_COLOR_TYPE_GRAY ? 1 : 4) * width;
-    size_t pixelSize = stride * height;
+    return result;
 
-    surface = malloc(sizeof(GGLSurface) + pixelSize);
+  exit:
+    if (result < 0) {
+        png_destroy_read_struct(png_ptr, info_ptr, NULL);
+    }
+    if (fp != NULL) {
+        fclose(fp);
+    }
+
+    return result;
+}
+
+// "display" surfaces are transformed into the framebuffer's required
+// pixel format (currently only RGBX is supported) at load time, so
+// gr_blit() can be nothing more than a memcpy() for each row.  The
+// next two functions are the only ones that know anything about the
+// framebuffer pixel format; they need to be modified if the
+// framebuffer format changes (but nothing else should).
+
+// Allocate and return a gr_surface sufficient for storing an image of
+// the indicated size in the framebuffer pixel format.
+static gr_surface init_display_surface(png_uint_32 width, png_uint_32 height) {
+    gr_surface surface;
+
+    surface = malloc_surface(width * height * 4);
+    if (surface == NULL) return NULL;
+
+    surface->width = width;
+    surface->height = height;
+    surface->row_bytes = width * 4;
+    surface->pixel_bytes = 4;
+
+    return surface;
+}
+
+// Copy 'input_row' to 'output_row', transforming it to the
+// framebuffer pixel format.  The input format depends on the value of
+// 'channels':
+//
+//   1 - input is 8-bit grayscale
+//   3 - input is 24-bit RGB
+//   4 - input is 32-bit RGBA/RGBX
+//
+// 'width' is the number of pixels in the row.
+static void transform_rgb_to_draw(unsigned char* input_row,
+                                  unsigned char* output_row,
+                                  int channels, int width) {
+    int x;
+    unsigned char* ip = input_row;
+    unsigned char* op = output_row;
+
+    switch (channels) {
+        case 1:
+            // expand gray level to RGBX
+            for (x = 0; x < width; ++x) {
+                *op++ = *ip;
+                *op++ = *ip;
+                *op++ = *ip;
+                *op++ = 0xff;
+                ip++;
+            }
+            break;
+
+        case 3:
+            // expand RGBA to RGBX
+            for (x = 0; x < width; ++x) {
+                *op++ = *ip++;
+                *op++ = *ip++;
+                *op++ = *ip++;
+                *op++ = 0xff;
+            }
+            break;
+
+        case 4:
+            // copy RGBA to RGBX
+            memcpy(output_row, input_row, width*4);
+            break;
+    }
+}
+
+int res_create_display_surface(const char* name, gr_surface* pSurface) {
+    gr_surface surface = NULL;
+    int result = 0;
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_uint_32 width, height;
+    png_byte channels;
+
+    *pSurface = NULL;
+
+    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+    if (result < 0) return result;
+
+    surface = init_display_surface(width, height);
     if (surface == NULL) {
         result = -8;
         goto exit;
     }
-    unsigned char* pData = (unsigned char*) (surface + 1);
-    surface->version = sizeof(GGLSurface);
-    surface->width = width;
-    surface->height = height;
-    surface->stride = width; /* Yes, pixels, not bytes */
-    surface->data = pData;
-    surface->format = (channels == 3) ? GGL_PIXEL_FORMAT_RGBX_8888 :
-        ((color_type == PNG_COLOR_TYPE_PALETTE ? GGL_PIXEL_FORMAT_RGBA_8888 : GGL_PIXEL_FORMAT_L_8));
 
-    int alpha = 0;
-    if (color_type == PNG_COLOR_TYPE_PALETTE) {
-        png_set_palette_to_rgb(png_ptr);
-    }
-    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
-        png_set_tRNS_to_alpha(png_ptr);
-        alpha = 1;
-    }
-    if (color_type == PNG_COLOR_TYPE_GRAY) {
-        alpha = 1;
-    }
-
+    unsigned char* p_row = malloc(width * 4);
     unsigned int y;
-    if (channels == 3 || (channels == 1 && !alpha)) {
-        for (y = 0; y < height; ++y) {
-            unsigned char* pRow = pData + y * stride;
-            png_read_row(png_ptr, pRow, NULL);
+    for (y = 0; y < height; ++y) {
+        png_read_row(png_ptr, p_row, NULL);
+        transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width);
+    }
+    free(p_row);
 
-            int x;
-            for(x = width - 1; x >= 0; x--) {
-                int sx = x * 3;
-                int dx = x * 4;
-                unsigned char r = pRow[sx];
-                unsigned char g = pRow[sx + 1];
-                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 + 3] = a;
+    *pSurface = surface;
+
+  exit:
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    if (result < 0 && surface != NULL) free(surface);
+    return result;
+}
+
+int res_create_multi_display_surface(const char* name, int* frames, gr_surface** pSurface) {
+    gr_surface* surface = NULL;
+    int result = 0;
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_uint_32 width, height;
+    png_byte channels;
+    int i;
+
+    *pSurface = NULL;
+    *frames = -1;
+
+    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+    if (result < 0) return result;
+
+    *frames = 1;
+    png_textp text;
+    int num_text;
+    if (png_get_text(png_ptr, info_ptr, &text, &num_text)) {
+        for (i = 0; i < num_text; ++i) {
+            if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) {
+                *frames = atoi(text[i].text);
+                break;
             }
         }
-    } else {
-        for (y = 0; y < height; ++y) {
-            unsigned char* pRow = pData + y * stride;
-            png_read_row(png_ptr, pRow, NULL);
+        printf("  found frames = %d\n", *frames);
+    }
+
+    if (height % *frames != 0) {
+        printf("bad height (%d) for frame count (%d)\n", height, *frames);
+        result = -9;
+        goto exit;
+    }
+
+    surface = malloc(*frames * sizeof(gr_surface));
+    if (surface == NULL) {
+        result = -8;
+        goto exit;
+    }
+    for (i = 0; i < *frames; ++i) {
+        surface[i] = init_display_surface(width, height / *frames);
+        if (surface[i] == NULL) {
+            result = -8;
+            goto exit;
         }
     }
 
-    *pSurface = (gr_surface) surface;
+    unsigned char* p_row = malloc(width * 4);
+    unsigned int y;
+    for (y = 0; y < height; ++y) {
+        png_read_row(png_ptr, p_row, NULL);
+        int frame = y % *frames;
+        unsigned char* out_row = surface[frame]->data +
+            (y / *frames) * surface[frame]->row_bytes;
+        transform_rgb_to_draw(p_row, out_row, channels, width);
+    }
+    free(p_row);
+
+    *pSurface = (gr_surface*) surface;
 
 exit:
     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 
-    if (fp != NULL) {
-        fclose(fp);
-    }
     if (result < 0) {
         if (surface) {
+            for (i = 0; i < *frames; ++i) {
+                if (surface[i]) free(surface[i]);
+            }
             free(surface);
         }
     }
     return result;
 }
 
-static int matches_locale(const char* loc) {
+int res_create_alpha_surface(const char* name, gr_surface* pSurface) {
+    gr_surface surface = NULL;
+    int result = 0;
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_uint_32 width, height;
+    png_byte channels;
+
+    *pSurface = NULL;
+
+    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+    if (result < 0) return result;
+
+    if (channels != 1) {
+        result = -7;
+        goto exit;
+    }
+
+    surface = malloc_surface(width * height);
+    if (surface == NULL) {
+        result = -8;
+        goto exit;
+    }
+    surface->width = width;
+    surface->height = height;
+    surface->row_bytes = width;
+    surface->pixel_bytes = 1;
+
+    unsigned char* p_row;
+    unsigned int y;
+    for (y = 0; y < height; ++y) {
+        p_row = surface->data + y * surface->row_bytes;
+        png_read_row(png_ptr, p_row, NULL);
+    }
+
+    *pSurface = surface;
+
+  exit:
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    if (result < 0 && surface != NULL) free(surface);
+    return result;
+}
+
+static int matches_locale(const char* loc, const char* locale) {
     if (locale == NULL) return 0;
 
     if (strcmp(loc, locale) == 0) return 1;
@@ -200,100 +369,61 @@
     return (strncmp(locale, loc, i) == 0 && locale[i] == '_');
 }
 
-int res_create_localized_surface(const char* name, gr_surface* pSurface) {
-    char resPath[256];
-    GGLSurface* surface = NULL;
+int res_create_localized_alpha_surface(const char* name,
+                                       const char* locale,
+                                       gr_surface* pSurface) {
+    gr_surface surface = NULL;
     int result = 0;
-    unsigned char header[8];
     png_structp png_ptr = NULL;
     png_infop info_ptr = NULL;
+    png_uint_32 width, height;
+    png_byte channels;
 
     *pSurface = NULL;
 
-    snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
-    resPath[sizeof(resPath)-1] = '\0';
-    FILE* fp = fopen(resPath, "rb");
-    if (fp == NULL) {
-        result = -1;
+    if (locale == NULL) {
+        surface = malloc_surface(0);
+        surface->width = 0;
+        surface->height = 0;
+        surface->row_bytes = 0;
+        surface->pixel_bytes = 1;
         goto exit;
     }
 
-    size_t bytesRead = fread(header, 1, sizeof(header), fp);
-    if (bytesRead != sizeof(header)) {
-        result = -2;
-        goto exit;
-    }
+    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+    if (result < 0) return result;
 
-    if (png_sig_cmp(header, 0, sizeof(header))) {
-        result = -3;
-        goto exit;
-    }
-
-    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-    if (!png_ptr) {
-        result = -4;
-        goto exit;
-    }
-
-    info_ptr = png_create_info_struct(png_ptr);
-    if (!info_ptr) {
-        result = -5;
-        goto exit;
-    }
-
-    if (setjmp(png_jmpbuf(png_ptr))) {
-        result = -6;
-        goto exit;
-    }
-
-    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;
-
-    int color_type = info_ptr->color_type;
-    int bit_depth = info_ptr->bit_depth;
-    int channels = info_ptr->channels;
-
-    if (!(bit_depth == 8 &&
-          (channels == 1 && color_type == PNG_COLOR_TYPE_GRAY))) {
-        return -7;
+    if (channels != 1) {
+        result = -7;
         goto exit;
     }
 
     unsigned char* row = malloc(width);
-    int y;
+    png_uint_32 y;
     for (y = 0; y < height; ++y) {
         png_read_row(png_ptr, row, NULL);
         int w = (row[1] << 8) | row[0];
         int h = (row[3] << 8) | row[2];
         int len = row[4];
-        char* loc = row+5;
+        char* loc = (char*)row+5;
 
-        if (y+1+h >= height || matches_locale(loc)) {
+        if (y+1+h >= height || matches_locale(loc, locale)) {
             printf("  %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
 
-            surface = malloc(sizeof(GGLSurface));
+            surface = malloc_surface(w*h);
             if (surface == NULL) {
                 result = -8;
                 goto exit;
             }
-            unsigned char* pData = malloc(w*h);
-
-            surface->version = sizeof(GGLSurface);
             surface->width = w;
             surface->height = h;
-            surface->stride = w; /* Yes, pixels, not bytes */
-            surface->data = pData;
-            surface->format = GGL_PIXEL_FORMAT_A_8;
+            surface->row_bytes = w;
+            surface->pixel_bytes = 1;
 
             int i;
             for (i = 0; i < h; ++i, ++y) {
                 png_read_row(png_ptr, row, NULL);
-                memcpy(pData + i*w, row, w);
+                memcpy(surface->data + i*w, row, w);
             }
 
             *pSurface = (gr_surface) surface;
@@ -308,21 +438,10 @@
 
 exit:
     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-
-    if (fp != NULL) {
-        fclose(fp);
-    }
-    if (result < 0) {
-        if (surface) {
-            free(surface);
-        }
-    }
+    if (result < 0 && surface != NULL) free(surface);
     return result;
 }
 
 void res_free_surface(gr_surface surface) {
-    GGLSurface* pSurface = (GGLSurface*) surface;
-    if (pSurface) {
-        free(pSurface);
-    }
+    free(surface);
 }
diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c
index 8dd5da1..fe2c880 100644
--- a/minzip/DirUtil.c
+++ b/minzip/DirUtil.c
@@ -234,61 +234,3 @@
     /* delete target directory */
     return rmdir(path);
 }
-
-int
-dirSetHierarchyPermissions(const char *path,
-        int uid, int gid, int dirMode, int fileMode)
-{
-    struct stat st;
-    if (lstat(path, &st)) {
-        return -1;
-    }
-
-    /* ignore symlinks */
-    if (S_ISLNK(st.st_mode)) {
-        return 0;
-    }
-
-    /* directories and files get different permissions */
-    if (chown(path, uid, gid) ||
-        chmod(path, S_ISDIR(st.st_mode) ? dirMode : fileMode)) {
-        return -1;
-    }
-
-    /* recurse over directory components */
-    if (S_ISDIR(st.st_mode)) {
-        DIR *dir = opendir(path);
-        if (dir == NULL) {
-            return -1;
-        }
-
-        errno = 0;
-        const struct dirent *de;
-        while (errno == 0 && (de = readdir(dir)) != NULL) {
-            if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
-                continue;
-            }
-
-            char dn[PATH_MAX];
-            snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
-            if (!dirSetHierarchyPermissions(dn, uid, gid, dirMode, fileMode)) {
-                errno = 0;
-            } else if (errno == 0) {
-                errno = -1;
-            }
-        }
-
-        if (errno != 0) {
-            int save = errno;
-            closedir(dir);
-            errno = save;
-            return -1;
-        }
-
-        if (closedir(dir)) {
-            return -1;
-        }
-    }
-
-    return 0;
-}
diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h
index a5cfa76..85a0012 100644
--- a/minzip/DirUtil.h
+++ b/minzip/DirUtil.h
@@ -48,14 +48,6 @@
  */
 int dirUnlinkHierarchy(const char *path);
 
-/* chown -R <uid>:<gid> <path>
- * chmod -R <mode> <path>
- *
- * Sets directories to <dirMode> and files to <fileMode>.  Skips symlinks.
- */
-int dirSetHierarchyPermissions(const char *path,
-         int uid, int gid, int dirMode, int fileMode);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c
index 31c76d6..ac6f5c3 100644
--- a/minzip/SysUtil.c
+++ b/minzip/SysUtil.c
@@ -8,42 +8,17 @@
 #include <unistd.h>
 #include <string.h>
 #include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 #include <limits.h>
 #include <errno.h>
 #include <assert.h>
 
-#define LOG_TAG "minzip"
+#define LOG_TAG "sysutil"
 #include "Log.h"
 #include "SysUtil.h"
 
-/*
- * Having trouble finding a portable way to get this.  sysconf(_SC_PAGE_SIZE)
- * seems appropriate, but we don't have that on the device.  Some systems
- * have getpagesize(2), though the linux man page has some odd cautions.
- */
-#define DEFAULT_PAGE_SIZE   4096
-
-
-/*
- * Create an anonymous shared memory segment large enough to hold "length"
- * bytes.  The actual segment may be larger because mmap() operates on
- * page boundaries (usually 4K).
- */
-static void* sysCreateAnonShmem(size_t length)
-{
-    void* ptr;
-
-    ptr = mmap(NULL, length, PROT_READ | PROT_WRITE,
-            MAP_SHARED | MAP_ANON, -1, 0);
-    if (ptr == MAP_FAILED) {
-        LOGW("mmap(%d, RW, SHARED|ANON) failed: %s\n", (int) length,
-            strerror(errno));
-        return NULL;
-    }
-
-    return ptr;
-}
-
 static int getFileStartAndLength(int fd, off_t *start_, size_t *length_)
 {
     off_t start, end;
@@ -74,48 +49,13 @@
 }
 
 /*
- * Pull the contents of a file into an new shared memory segment.  We grab
- * everything from fd's current offset on.
- *
- * We need to know the length ahead of time so we can allocate a segment
- * of sufficient size.
- */
-int sysLoadFileInShmem(int fd, MemMapping* pMap)
-{
-    off_t start;
-    size_t length, actual;
-    void* memPtr;
-
-    assert(pMap != NULL);
-
-    if (getFileStartAndLength(fd, &start, &length) < 0)
-        return -1;
-
-    memPtr = sysCreateAnonShmem(length);
-    if (memPtr == NULL)
-        return -1;
-
-    pMap->baseAddr = pMap->addr = memPtr;
-    pMap->baseLength = pMap->length = length;
-
-    actual = TEMP_FAILURE_RETRY(read(fd, memPtr, length));
-    if (actual != length) {
-        LOGE("only read %d of %d bytes\n", (int) actual, (int) length);
-        sysReleaseShmem(pMap);
-        return -1;
-    }
-
-    return 0;
-}
-
-/*
- * Map a file (from fd's current offset) into a shared, read-only memory
+ * Map a file (from fd's current offset) into a private, read-only memory
  * segment.  The file offset must be a multiple of the page size.
  *
  * On success, returns 0 and fills out "pMap".  On failure, returns a nonzero
  * value and does not disturb "pMap".
  */
-int sysMapFileInShmem(int fd, MemMapping* pMap)
+static int sysMapFD(int fd, MemMapping* pMap)
 {
     off_t start;
     size_t length;
@@ -126,87 +66,148 @@
     if (getFileStartAndLength(fd, &start, &length) < 0)
         return -1;
 
-    memPtr = mmap(NULL, length, PROT_READ, MAP_FILE | MAP_SHARED, fd, start);
+    memPtr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, start);
     if (memPtr == MAP_FAILED) {
-        LOGW("mmap(%d, R, FILE|SHARED, %d, %d) failed: %s\n", (int) length,
+        LOGW("mmap(%d, R, PRIVATE, %d, %d) failed: %s\n", (int) length,
             fd, (int) start, strerror(errno));
         return -1;
     }
 
-    pMap->baseAddr = pMap->addr = memPtr;
-    pMap->baseLength = pMap->length = length;
+    pMap->addr = memPtr;
+    pMap->length = length;
+    pMap->range_count = 1;
+    pMap->ranges = malloc(sizeof(MappedRange));
+    pMap->ranges[0].addr = memPtr;
+    pMap->ranges[0].length = length;
 
     return 0;
 }
 
-/*
- * Map part of a file (from fd's current offset) into a shared, read-only
- * memory segment.
- *
- * On success, returns 0 and fills out "pMap".  On failure, returns a nonzero
- * value and does not disturb "pMap".
- */
-int sysMapFileSegmentInShmem(int fd, off_t start, long length,
-    MemMapping* pMap)
+static int sysMapBlockFile(FILE* mapf, MemMapping* pMap)
 {
-    off_t dummy;
-    size_t fileLength, actualLength;
-    off_t actualStart;
-    int adjust;
-    void* memPtr;
+    char block_dev[PATH_MAX+1];
+    size_t size;
+    unsigned int blksize;
+    unsigned int blocks;
+    unsigned int range_count;
+    unsigned int i;
 
-    assert(pMap != NULL);
-
-    if (getFileStartAndLength(fd, &dummy, &fileLength) < 0)
+    if (fgets(block_dev, sizeof(block_dev), mapf) == NULL) {
+        LOGW("failed to read block device from header\n");
         return -1;
+    }
+    for (i = 0; i < sizeof(block_dev); ++i) {
+        if (block_dev[i] == '\n') {
+            block_dev[i] = 0;
+            break;
+        }
+    }
 
-    if (start + length > (long)fileLength) {
-        LOGW("bad segment: st=%d len=%ld flen=%d\n",
-            (int) start, length, (int) fileLength);
+    if (fscanf(mapf, "%zu %u\n%u\n", &size, &blksize, &range_count) != 3) {
+        LOGW("failed to parse block map header\n");
         return -1;
     }
 
-    /* adjust to be page-aligned */
-    adjust = start % DEFAULT_PAGE_SIZE;
-    actualStart = start - adjust;
-    actualLength = length + adjust;
+    blocks = ((size-1) / blksize) + 1;
 
-    memPtr = mmap(NULL, actualLength, PROT_READ, MAP_FILE | MAP_SHARED,
-                fd, actualStart);
-    if (memPtr == MAP_FAILED) {
-        LOGW("mmap(%d, R, FILE|SHARED, %d, %d) failed: %s\n",
-            (int) actualLength, fd, (int) actualStart, strerror(errno));
+    pMap->range_count = range_count;
+    pMap->ranges = malloc(range_count * sizeof(MappedRange));
+    memset(pMap->ranges, 0, range_count * sizeof(MappedRange));
+
+    // Reserve enough contiguous address space for the whole file.
+    unsigned char* reserve;
+    reserve = mmap64(NULL, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
+    if (reserve == MAP_FAILED) {
+        LOGW("failed to reserve address space: %s\n", strerror(errno));
         return -1;
     }
 
-    pMap->baseAddr = memPtr;
-    pMap->baseLength = actualLength;
-    pMap->addr = (char*)memPtr + adjust;
-    pMap->length = length;
+    pMap->ranges[range_count-1].addr = reserve;
+    pMap->ranges[range_count-1].length = blocks * blksize;
 
-    LOGVV("mmap seg (st=%d ln=%d): bp=%p bl=%d ad=%p ln=%d\n",
-        (int) start, (int) length,
-        pMap->baseAddr, (int) pMap->baseLength,
-        pMap->addr, (int) pMap->length);
+    int fd = open(block_dev, O_RDONLY);
+    if (fd < 0) {
+        LOGW("failed to open block device %s: %s\n", block_dev, strerror(errno));
+        return -1;
+    }
 
+    unsigned char* next = reserve;
+    for (i = 0; i < range_count; ++i) {
+        int start, end;
+        if (fscanf(mapf, "%d %d\n", &start, &end) != 2) {
+            LOGW("failed to parse range %d in block map\n", i);
+            return -1;
+        }
+
+        void* addr = mmap64(next, (end-start)*blksize, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize);
+        if (addr == MAP_FAILED) {
+            LOGW("failed to map block %d: %s\n", i, strerror(errno));
+            return -1;
+        }
+        pMap->ranges[i].addr = addr;
+        pMap->ranges[i].length = (end-start)*blksize;
+
+        next += pMap->ranges[i].length;
+    }
+
+    pMap->addr = reserve;
+    pMap->length = size;
+
+    LOGI("mmapped %d ranges\n", range_count);
+
+    return 0;
+}
+
+int sysMapFile(const char* fn, MemMapping* pMap)
+{
+    memset(pMap, 0, sizeof(*pMap));
+
+    if (fn && fn[0] == '@') {
+        // A map of blocks
+        FILE* mapf = fopen(fn+1, "r");
+        if (mapf == NULL) {
+            LOGV("Unable to open '%s': %s\n", fn+1, strerror(errno));
+            return -1;
+        }
+
+        if (sysMapBlockFile(mapf, pMap) != 0) {
+            LOGW("Map of '%s' failed\n", fn);
+            return -1;
+        }
+
+        fclose(mapf);
+    } else {
+        // This is a regular file.
+        int fd = open(fn, O_RDONLY, 0);
+        if (fd < 0) {
+            LOGE("Unable to open '%s': %s\n", fn, strerror(errno));
+            return -1;
+        }
+
+        if (sysMapFD(fd, pMap) != 0) {
+            LOGE("Map of '%s' failed\n", fn);
+            close(fd);
+            return -1;
+        }
+
+        close(fd);
+    }
     return 0;
 }
 
 /*
  * Release a memory mapping.
  */
-void sysReleaseShmem(MemMapping* pMap)
+void sysReleaseMap(MemMapping* pMap)
 {
-    if (pMap->baseAddr == NULL && pMap->baseLength == 0)
-        return;
-
-    if (munmap(pMap->baseAddr, pMap->baseLength) < 0) {
-        LOGW("munmap(%p, %d) failed: %s\n",
-            pMap->baseAddr, (int)pMap->baseLength, strerror(errno));
-    } else {
-        LOGV("munmap(%p, %d) succeeded\n", pMap->baseAddr, pMap->baseLength);
-        pMap->baseAddr = NULL;
-        pMap->baseLength = 0;
+    int i;
+    for (i = 0; i < pMap->range_count; ++i) {
+        if (munmap(pMap->ranges[i].addr, pMap->ranges[i].length) < 0) {
+            LOGW("munmap(%p, %d) failed: %s\n",
+                 pMap->ranges[i].addr, (int)pMap->ranges[i].length, strerror(errno));
+        }
     }
+    free(pMap->ranges);
+    pMap->ranges = NULL;
+    pMap->range_count = 0;
 }
-
diff --git a/minzip/SysUtil.h b/minzip/SysUtil.h
index ec3a4bc..7adff1e 100644
--- a/minzip/SysUtil.h
+++ b/minzip/SysUtil.h
@@ -6,56 +6,47 @@
 #ifndef _MINZIP_SYSUTIL
 #define _MINZIP_SYSUTIL
 
-#include "inline_magic.h"
-
+#include <stdio.h>
 #include <sys/types.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct MappedRange {
+    void* addr;
+    size_t length;
+} MappedRange;
+
 /*
  * Use this to keep track of mapped segments.
  */
 typedef struct MemMapping {
-    void*   addr;           /* start of data */
-    size_t  length;         /* length of data */
+    unsigned char* addr;           /* start of data */
+    size_t         length;         /* length of data */
 
-    void*   baseAddr;       /* page-aligned base address */
-    size_t  baseLength;     /* length of mapping */
+    int            range_count;
+    MappedRange*   ranges;
 } MemMapping;
 
-/* copy a map */
-INLINE void sysCopyMap(MemMapping* dst, const MemMapping* src) {
-    *dst = *src;
-}
-
 /*
- * Load a file into a new shared memory segment.  All data from the current
- * offset to the end of the file is pulled in.
- *
- * The segment is read-write, allowing VM fixups.  (It should be modified
- * to support .gz/.zip compressed data.)
+ * Map a file into a private, read-only memory segment.  If 'fn'
+ * begins with an '@' character, it is a map of blocks to be mapped,
+ * otherwise it is treated as an ordinary file.
  *
  * On success, "pMap" is filled in, and zero is returned.
  */
-int sysLoadFileInShmem(int fd, MemMapping* pMap);
-
-/*
- * Map a file (from fd's current offset) into a shared,
- * read-only memory segment.
- *
- * On success, "pMap" is filled in, and zero is returned.
- */
-int sysMapFileInShmem(int fd, MemMapping* pMap);
-
-/*
- * Like sysMapFileInShmem, but on only part of a file.
- */
-int sysMapFileSegmentInShmem(int fd, off_t start, long length,
-    MemMapping* pMap);
+int sysMapFile(const char* fn, MemMapping* pMap);
 
 /*
  * Release the pages associated with a shared memory segment.
  *
  * This does not free "pMap"; it just releases the memory.
  */
-void sysReleaseShmem(MemMapping* pMap);
+void sysReleaseMap(MemMapping* pMap);
+
+#ifdef __cplusplus
+}
+#endif
 
 #endif /*_MINZIP_SYSUTIL*/
diff --git a/minzip/Zip.c b/minzip/Zip.c
index 439e5d9..70aff00 100644
--- a/minzip/Zip.c
+++ b/minzip/Zip.c
@@ -184,7 +184,7 @@
  *
  * Returns "true" on success.
  */
-static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap)
+static bool parseZipArchive(ZipArchive* pArchive)
 {
     bool result = false;
     const unsigned char* ptr;
@@ -196,7 +196,7 @@
      * signature for the first file (LOCSIG) or, if the archive doesn't
      * have any files in it, the end-of-central-directory signature (ENDSIG).
      */
-    val = get4LE(pMap->addr);
+    val = get4LE(pArchive->addr);
     if (val == ENDSIG) {
         LOGI("Found Zip archive, but it looks empty\n");
         goto bail;
@@ -209,14 +209,14 @@
      * Find the EOCD.  We'll find it immediately unless they have a file
      * comment.
      */
-    ptr = pMap->addr + pMap->length - ENDHDR;
+    ptr = pArchive->addr + pArchive->length - ENDHDR;
 
-    while (ptr >= (const unsigned char*) pMap->addr) {
+    while (ptr >= (const unsigned char*) pArchive->addr) {
         if (*ptr == (ENDSIG & 0xff) && get4LE(ptr) == ENDSIG)
             break;
         ptr--;
     }
-    if (ptr < (const unsigned char*) pMap->addr) {
+    if (ptr < (const unsigned char*) pArchive->addr) {
         LOGI("Could not find end-of-central-directory in Zip\n");
         goto bail;
     }
@@ -230,9 +230,9 @@
     cdOffset = get4LE(ptr + ENDOFF);
 
     LOGVV("numEntries=%d cdOffset=%d\n", numEntries, cdOffset);
-    if (numEntries == 0 || cdOffset >= pMap->length) {
+    if (numEntries == 0 || cdOffset >= pArchive->length) {
         LOGW("Invalid entries=%d offset=%d (len=%zd)\n",
-            numEntries, cdOffset, pMap->length);
+            numEntries, cdOffset, pArchive->length);
         goto bail;
     }
 
@@ -245,14 +245,14 @@
     if (pArchive->pEntries == NULL || pArchive->pHash == NULL)
         goto bail;
 
-    ptr = pMap->addr + cdOffset;
+    ptr = pArchive->addr + cdOffset;
     for (i = 0; i < numEntries; i++) {
         ZipEntry* pEntry;
         unsigned int fileNameLen, extraLen, commentLen, localHdrOffset;
         const unsigned char* localHdr;
         const char *fileName;
 
-        if (ptr + CENHDR > (const unsigned char*)pMap->addr + pMap->length) {
+        if (ptr + CENHDR > (const unsigned char*)pArchive->addr + pArchive->length) {
             LOGW("Ran off the end (at %d)\n", i);
             goto bail;
         }
@@ -266,7 +266,7 @@
         extraLen = get2LE(ptr + CENEXT);
         commentLen = get2LE(ptr + CENCOM);
         fileName = (const char*)ptr + CENHDR;
-        if (fileName + fileNameLen > (const char*)pMap->addr + pMap->length) {
+        if (fileName + fileNameLen > (const char*)pArchive->addr + pArchive->length) {
             LOGW("Filename ran off the end (at %d)\n", i);
             goto bail;
         }
@@ -352,15 +352,15 @@
         }
         pEntry->externalFileAttributes = get4LE(ptr + CENATX);
 
-        // Perform pMap->addr + localHdrOffset, ensuring that it won't
+        // Perform pArchive->addr + localHdrOffset, ensuring that it won't
         // overflow. This is needed because localHdrOffset is untrusted.
-        if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pMap->addr,
+        if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pArchive->addr,
             (uintptr_t)localHdrOffset)) {
             LOGW("Integer overflow adding in parseZipArchive\n");
             goto bail;
         }
         if ((uintptr_t)localHdr + LOCHDR >
-            (uintptr_t)pMap->addr + pMap->length) {
+            (uintptr_t)pArchive->addr + pArchive->length) {
             LOGW("Bad offset to local header: %d (at %d)\n", localHdrOffset, i);
             goto bail;
         }
@@ -374,7 +374,7 @@
             LOGW("Integer overflow adding in parseZipArchive\n");
             goto bail;
         }
-        if ((size_t)pEntry->offset + pEntry->compLen > pMap->length) {
+        if ((size_t)pEntry->offset + pEntry->compLen > pArchive->length) {
             LOGW("Data ran off the end (at %d)\n", i);
             goto bail;
         }
@@ -427,50 +427,30 @@
  *
  * On success, we fill out the contents of "pArchive".
  */
-int mzOpenZipArchive(const char* fileName, ZipArchive* pArchive)
+int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive)
 {
-    MemMapping map;
     int err;
 
-    LOGV("Opening archive '%s' %p\n", fileName, pArchive);
-
-    map.addr = NULL;
-    memset(pArchive, 0, sizeof(*pArchive));
-
-    pArchive->fd = open(fileName, O_RDONLY, 0);
-    if (pArchive->fd < 0) {
-        err = errno ? errno : -1;
-        LOGV("Unable to open '%s': %s\n", fileName, strerror(err));
-        goto bail;
-    }
-
-    if (sysMapFileInShmem(pArchive->fd, &map) != 0) {
-        err = -1;
-        LOGW("Map of '%s' failed\n", fileName);
-        goto bail;
-    }
-
-    if (map.length < ENDHDR) {
+    if (length < ENDHDR) {
         err = -1;
         LOGV("File '%s' too small to be zip (%zd)\n", fileName, map.length);
         goto bail;
     }
 
-    if (!parseZipArchive(pArchive, &map)) {
+    pArchive->addr = addr;
+    pArchive->length = length;
+
+    if (!parseZipArchive(pArchive)) {
         err = -1;
         LOGV("Parsing '%s' failed\n", fileName);
         goto bail;
     }
 
     err = 0;
-    sysCopyMap(&pArchive->map, &map);
-    map.addr = NULL;
 
 bail:
     if (err != 0)
         mzCloseZipArchive(pArchive);
-    if (map.addr != NULL)
-        sysReleaseShmem(&map);
     return err;
 }
 
@@ -483,16 +463,10 @@
 {
     LOGV("Closing archive %p\n", pArchive);
 
-    if (pArchive->fd >= 0)
-        close(pArchive->fd);
-    if (pArchive->map.addr != NULL)
-        sysReleaseShmem(&pArchive->map);
-
     free(pArchive->pEntries);
 
     mzHashTableFree(pArchive->pHash);
 
-    pArchive->fd = -1;
     pArchive->pHash = NULL;
     pArchive->pEntries = NULL;
 }
@@ -528,29 +502,7 @@
     const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
     void *cookie)
 {
-    size_t bytesLeft = pEntry->compLen;
-    while (bytesLeft > 0) {
-        unsigned char buf[32 * 1024];
-        ssize_t n;
-        size_t count;
-        bool ret;
-
-        count = bytesLeft;
-        if (count > sizeof(buf)) {
-            count = sizeof(buf);
-        }
-        n = read(pArchive->fd, buf, count);
-        if (n < 0 || (size_t)n != count) {
-            LOGE("Can't read %zu bytes from zip file: %ld\n", count, n);
-            return false;
-        }
-        ret = processFunction(buf, n, cookie);
-        if (!ret) {
-            return false;
-        }
-        bytesLeft -= count;
-    }
-    return true;
+    return processFunction(pArchive->addr + pEntry->offset, pEntry->uncompLen, cookie);
 }
 
 static bool processDeflatedEntry(const ZipArchive *pArchive,
@@ -573,8 +525,8 @@
     zstream.zalloc = Z_NULL;
     zstream.zfree = Z_NULL;
     zstream.opaque = Z_NULL;
-    zstream.next_in = NULL;
-    zstream.avail_in = 0;
+    zstream.next_in = pArchive->addr + pEntry->offset;
+    zstream.avail_in = pEntry->compLen;
     zstream.next_out = (Bytef*) procBuf;
     zstream.avail_out = sizeof(procBuf);
     zstream.data_type = Z_UNKNOWN;
@@ -598,25 +550,6 @@
      * Loop while we have data.
      */
     do {
-        /* read as much as we can */
-        if (zstream.avail_in == 0) {
-            long getSize = (compRemaining > (long)sizeof(readBuf)) ?
-                        (long)sizeof(readBuf) : compRemaining;
-            LOGVV("+++ reading %ld bytes (%ld left)\n",
-                getSize, compRemaining);
-
-            int cc = read(pArchive->fd, readBuf, getSize);
-            if (cc != (int) getSize) {
-                LOGW("inflate read failed (%d vs %ld)\n", cc, getSize);
-                goto z_bail;
-            }
-
-            compRemaining -= getSize;
-
-            zstream.next_in = readBuf;
-            zstream.avail_in = getSize;
-        }
-
         /* uncompress the data */
         zerr = inflate(&zstream, Z_NO_FLUSH);
         if (zerr != Z_OK && zerr != Z_STREAM_END) {
@@ -676,12 +609,6 @@
     bool ret = false;
     off_t oldOff;
 
-    /* save current offset */
-    oldOff = lseek(pArchive->fd, 0, SEEK_CUR);
-
-    /* Seek to the beginning of the entry's compressed data. */
-    lseek(pArchive->fd, pEntry->offset, SEEK_SET);
-
     switch (pEntry->compression) {
     case STORED:
         ret = processStoredEntry(pArchive, pEntry, processFunction, cookie);
@@ -695,8 +622,6 @@
         break;
     }
 
-    /* restore file offset */
-    lseek(pArchive->fd, oldOff, SEEK_SET);
     return ret;
 }
 
@@ -772,13 +697,15 @@
 static bool writeProcessFunction(const unsigned char *data, int dataLen,
                                  void *cookie)
 {
-    int fd = (int)cookie;
-
+    int fd = (int)(intptr_t)cookie;
+    if (dataLen == 0) {
+        return true;
+    }
     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",
+            LOGE("Error writing %zd bytes from zip file from %p: %s\n",
                  dataLen-soFar, data+soFar, strerror(errno));
             if (errno != EINTR) {
               return false;
@@ -787,7 +714,7 @@
             soFar += n;
             if (soFar == dataLen) return true;
             if (soFar > dataLen) {
-                LOGE("write overrun?  (%ld bytes instead of %d)\n",
+                LOGE("write overrun?  (%zd bytes instead of %d)\n",
                      soFar, dataLen);
                 return false;
             }
@@ -802,7 +729,7 @@
     const ZipEntry *pEntry, int fd)
 {
     bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction,
-                                         (void*)fd);
+                                         (void*)(intptr_t)fd);
     if (!ret) {
         LOGE("Can't extract entry to file.\n");
         return false;
@@ -810,6 +737,23 @@
     return true;
 }
 
+/*
+ * Obtain a pointer to the in-memory representation of a stored entry.
+ */
+bool mzGetStoredEntry(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, unsigned char **addr, size_t *length)
+{
+    if (pEntry->compression != STORED) {
+        LOGE("Can't getStoredEntry for '%s'; not stored\n",
+             pEntry->fileName);
+        return false;
+    }
+
+    *addr = pArchive->addr + pEntry->offset;
+    *length = pEntry->uncompLen;
+    return true;
+}
+
 typedef struct {
     unsigned char* buffer;
     long len;
@@ -1123,7 +1067,8 @@
                     setfscreatecon(secontext);
                 }
 
-                int fd = creat(targetFile, UNZIP_FILEMODE);
+                int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC
+                        , UNZIP_FILEMODE);
 
                 if (secontext) {
                     freecon(secontext);
@@ -1138,7 +1083,12 @@
                 }
 
                 bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd);
-                close(fd);
+                if (ok) {
+                    ok = (fsync(fd) == 0);
+                }
+                if (close(fd) != 0) {
+                    ok = false;
+                }
                 if (!ok) {
                     LOGE("Error extracting \"%s\"\n", targetFile);
                     ok = false;
diff --git a/minzip/Zip.h b/minzip/Zip.h
index c942828..2054b38 100644
--- a/minzip/Zip.h
+++ b/minzip/Zip.h
@@ -46,11 +46,11 @@
  * One Zip archive.  Treat as opaque.
  */
 typedef struct ZipArchive {
-    int         fd;
-    unsigned int numEntries;
-    ZipEntry*   pEntries;
-    HashTable*  pHash;          // maps file name to ZipEntry
-    MemMapping  map;
+    unsigned int   numEntries;
+    ZipEntry*      pEntries;
+    HashTable*     pHash;          // maps file name to ZipEntry
+    unsigned char* addr;
+    size_t         length;
 } ZipArchive;
 
 /*
@@ -68,7 +68,7 @@
  * On success, returns 0 and populates "pArchive".  Returns nonzero errno
  * value on failure.
  */
-int mzOpenZipArchive(const char* fileName, ZipArchive* pArchive);
+int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive);
 
 /*
  * Close archive, releasing resources associated with it.
@@ -183,6 +183,17 @@
     const ZipEntry *pEntry, unsigned char* buffer);
 
 /*
+ * Return a pointer and length for a given entry.  The returned region
+ * should be valid until pArchive is closed, and should be treated as
+ * read-only.
+ *
+ * Only makes sense for entries which are stored (ie, not compressed).
+ * No guarantees are made regarding alignment of the returned pointer.
+ */
+bool mzGetStoredEntry(const ZipArchive *pArchive,
+    const ZipEntry* pEntry, unsigned char **addr, size_t *length);
+
+/*
  * Inflate all entries under zipDir to the directory specified by
  * targetDir, which must exist and be a writable directory.
  *
diff --git a/mtdutils/flash_image.c b/mtdutils/flash_image.c
index a39d600..5657dfc 100644
--- a/mtdutils/flash_image.c
+++ b/mtdutils/flash_image.c
@@ -24,6 +24,9 @@
 #include "cutils/log.h"
 #include "mtdutils.h"
 
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
 #define LOG_TAG "flash_image"
 
 #define HEADER_SIZE 2048  // size of header to compare for equality
diff --git a/recovery.cpp b/recovery.cpp
index 6409130..c6e1270 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -48,6 +48,8 @@
 #include "adb_install.h"
 extern "C" {
 #include "minadbd/adb.h"
+#include "fuse_sideload.h"
+#include "fuse_sdcard_provider.h"
 }
 
 extern "C" {
@@ -72,6 +74,9 @@
   { "show_text", no_argument, NULL, 't' },
   { "just_exit", no_argument, NULL, 'x' },
   { "locale", required_argument, NULL, 'l' },
+  { "stages", required_argument, NULL, 'g' },
+  { "shutdown_after", no_argument, NULL, 'p' },
+  { "reason", required_argument, NULL, 'r' },
   { NULL, 0, NULL, 0 },
 };
 
@@ -87,11 +92,14 @@
 static const char *SDCARD_ROOT = "/sdcard";
 static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
 static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
-static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload";
+
+#define KEEP_LOG_COUNT 10
 
 RecoveryUI* ui = NULL;
 char* locale = NULL;
 char recovery_version[PROPERTY_VALUE_MAX+1];
+char* stage = NULL;
+char* reason = NULL;
 
 /*
  * The recovery tool communicates with the main system through /cache files.
@@ -171,6 +179,12 @@
     return fp;
 }
 
+static void redirect_stdio(const char* filename) {
+    // If these fail, there's not really anywhere to complain...
+    freopen(filename, "a", stdout); setbuf(stdout, NULL);
+    freopen(filename, "a", stderr); setbuf(stderr, NULL);
+}
+
 // close a file, log an error if the error indicator is set
 static void
 check_and_fclose(FILE *fp, const char *name) {
@@ -188,13 +202,14 @@
     struct bootloader_message boot;
     memset(&boot, 0, sizeof(boot));
     get_bootloader_message(&boot);  // this may fail, leaving a zeroed structure
+    stage = strndup(boot.stage, sizeof(boot.stage));
 
     if (boot.command[0] != 0 && boot.command[0] != 255) {
-        LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command);
+        LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command);
     }
 
     if (boot.status[0] != 0 && boot.status[0] != 255) {
-        LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status);
+        LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status);
     }
 
     // --- if arguments weren't supplied, look in the bootloader control block
@@ -451,96 +466,6 @@
     return result;
 }
 
-static char*
-copy_sideloaded_package(const char* original_path) {
-  if (ensure_path_mounted(original_path) != 0) {
-    LOGE("Can't mount %s\n", original_path);
-    return NULL;
-  }
-
-  if (ensure_path_mounted(SIDELOAD_TEMP_DIR) != 0) {
-    LOGE("Can't mount %s\n", SIDELOAD_TEMP_DIR);
-    return NULL;
-  }
-
-  if (mkdir(SIDELOAD_TEMP_DIR, 0700) != 0) {
-    if (errno != EEXIST) {
-      LOGE("Can't mkdir %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno));
-      return NULL;
-    }
-  }
-
-  // verify that SIDELOAD_TEMP_DIR is exactly what we expect: a
-  // directory, owned by root, readable and writable only by root.
-  struct stat st;
-  if (stat(SIDELOAD_TEMP_DIR, &st) != 0) {
-    LOGE("failed to stat %s (%s)\n", SIDELOAD_TEMP_DIR, strerror(errno));
-    return NULL;
-  }
-  if (!S_ISDIR(st.st_mode)) {
-    LOGE("%s isn't a directory\n", SIDELOAD_TEMP_DIR);
-    return NULL;
-  }
-  if ((st.st_mode & 0777) != 0700) {
-    LOGE("%s has perms %o\n", SIDELOAD_TEMP_DIR, st.st_mode);
-    return NULL;
-  }
-  if (st.st_uid != 0) {
-    LOGE("%s owned by %lu; not root\n", SIDELOAD_TEMP_DIR, st.st_uid);
-    return NULL;
-  }
-
-  char copy_path[PATH_MAX];
-  strcpy(copy_path, SIDELOAD_TEMP_DIR);
-  strcat(copy_path, "/package.zip");
-
-  char* buffer = (char*)malloc(BUFSIZ);
-  if (buffer == NULL) {
-    LOGE("Failed to allocate buffer\n");
-    return NULL;
-  }
-
-  size_t read;
-  FILE* fin = fopen(original_path, "rb");
-  if (fin == NULL) {
-    LOGE("Failed to open %s (%s)\n", original_path, strerror(errno));
-    return NULL;
-  }
-  FILE* fout = fopen(copy_path, "wb");
-  if (fout == NULL) {
-    LOGE("Failed to open %s (%s)\n", copy_path, strerror(errno));
-    return NULL;
-  }
-
-  while ((read = fread(buffer, 1, BUFSIZ, fin)) > 0) {
-    if (fwrite(buffer, 1, read, fout) != read) {
-      LOGE("Short write of %s (%s)\n", copy_path, strerror(errno));
-      return NULL;
-    }
-  }
-
-  free(buffer);
-
-  if (fclose(fout) != 0) {
-    LOGE("Failed to close %s (%s)\n", copy_path, strerror(errno));
-    return NULL;
-  }
-
-  if (fclose(fin) != 0) {
-    LOGE("Failed to close %s (%s)\n", original_path, strerror(errno));
-    return NULL;
-  }
-
-  // "adb push" is happy to overwrite read-only files when it's
-  // running as root, but we'll try anyway.
-  if (chmod(copy_path, 0400) != 0) {
-    LOGE("Failed to chmod %s (%s)\n", copy_path, strerror(errno));
-    return NULL;
-  }
-
-  return strdup(copy_path);
-}
-
 static const char**
 prepend_title(const char* const* headers) {
     // count the number of lines in our title, plus the
@@ -616,9 +541,9 @@
     return strcmp(*(const char**)a, *(const char**)b);
 }
 
-static int
-update_directory(const char* path, const char* unmount_when_done,
-                 int* wipe_cache, Device* device) {
+// Returns a malloc'd path, or NULL.
+static char*
+browse_directory(const char* path, Device* device) {
     ensure_path_mounted(path);
 
     const char* MENU_HEADERS[] = { "Choose a package to install:",
@@ -630,10 +555,7 @@
     d = opendir(path);
     if (d == NULL) {
         LOGE("error opening %s: %s\n", path, strerror(errno));
-        if (unmount_when_done != NULL) {
-            ensure_path_unmounted(unmount_when_done);
-        }
-        return 0;
+        return NULL;
     }
 
     const char** headers = prepend_title(MENU_HEADERS);
@@ -689,58 +611,41 @@
     z_size += d_size;
     zips[z_size] = NULL;
 
-    int result;
+    char* result;
     int chosen_item = 0;
-    do {
+    while (true) {
         chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device);
 
         char* item = zips[chosen_item];
         int item_len = strlen(item);
         if (chosen_item == 0) {          // item 0 is always "../"
             // go up but continue browsing (if the caller is update_directory)
-            result = -1;
-            break;
-        } else if (item[item_len-1] == '/') {
-            // recurse down into a subdirectory
-            char new_path[PATH_MAX];
-            strlcpy(new_path, path, PATH_MAX);
-            strlcat(new_path, "/", PATH_MAX);
-            strlcat(new_path, item, PATH_MAX);
-            new_path[strlen(new_path)-1] = '\0';  // truncate the trailing '/'
-            result = update_directory(new_path, unmount_when_done, wipe_cache, device);
-            if (result >= 0) break;
-        } else {
-            // selected a zip file:  attempt to install it, and return
-            // the status to the caller.
-            char new_path[PATH_MAX];
-            strlcpy(new_path, path, PATH_MAX);
-            strlcat(new_path, "/", PATH_MAX);
-            strlcat(new_path, item, PATH_MAX);
-
-            ui->Print("\n-- Install %s ...\n", path);
-            set_sdcard_update_bootloader_message();
-            char* copy = copy_sideloaded_package(new_path);
-            if (unmount_when_done != NULL) {
-                ensure_path_unmounted(unmount_when_done);
-            }
-            if (copy) {
-                result = install_package(copy, wipe_cache, TEMPORARY_INSTALL_FILE);
-                free(copy);
-            } else {
-                result = INSTALL_ERROR;
-            }
+            result = NULL;
             break;
         }
-    } while (true);
+
+        char new_path[PATH_MAX];
+        strlcpy(new_path, path, PATH_MAX);
+        strlcat(new_path, "/", PATH_MAX);
+        strlcat(new_path, item, PATH_MAX);
+
+        if (item[item_len-1] == '/') {
+            // recurse down into a subdirectory
+            new_path[strlen(new_path)-1] = '\0';  // truncate the trailing '/'
+            result = browse_directory(new_path, device);
+            if (result) break;
+        } else {
+            // selected a zip file: return the malloc'd path to the caller.
+            result = strdup(new_path);
+            break;
+        }
+    }
 
     int i;
     for (i = 0; i < z_size; ++i) free(zips[i]);
     free(zips);
     free(headers);
 
-    if (unmount_when_done != NULL) {
-        ensure_path_unmounted(unmount_when_done);
-    }
     return result;
 }
 
@@ -780,10 +685,73 @@
     device->WipeData();
     erase_volume("/data");
     erase_volume("/cache");
+    erase_persistent_partition();
     ui->Print("Data wipe complete.\n");
 }
 
-static void
+static void file_to_ui(const char* fn) {
+    FILE *fp = fopen_path(fn, "re");
+    if (fp == NULL) {
+        ui->Print("  Unable to open %s: %s\n", fn, strerror(errno));
+        return;
+    }
+    char line[1024];
+    int ct = 0;
+    redirect_stdio("/dev/null");
+    while(fgets(line, sizeof(line), fp) != NULL) {
+        ui->Print("%s", line);
+        ct++;
+        if (ct % 30 == 0) {
+            // give the user time to glance at the entries
+            ui->WaitKey();
+        }
+    }
+    redirect_stdio(TEMPORARY_LOG_FILE);
+    fclose(fp);
+}
+
+static void choose_recovery_file(Device* device) {
+    int i;
+    static const char** title_headers = NULL;
+    char *filename;
+    const char* headers[] = { "Select file to view",
+                              "",
+                              NULL };
+    char* entries[KEEP_LOG_COUNT + 2];
+    memset(entries, 0, sizeof(entries));
+
+    for (i = 0; i < KEEP_LOG_COUNT; i++) {
+        char *filename;
+        if (asprintf(&filename, (i==0) ? LAST_LOG_FILE : (LAST_LOG_FILE ".%d"), i) == -1) {
+            // memory allocation failure - return early. Should never happen.
+            return;
+        }
+        if ((ensure_path_mounted(filename) != 0) || (access(filename, R_OK) == -1)) {
+            free(filename);
+            entries[i+1] = NULL;
+            break;
+        }
+        entries[i+1] = filename;
+    }
+
+    entries[0] = strdup("Go back");
+    title_headers = prepend_title((const char**)headers);
+
+    while(1) {
+        int chosen_item = get_menu_selection(title_headers, entries, 1, 0, device);
+        if (chosen_item == 0) break;
+        file_to_ui(entries[chosen_item]);
+    }
+
+    for (i = 0; i < KEEP_LOG_COUNT + 1; i++) {
+        free(entries[i]);
+    }
+}
+
+// Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER.  Returning NO_ACTION
+// means to take the default, which is to reboot or shutdown depending
+// on if the --shutdown_after flag was passed to recovery.
+static Device::BuiltinAction
 prompt_and_wait(Device* device, int status) {
     const char* const* headers = prepend_title(device->GetMenuHeaders());
 
@@ -807,27 +775,48 @@
         // device-specific code may take some action here.  It may
         // return one of the core actions handled in the switch
         // statement below.
-        chosen_item = device->InvokeMenuItem(chosen_item);
+        Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item);
 
-        int wipe_cache;
-        switch (chosen_item) {
+        int wipe_cache = 0;
+        switch (chosen_action) {
+            case Device::NO_ACTION:
+                break;
+
             case Device::REBOOT:
-                return;
+            case Device::SHUTDOWN:
+            case Device::REBOOT_BOOTLOADER:
+                return chosen_action;
 
             case Device::WIPE_DATA:
                 wipe_data(ui->IsTextVisible(), device);
-                if (!ui->IsTextVisible()) return;
+                if (!ui->IsTextVisible()) return Device::NO_ACTION;
                 break;
 
             case Device::WIPE_CACHE:
                 ui->Print("\n-- Wiping cache...\n");
                 erase_volume("/cache");
                 ui->Print("Cache wipe complete.\n");
-                if (!ui->IsTextVisible()) return;
+                if (!ui->IsTextVisible()) return Device::NO_ACTION;
                 break;
 
-            case Device::APPLY_EXT:
-                status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache, device);
+            case Device::APPLY_EXT: {
+                ensure_path_mounted(SDCARD_ROOT);
+                char* path = browse_directory(SDCARD_ROOT, device);
+                if (path == NULL) {
+                    ui->Print("\n-- No package file selected.\n", path);
+                    break;
+                }
+
+                ui->Print("\n-- Install %s ...\n", path);
+                set_sdcard_update_bootloader_message();
+                void* token = start_sdcard_fuse(path);
+
+                int status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache,
+                                             TEMPORARY_INSTALL_FILE, false);
+
+                finish_sdcard_fuse(token);
+                ensure_path_unmounted(SDCARD_ROOT);
+
                 if (status == INSTALL_SUCCESS && wipe_cache) {
                     ui->Print("\n-- Wiping cache (at package request)...\n");
                     if (erase_volume("/cache")) {
@@ -836,39 +825,26 @@
                         ui->Print("Cache wipe complete.\n");
                     }
                 }
+
                 if (status >= 0) {
                     if (status != INSTALL_SUCCESS) {
                         ui->SetBackground(RecoveryUI::ERROR);
                         ui->Print("Installation aborted.\n");
                     } else if (!ui->IsTextVisible()) {
-                        return;  // reboot if logs aren't visible
+                        return Device::NO_ACTION;  // reboot if logs aren't visible
                     } else {
                         ui->Print("\nInstall from sdcard complete.\n");
                     }
                 }
                 break;
+            }
 
             case Device::APPLY_CACHE:
-                // Don't unmount cache at the end of this.
-                status = update_directory(CACHE_ROOT, NULL, &wipe_cache, device);
-                if (status == INSTALL_SUCCESS && wipe_cache) {
-                    ui->Print("\n-- Wiping cache (at package request)...\n");
-                    if (erase_volume("/cache")) {
-                        ui->Print("Cache wipe failed.\n");
-                    } else {
-                        ui->Print("Cache wipe complete.\n");
-                    }
-                }
-                if (status >= 0) {
-                    if (status != INSTALL_SUCCESS) {
-                        ui->SetBackground(RecoveryUI::ERROR);
-                        ui->Print("Installation aborted.\n");
-                    } else if (!ui->IsTextVisible()) {
-                        return;  // reboot if logs aren't visible
-                    } else {
-                        ui->Print("\nInstall from cache complete.\n");
-                    }
-                }
+                ui->Print("\nAPPLY_CACHE is deprecated.\n");
+                break;
+
+            case Device::READ_RECOVERY_LASTLOG:
+                choose_recovery_file(device);
                 break;
 
             case Device::APPLY_ADB_SIDELOAD:
@@ -879,7 +855,7 @@
                         ui->Print("Installation aborted.\n");
                         copy_logs();
                     } else if (!ui->IsTextVisible()) {
-                        return;  // reboot if logs aren't visible
+                        return Device::NO_ACTION;  // reboot if logs aren't visible
                     } else {
                         ui->Print("\nInstall from ADB complete.\n");
                     }
@@ -939,9 +915,7 @@
 
     time_t start = time(NULL);
 
-    // If these fail, there's not really anywhere to complain...
-    freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
-    freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
+    redirect_stdio(TEMPORARY_LOG_FILE);
 
     // If this binary is started with the single argument "--adbd",
     // instead of being the normal recovery binary, it turns into kind
@@ -955,6 +929,7 @@
         return 0;
     }
 
+<<<<<<< HEAD
     printf("Starting TWRP %s on %s", TW_VERSION_STR, ctime(&start));
 
     Device* device = make_device();
@@ -984,20 +959,19 @@
     load_volume_table();
     ensure_path_mounted(LAST_LOG_FILE);
 
-    rotate_last_logs(10);
+    rotate_last_logs(KEEP_LOG_COUNT);
     get_args(&argc, &argv);
 
-    int previous_runs = 0;
     const char *send_intent = NULL;
     const char *update_package = NULL;
     int wipe_data = 0, wipe_cache = 0, show_text = 0;
     bool just_exit = false;
 	bool perform_backup = false;
+    bool shutdown_after = false;
 
     int arg;
     while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
         switch (arg) {
-        case 'p': previous_runs = atoi(optarg); break;
         case 's': send_intent = optarg; break;
         case 'u': update_package = optarg; break;
         case 'w': wipe_data = wipe_cache = 1; break;
@@ -1005,6 +979,16 @@
         case 't': show_text = 1; break;
         case 'x': just_exit = true; break;
         case 'l': locale = optarg; break;
+        case 'g': {
+            if (stage == NULL || *stage == '\0') {
+                char buffer[20] = "1/";
+                strncat(buffer, optarg, sizeof(buffer)-3);
+                stage = strdup(buffer);
+            }
+            break;
+        }
+        case 'p': shutdown_after = true; break;
+        case 'r': reason = optarg; break;
         case '?':
             LOGE("Invalid command argument\n");
             continue;
@@ -1015,13 +999,21 @@
         load_locale_from_cache();
     }
     printf("locale is [%s]\n", locale);
+    printf("stage is [%s]\n", stage);
+    printf("reason is [%s]\n", reason);
 
     Device* device = make_device();
     ui = device->GetUI();
     gCurrentUI = ui;
 
-    ui->Init();
     ui->SetLocale(locale);
+    ui->Init();
+
+    int st_cur, st_max;
+    if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) {
+        ui->SetStage(st_cur, st_max);
+    }
+
     ui->SetBackground(RecoveryUI::NONE);
     if (show_text) ui->ShowText(true);
 
@@ -1102,7 +1094,7 @@
 		else
 			status = INSTALL_ERROR;
 		/*
-        status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE);
+        status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true);
         if (status == INSTALL_SUCCESS && wipe_cache) {
             if (erase_volume("/cache")) {
                 LOGE("Cache wipe (requested by package) failed.");
@@ -1128,6 +1120,7 @@
         if (device->WipeData()) status = INSTALL_ERROR;
         if (erase_volume("/data")) status = INSTALL_ERROR;
         if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
+        if (erase_persistent_partition() == -1 ) status = INSTALL_ERROR;
 		*/
         if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");
     } else if (wipe_cache) {
@@ -1186,11 +1179,13 @@
         copy_logs();
         ui->SetBackground(RecoveryUI::ERROR);
     }
+    Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
     if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
-        prompt_and_wait(device, status);
+        Device::BuiltinAction temp = prompt_and_wait(device, status);
+        if (temp != Device::NO_ACTION) after = temp;
     }
 
-    // Otherwise, get ready to boot the main system...
+    // Save logs and clean up before rebooting or shutting down.
     finish_recovery(send_intent);
     ui->Print("Rebooting...\n");
 	char backup_arg_char[50];
@@ -1213,5 +1208,23 @@
 	reboot(RB_AUTOBOOT);
 #endif
     property_set(ANDROID_RB_PROPERTY, "reboot,");
+
+    switch (after) {
+        case Device::SHUTDOWN:
+            ui->Print("Shutting down...\n");
+            property_set(ANDROID_RB_PROPERTY, "shutdown,");
+            break;
+
+        case Device::REBOOT_BOOTLOADER:
+            ui->Print("Rebooting to bootloader...\n");
+            property_set(ANDROID_RB_PROPERTY, "reboot,bootloader");
+            break;
+
+        default:
+            ui->Print("Rebooting...\n");
+            property_set(ANDROID_RB_PROPERTY, "reboot,");
+            break;
+    }
+    sleep(5); // should reboot before this finishes
     return EXIT_SUCCESS;
 }
diff --git a/res-hdpi/images/erasing_text.png b/res-hdpi/images/erasing_text.png
new file mode 100644
index 0000000..774244c
--- /dev/null
+++ b/res-hdpi/images/erasing_text.png
Binary files differ
diff --git a/res-hdpi/images/error_text.png b/res-hdpi/images/error_text.png
new file mode 100644
index 0000000..64a57ec
--- /dev/null
+++ b/res-hdpi/images/error_text.png
Binary files differ
diff --git a/res-hdpi/images/icon_error.png b/res-hdpi/images/icon_error.png
new file mode 100644
index 0000000..cb3d1ab
--- /dev/null
+++ b/res-hdpi/images/icon_error.png
Binary files differ
diff --git a/res-hdpi/images/icon_installing.png b/res-hdpi/images/icon_installing.png
new file mode 100644
index 0000000..c2c0201
--- /dev/null
+++ b/res-hdpi/images/icon_installing.png
Binary files differ
diff --git a/res-hdpi/images/installing_text.png b/res-hdpi/images/installing_text.png
new file mode 100644
index 0000000..33b54f1
--- /dev/null
+++ b/res-hdpi/images/installing_text.png
Binary files differ
diff --git a/res-hdpi/images/no_command_text.png b/res-hdpi/images/no_command_text.png
new file mode 100644
index 0000000..9927ecb
--- /dev/null
+++ b/res-hdpi/images/no_command_text.png
Binary files differ
diff --git a/res-hdpi/images/progress_empty.png b/res-hdpi/images/progress_empty.png
new file mode 100644
index 0000000..7258183
--- /dev/null
+++ b/res-hdpi/images/progress_empty.png
Binary files differ
diff --git a/res-hdpi/images/progress_fill.png b/res-hdpi/images/progress_fill.png
new file mode 100644
index 0000000..becf87b
--- /dev/null
+++ b/res-hdpi/images/progress_fill.png
Binary files differ
diff --git a/res-hdpi/images/stage_empty.png b/res-hdpi/images/stage_empty.png
new file mode 100644
index 0000000..251ec19
--- /dev/null
+++ b/res-hdpi/images/stage_empty.png
Binary files differ
diff --git a/res-hdpi/images/stage_fill.png b/res-hdpi/images/stage_fill.png
new file mode 100644
index 0000000..1ab79e8
--- /dev/null
+++ b/res-hdpi/images/stage_fill.png
Binary files differ
diff --git a/res-mdpi/images/erasing_text.png b/res-mdpi/images/erasing_text.png
new file mode 100644
index 0000000..fd86c3f
--- /dev/null
+++ b/res-mdpi/images/erasing_text.png
Binary files differ
diff --git a/res-mdpi/images/error_text.png b/res-mdpi/images/error_text.png
new file mode 100644
index 0000000..f1b44c9
--- /dev/null
+++ b/res-mdpi/images/error_text.png
Binary files differ
diff --git a/res-mdpi/images/icon_error.png b/res-mdpi/images/icon_error.png
new file mode 100644
index 0000000..cb3d1ab
--- /dev/null
+++ b/res-mdpi/images/icon_error.png
Binary files differ
diff --git a/res-mdpi/images/icon_installing.png b/res-mdpi/images/icon_installing.png
new file mode 100644
index 0000000..c2c0201
--- /dev/null
+++ b/res-mdpi/images/icon_installing.png
Binary files differ
diff --git a/res-mdpi/images/installing_text.png b/res-mdpi/images/installing_text.png
new file mode 100644
index 0000000..064b2a3
--- /dev/null
+++ b/res-mdpi/images/installing_text.png
Binary files differ
diff --git a/res-mdpi/images/no_command_text.png b/res-mdpi/images/no_command_text.png
new file mode 100644
index 0000000..1f29b89
--- /dev/null
+++ b/res-mdpi/images/no_command_text.png
Binary files differ
diff --git a/res-mdpi/images/progress_empty.png b/res-mdpi/images/progress_empty.png
new file mode 100644
index 0000000..7258183
--- /dev/null
+++ b/res-mdpi/images/progress_empty.png
Binary files differ
diff --git a/res-mdpi/images/progress_fill.png b/res-mdpi/images/progress_fill.png
new file mode 100644
index 0000000..becf87b
--- /dev/null
+++ b/res-mdpi/images/progress_fill.png
Binary files differ
diff --git a/res-mdpi/images/stage_empty.png b/res-mdpi/images/stage_empty.png
new file mode 100644
index 0000000..251ec19
--- /dev/null
+++ b/res-mdpi/images/stage_empty.png
Binary files differ
diff --git a/res-mdpi/images/stage_fill.png b/res-mdpi/images/stage_fill.png
new file mode 100644
index 0000000..1ab79e8
--- /dev/null
+++ b/res-mdpi/images/stage_fill.png
Binary files differ
diff --git a/res-xhdpi/images/erasing_text.png b/res-xhdpi/images/erasing_text.png
new file mode 100644
index 0000000..f88e0e6
--- /dev/null
+++ b/res-xhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xhdpi/images/error_text.png b/res-xhdpi/images/error_text.png
new file mode 100644
index 0000000..c3a4cc6
--- /dev/null
+++ b/res-xhdpi/images/error_text.png
Binary files differ
diff --git a/res-xhdpi/images/icon_error.png b/res-xhdpi/images/icon_error.png
new file mode 100644
index 0000000..cb3d1ab
--- /dev/null
+++ b/res-xhdpi/images/icon_error.png
Binary files differ
diff --git a/res-xhdpi/images/icon_installing.png b/res-xhdpi/images/icon_installing.png
new file mode 100644
index 0000000..c2c0201
--- /dev/null
+++ b/res-xhdpi/images/icon_installing.png
Binary files differ
diff --git a/res-xhdpi/images/installing_text.png b/res-xhdpi/images/installing_text.png
new file mode 100644
index 0000000..a4dacd0
--- /dev/null
+++ b/res-xhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xhdpi/images/no_command_text.png b/res-xhdpi/images/no_command_text.png
new file mode 100644
index 0000000..eb34e94
--- /dev/null
+++ b/res-xhdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xhdpi/images/progress_empty.png b/res-xhdpi/images/progress_empty.png
new file mode 100644
index 0000000..7258183
--- /dev/null
+++ b/res-xhdpi/images/progress_empty.png
Binary files differ
diff --git a/res-xhdpi/images/progress_fill.png b/res-xhdpi/images/progress_fill.png
new file mode 100644
index 0000000..becf87b
--- /dev/null
+++ b/res-xhdpi/images/progress_fill.png
Binary files differ
diff --git a/res-xhdpi/images/stage_empty.png b/res-xhdpi/images/stage_empty.png
new file mode 100644
index 0000000..251ec19
--- /dev/null
+++ b/res-xhdpi/images/stage_empty.png
Binary files differ
diff --git a/res-xhdpi/images/stage_fill.png b/res-xhdpi/images/stage_fill.png
new file mode 100644
index 0000000..1ab79e8
--- /dev/null
+++ b/res-xhdpi/images/stage_fill.png
Binary files differ
diff --git a/res-xxhdpi/images/erasing_text.png b/res-xxhdpi/images/erasing_text.png
new file mode 100644
index 0000000..c87fd52
--- /dev/null
+++ b/res-xxhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xxhdpi/images/error_text.png b/res-xxhdpi/images/error_text.png
new file mode 100644
index 0000000..486e951
--- /dev/null
+++ b/res-xxhdpi/images/error_text.png
Binary files differ
diff --git a/res-xxhdpi/images/icon_error.png b/res-xxhdpi/images/icon_error.png
new file mode 100644
index 0000000..cb3d1ab
--- /dev/null
+++ b/res-xxhdpi/images/icon_error.png
Binary files differ
diff --git a/res-xxhdpi/images/icon_installing.png b/res-xxhdpi/images/icon_installing.png
new file mode 100644
index 0000000..c2c0201
--- /dev/null
+++ b/res-xxhdpi/images/icon_installing.png
Binary files differ
diff --git a/res-xxhdpi/images/installing_text.png b/res-xxhdpi/images/installing_text.png
new file mode 100644
index 0000000..ef6e8f3
--- /dev/null
+++ b/res-xxhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xxhdpi/images/no_command_text.png b/res-xxhdpi/images/no_command_text.png
new file mode 100644
index 0000000..cc98bb1
--- /dev/null
+++ b/res-xxhdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xxhdpi/images/progress_empty.png b/res-xxhdpi/images/progress_empty.png
new file mode 100644
index 0000000..7258183
--- /dev/null
+++ b/res-xxhdpi/images/progress_empty.png
Binary files differ
diff --git a/res-xxhdpi/images/progress_fill.png b/res-xxhdpi/images/progress_fill.png
new file mode 100644
index 0000000..becf87b
--- /dev/null
+++ b/res-xxhdpi/images/progress_fill.png
Binary files differ
diff --git a/res-xxhdpi/images/stage_empty.png b/res-xxhdpi/images/stage_empty.png
new file mode 100644
index 0000000..251ec19
--- /dev/null
+++ b/res-xxhdpi/images/stage_empty.png
Binary files differ
diff --git a/res-xxhdpi/images/stage_fill.png b/res-xxhdpi/images/stage_fill.png
new file mode 100644
index 0000000..1ab79e8
--- /dev/null
+++ b/res-xxhdpi/images/stage_fill.png
Binary files differ
diff --git a/res-xxxhdpi/images/erasing_text.png b/res-xxxhdpi/images/erasing_text.png
new file mode 100644
index 0000000..612e7a3
--- /dev/null
+++ b/res-xxxhdpi/images/erasing_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/error_text.png b/res-xxxhdpi/images/error_text.png
new file mode 100644
index 0000000..50d2fad
--- /dev/null
+++ b/res-xxxhdpi/images/error_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/icon_error.png b/res-xxxhdpi/images/icon_error.png
new file mode 100644
index 0000000..cb3d1ab
--- /dev/null
+++ b/res-xxxhdpi/images/icon_error.png
Binary files differ
diff --git a/res-xxxhdpi/images/icon_installing.png b/res-xxxhdpi/images/icon_installing.png
new file mode 100644
index 0000000..c2c0201
--- /dev/null
+++ b/res-xxxhdpi/images/icon_installing.png
Binary files differ
diff --git a/res-xxxhdpi/images/installing_text.png b/res-xxxhdpi/images/installing_text.png
new file mode 100644
index 0000000..9bd093b
--- /dev/null
+++ b/res-xxxhdpi/images/installing_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/no_command_text.png b/res-xxxhdpi/images/no_command_text.png
new file mode 100644
index 0000000..6354e6a
--- /dev/null
+++ b/res-xxxhdpi/images/no_command_text.png
Binary files differ
diff --git a/res-xxxhdpi/images/progress_empty.png b/res-xxxhdpi/images/progress_empty.png
new file mode 100644
index 0000000..7258183
--- /dev/null
+++ b/res-xxxhdpi/images/progress_empty.png
Binary files differ
diff --git a/res-xxxhdpi/images/progress_fill.png b/res-xxxhdpi/images/progress_fill.png
new file mode 100644
index 0000000..becf87b
--- /dev/null
+++ b/res-xxxhdpi/images/progress_fill.png
Binary files differ
diff --git a/res-xxxhdpi/images/stage_empty.png b/res-xxxhdpi/images/stage_empty.png
new file mode 100644
index 0000000..251ec19
--- /dev/null
+++ b/res-xxxhdpi/images/stage_empty.png
Binary files differ
diff --git a/res-xxxhdpi/images/stage_fill.png b/res-xxxhdpi/images/stage_fill.png
new file mode 100644
index 0000000..1ab79e8
--- /dev/null
+++ b/res-xxxhdpi/images/stage_fill.png
Binary files differ
diff --git a/roots.cpp b/roots.cpp
index 63a8eaf..d69245f 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -19,8 +19,10 @@
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 #include <unistd.h>
 #include <ctype.h>
+#include <fcntl.h>
 
 extern "C" {
 #include <fs_mgr.h>
@@ -30,12 +32,17 @@
 #include "roots.h"
 #include "common.h"
 #include "make_ext4fs.h"
-#include "partitions.hpp"
+extern "C" {
+#include "wipe.h"
+#include "cryptfs.h"
+}
 
 static struct fstab *fstab = NULL;
 
 extern struct selabel_handle *sehandle;
 
+static const char* PERSISTENT_PATH = "/persistent";
+
 void load_volume_table()
 {
     int i;
@@ -47,7 +54,7 @@
         return;
     }
 
-    ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk", 0);
+    ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
     if (ret < 0 ) {
         LOGE("failed to add /tmp entry to fstab\n");
         fs_mgr_free_fstab(fstab);
@@ -157,6 +164,20 @@
     return unmount_mounted_volume(mv);
 }
 
+static int exec_cmd(const char* path, char* const argv[]) {
+    int status;
+    pid_t child;
+    if ((child = vfork()) == 0) {
+        execv(path, argv);
+        _exit(-1);
+    }
+    waitpid(child, &status, 0);
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        LOGE("%s failed with status %d\n", path, WEXITSTATUS(status));
+    }
+    return WEXITSTATUS(status);
+}
+
 int format_volume(const char* volume) {
 	if (PartitionManager.Wipe_By_Path(volume))
 		return 0;
@@ -205,10 +226,51 @@
         return 0;
     }
 
-    if (strcmp(v->fs_type, "ext4") == 0) {
-        int result = make_ext4fs(v->blk_device, v->length, volume, sehandle);
+    if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "f2fs") == 0) {
+        // if there's a key_loc that looks like a path, it should be a
+        // block device for storing encryption metadata.  wipe it too.
+        if (v->key_loc != NULL && v->key_loc[0] == '/') {
+            LOGI("wiping %s\n", v->key_loc);
+            int fd = open(v->key_loc, O_WRONLY | O_CREAT, 0644);
+            if (fd < 0) {
+                LOGE("format_volume: failed to open %s\n", v->key_loc);
+                return -1;
+            }
+            wipe_block_device(fd, get_file_size(fd));
+            close(fd);
+        }
+
+        ssize_t length = 0;
+        if (v->length != 0) {
+            length = v->length;
+        } else if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0) {
+            length = -CRYPT_FOOTER_OFFSET;
+        }
+        int result;
+        if (strcmp(v->fs_type, "ext4") == 0) {
+            result = make_ext4fs(v->blk_device, length, volume, sehandle);
+        } else {   /* Has to be f2fs because we checked earlier. */
+            if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) {
+                LOGE("format_volume: crypt footer + negative length (%zd) not supported on %s\n", length, v->fs_type);
+                return -1;
+            }
+            if (length < 0) {
+                LOGE("format_volume: negative length (%zd) not supported on %s\n", length, v->fs_type);
+                return -1;
+            }
+            char *num_sectors;
+            if (asprintf(&num_sectors, "%zd", length / 512) <= 0) {
+                LOGE("format_volume: failed to create %s command for %s\n", v->fs_type, v->blk_device);
+                return -1;
+            }
+            const char *f2fs_path = "/sbin/mkfs.f2fs";
+            const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, NULL};
+
+            result = exec_cmd(f2fs_path, (char* const*)f2fs_argv);
+            free(num_sectors);
+        }
         if (result != 0) {
-            LOGE("format_volume: make_extf4fs failed on %s\n", v->blk_device);
+            LOGE("format_volume: make %s failed on %s with %d(%s)\n", v->fs_type, v->blk_device, result, strerror(errno));
             return -1;
         }
         return 0;
@@ -218,6 +280,41 @@
     return -1;
 }
 
+int erase_persistent_partition() {
+    Volume *v = volume_for_path(PERSISTENT_PATH);
+    if (v == NULL) {
+        // most devices won't have /persistent, so this is not an error.
+        return 0;
+    }
+
+    int fd = open(v->blk_device, O_RDWR);
+    uint64_t size = get_file_size(fd);
+    if (size == 0) {
+        LOGE("failed to stat size of /persistent\n");
+        close(fd);
+        return -1;
+    }
+
+    char oem_unlock_enabled;
+    lseek(fd, size - 1, SEEK_SET);
+    read(fd, &oem_unlock_enabled, 1);
+
+    if (oem_unlock_enabled) {
+        if (wipe_block_device(fd, size)) {
+           LOGE("error wiping /persistent: %s\n", strerror(errno));
+           close(fd);
+           return -1;
+        }
+
+        lseek(fd, size - 1, SEEK_SET);
+        write(fd, &oem_unlock_enabled, 1);
+    }
+
+    close(fd);
+
+    return (int) oem_unlock_enabled;
+}
+
 int setup_install_mounts() {
     if (fstab == NULL) {
         LOGE("can't set up install mounts: no fstab loaded\n");
@@ -228,10 +325,16 @@
 
         if (strcmp(v->mount_point, "/tmp") == 0 ||
             strcmp(v->mount_point, "/cache") == 0) {
-            if (ensure_path_mounted(v->mount_point) != 0) return -1;
+            if (ensure_path_mounted(v->mount_point) != 0) {
+                LOGE("failed to mount %s\n", v->mount_point);
+                return -1;
+            }
 
         } else {
-            if (ensure_path_unmounted(v->mount_point) != 0) return -1;
+            if (ensure_path_unmounted(v->mount_point) != 0) {
+                LOGE("failed to unmount %s\n", v->mount_point);
+                return -1;
+            }
         }
     }
     return 0;
diff --git a/roots.h b/roots.h
index 230d9de..b62a5b1 100644
--- a/roots.h
+++ b/roots.h
@@ -46,6 +46,11 @@
 // mounted (/tmp and /cache) are mounted.  Returns 0 on success.
 int setup_install_mounts();
 
+// Conditionally wipes the /persistent partition if it's marked
+// to wipe. Returns -1 on failure, 1 if the partition was wiped
+// and 0 if the partition was not wiped.
+int erase_persistent_partition();
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 7e7bd8a..6fff30a 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -58,6 +58,7 @@
 ScreenRecoveryUI::ScreenRecoveryUI() :
     currentIcon(NONE),
     installingFrame(0),
+    locale(NULL),
     rtl_locale(false),
     progressBarType(EMPTY),
     progressScopeStart(0),
@@ -75,67 +76,59 @@
     menu_top(0),
     menu_items(0),
     menu_sel(0),
-
-    // These values are correct for the default image resources
-    // provided with the android platform.  Devices which use
-    // different resources should have a subclass of ScreenRecoveryUI
-    // that overrides Init() to set these values appropriately and
-    // then call the superclass Init().
     animation_fps(20),
-    indeterminate_frames(6),
-    installing_frames(7),
-    install_overlay_offset_x(13),
-    install_overlay_offset_y(190),
-    overlay_offset_x(-1),
-    overlay_offset_y(-1) {
+    installing_frames(-1),
+    stage(-1),
+    max_stage(-1) {
 
     for (int i = 0; i < 5; i++)
         backgroundIcon[i] = NULL;
 
+    memset(text, 0, sizeof(text));
+
     pthread_mutex_init(&updateMutex, NULL);
     self = this;
 }
 
-// Draw the given frame over the installation overlay animation.  The
-// background is not cleared or draw with the base icon first; we
-// assume that the frame already contains some other frame of the
-// animation.  Does nothing if no overlay animation is defined.
-// Should only be called with updateMutex locked.
-void ScreenRecoveryUI::draw_install_overlay_locked(int frame) {
-    if (installationOverlay == NULL || overlay_offset_x < 0) return;
-    gr_surface surface = installationOverlay[frame];
-    int iconWidth = gr_get_width(surface);
-    int iconHeight = gr_get_height(surface);
-    gr_blit(surface, 0, 0, iconWidth, iconHeight,
-            overlay_offset_x, overlay_offset_y);
-}
-
 // Clear the screen and draw the currently selected background icon (if any).
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::draw_background_locked(Icon icon)
 {
     pagesIdentical = false;
     gr_color(0, 0, 0, 255);
-    gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+    gr_clear();
 
     if (icon) {
         gr_surface surface = backgroundIcon[icon];
+        if (icon == INSTALLING_UPDATE || icon == ERASING) {
+            surface = installation[installingFrame];
+        }
         gr_surface text_surface = backgroundText[icon];
 
         int iconWidth = gr_get_width(surface);
         int iconHeight = gr_get_height(surface);
         int textWidth = gr_get_width(text_surface);
         int textHeight = gr_get_height(text_surface);
+        int stageHeight = gr_get_height(stageMarkerEmpty);
 
-        int iconX = (gr_fb_width() - iconWidth) / 2;
-        int iconY = (gr_fb_height() - (iconHeight+textHeight+40)) / 2;
+        int sh = (max_stage >= 0) ? stageHeight : 0;
+
+        iconX = (gr_fb_width() - iconWidth) / 2;
+        iconY = (gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2;
 
         int textX = (gr_fb_width() - textWidth) / 2;
-        int textY = ((gr_fb_height() - (iconHeight+textHeight+40)) / 2) + iconHeight + 40;
+        int textY = ((gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2) + iconHeight + 40;
 
         gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY);
-        if (icon == INSTALLING_UPDATE || icon == ERASING) {
-            draw_install_overlay_locked(installingFrame);
+        if (stageHeight > 0) {
+            int sw = gr_get_width(stageMarkerEmpty);
+            int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2;
+            int y = iconY + iconHeight + 20;
+            for (int i = 0; i < max_stage; ++i) {
+                gr_blit((i < stage) ? stageMarkerFill : stageMarkerEmpty,
+                        0, 0, sw, stageHeight, x, y);
+                x += sw;
+            }
         }
 
         gr_color(255, 255, 255, 255);
@@ -150,7 +143,8 @@
     if (currentIcon == ERROR) return;
 
     if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
-        draw_install_overlay_locked(installingFrame);
+        gr_surface icon = installation[installingFrame];
+        gr_blit(icon, 0, 0, gr_get_width(icon), gr_get_height(icon), iconX, iconY);
     }
 
     if (progressBarType != EMPTY) {
@@ -187,18 +181,6 @@
                 }
             }
         }
-
-        if (progressBarType == INDETERMINATE) {
-            static int frame = 0;
-            gr_blit(progressBarIndeterminate[frame], 0, 0, width, height, dx, dy);
-            // in RTL locales, we run the animation backwards, which
-            // makes the spinner spin the other way.
-            if (rtl_locale) {
-                frame = (frame + indeterminate_frames - 1) % indeterminate_frames;
-            } else {
-                frame = (frame + 1) % indeterminate_frames;
-            }
-        }
     }
 }
 
@@ -230,12 +212,12 @@
 // Should only be called with updateMutex locked.
 void ScreenRecoveryUI::draw_screen_locked()
 {
-    draw_background_locked(currentIcon);
-    draw_progress_locked();
-
-    if (show_text) {
-        SetColor(TEXT_FILL);
-        gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+    if (!show_text) {
+        draw_background_locked(currentIcon);
+        draw_progress_locked();
+    } else {
+        gr_color(0, 0, 0, 255);
+        gr_clear();
 
         int y = 0;
         int i = 0;
@@ -325,12 +307,6 @@
             redraw = 1;
         }
 
-        // update the progress bar animation, if active
-        // skip this if we have a text overlay (too expensive to update)
-        if (progressBarType == INDETERMINATE && !show_text) {
-            redraw = 1;
-        }
-
         // move the progress bar forward on timed intervals, if configured
         int duration = progressScopeDuration;
         if (progressBarType == DETERMINATE && duration > 0) {
@@ -355,14 +331,21 @@
 }
 
 void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) {
-    int result = res_create_surface(filename, surface);
+    int result = res_create_display_surface(filename, surface);
+    if (result < 0) {
+        LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
+    }
+}
+
+void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, gr_surface** surface) {
+    int result = res_create_multi_display_surface(filename, frames, surface);
     if (result < 0) {
         LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
     }
 }
 
 void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, gr_surface* surface) {
-    int result = res_create_localized_surface(filename, surface);
+    int result = res_create_localized_alpha_surface(filename, locale, surface);
     if (result < 0) {
         LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
     }
@@ -382,51 +365,31 @@
     text_cols = gr_fb_width() / char_width;
     if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1;
 
-    LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]);
+    backgroundIcon[NONE] = NULL;
+    LoadBitmapArray("icon_installing", &installing_frames, &installation);
+    backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : NULL;
     backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
     LoadBitmap("icon_error", &backgroundIcon[ERROR]);
     backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
 
     LoadBitmap("progress_empty", &progressBarEmpty);
     LoadBitmap("progress_fill", &progressBarFill);
+    LoadBitmap("stage_empty", &stageMarkerEmpty);
+    LoadBitmap("stage_fill", &stageMarkerFill);
 
     LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]);
     LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]);
     LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]);
     LoadLocalizedBitmap("error_text", &backgroundText[ERROR]);
 
-    int i;
-
-    progressBarIndeterminate = (gr_surface*)malloc(indeterminate_frames *
-                                                    sizeof(gr_surface));
-    for (i = 0; i < indeterminate_frames; ++i) {
-        char filename[40];
-        // "indeterminate01.png", "indeterminate02.png", ...
-        sprintf(filename, "indeterminate%02d", i+1);
-        LoadBitmap(filename, progressBarIndeterminate+i);
-    }
-
-    if (installing_frames > 0) {
-        installationOverlay = (gr_surface*)malloc(installing_frames *
-                                                   sizeof(gr_surface));
-        for (i = 0; i < installing_frames; ++i) {
-            char filename[40];
-            // "icon_installing_overlay01.png",
-            // "icon_installing_overlay02.png", ...
-            sprintf(filename, "icon_installing_overlay%02d", i+1);
-            LoadBitmap(filename, installationOverlay+i);
-        }
-    } else {
-        installationOverlay = NULL;
-    }
-
     pthread_create(&progress_t, NULL, progress_thread, NULL);
 
     RecoveryUI::Init();
 }
 
-void ScreenRecoveryUI::SetLocale(const char* locale) {
-    if (locale) {
+void ScreenRecoveryUI::SetLocale(const char* new_locale) {
+    if (new_locale) {
+        this->locale = new_locale;
         char* lang = strdup(locale);
         for (char* p = lang; *p; ++p) {
             if (*p == '_') {
@@ -445,6 +408,8 @@
             rtl_locale = true;
         }
         free(lang);
+    } else {
+        new_locale = NULL;
     }
 }
 
@@ -452,16 +417,6 @@
 {
     pthread_mutex_lock(&updateMutex);
 
-    // Adjust the offset to account for the positioning of the
-    // base image on the screen.
-    if (backgroundIcon[icon] != NULL) {
-        gr_surface bg = backgroundIcon[icon];
-        gr_surface text = backgroundText[icon];
-        overlay_offset_x = install_overlay_offset_x + (gr_fb_width() - gr_get_width(bg)) / 2;
-        overlay_offset_y = install_overlay_offset_y +
-            (gr_fb_height() - (gr_get_height(bg) + gr_get_height(text) + 40)) / 2;
-    }
-
     currentIcon = icon;
     update_screen_locked();
 
@@ -506,7 +461,7 @@
     if (fraction > 1.0) fraction = 1.0;
     if (progressBarType == DETERMINATE && fraction > progress) {
         // Skip updates that aren't visibly different.
-        int width = gr_get_width(progressBarIndeterminate[0]);
+        int width = gr_get_width(progressBarEmpty);
         float scale = width * progressScopeSize;
         if ((int) (progress * scale) != (int) (fraction * scale)) {
             progress = fraction;
@@ -516,6 +471,13 @@
     pthread_mutex_unlock(&updateMutex);
 }
 
+void ScreenRecoveryUI::SetStage(int current, int max) {
+    pthread_mutex_lock(&updateMutex);
+    stage = current;
+    max_stage = max;
+    pthread_mutex_unlock(&updateMutex);
+}
+
 void ScreenRecoveryUI::Print(const char *fmt, ...)
 {
     char buf[256];
diff --git a/screen_ui.h b/screen_ui.h
index fc35d95..01a33bf 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -39,6 +39,8 @@
     void ShowProgress(float portion, float seconds);
     void SetProgress(float fraction);
 
+    void SetStage(int current, int max);
+
     // text log
     void ShowText(bool visible);
     bool IsTextVisible();
@@ -58,21 +60,20 @@
     enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL };
     virtual void SetColor(UIElement e);
 
-  protected:
-    int install_overlay_offset_x, install_overlay_offset_y;
-
   private:
     Icon currentIcon;
     int installingFrame;
+    const char* locale;
     bool rtl_locale;
 
     pthread_mutex_t updateMutex;
     gr_surface backgroundIcon[5];
     gr_surface backgroundText[5];
-    gr_surface *installationOverlay;
-    gr_surface *progressBarIndeterminate;
+    gr_surface *installation;
     gr_surface progressBarEmpty;
     gr_surface progressBarFill;
+    gr_surface stageMarkerEmpty;
+    gr_surface stageMarkerFill;
 
     ProgressType progressBarType;
 
@@ -100,11 +101,14 @@
     pthread_t progress_t;
 
     int animation_fps;
-    int indeterminate_frames;
     int installing_frames;
-    int overlay_offset_x, overlay_offset_y;
+  protected:
+  private:
 
-    void draw_install_overlay_locked(int frame);
+    int iconX, iconY;
+
+    int stage, max_stage;
+
     void draw_background_locked(Icon icon);
     void draw_progress_locked();
     void draw_screen_locked();
@@ -114,6 +118,7 @@
     void progress_loop();
 
     void LoadBitmap(const char* filename, gr_surface* surface);
+    void LoadBitmapArray(const char* filename, int* frames, gr_surface** surface);
     void LoadLocalizedBitmap(const char* filename, gr_surface* surface);
 };
 
diff --git a/testdata/otasigned_ecdsa_sha256.zip b/testdata/otasigned_ecdsa_sha256.zip
new file mode 100644
index 0000000..999fcdd
--- /dev/null
+++ b/testdata/otasigned_ecdsa_sha256.zip
Binary files differ
diff --git a/testdata/testkey_ecdsa.pk8 b/testdata/testkey_ecdsa.pk8
new file mode 100644
index 0000000..9a521c8
--- /dev/null
+++ b/testdata/testkey_ecdsa.pk8
Binary files differ
diff --git a/testdata/testkey_ecdsa.x509.pem b/testdata/testkey_ecdsa.x509.pem
new file mode 100644
index 0000000..b122836
--- /dev/null
+++ b/testdata/testkey_ecdsa.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBezCCASACCQC4g5wurPSmtzAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTET
+MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMB4XDTEzMTAwODIxMTAxM1oXDTE0MTAwODIxMTAxM1owRTELMAkGA1UE
+BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
+ZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGcO1QDowF2E
+RboWVmAYI2oXTr5MHAJ4xpMUFsrWVvoktYSN2RhNuOl5jZGvSBsQII9p/4qfjLmS
+TBaCfQ0Xmt4wCgYIKoZIzj0EAwIDSQAwRgIhAIJjWmZAwngc2VcHUhYp2oSLoCQ+
+P+7AtbAn5242AqfOAiEAghO0t6jTKs0LUhLJrQwbOkHyZMVdZaG2vcwV9y9H5Qc=
+-----END CERTIFICATE-----
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..4d99d52
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,26 @@
+# Build the unit tests.
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Build the unit tests.
+test_src_files := \
+    asn1_decoder_test.cpp
+
+shared_libraries := \
+    liblog \
+    libcutils
+
+static_libraries := \
+    libgtest \
+    libgtest_main \
+    libverifier
+
+$(foreach file,$(test_src_files), \
+    $(eval include $(CLEAR_VARS)) \
+    $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+    $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+    $(eval LOCAL_SRC_FILES := $(file)) \
+    $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+    $(eval LOCAL_C_INCLUDES := $(LOCAL_PATH)/..) \
+    $(eval include $(BUILD_NATIVE_TEST)) \
+)
\ No newline at end of file
diff --git a/tests/asn1_decoder_test.cpp b/tests/asn1_decoder_test.cpp
new file mode 100644
index 0000000..af96d87
--- /dev/null
+++ b/tests/asn1_decoder_test.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#define LOG_TAG "asn1_decoder_test"
+
+#include <cutils/log.h>
+#include <gtest/gtest.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include "asn1_decoder.h"
+
+namespace android {
+
+class Asn1DecoderTest : public testing::Test {
+};
+
+TEST_F(Asn1DecoderTest, Empty_Failure) {
+    uint8_t empty[] = { };
+    asn1_context_t* ctx = asn1_context_new(empty, sizeof(empty));
+
+    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+    EXPECT_FALSE(asn1_constructed_skip_all(ctx));
+    EXPECT_EQ(0, asn1_constructed_type(ctx));
+    EXPECT_EQ(NULL, asn1_sequence_get(ctx));
+    EXPECT_EQ(NULL, asn1_set_get(ctx));
+    EXPECT_FALSE(asn1_sequence_next(ctx));
+
+    uint8_t* junk;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ctx, &junk, &length));
+    EXPECT_FALSE(asn1_octet_string_get(ctx, &junk, &length));
+
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0xA0, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) {
+    uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A,
+                            0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) {
+    uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_constructed_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    EXPECT_EQ(5, asn1_constructed_type(ptr));
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_Success) {
+    uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_constructed_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    EXPECT_EQ(5, asn1_constructed_type(ptr));
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0x01U, *oid);
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0xA2, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_FALSE(asn1_constructed_skip_all(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedSkipAll_Success) {
+    uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01,
+                            0xA1, 0x03, 0x02, 0x01, 0x01,
+                            0x06, 0x01, 0xA5, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    ASSERT_TRUE(asn1_constructed_skip_all(ctx));
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0xA5U, *oid);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0x30, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_sequence_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) {
+    uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_sequence_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_Success) {
+    uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_sequence_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0x01U, *oid);
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0x31, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_set_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) {
+    uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_set_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_Success) {
+    uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_set_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0xBAU, *oid);
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_LengthZero_Failure) {
+    uint8_t data[] = { 0x06, 0x00, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_TooSmall_Failure) {
+    uint8_t data[] = { 0x06, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_Success) {
+    uint8_t data[] = { 0x06, 0x01, 0x99, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0x99U, *oid);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) {
+    uint8_t data[] = { 0x04, 0x00, 0x55, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* string;
+    size_t length;
+    ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) {
+    uint8_t data[] = { 0x04, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* string;
+    size_t length;
+    ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_Success) {
+    uint8_t data[] = { 0x04, 0x01, 0xAA, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* string;
+    size_t length;
+    ASSERT_TRUE(asn1_octet_string_get(ctx, &string, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0xAAU, *string);
+    asn1_context_free(ctx);
+}
+
+} // namespace android
diff --git a/tools/ota/add-property-tag.c b/tools/ota/add-property-tag.c
index 5277edd..aab30b2 100644
--- a/tools/ota/add-property-tag.c
+++ b/tools/ota/add-property-tag.c
@@ -57,9 +57,9 @@
     const char *end = line + strlen(line);
     while (end > line && isspace(end[-1])) --end;
     if (number > 0) {
-        fprintf(out, "%.*s%s%d%s", end - line, line, tag, number, end);
+        fprintf(out, "%.*s%s%d%s", (int)(end - line), line, tag, number, end);
     } else {
-        fprintf(out, "%.*s%s%s", end - line, line, tag, end);
+        fprintf(out, "%.*s%s%s", (int)(end - line), line, tag, end);
     }
 }
 
diff --git a/twrp.cpp b/twrp.cpp
index 7361396..2849af7 100644
--- a/twrp.cpp
+++ b/twrp.cpp
@@ -91,7 +91,7 @@
 	property_set("ro.twrp.version", TW_VERSION_STR);
 
 	time_t StartupTime = time(NULL);
-	printf("Starting TWRP %s on %s", TW_VERSION_STR, ctime(&StartupTime));
+	printf("Starting TWRP %s on %s (pid %d)", TW_VERSION_STR, ctime(&StartupTime), getpid());
 
 	// Load default values to set DataManager constants and handle ifdefs
 	DataManager::SetDefaultValues();
@@ -160,7 +160,7 @@
 	PartitionManager.Mount_By_Path("/cache", true);
 
 	string Zip_File, Reboot_Value;
-	bool Cache_Wipe = false, Factory_Reset = false, Perform_Backup = false;
+	bool Cache_Wipe = false, Factory_Reset = false, Perform_Backup = false, Shutdown = false;
 
 	{
 		TWPartition* misc = PartitionManager.Find_Partition_By_Path("/misc");
@@ -206,6 +206,8 @@
 					Cache_Wipe = true;
 			} else if (*argptr == 'n') {
 				Perform_Backup = true;
+			} else if (*argptr == 'p') {
+				Shutdown = true;
 			} else if (*argptr == 's') {
 				ptr = argptr;
 				index2 = 0;
diff --git a/ui.cpp b/ui.cpp
index 44656d1..8e5b0f0 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -33,6 +33,7 @@
 #endif
 
 #include "common.h"
+#include "roots.h"
 #include "device.h"
 #include "minui/minui.h"
 #include "screen_ui.h"
@@ -49,10 +50,15 @@
     key_queue_len(0),
     key_last_down(-1),
     key_long_press(false),
-    key_down_count(0) {
+    key_down_count(0),
+    enable_reboot(true),
+    consecutive_power_keys(0),
+    consecutive_alternate_keys(0),
+    last_key(-1) {
     pthread_mutex_init(&key_queue_mutex, NULL);
     pthread_cond_init(&key_queue_cond, NULL);
     self = this;
+    memset(key_pressed, 0, sizeof(key_pressed));
 }
 
 void RecoveryUI::Init() {
@@ -61,12 +67,12 @@
 }
 
 
-int RecoveryUI::input_callback(int fd, short revents, void* data)
+int RecoveryUI::input_callback(int fd, uint32_t epevents, void* data)
 {
     struct input_event ev;
     int ret;
 
-    ret = ev_get_input(fd, revents, &ev);
+    ret = ev_get_input(fd, epevents, &ev);
     if (ret)
         return -1;
 
@@ -114,6 +120,7 @@
 void RecoveryUI::process_key(int key_code, int updown) {
     bool register_key = false;
     bool long_press = false;
+    bool reboot_enabled;
 
     pthread_mutex_lock(&key_queue_mutex);
     key_pressed[key_code] = updown;
@@ -135,6 +142,7 @@
         }
         key_last_down = -1;
     }
+    reboot_enabled = enable_reboot;
     pthread_mutex_unlock(&key_queue_mutex);
 
     if (register_key) {
@@ -149,13 +157,22 @@
 
           case RecoveryUI::REBOOT:
 #ifdef ANDROID_RB_RESTART
-            android_reboot(ANDROID_RB_RESTART, 0, 0);
+            if (reboot_enabled) {
+                android_reboot(ANDROID_RB_RESTART, 0, 0);
+            }
 #endif
             break;
 
           case RecoveryUI::ENQUEUE:
             EnqueueKey(key_code);
             break;
+
+          case RecoveryUI::MOUNT_SYSTEM:
+#ifndef NO_RECOVERY_MOUNT
+            ensure_path_mounted("/system");
+            Print("Mounted /system.");
+#endif
+            break;
         }
     }
 }
@@ -262,8 +279,47 @@
     pthread_mutex_unlock(&key_queue_mutex);
 }
 
+// The default CheckKey implementation assumes the device has power,
+// volume up, and volume down keys.
+//
+// - Hold power and press vol-up to toggle display.
+// - Press power seven times in a row to reboot.
+// - Alternate vol-up and vol-down seven times to mount /system.
 RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) {
-    return RecoveryUI::ENQUEUE;
+    if ((IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) || key == KEY_HOME) {
+        return TOGGLE;
+    }
+
+    if (key == KEY_POWER) {
+        pthread_mutex_lock(&key_queue_mutex);
+        bool reboot_enabled = enable_reboot;
+        pthread_mutex_unlock(&key_queue_mutex);
+
+        if (reboot_enabled) {
+            ++consecutive_power_keys;
+            if (consecutive_power_keys >= 7) {
+                return REBOOT;
+            }
+        }
+    } else {
+        consecutive_power_keys = 0;
+    }
+
+    if ((key == KEY_VOLUMEUP &&
+         (last_key == KEY_VOLUMEDOWN || last_key == -1)) ||
+        (key == KEY_VOLUMEDOWN &&
+         (last_key == KEY_VOLUMEUP || last_key == -1))) {
+        ++consecutive_alternate_keys;
+        if (consecutive_alternate_keys >= 7) {
+            consecutive_alternate_keys = 0;
+            return MOUNT_SYSTEM;
+        }
+    } else {
+        consecutive_alternate_keys = 0;
+    }
+    last_key = key;
+
+    return ENQUEUE;
 }
 
 void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) {
@@ -271,3 +327,9 @@
 
 void RecoveryUI::KeyLongPress(int key) {
 }
+
+void RecoveryUI::SetEnableReboot(bool enabled) {
+    pthread_mutex_lock(&key_queue_mutex);
+    enable_reboot = enabled;
+    pthread_mutex_unlock(&key_queue_mutex);
+}
diff --git a/ui.h b/ui.h
index 6c8987a..31a8a7f 100644
--- a/ui.h
+++ b/ui.h
@@ -30,6 +30,8 @@
 
     // Initialize the object; called before anything else.
     virtual void Init();
+    // Show a stage indicator.  Call immediately after Init().
+    virtual void SetStage(int current, int max) { }
 
     // After calling Init(), you can tell the UI what locale it is operating in.
     virtual void SetLocale(const char* locale) { }
@@ -77,7 +79,7 @@
     // Return value indicates whether an immediate operation should be
     // triggered (toggling the display, rebooting the device), or if
     // the key should be enqueued for use by the main thread.
-    enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE };
+    enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE, MOUNT_SYSTEM };
     virtual KeyAction CheckKey(int key);
 
     // Called immediately before each call to CheckKey(), tell you if
@@ -91,6 +93,13 @@
     // be called with "true".
     virtual void KeyLongPress(int key);
 
+    // Normally in recovery there's a key sequence that triggers
+    // immediate reboot of the device, regardless of what recovery is
+    // doing (with the default CheckKey implementation, it's pressing
+    // the power button 7 times in row).  Call this to enable or
+    // disable that feature.  It is enabled by default.
+    virtual void SetEnableReboot(bool enabled);
+
     // --- menu display ---
 
     // Display some header text followed by a menu of items, which appears
@@ -119,8 +128,13 @@
     int key_last_down;                 // under key_queue_mutex
     bool key_long_press;               // under key_queue_mutex
     int key_down_count;                // under key_queue_mutex
+    bool enable_reboot;                // under key_queue_mutex
     int rel_sum;
 
+    int consecutive_power_keys;
+    int consecutive_alternate_keys;
+    int last_key;
+
     typedef struct {
         RecoveryUI* ui;
         int key_code;
@@ -130,7 +144,7 @@
     pthread_t input_t;
 
     static void* input_thread(void* cookie);
-    static int input_callback(int fd, short revents, void* data);
+    static int input_callback(int fd, uint32_t epevents, void* data);
     void process_key(int key_code, int updown);
     bool usb_connected();
 
diff --git a/minelf/Android.mk b/uncrypt/Android.mk
similarity index 77%
rename from minelf/Android.mk
rename to uncrypt/Android.mk
index 10818ea..b8755fb 100644
--- a/minelf/Android.mk
+++ b/uncrypt/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 The Android Open Source Project
+# Copyright (C) 2014 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.
@@ -13,15 +13,14 @@
 # limitations under the License.
 
 LOCAL_PATH := $(call my-dir)
+
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := \
-	Retouch.c
-
 LOCAL_C_INCLUDES += $(commands_recovery_local_path)
+LOCAL_SRC_FILES := uncrypt.c
 
-LOCAL_MODULE := libminelf
+LOCAL_MODULE := uncrypt
 
-LOCAL_CFLAGS += -Wall
+LOCAL_STATIC_LIBRARIES := libfs_mgr liblog libcutils
 
-include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_EXECUTABLE)
diff --git a/uncrypt/uncrypt.c b/uncrypt/uncrypt.c
new file mode 100644
index 0000000..189fa57
--- /dev/null
+++ b/uncrypt/uncrypt.c
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+// This program takes a file on an ext4 filesystem and produces a list
+// of the blocks that file occupies, which enables the file contents
+// to be read directly from the block device without mounting the
+// filesystem.
+//
+// If the filesystem is using an encrypted block device, it will also
+// read the file and rewrite it to the same blocks of the underlying
+// (unencrypted) block device, so the file contents can be read
+// without the need for the decryption key.
+//
+// The output of this program is a "block map" which looks like this:
+//
+//     /dev/block/platform/msm_sdcc.1/by-name/userdata     # block device
+//     49652 4096                        # file size in bytes, block size
+//     3                                 # count of block ranges
+//     1000 1008                         # block range 0
+//     2100 2102                         # ... block range 1
+//     30 33                             # ... block range 2
+//
+// Each block range represents a half-open interval; the line "30 33"
+// reprents the blocks [30, 31, 32].
+//
+// Recovery can take this block map file and retrieve the underlying
+// file data to use as an update package.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/mman.h>
+
+#define LOG_TAG "uncrypt"
+#include <log/log.h>
+#include <cutils/properties.h>
+#include <fs_mgr.h>
+
+#define WINDOW_SIZE 5
+#define RECOVERY_COMMAND_FILE "/cache/recovery/command"
+#define RECOVERY_COMMAND_FILE_TMP "/cache/recovery/command.tmp"
+#define CACHE_BLOCK_MAP "/cache/recovery/block.map"
+
+static struct fstab* fstab = NULL;
+
+static int write_at_offset(unsigned char* buffer, size_t size,
+                           int wfd, off64_t offset)
+{
+    lseek64(wfd, offset, SEEK_SET);
+    size_t written = 0;
+    while (written < size) {
+        ssize_t wrote = write(wfd, buffer + written, size - written);
+        if (wrote < 0) {
+            ALOGE("error writing offset %lld: %s\n", offset, strerror(errno));
+            return -1;
+        }
+        written += wrote;
+    }
+    return 0;
+}
+
+void add_block_to_ranges(int** ranges, int* range_alloc, int* range_used, int new_block)
+{
+    // If the current block start is < 0, set the start to the new
+    // block.  (This only happens for the very first block of the very
+    // first range.)
+    if ((*ranges)[*range_used*2-2] < 0) {
+        (*ranges)[*range_used*2-2] = new_block;
+        (*ranges)[*range_used*2-1] = new_block;
+    }
+
+    if (new_block == (*ranges)[*range_used*2-1]) {
+        // If the new block comes immediately after the current range,
+        // all we have to do is extend the current range.
+        ++(*ranges)[*range_used*2-1];
+    } else {
+        // We need to start a new range.
+
+        // If there isn't enough room in the array, we need to expand it.
+        if (*range_used >= *range_alloc) {
+            *range_alloc *= 2;
+            *ranges = realloc(*ranges, *range_alloc * 2 * sizeof(int));
+        }
+
+        ++*range_used;
+        (*ranges)[*range_used*2-2] = new_block;
+        (*ranges)[*range_used*2-1] = new_block+1;
+    }
+}
+
+static struct fstab* read_fstab()
+{
+    fstab = NULL;
+
+    // The fstab path is always "/fstab.${ro.hardware}".
+    char fstab_path[PATH_MAX+1] = "/fstab.";
+    if (!property_get("ro.hardware", fstab_path+strlen(fstab_path), "")) {
+        ALOGE("failed to get ro.hardware\n");
+        return NULL;
+    }
+
+    fstab = fs_mgr_read_fstab(fstab_path);
+    if (!fstab) {
+        ALOGE("failed to read %s\n", fstab_path);
+        return NULL;
+    }
+
+    return fstab;
+}
+
+const char* find_block_device(const char* path, int* encryptable, int* encrypted)
+{
+    // Look for a volume whose mount point is the prefix of path and
+    // return its block device.  Set encrypted if it's currently
+    // encrypted.
+    int i;
+    for (i = 0; i < fstab->num_entries; ++i) {
+        struct fstab_rec* v = &fstab->recs[i];
+        if (!v->mount_point) continue;
+        int len = strlen(v->mount_point);
+        if (strncmp(path, v->mount_point, len) == 0 &&
+            (path[len] == '/' || path[len] == 0)) {
+            *encrypted = 0;
+            *encryptable = 0;
+            if (fs_mgr_is_encryptable(v)) {
+                *encryptable = 1;
+                char buffer[PROPERTY_VALUE_MAX+1];
+                if (property_get("ro.crypto.state", buffer, "") &&
+                    strcmp(buffer, "encrypted") == 0) {
+                    *encrypted = 1;
+                }
+            }
+            return v->blk_device;
+        }
+    }
+
+    return NULL;
+}
+
+char* parse_recovery_command_file()
+{
+    char* fn = NULL;
+    int count = 0;
+    char temp[1024];
+
+    FILE* f = fopen(RECOVERY_COMMAND_FILE, "r");
+    if (f == NULL) {
+        return NULL;
+    }
+    FILE* fo = fopen(RECOVERY_COMMAND_FILE_TMP, "w");
+
+    while (fgets(temp, sizeof(temp), f)) {
+        printf("read: %s", temp);
+        if (strncmp(temp, "--update_package=/data/", strlen("--update_package=/data/")) == 0) {
+            fn = strdup(temp + strlen("--update_package="));
+            strcpy(temp, "--update_package=@" CACHE_BLOCK_MAP "\n");
+        }
+        fputs(temp, fo);
+    }
+    fclose(f);
+    fclose(fo);
+
+    if (fn) {
+        char* newline = strchr(fn, '\n');
+        if (newline) *newline = 0;
+    }
+    return fn;
+}
+
+int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
+                      int encrypted)
+{
+    struct stat sb;
+    int ret;
+
+    FILE* mapf = fopen(map_file, "w");
+
+    ret = stat(path, &sb);
+    if (ret != 0) {
+        ALOGE("failed to stat %s\n", path);
+        return -1;
+    }
+
+    ALOGI(" block size: %ld bytes\n", (long)sb.st_blksize);
+
+    int blocks = ((sb.st_size-1) / sb.st_blksize) + 1;
+    ALOGI("  file size: %lld bytes, %d blocks\n", (long long)sb.st_size, blocks);
+
+    int* ranges;
+    int range_alloc = 1;
+    int range_used = 1;
+    ranges = malloc(range_alloc * 2 * sizeof(int));
+    ranges[0] = -1;
+    ranges[1] = -1;
+
+    fprintf(mapf, "%s\n%lld %lu\n", blk_dev, (long long)sb.st_size, (unsigned long)sb.st_blksize);
+
+    unsigned char* buffers[WINDOW_SIZE];
+    int i;
+    if (encrypted) {
+        for (i = 0; i < WINDOW_SIZE; ++i) {
+            buffers[i] = malloc(sb.st_blksize);
+        }
+    }
+    int head_block = 0;
+    int head = 0, tail = 0;
+    size_t pos = 0;
+
+    int fd = open(path, O_RDONLY);
+    if (fd < 0) {
+        ALOGE("failed to open fd for reading: %s\n", strerror(errno));
+        return -1;
+    }
+    fsync(fd);
+
+    int wfd = -1;
+    if (encrypted) {
+        wfd = open(blk_dev, O_WRONLY);
+        if (wfd < 0) {
+            ALOGE("failed to open fd for writing: %s\n", strerror(errno));
+            return -1;
+        }
+    }
+
+    while (pos < sb.st_size) {
+        if ((tail+1) % WINDOW_SIZE == head) {
+            // write out head buffer
+            int block = head_block;
+            ret = ioctl(fd, FIBMAP, &block);
+            if (ret != 0) {
+                ALOGE("failed to find block %d\n", head_block);
+                return -1;
+            }
+            add_block_to_ranges(&ranges, &range_alloc, &range_used, block);
+            if (encrypted) {
+                if (write_at_offset(buffers[head], sb.st_blksize, wfd, (off64_t)sb.st_blksize * block) != 0) {
+                    return -1;
+                }
+            }
+            head = (head + 1) % WINDOW_SIZE;
+            ++head_block;
+        }
+
+        // read next block to tail
+        if (encrypted) {
+            size_t so_far = 0;
+            while (so_far < sb.st_blksize && pos < sb.st_size) {
+                ssize_t this_read = read(fd, buffers[tail] + so_far, sb.st_blksize - so_far);
+                if (this_read < 0) {
+                    ALOGE("failed to read: %s\n", strerror(errno));
+                    return -1;
+                }
+                so_far += this_read;
+                pos += this_read;
+            }
+        } else {
+            // If we're not encrypting; we don't need to actually read
+            // anything, just skip pos forward as if we'd read a
+            // block.
+            pos += sb.st_blksize;
+        }
+        tail = (tail+1) % WINDOW_SIZE;
+    }
+
+    while (head != tail) {
+        // write out head buffer
+        int block = head_block;
+        ret = ioctl(fd, FIBMAP, &block);
+        if (ret != 0) {
+            ALOGE("failed to find block %d\n", head_block);
+            return -1;
+        }
+        add_block_to_ranges(&ranges, &range_alloc, &range_used, block);
+        if (encrypted) {
+            if (write_at_offset(buffers[head], sb.st_blksize, wfd, (off64_t)sb.st_blksize * block) != 0) {
+                return -1;
+            }
+        }
+        head = (head + 1) % WINDOW_SIZE;
+        ++head_block;
+    }
+
+    fprintf(mapf, "%d\n", range_used);
+    for (i = 0; i < range_used; ++i) {
+        fprintf(mapf, "%d %d\n", ranges[i*2], ranges[i*2+1]);
+    }
+
+    fclose(mapf);
+    close(fd);
+    if (encrypted) {
+        close(wfd);
+    }
+
+    return 0;
+}
+
+void wipe_misc() {
+    ALOGI("removing old commands from misc");
+    int i;
+    for (i = 0; i < fstab->num_entries; ++i) {
+        struct fstab_rec* v = &fstab->recs[i];
+        if (!v->mount_point) continue;
+        if (strcmp(v->mount_point, "/misc") == 0) {
+            int fd = open(v->blk_device, O_WRONLY);
+            uint8_t zeroes[1088];   // sizeof(bootloader_message) from recovery
+            memset(zeroes, 0, sizeof(zeroes));
+
+            size_t written = 0;
+            size_t size = sizeof(zeroes);
+            while (written < size) {
+                ssize_t w = write(fd, zeroes, size-written);
+                if (w < 0 && errno != EINTR) {
+                    ALOGE("zero write failed: %s\n", strerror(errno));
+                    return;
+                } else {
+                    written += w;
+                }
+            }
+
+            close(fd);
+        }
+    }
+}
+
+void reboot_to_recovery() {
+    ALOGI("rebooting to recovery");
+    property_set("sys.powerctl", "reboot,recovery");
+    sleep(10);
+    ALOGE("reboot didn't succeed?");
+}
+
+int main(int argc, char** argv)
+{
+    const char* input_path;
+    const char* map_file;
+    int do_reboot = 1;
+
+    if (argc != 1 && argc != 3) {
+        fprintf(stderr, "usage: %s [<transform_path> <map_file>]\n", argv[0]);
+        return 2;
+    }
+
+    if (argc == 3) {
+        // when command-line args are given this binary is being used
+        // for debugging; don't reboot to recovery at the end.
+        input_path = argv[1];
+        map_file = argv[2];
+        do_reboot = 0;
+    } else {
+        input_path = parse_recovery_command_file();
+        if (input_path == NULL) {
+            // if we're rebooting to recovery without a package (say,
+            // to wipe data), then we don't need to do anything before
+            // going to recovery.
+            ALOGI("no recovery command file or no update package arg");
+            reboot_to_recovery();
+            return 1;
+        }
+        map_file = CACHE_BLOCK_MAP;
+    }
+
+    ALOGI("update package is %s", input_path);
+
+    // Turn the name of the file we're supposed to convert into an
+    // absolute path, so we can find what filesystem it's on.
+    char path[PATH_MAX+1];
+    if (realpath(input_path, path) == NULL) {
+        ALOGE("failed to convert %s to absolute path: %s", input_path, strerror(errno));
+        return 1;
+    }
+
+    int encryptable;
+    int encrypted;
+    if (read_fstab() == NULL) {
+        return 1;
+    }
+    const char* blk_dev = find_block_device(path, &encryptable, &encrypted);
+    if (blk_dev == NULL) {
+        ALOGE("failed to find block device for %s", path);
+        return 1;
+    }
+
+    // If the filesystem it's on isn't encrypted, we only produce the
+    // block map, we don't rewrite the file contents (it would be
+    // pointless to do so).
+    ALOGI("encryptable: %s\n", encryptable ? "yes" : "no");
+    ALOGI("  encrypted: %s\n", encrypted ? "yes" : "no");
+
+    // Recovery supports installing packages from 3 paths: /cache,
+    // /data, and /sdcard.  (On a particular device, other locations
+    // may work, but those are three we actually expect.)
+    //
+    // On /data we want to convert the file to a block map so that we
+    // can read the package without mounting the partition.  On /cache
+    // and /sdcard we leave the file alone.
+    if (strncmp(path, "/data/", 6) != 0) {
+        // path does not start with "/data/"; leave it alone.
+        unlink(RECOVERY_COMMAND_FILE_TMP);
+    } else {
+        ALOGI("writing block map %s", map_file);
+        if (produce_block_map(path, map_file, blk_dev, encrypted) != 0) {
+            return 1;
+        }
+    }
+
+    wipe_misc();
+    rename(RECOVERY_COMMAND_FILE_TMP, RECOVERY_COMMAND_FILE);
+    if (do_reboot) reboot_to_recovery();
+    return 0;
+}
diff --git a/updater/Android.mk b/updater/Android.mk
index d86fb9e..5c28969 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -4,6 +4,7 @@
 
 updater_src_files := \
 	install.c \
+	blockimg.c \
 	updater.c
 
 #
@@ -20,6 +21,7 @@
 
 ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
 LOCAL_CFLAGS += -DUSE_EXT4
+LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_C_INCLUDES += system/extras/ext4_utils
 LOCAL_STATIC_LIBRARIES += \
     libext4_utils \
diff --git a/updater/MODULE_LICENSE_GPL b/updater/MODULE_LICENSE_GPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/updater/MODULE_LICENSE_GPL
diff --git a/updater/NOTICE b/updater/NOTICE
new file mode 100644
index 0000000..e77696a
--- /dev/null
+++ b/updater/NOTICE
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                          675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/updater/blockimg.c b/updater/blockimg.c
new file mode 100644
index 0000000..c3319c9
--- /dev/null
+++ b/updater/blockimg.c
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2014 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 <fcntl.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "applypatch/applypatch.h"
+#include "edify/expr.h"
+#include "mincrypt/sha.h"
+#include "minzip/DirUtil.h"
+#include "updater.h"
+
+#define BLOCKSIZE 4096
+
+// Set this to 0 to interpret 'erase' transfers to mean do a
+// BLKDISCARD ioctl (the normal behavior).  Set to 1 to interpret
+// erase to mean fill the region with zeroes.
+#define DEBUG_ERASE  0
+
+#ifndef BLKDISCARD
+#define BLKDISCARD _IO(0x12,119)
+#endif
+
+char* PrintSha1(const uint8_t* digest);
+
+typedef struct {
+    int count;
+    int size;
+    int pos[0];
+} RangeSet;
+
+static RangeSet* parse_range(char* text) {
+    char* save;
+    int num;
+    num = strtol(strtok_r(text, ",", &save), NULL, 0);
+
+    RangeSet* out = malloc(sizeof(RangeSet) + num * sizeof(int));
+    if (out == NULL) {
+        fprintf(stderr, "failed to allocate range of %lu bytes\n",
+                sizeof(RangeSet) + num * sizeof(int));
+        exit(1);
+    }
+    out->count = num / 2;
+    out->size = 0;
+    int i;
+    for (i = 0; i < num; ++i) {
+        out->pos[i] = strtol(strtok_r(NULL, ",", &save), NULL, 0);
+        if (i%2) {
+            out->size += out->pos[i];
+        } else {
+            out->size -= out->pos[i];
+        }
+    }
+
+    return out;
+}
+
+static void readblock(int fd, uint8_t* data, size_t size) {
+    size_t so_far = 0;
+    while (so_far < size) {
+        ssize_t r = read(fd, data+so_far, size-so_far);
+        if (r < 0 && errno != EINTR) {
+            fprintf(stderr, "read failed: %s\n", strerror(errno));
+            return;
+        } else {
+            so_far += r;
+        }
+    }
+}
+
+static void writeblock(int fd, const uint8_t* data, size_t size) {
+    size_t written = 0;
+    while (written < size) {
+        ssize_t w = write(fd, data+written, size-written);
+        if (w < 0 && errno != EINTR) {
+            fprintf(stderr, "write failed: %s\n", strerror(errno));
+            return;
+        } else {
+            written += w;
+        }
+    }
+}
+
+static void check_lseek(int fd, off64_t offset, int whence) {
+    while (true) {
+        off64_t ret = lseek64(fd, offset, whence);
+        if (ret < 0) {
+            if (errno != EINTR) {
+                fprintf(stderr, "lseek64 failed: %s\n", strerror(errno));
+                exit(1);
+            }
+        } else {
+            break;
+        }
+    }
+}
+
+static void allocate(size_t size, uint8_t** buffer, size_t* buffer_alloc) {
+    // if the buffer's big enough, reuse it.
+    if (size <= *buffer_alloc) return;
+
+    free(*buffer);
+
+    *buffer = (uint8_t*) malloc(size);
+    if (*buffer == NULL) {
+        fprintf(stderr, "failed to allocate %zu bytes\n", size);
+        exit(1);
+    }
+    *buffer_alloc = size;
+}
+
+typedef struct {
+    int fd;
+    RangeSet* tgt;
+    int p_block;
+    size_t p_remain;
+} RangeSinkState;
+
+static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) {
+    RangeSinkState* rss = (RangeSinkState*) token;
+
+    if (rss->p_remain <= 0) {
+        fprintf(stderr, "range sink write overrun");
+        exit(1);
+    }
+
+    ssize_t written = 0;
+    while (size > 0) {
+        size_t write_now = size;
+        if (rss->p_remain < write_now) write_now = rss->p_remain;
+        writeblock(rss->fd, data, write_now);
+        data += write_now;
+        size -= write_now;
+
+        rss->p_remain -= write_now;
+        written += write_now;
+
+        if (rss->p_remain == 0) {
+            // move to the next block
+            ++rss->p_block;
+            if (rss->p_block < rss->tgt->count) {
+                rss->p_remain = (rss->tgt->pos[rss->p_block*2+1] - rss->tgt->pos[rss->p_block*2]) * BLOCKSIZE;
+                check_lseek(rss->fd, (off64_t)rss->tgt->pos[rss->p_block*2] * BLOCKSIZE, SEEK_SET);
+            } else {
+                // we can't write any more; return how many bytes have
+                // been written so far.
+                return written;
+            }
+        }
+    }
+
+    return written;
+}
+
+// All of the data for all the 'new' transfers is contained in one
+// file in the update package, concatenated together in the order in
+// which transfers.list will need it.  We want to stream it out of the
+// archive (it's compressed) without writing it to a temp file, but we
+// can't write each section until it's that transfer's turn to go.
+//
+// To achieve this, we expand the new data from the archive in a
+// background thread, and block that threads 'receive uncompressed
+// data' function until the main thread has reached a point where we
+// want some new data to be written.  We signal the background thread
+// with the destination for the data and block the main thread,
+// waiting for the background thread to complete writing that section.
+// Then it signals the main thread to wake up and goes back to
+// blocking waiting for a transfer.
+//
+// NewThreadInfo is the struct used to pass information back and forth
+// between the two threads.  When the main thread wants some data
+// written, it sets rss to the destination location and signals the
+// condition.  When the background thread is done writing, it clears
+// rss and signals the condition again.
+
+typedef struct {
+    ZipArchive* za;
+    const ZipEntry* entry;
+
+    RangeSinkState* rss;
+
+    pthread_mutex_t mu;
+    pthread_cond_t cv;
+} NewThreadInfo;
+
+static bool receive_new_data(const unsigned char* data, int size, void* cookie) {
+    NewThreadInfo* nti = (NewThreadInfo*) cookie;
+
+    while (size > 0) {
+        // Wait for nti->rss to be non-NULL, indicating some of this
+        // data is wanted.
+        pthread_mutex_lock(&nti->mu);
+        while (nti->rss == NULL) {
+            pthread_cond_wait(&nti->cv, &nti->mu);
+        }
+        pthread_mutex_unlock(&nti->mu);
+
+        // At this point nti->rss is set, and we own it.  The main
+        // thread is waiting for it to disappear from nti.
+        ssize_t written = RangeSinkWrite(data, size, nti->rss);
+        data += written;
+        size -= written;
+
+        if (nti->rss->p_block == nti->rss->tgt->count) {
+            // we have written all the bytes desired by this rss.
+
+            pthread_mutex_lock(&nti->mu);
+            nti->rss = NULL;
+            pthread_cond_broadcast(&nti->cv);
+            pthread_mutex_unlock(&nti->mu);
+        }
+    }
+
+    return true;
+}
+
+static void* unzip_new_data(void* cookie) {
+    NewThreadInfo* nti = (NewThreadInfo*) cookie;
+    mzProcessZipEntryContents(nti->za, nti->entry, receive_new_data, nti);
+    return NULL;
+}
+
+// args:
+//    - block device (or file) to modify in-place
+//    - transfer list (blob)
+//    - new data stream (filename within package.zip)
+//    - patch stream (filename within package.zip, must be uncompressed)
+
+Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) {
+    Value* blockdev_filename;
+    Value* transfer_list_value;
+    char* transfer_list = NULL;
+    Value* new_data_fn;
+    Value* patch_data_fn;
+    bool success = false;
+
+    if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value,
+                      &new_data_fn, &patch_data_fn) < 0) {
+        return NULL;
+    }
+
+    if (blockdev_filename->type != VAL_STRING) {
+        ErrorAbort(state, "blockdev_filename argument to %s must be string", name);
+        goto done;
+    }
+    if (transfer_list_value->type != VAL_BLOB) {
+        ErrorAbort(state, "transfer_list argument to %s must be blob", name);
+        goto done;
+    }
+    if (new_data_fn->type != VAL_STRING) {
+        ErrorAbort(state, "new_data_fn argument to %s must be string", name);
+        goto done;
+    }
+    if (patch_data_fn->type != VAL_STRING) {
+        ErrorAbort(state, "patch_data_fn argument to %s must be string", name);
+        goto done;
+    }
+
+    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
+    FILE* cmd_pipe = ui->cmd_pipe;
+
+    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
+
+    const ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data);
+    if (patch_entry == NULL) {
+        ErrorAbort(state, "%s(): no file \"%s\" in package", name, patch_data_fn->data);
+        goto done;
+    }
+
+    uint8_t* patch_start = ((UpdaterInfo*)(state->cookie))->package_zip_addr +
+        mzGetZipEntryOffset(patch_entry);
+
+    const ZipEntry* new_entry = mzFindZipEntry(za, new_data_fn->data);
+    if (new_entry == NULL) {
+        ErrorAbort(state, "%s(): no file \"%s\" in package", name, new_data_fn->data);
+        goto done;
+    }
+
+    // The transfer list is a text file containing commands to
+    // transfer data from one place to another on the target
+    // partition.  We parse it and execute the commands in order:
+    //
+    //    zero [rangeset]
+    //      - fill the indicated blocks with zeros
+    //
+    //    new [rangeset]
+    //      - fill the blocks with data read from the new_data file
+    //
+    //    bsdiff patchstart patchlen [src rangeset] [tgt rangeset]
+    //    imgdiff patchstart patchlen [src rangeset] [tgt rangeset]
+    //      - read the source blocks, apply a patch, write result to
+    //        target blocks.  bsdiff or imgdiff specifies the type of
+    //        patch.
+    //
+    //    move [src rangeset] [tgt rangeset]
+    //      - copy data from source blocks to target blocks (no patch
+    //        needed; rangesets are the same size)
+    //
+    //    erase [rangeset]
+    //      - mark the given blocks as empty
+    //
+    // The creator of the transfer list will guarantee that no block
+    // is read (ie, used as the source for a patch or move) after it
+    // has been written.
+    //
+    // Within one command the source and target ranges may overlap so
+    // in general we need to read the entire source into memory before
+    // writing anything to the target blocks.
+    //
+    // All the patch data is concatenated into one patch_data file in
+    // the update package.  It must be stored uncompressed because we
+    // memory-map it in directly from the archive.  (Since patches are
+    // already compressed, we lose very little by not compressing
+    // their concatenation.)
+
+    pthread_t new_data_thread;
+    NewThreadInfo nti;
+    nti.za = za;
+    nti.entry = new_entry;
+    nti.rss = NULL;
+    pthread_mutex_init(&nti.mu, NULL);
+    pthread_cond_init(&nti.cv, NULL);
+
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+    pthread_create(&new_data_thread, &attr, unzip_new_data, &nti);
+
+    int i, j;
+
+    char* linesave;
+    char* wordsave;
+
+    int fd = open(blockdev_filename->data, O_RDWR);
+    if (fd < 0) {
+        ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno));
+        goto done;
+    }
+
+    char* line;
+    char* word;
+
+    // The data in transfer_list_value is not necessarily
+    // null-terminated, so we need to copy it to a new buffer and add
+    // the null that strtok_r will need.
+    transfer_list = malloc(transfer_list_value->size+1);
+    if (transfer_list == NULL) {
+        fprintf(stderr, "failed to allocate %zd bytes for transfer list\n",
+                transfer_list_value->size+1);
+        exit(1);
+    }
+    memcpy(transfer_list, transfer_list_value->data, transfer_list_value->size);
+    transfer_list[transfer_list_value->size] = '\0';
+
+    line = strtok_r(transfer_list, "\n", &linesave);
+
+    // first line in transfer list is the version number; currently
+    // there's only version 1.
+    if (strcmp(line, "1") != 0) {
+        ErrorAbort(state, "unexpected transfer list version [%s]\n", line);
+        goto done;
+    }
+
+    // second line in transfer list is the total number of blocks we
+    // expect to write.
+    line = strtok_r(NULL, "\n", &linesave);
+    int total_blocks = strtol(line, NULL, 0);
+    // shouldn't happen, but avoid divide by zero.
+    if (total_blocks == 0) ++total_blocks;
+    int blocks_so_far = 0;
+
+    uint8_t* buffer = NULL;
+    size_t buffer_alloc = 0;
+
+    // third and subsequent lines are all individual transfer commands.
+    for (line = strtok_r(NULL, "\n", &linesave); line;
+         line = strtok_r(NULL, "\n", &linesave)) {
+        char* style;
+        style = strtok_r(line, " ", &wordsave);
+
+        if (strcmp("move", style) == 0) {
+            word = strtok_r(NULL, " ", &wordsave);
+            RangeSet* src = parse_range(word);
+            word = strtok_r(NULL, " ", &wordsave);
+            RangeSet* tgt = parse_range(word);
+
+            printf("  moving %d blocks\n", src->size);
+
+            allocate(src->size * BLOCKSIZE, &buffer, &buffer_alloc);
+            size_t p = 0;
+            for (i = 0; i < src->count; ++i) {
+                check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET);
+                size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE;
+                readblock(fd, buffer+p, sz);
+                p += sz;
+            }
+
+            p = 0;
+            for (i = 0; i < tgt->count; ++i) {
+                check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET);
+                size_t sz = (tgt->pos[i*2+1] - tgt->pos[i*2]) * BLOCKSIZE;
+                writeblock(fd, buffer+p, sz);
+                p += sz;
+            }
+
+            blocks_so_far += tgt->size;
+            fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
+            fflush(cmd_pipe);
+
+            free(src);
+            free(tgt);
+
+        } else if (strcmp("zero", style) == 0 ||
+                   (DEBUG_ERASE && strcmp("erase", style) == 0)) {
+            word = strtok_r(NULL, " ", &wordsave);
+            RangeSet* tgt = parse_range(word);
+
+            printf("  zeroing %d blocks\n", tgt->size);
+
+            allocate(BLOCKSIZE, &buffer, &buffer_alloc);
+            memset(buffer, 0, BLOCKSIZE);
+            for (i = 0; i < tgt->count; ++i) {
+                check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET);
+                for (j = tgt->pos[i*2]; j < tgt->pos[i*2+1]; ++j) {
+                    writeblock(fd, buffer, BLOCKSIZE);
+                }
+            }
+
+            if (style[0] == 'z') {   // "zero" but not "erase"
+                blocks_so_far += tgt->size;
+                fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
+                fflush(cmd_pipe);
+            }
+
+            free(tgt);
+        } else if (strcmp("new", style) == 0) {
+
+            word = strtok_r(NULL, " ", &wordsave);
+            RangeSet* tgt = parse_range(word);
+
+            printf("  writing %d blocks of new data\n", tgt->size);
+
+            RangeSinkState rss;
+            rss.fd = fd;
+            rss.tgt = tgt;
+            rss.p_block = 0;
+            rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE;
+            check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET);
+
+            pthread_mutex_lock(&nti.mu);
+            nti.rss = &rss;
+            pthread_cond_broadcast(&nti.cv);
+            while (nti.rss) {
+                pthread_cond_wait(&nti.cv, &nti.mu);
+            }
+            pthread_mutex_unlock(&nti.mu);
+
+            blocks_so_far += tgt->size;
+            fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
+            fflush(cmd_pipe);
+
+            free(tgt);
+
+        } else if (strcmp("bsdiff", style) == 0 ||
+                   strcmp("imgdiff", style) == 0) {
+            word = strtok_r(NULL, " ", &wordsave);
+            size_t patch_offset = strtoul(word, NULL, 0);
+            word = strtok_r(NULL, " ", &wordsave);
+            size_t patch_len = strtoul(word, NULL, 0);
+
+            word = strtok_r(NULL, " ", &wordsave);
+            RangeSet* src = parse_range(word);
+            word = strtok_r(NULL, " ", &wordsave);
+            RangeSet* tgt = parse_range(word);
+
+            printf("  patching %d blocks to %d\n", src->size, tgt->size);
+
+            // Read the source into memory.
+            allocate(src->size * BLOCKSIZE, &buffer, &buffer_alloc);
+            size_t p = 0;
+            for (i = 0; i < src->count; ++i) {
+                check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET);
+                size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE;
+                readblock(fd, buffer+p, sz);
+                p += sz;
+            }
+
+            Value patch_value;
+            patch_value.type = VAL_BLOB;
+            patch_value.size = patch_len;
+            patch_value.data = (char*)(patch_start + patch_offset);
+
+            RangeSinkState rss;
+            rss.fd = fd;
+            rss.tgt = tgt;
+            rss.p_block = 0;
+            rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE;
+            check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET);
+
+            if (style[0] == 'i') {      // imgdiff
+                ApplyImagePatch(buffer, src->size * BLOCKSIZE,
+                                &patch_value,
+                                &RangeSinkWrite, &rss, NULL, NULL);
+            } else {
+                ApplyBSDiffPatch(buffer, src->size * BLOCKSIZE,
+                                 &patch_value, 0,
+                                 &RangeSinkWrite, &rss, NULL);
+            }
+
+            // We expect the output of the patcher to fill the tgt ranges exactly.
+            if (rss.p_block != tgt->count || rss.p_remain != 0) {
+                fprintf(stderr, "range sink underrun?\n");
+            }
+
+            blocks_so_far += tgt->size;
+            fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
+            fflush(cmd_pipe);
+
+            free(src);
+            free(tgt);
+        } else if (!DEBUG_ERASE && strcmp("erase", style) == 0) {
+            struct stat st;
+            if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)) {
+                word = strtok_r(NULL, " ", &wordsave);
+                RangeSet* tgt = parse_range(word);
+
+                printf("  erasing %d blocks\n", tgt->size);
+
+                for (i = 0; i < tgt->count; ++i) {
+                    uint64_t range[2];
+                    // offset in bytes
+                    range[0] = tgt->pos[i*2] * (uint64_t)BLOCKSIZE;
+                    // len in bytes
+                    range[1] = (tgt->pos[i*2+1] - tgt->pos[i*2]) * (uint64_t)BLOCKSIZE;
+
+                    if (ioctl(fd, BLKDISCARD, &range) < 0) {
+                        printf("    blkdiscard failed: %s\n", strerror(errno));
+                    }
+                }
+
+                free(tgt);
+            } else {
+                printf("  ignoring erase (not block device)\n");
+            }
+        } else {
+            fprintf(stderr, "unknown transfer style \"%s\"\n", style);
+            exit(1);
+        }
+    }
+
+    pthread_join(new_data_thread, NULL);
+    success = true;
+
+    free(buffer);
+    printf("wrote %d blocks; expected %d\n", blocks_so_far, total_blocks);
+    printf("max alloc needed was %zu\n", buffer_alloc);
+
+  done:
+    free(transfer_list);
+    FreeValue(blockdev_filename);
+    FreeValue(transfer_list_value);
+    FreeValue(new_data_fn);
+    FreeValue(patch_data_fn);
+    return StringValue(success ? strdup("t") : strdup(""));
+}
+
+Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) {
+    Value* blockdev_filename;
+    Value* ranges;
+    const uint8_t* digest = NULL;
+    if (ReadValueArgs(state, argv, 2, &blockdev_filename, &ranges) < 0) {
+        return NULL;
+    }
+
+    if (blockdev_filename->type != VAL_STRING) {
+        ErrorAbort(state, "blockdev_filename argument to %s must be string", name);
+        goto done;
+    }
+    if (ranges->type != VAL_STRING) {
+        ErrorAbort(state, "ranges argument to %s must be string", name);
+        goto done;
+    }
+
+    int fd = open(blockdev_filename->data, O_RDWR);
+    if (fd < 0) {
+        ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno));
+        goto done;
+    }
+
+    RangeSet* rs = parse_range(ranges->data);
+    uint8_t buffer[BLOCKSIZE];
+
+    SHA_CTX ctx;
+    SHA_init(&ctx);
+
+    int i, j;
+    for (i = 0; i < rs->count; ++i) {
+        check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET);
+        for (j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) {
+            readblock(fd, buffer, BLOCKSIZE);
+            SHA_update(&ctx, buffer, BLOCKSIZE);
+        }
+    }
+    digest = SHA_final(&ctx);
+    close(fd);
+
+  done:
+    FreeValue(blockdev_filename);
+    FreeValue(ranges);
+    if (digest == NULL) {
+        return StringValue(strdup(""));
+    } else {
+        return StringValue(PrintSha1(digest));
+    }
+}
+
+void RegisterBlockImageFunctions() {
+    RegisterFunction("block_image_update", BlockImageUpdateFn);
+    RegisterFunction("range_sha1", RangeSha1Fn);
+}
diff --git a/updater/blockimg.h b/updater/blockimg.h
new file mode 100644
index 0000000..2f4ad3c
--- /dev/null
+++ b/updater/blockimg.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2014 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_BLOCKIMG_H_
+#define _UPDATER_BLOCKIMG_H_
+
+void RegisterBlockImageFunctions();
+
+#endif
diff --git a/updater/install.c b/updater/install.c
index ff4dd58..0030647 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -34,6 +34,9 @@
 #include <linux/xattr.h>
 #include <inttypes.h>
 
+#include "bootloader.h"
+#include "applypatch/applypatch.h"
+#include "cutils/android_reboot.h"
 #include "cutils/misc.h"
 #include "cutils/properties.h"
 #include "edify/expr.h"
@@ -44,27 +47,73 @@
 #include "updater.h"
 #include "applypatch/applypatch.h"
 #include "flashutils/flashutils.h"
+#include "install.h"
 
 #ifdef USE_EXT4
 #include "make_ext4fs.h"
+#include "wipe.h"
 #endif
 
+void uiPrint(State* state, char* buffer) {
+    char* line = strtok(buffer, "\n");
+    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
+    while (line) {
+        fprintf(ui->cmd_pipe, "ui_print %s\n", line);
+        line = strtok(NULL, "\n");
+    }
+    fprintf(ui->cmd_pipe, "ui_print\n");
+}
+
+__attribute__((__format__(printf, 2, 3))) __nonnull((2))
+void uiPrintf(State* state, const char* format, ...) {
+    char error_msg[1024];
+    va_list ap;
+    va_start(ap, format);
+    vsnprintf(error_msg, sizeof(error_msg), format, ap);
+    va_end(ap);
+    uiPrint(state, error_msg);
+}
+
+// Take a sha-1 digest and return it as a newly-allocated hex string.
+char* PrintSha1(const uint8_t* digest) {
+    char* buffer = malloc(SHA_DIGEST_SIZE*2 + 1);
+    int i;
+    const char* alphabet = "0123456789abcdef";
+    for (i = 0; i < SHA_DIGEST_SIZE; ++i) {
+        buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf];
+        buffer[i*2+1] = alphabet[digest[i] & 0xf];
+    }
+    buffer[i*2] = '\0';
+    return buffer;
+}
+
 // mount(fs_type, partition_type, location, mount_point)
 //
 //    fs_type="yaffs2" partition_type="MTD"     location=partition
 //    fs_type="ext4"   partition_type="EMMC"    location=device
 Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
-    if (argc != 4) {
-        return ErrorAbort(state, "%s() expects 4 args, got %d", name, argc);
+    if (argc != 4 && argc != 5) {
+        return ErrorAbort(state, "%s() expects 4-5 args, got %d", name, argc);
     }
     char* fs_type;
     char* partition_type;
     char* location;
     char* mount_point;
-    if (ReadArgs(state, argv, 4, &fs_type, &partition_type,
+    char* mount_options;
+    bool has_mount_options;
+    if (argc == 5) {
+        has_mount_options = true;
+        if (ReadArgs(state, argv, 5, &fs_type, &partition_type,
+                 &location, &mount_point, &mount_options) < 0) {
+            return NULL;
+        }
+    } else {
+        has_mount_options = false;
+        if (ReadArgs(state, argv, 4, &fs_type, &partition_type,
                  &location, &mount_point) < 0) {
-        return NULL;
+            return NULL;
+        }
     }
 
     if (strlen(fs_type) == 0) {
@@ -104,13 +153,13 @@
         const MtdPartition* mtd;
         mtd = mtd_find_partition_by_name(location);
         if (mtd == NULL) {
-            printf("%s: no mtd partition named \"%s\"",
+            uiPrintf(state, "%s: no mtd partition named \"%s\"",
                     name, location);
             result = strdup("");
             goto done;
         }
         if (mtd_mount_partition(mtd, mount_point, fs_type, 0 /* rw */) != 0) {
-            printf("mtd mount of %s failed: %s\n",
+            uiPrintf(state, "mtd mount of %s failed: %s\n",
                     location, strerror(errno));
             result = strdup("");
             goto done;
@@ -118,8 +167,9 @@
         result = mount_point;
     } else {
         if (mount(location, mount_point, fs_type,
-                  MS_NOATIME | MS_NODEV | MS_NODIRATIME, "") < 0) {
-            printf("%s: failed to mount %s at %s: %s\n",
+                  MS_NOATIME | MS_NODEV | MS_NODIRATIME,
+                  has_mount_options ? mount_options : "") < 0) {
+            uiPrintf(state, "%s: failed to mount %s at %s: %s\n",
                     name, location, mount_point, strerror(errno));
             result = strdup("");
         } else {
@@ -132,6 +182,7 @@
     free(partition_type);
     free(location);
     if (result != mount_point) free(mount_point);
+    if (has_mount_options) free(mount_options);
     return StringValue(result);
 }
 
@@ -182,10 +233,14 @@
     scan_mounted_volumes();
     const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point);
     if (vol == NULL) {
-        printf("unmount of %s failed; no such volume\n", mount_point);
+        uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point);
         result = strdup("");
     } else {
-        unmount_mounted_volume(vol);
+        int ret = unmount_mounted_volume(vol);
+        if (ret != 0) {
+           uiPrintf(state, "unmount of %s failed (%d): %s\n",
+                    mount_point, ret, strerror(errno));
+        }
         result = mount_point;
     }
 
@@ -194,14 +249,29 @@
     return StringValue(result);
 }
 
+static int exec_cmd(const char* path, char* const argv[]) {
+    int status;
+    pid_t child;
+    if ((child = vfork()) == 0) {
+        execv(path, argv);
+        _exit(-1);
+    }
+    waitpid(child, &status, 0);
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        printf("%s failed with status %d\n", path, WEXITSTATUS(status));
+    }
+    return WEXITSTATUS(status);
+}
+
 
 // format(fs_type, partition_type, location, fs_size, mount_point)
 //
 //    fs_type="yaffs2" partition_type="MTD"     location=partition fs_size=<bytes> mount_point=<location>
 //    fs_type="ext4"   partition_type="EMMC"    location=device    fs_size=<bytes> mount_point=<location>
-//    if fs_size == 0, then make_ext4fs uses the entire partition.
+//    fs_type="f2fs"   partition_type="EMMC"    location=device    fs_size=<bytes> mount_point=<location>
+//    if fs_size == 0, then make fs uses the entire partition.
 //    if fs_size > 0, that is the size to use
-//    if fs_size < 0, then reserve that many bytes at the end of the partition
+//    if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs")
 Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     if (argc != 5) {
@@ -273,6 +343,24 @@
             goto done;
         }
         result = location;
+    } else if (strcmp(fs_type, "f2fs") == 0) {
+        char *num_sectors;
+        if (asprintf(&num_sectors, "%lld", atoll(fs_size) / 512) <= 0) {
+            printf("format_volume: failed to create %s command for %s\n", fs_type, location);
+            result = strdup("");
+            goto done;
+        }
+        const char *f2fs_path = "/sbin/mkfs.f2fs";
+        const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", location, num_sectors, NULL};
+        int status = exec_cmd(f2fs_path, (char* const*)f2fs_argv);
+        free(num_sectors);
+        if (status != 0) {
+            printf("%s: mkfs.f2fs failed (%d) on %s",
+                    name, status, location);
+            result = strdup("");
+            goto done;
+        }
+        result = location;
 #endif
     } else {
         printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"",
@@ -286,6 +374,44 @@
     return StringValue(result);
 }
 
+Value* RenameFn(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* src_name;
+    char* dst_name;
+
+    if (ReadArgs(state, argv, 2, &src_name, &dst_name) < 0) {
+        return NULL;
+    }
+    if (strlen(src_name) == 0) {
+        ErrorAbort(state, "src_name argument to %s() can't be empty", name);
+        goto done;
+    }
+    if (strlen(dst_name) == 0) {
+        ErrorAbort(state, "dst_name argument to %s() can't be empty", name);
+        goto done;
+    }
+    if (make_parents(dst_name) != 0) {
+        ErrorAbort(state, "Creating parent of %s failed, error %s",
+          dst_name, strerror(errno));
+    } else if (access(dst_name, F_OK) == 0 && access(src_name, F_OK) != 0) {
+        // File was already moved
+        result = dst_name;
+    } else if (rename(src_name, dst_name) != 0) {
+        ErrorAbort(state, "Rename of %s to %s failed, error %s",
+          src_name, dst_name, strerror(errno));
+    } else {
+        result = dst_name;
+    }
+
+done:
+    free(src_name);
+    if (result != dst_name) free(dst_name);
+    return StringValue(result);
+}
 
 Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) {
     char** paths = malloc(argc * sizeof(char*));
@@ -386,19 +512,23 @@
 //   function (the char* returned is actually a FileContents*).
 Value* PackageExtractFileFn(const char* name, State* state,
                            int argc, Expr* argv[]) {
-    if (argc != 1 && argc != 2) {
+    if (argc < 1 || argc > 2) {
         return ErrorAbort(state, "%s() expects 1 or 2 args, got %d",
                           name, argc);
     }
     bool success = false;
+
+    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
+
     if (argc == 2) {
         // The two-argument version extracts to a file.
 
+        ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
+
         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;
         const ZipEntry* entry = mzFindZipEntry(za, zip_path);
         if (entry == NULL) {
             printf("%s: no %s in package\n", name, zip_path);
@@ -467,7 +597,7 @@
         *p = '\0';
         if (make_parents(name) < 0) return -1;
         int result = mkdir(name, 0700);
-        if (result == 0) printf("symlink(): created [%s]\n", name);
+        if (result == 0) printf("created [%s]\n", name);
         *p = '/';
         if (result == 0 || errno == EEXIST) {
             // successfully created or already existed; we're done
@@ -525,88 +655,6 @@
     return StringValue(strdup(""));
 }
 
-
-Value* 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, min_args, argc);
-    }
-
-    char** args = ReadVarArgs(state, argc, argv);
-    if (args == NULL) return NULL;
-
-    char* end;
-    int i;
-    int bad = 0;
-
-    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) {
-            if (chown(args[i], uid, gid) < 0) {
-                printf("%s: chown of %s to %d %d failed: %s\n",
-                        name, args[i], uid, gid, strerror(errno));
-                ++bad;
-            }
-            if (chmod(args[i], mode) < 0) {
-                printf("%s: chmod of %s to %o failed: %s\n",
-                        name, args[i], mode, strerror(errno));
-                ++bad;
-            }
-        }
-    }
-    result = strdup("");
-
-done:
-    for (i = 0; i < argc; ++i) {
-        free(args[i]);
-    }
-    free(args);
-
-    if (bad) {
-        free(result);
-        return ErrorAbort(state, "%s: some changes failed", name);
-    }
-    return StringValue(result);
-}
-
 struct perm_parsed_args {
     bool has_uid;
     uid_t uid;
@@ -624,7 +672,7 @@
     uint64_t capabilities;
 };
 
-static struct perm_parsed_args ParsePermArgs(int argc, char** args) {
+static struct perm_parsed_args ParsePermArgs(State * state, int argc, char** args) {
     int i;
     struct perm_parsed_args parsed;
     int bad = 0;
@@ -639,7 +687,7 @@
                 parsed.uid = uid;
                 parsed.has_uid = true;
             } else {
-                printf("ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]);
+                uiPrintf(state, "ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]);
                 bad++;
             }
             continue;
@@ -650,7 +698,7 @@
                 parsed.gid = gid;
                 parsed.has_gid = true;
             } else {
-                printf("ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]);
+                uiPrintf(state, "ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]);
                 bad++;
             }
             continue;
@@ -661,7 +709,7 @@
                 parsed.mode = mode;
                 parsed.has_mode = true;
             } else {
-                printf("ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]);
+                uiPrintf(state, "ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]);
                 bad++;
             }
             continue;
@@ -672,7 +720,7 @@
                 parsed.dmode = mode;
                 parsed.has_dmode = true;
             } else {
-                printf("ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]);
+                uiPrintf(state, "ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]);
                 bad++;
             }
             continue;
@@ -683,7 +731,7 @@
                 parsed.fmode = mode;
                 parsed.has_fmode = true;
             } else {
-                printf("ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]);
+                uiPrintf(state, "ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]);
                 bad++;
             }
             continue;
@@ -694,7 +742,7 @@
                 parsed.capabilities = capabilities;
                 parsed.has_capabilities = true;
             } else {
-                printf("ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]);
+                uiPrintf(state, "ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]);
                 bad++;
             }
             continue;
@@ -704,7 +752,7 @@
                 parsed.selabel = args[i+1];
                 parsed.has_selabel = true;
             } else {
-                printf("ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]);
+                uiPrintf(state, "ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]);
                 bad++;
             }
             continue;
@@ -721,71 +769,71 @@
 }
 
 static int ApplyParsedPerms(
+        State * state,
         const char* filename,
         const struct stat *statptr,
         struct perm_parsed_args parsed)
 {
     int bad = 0;
 
+    if (parsed.has_selabel) {
+        if (lsetfilecon(filename, parsed.selabel) != 0) {
+            uiPrintf(state, "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n",
+                    filename, parsed.selabel, strerror(errno));
+            bad++;
+        }
+    }
+
     /* ignore symlinks */
     if (S_ISLNK(statptr->st_mode)) {
-        return 0;
+        return bad;
     }
 
     if (parsed.has_uid) {
         if (chown(filename, parsed.uid, -1) < 0) {
-            printf("ApplyParsedPerms: chown of %s to %d failed: %s\n",
-                   filename, parsed.uid, strerror(errno));
+            uiPrintf(state, "ApplyParsedPerms: chown of %s to %d failed: %s\n",
+                    filename, parsed.uid, strerror(errno));
             bad++;
         }
     }
 
     if (parsed.has_gid) {
         if (chown(filename, -1, parsed.gid) < 0) {
-            printf("ApplyParsedPerms: chgrp of %s to %d failed: %s\n",
-                   filename, parsed.gid, strerror(errno));
+            uiPrintf(state, "ApplyParsedPerms: chgrp of %s to %d failed: %s\n",
+                    filename, parsed.gid, strerror(errno));
             bad++;
         }
     }
 
     if (parsed.has_mode) {
         if (chmod(filename, parsed.mode) < 0) {
-            printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n",
-                   filename, parsed.mode, strerror(errno));
+            uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+                    filename, parsed.mode, strerror(errno));
             bad++;
         }
     }
 
     if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) {
         if (chmod(filename, parsed.dmode) < 0) {
-            printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n",
-                   filename, parsed.dmode, strerror(errno));
+            uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+                    filename, parsed.dmode, strerror(errno));
             bad++;
         }
     }
 
     if (parsed.has_fmode && S_ISREG(statptr->st_mode)) {
         if (chmod(filename, parsed.fmode) < 0) {
-            printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+            uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
                    filename, parsed.fmode, strerror(errno));
             bad++;
         }
     }
 
-    if (parsed.has_selabel) {
-        // TODO: Don't silently ignore ENOTSUP
-        if (lsetfilecon(filename, parsed.selabel) && (errno != ENOTSUP)) {
-            printf("ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n",
-                   filename, parsed.selabel, strerror(errno));
-            bad++;
-        }
-    }
-
     if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) {
         if (parsed.capabilities == 0) {
             if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) {
                 // Report failure unless it's ENODATA (attribute not set)
-                printf("ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n",
+                uiPrintf(state, "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n",
                        filename, parsed.capabilities, strerror(errno));
                 bad++;
             }
@@ -798,8 +846,8 @@
             cap_data.data[1].permitted = (uint32_t) (parsed.capabilities >> 32);
             cap_data.data[1].inheritable = 0;
             if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) {
-                printf("ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n",
-                       filename, parsed.capabilities, strerror(errno));
+                uiPrintf(state, "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n",
+                        filename, parsed.capabilities, strerror(errno));
                 bad++;
             }
         }
@@ -811,10 +859,11 @@
 // nftw doesn't allow us to pass along context, so we need to use
 // global variables.  *sigh*
 static struct perm_parsed_args recursive_parsed_args;
+static State* recursive_state;
 
 static int do_SetMetadataRecursive(const char* filename, const struct stat *statptr,
         int fileflags, struct FTW *pfwt) {
-    return ApplyParsedPerms(filename, statptr, recursive_parsed_args);
+    return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args);
 }
 
 static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv[]) {
@@ -839,14 +888,16 @@
         goto done;
     }
 
-    struct perm_parsed_args parsed = ParsePermArgs(argc, args);
+    struct perm_parsed_args parsed = ParsePermArgs(state, argc, args);
 
     if (recursive) {
         recursive_parsed_args = parsed;
+        recursive_state = state;
         bad += nftw(args[0], do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS);
         memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args));
+        recursive_state = NULL;
     } else {
-        bad += ApplyParsedPerms(args[0], &sb, parsed);
+        bad += ApplyParsedPerms(state, args[0], &sb, parsed);
     }
 
 done:
@@ -885,8 +936,8 @@
 // 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).
+//   per line. # comment lines,blank lines, lines without '=' ignored),
+//   and returns the value for 'key' (or "" if it isn't defined).
 Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     char* buffer = NULL;
@@ -913,7 +964,7 @@
 
     buffer = malloc(st.st_size+1);
     if (buffer == NULL) {
-        ErrorAbort(state, "%s: failed to alloc %lld bytes", name, st.st_size+1);
+        ErrorAbort(state, "%s: failed to alloc %lld bytes", name, (long long)st.st_size+1);
         goto done;
     }
 
@@ -926,7 +977,7 @@
 
     if (fread(buffer, 1, st.st_size, f) != st.st_size) {
         ErrorAbort(state, "%s: failed to read %lld bytes from %s",
-                   name, st.st_size+1, filename);
+                   name, (long long)st.st_size+1, filename);
         fclose(f);
         goto done;
     }
@@ -944,9 +995,7 @@
 
         char* equal = strchr(line, '=');
         if (equal == NULL) {
-            ErrorAbort(state, "%s: malformed line \"%s\": %s not a prop file?",
-                       name, line, filename);
-            goto done;
+            continue;
         }
 
         // trim whitespace between key and '='
@@ -1048,8 +1097,8 @@
     return StringValue(strdup(CacheSizeCheck(bytes) ? "" : "t"));
 }
 
+// apply_patch(file, size, init_sha1, tgt_sha1, patch)
 
-// apply_patch(srcfile, tgtfile, tgtsha1, tgtsize, sha1_1, patch_1, ...)
 Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) {
     if (argc < 6 || (argc % 2) == 1) {
         return ErrorAbort(state, "%s(): expected at least 6 args and an "
@@ -1168,15 +1217,7 @@
     }
     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");
-
+    uiPrint(state, buffer);
     return StringValue(buffer);
 }
 
@@ -1234,19 +1275,6 @@
     return StringValue(strdup(buffer));
 }
 
-// Take a sha-1 digest and return it as a newly-allocated hex string.
-static char* PrintSha1(uint8_t* digest) {
-    char* buffer = malloc(SHA_DIGEST_SIZE*2 + 1);
-    int i;
-    const char* alphabet = "0123456789abcdef";
-    for (i = 0; i < SHA_DIGEST_SIZE; ++i) {
-        buffer[i*2] = alphabet[(digest[i] >> 4) & 0xf];
-        buffer[i*2+1] = alphabet[digest[i] & 0xf];
-    }
-    buffer[i*2] = '\0';
-    return buffer;
-}
-
 // sha1_check(data)
 //    to return the sha1 of the data (given in the format returned by
 //    read_file).
@@ -1266,7 +1294,6 @@
     }
 
     if (args[0]->size < 0) {
-        printf("%s(): no file contents received", name);
         return StringValue(strdup(""));
     }
     uint8_t digest[SHA_DIGEST_SIZE];
@@ -1318,13 +1345,12 @@
     v->type = VAL_BLOB;
 
     FileContents fc;
-    if (LoadFileContents(filename, &fc, RETOUCH_DONT_MASK) != 0) {
-        ErrorAbort(state, "%s() loading \"%s\" failed: %s",
-                   name, filename, strerror(errno));
+    if (LoadFileContents(filename, &fc) != 0) {
         free(filename);
-        free(v);
+        v->size = -1;
+        v->data = NULL;
         free(fc.data);
-        return NULL;
+        return v;
     }
 
     v->size = fc.size;
@@ -1334,6 +1360,135 @@
     return v;
 }
 
+// Immediately reboot the device.  Recovery is not finished normally,
+// so if you reboot into recovery it will re-start applying the
+// current package (because nothing has cleared the copy of the
+// arguments stored in the BCB).
+//
+// The argument is the partition name passed to the android reboot
+// property.  It can be "recovery" to boot from the recovery
+// partition, or "" (empty string) to boot from the regular boot
+// partition.
+Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 2) {
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    }
+
+    char* filename;
+    char* property;
+    if (ReadArgs(state, argv, 2, &filename, &property) < 0) return NULL;
+
+    char buffer[80];
+
+    // zero out the 'command' field of the bootloader message.
+    memset(buffer, 0, sizeof(((struct bootloader_message*)0)->command));
+    FILE* f = fopen(filename, "r+b");
+    fseek(f, offsetof(struct bootloader_message, command), SEEK_SET);
+    fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f);
+    fclose(f);
+    free(filename);
+
+    strcpy(buffer, "reboot,");
+    if (property != NULL) {
+        strncat(buffer, property, sizeof(buffer)-10);
+    }
+
+    property_set(ANDROID_RB_PROPERTY, buffer);
+
+    sleep(5);
+    free(property);
+    ErrorAbort(state, "%s() failed to reboot", name);
+    return NULL;
+}
+
+// Store a string value somewhere that future invocations of recovery
+// can access it.  This value is called the "stage" and can be used to
+// drive packages that need to do reboots in the middle of
+// installation and keep track of where they are in the multi-stage
+// install.
+//
+// The first argument is the block device for the misc partition
+// ("/misc" in the fstab), which is where this value is stored.  The
+// second argument is the string to store; it should not exceed 31
+// bytes.
+Value* SetStageFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 2) {
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    }
+
+    char* filename;
+    char* stagestr;
+    if (ReadArgs(state, argv, 2, &filename, &stagestr) < 0) return NULL;
+
+    // Store this value in the misc partition, immediately after the
+    // bootloader message that the main recovery uses to save its
+    // arguments in case of the device restarting midway through
+    // package installation.
+    FILE* f = fopen(filename, "r+b");
+    fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET);
+    int to_write = strlen(stagestr)+1;
+    int max_size = sizeof(((struct bootloader_message*)0)->stage);
+    if (to_write > max_size) {
+        to_write = max_size;
+        stagestr[max_size-1] = 0;
+    }
+    fwrite(stagestr, to_write, 1, f);
+    fclose(f);
+
+    free(stagestr);
+    return StringValue(filename);
+}
+
+// Return the value most recently saved with SetStageFn.  The argument
+// is the block device for the misc partition.
+Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 1) {
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
+    }
+
+    char* filename;
+    if (ReadArgs(state, argv, 1, &filename) < 0) return NULL;
+
+    char buffer[sizeof(((struct bootloader_message*)0)->stage)];
+    FILE* f = fopen(filename, "rb");
+    fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET);
+    fread(buffer, sizeof(buffer), 1, f);
+    fclose(f);
+    buffer[sizeof(buffer)-1] = '\0';
+
+    return StringValue(strdup(buffer));
+}
+
+Value* WipeBlockDeviceFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 2) {
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+    }
+
+    char* filename;
+    char* len_str;
+    if (ReadArgs(state, argv, 2, &filename, &len_str) < 0) return NULL;
+
+    size_t len = strtoull(len_str, NULL, 0);
+    int fd = open(filename, O_WRONLY, 0644);
+    int success = wipe_block_device(fd, len);
+
+    free(filename);
+    free(len_str);
+
+    close(fd);
+
+    return StringValue(strdup(success ? "t" : ""));
+}
+
+Value* EnableRebootFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 0) {
+        return ErrorAbort(state, "%s() expects no args, got %d", name, argc);
+    }
+    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
+    fprintf(ui->cmd_pipe, "enable_reboot\n");
+    return StringValue(strdup("t"));
+}
+
 void RegisterInstallFunctions() {
     RegisterFunction("mount", MountFn);
     RegisterFunction("is_mounted", IsMountedFn);
@@ -1347,11 +1502,6 @@
     RegisterFunction("package_extract_file", PackageExtractFileFn);
     RegisterFunction("symlink", SymlinkFn);
 
-    // Maybe, at some future point, we can delete these functions? They have been
-    // replaced by perm_set and perm_set_recursive.
-    RegisterFunction("set_perm", SetPermFn);
-    RegisterFunction("set_perm_recursive", SetPermFn);
-
     // Usage:
     //   set_metadata("filename", "key1", "value1", "key2", "value2", ...)
     // Example:
@@ -1372,12 +1522,21 @@
     RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
     RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
 
+    RegisterFunction("wipe_block_device", WipeBlockDeviceFn);
+
     RegisterFunction("read_file", ReadFileFn);
     RegisterFunction("sha1_check", Sha1CheckFn);
+    RegisterFunction("rename", RenameFn);
 
     RegisterFunction("wipe_cache", WipeCacheFn);
 
     RegisterFunction("ui_print", UIPrintFn);
 
     RegisterFunction("run_program", RunProgramFn);
+
+    RegisterFunction("reboot_now", RebootNowFn);
+    RegisterFunction("get_stage", GetStageFn);
+    RegisterFunction("set_stage", SetStageFn);
+
+    RegisterFunction("enable_reboot", EnableRebootFn);
 }
diff --git a/updater/install.h b/updater/install.h
index 94f344f..659c8b4 100644
--- a/updater/install.h
+++ b/updater/install.h
@@ -19,4 +19,6 @@
 
 void RegisterInstallFunctions();
 
+static int make_parents(char* name);
+
 #endif
diff --git a/updater/updater.c b/updater/updater.c
index 39c52b4..78c0dc1 100644
--- a/updater/updater.c
+++ b/updater/updater.c
@@ -22,7 +22,9 @@
 #include "edify/expr.h"
 #include "updater.h"
 #include "install.h"
+#include "blockimg.h"
 #include "minzip/Zip.h"
+#include "minzip/SysUtil.h"
 
 // Generated by the makefile, this function defines the
 // RegisterDeviceExtensions() function, which calls all the
@@ -68,19 +70,24 @@
 
     // Extract the script from the package.
 
-    char* package_data = argv[3];
+    const char* package_filename = argv[3];
+    MemMapping map;
+    if (sysMapFile(package_filename, &map) != 0) {
+        printf("failed to map package %s\n", argv[3]);
+        return 3;
+    }
     ZipArchive za;
     int err;
-    err = mzOpenZipArchive(package_data, &za);
+    err = mzOpenZipArchive(map.addr, map.length, &za);
     if (err != 0) {
         printf("failed to open package %s: %s\n",
-                package_data, strerror(err));
+               argv[3], strerror(err));
         return 3;
     }
 
     const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
     if (script_entry == NULL) {
-        printf("failed to find %s in %s\n", SCRIPT_NAME, package_data);
+        printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename);
         return 4;
     }
 
@@ -112,6 +119,7 @@
 
     RegisterBuiltins();
     RegisterInstallFunctions();
+    RegisterBlockImageFunctions();
     RegisterDeviceExtensions();
     FinishRegistration();
 
@@ -119,8 +127,7 @@
 
     Expr* root;
     int error_count = 0;
-    yy_scan_string(script);
-    int error = yyparse(&root, &error_count);
+    int error = parse_string(script, &root, &error_count);
     if (error != 0 || error_count > 0) {
         printf("%d parse errors\n", error_count);
         return 6;
@@ -150,6 +157,8 @@
     updater_info.cmd_pipe = cmd_pipe;
     updater_info.package_zip = &za;
     updater_info.version = atoi(version);
+    updater_info.package_zip_addr = map.addr;
+    updater_info.package_zip_len = map.length;
 
     State state;
     state.cookie = &updater_info;
@@ -180,6 +189,7 @@
     if (updater_info.package_zip) {
         mzCloseZipArchive(updater_info.package_zip);
     }
+    sysReleaseMap(&map);
     free(script);
 
     return 0;
diff --git a/updater/updater.h b/updater/updater.h
index d2e9011..d1dfdd0 100644
--- a/updater/updater.h
+++ b/updater/updater.h
@@ -27,6 +27,9 @@
     FILE* cmd_pipe;
     ZipArchive* package_zip;
     int version;
+
+    uint8_t* package_zip_addr;
+    size_t package_zip_len;
 } UpdaterInfo;
 
 extern struct selabel_handle *sehandle;
diff --git a/verifier.cpp b/verifier.cpp
index ccc0742..b96ba3a 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -14,10 +14,14 @@
  * limitations under the License.
  */
 
+#include "asn1_decoder.h"
 #include "common.h"
-#include "verifier.h"
 #include "ui.h"
+#include "verifier.h"
 
+#include "mincrypt/dsa_sig.h"
+#include "mincrypt/p256.h"
+#include "mincrypt/p256_ecdsa.h"
 #include "mincrypt/rsa.h"
 #include "mincrypt/sha.h"
 #include "mincrypt/sha256.h"
@@ -30,13 +34,85 @@
 
 #define PUBLIC_KEYS_FILE "/res/keys"
 
+/*
+ * Simple version of PKCS#7 SignedData extraction. This extracts the
+ * signature OCTET STRING to be used for signature verification.
+ *
+ * For full details, see http://www.ietf.org/rfc/rfc3852.txt
+ *
+ * The PKCS#7 structure looks like:
+ *
+ *   SEQUENCE (ContentInfo)
+ *     OID (ContentType)
+ *     [0] (content)
+ *       SEQUENCE (SignedData)
+ *         INTEGER (version CMSVersion)
+ *         SET (DigestAlgorithmIdentifiers)
+ *         SEQUENCE (EncapsulatedContentInfo)
+ *         [0] (CertificateSet OPTIONAL)
+ *         [1] (RevocationInfoChoices OPTIONAL)
+ *         SET (SignerInfos)
+ *           SEQUENCE (SignerInfo)
+ *             INTEGER (CMSVersion)
+ *             SEQUENCE (SignerIdentifier)
+ *             SEQUENCE (DigestAlgorithmIdentifier)
+ *             SEQUENCE (SignatureAlgorithmIdentifier)
+ *             OCTET STRING (SignatureValue)
+ */
+static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_der,
+        size_t* sig_der_length) {
+    asn1_context_t* ctx = asn1_context_new(pkcs7_der, pkcs7_der_len);
+    if (ctx == NULL) {
+        return false;
+    }
+
+    asn1_context_t* pkcs7_seq = asn1_sequence_get(ctx);
+    if (pkcs7_seq != NULL && asn1_sequence_next(pkcs7_seq)) {
+        asn1_context_t *signed_data_app = asn1_constructed_get(pkcs7_seq);
+        if (signed_data_app != NULL) {
+            asn1_context_t* signed_data_seq = asn1_sequence_get(signed_data_app);
+            if (signed_data_seq != NULL
+                    && asn1_sequence_next(signed_data_seq)
+                    && asn1_sequence_next(signed_data_seq)
+                    && asn1_sequence_next(signed_data_seq)
+                    && asn1_constructed_skip_all(signed_data_seq)) {
+                asn1_context_t *sig_set = asn1_set_get(signed_data_seq);
+                if (sig_set != NULL) {
+                    asn1_context_t* sig_seq = asn1_sequence_get(sig_set);
+                    if (sig_seq != NULL
+                            && asn1_sequence_next(sig_seq)
+                            && asn1_sequence_next(sig_seq)
+                            && asn1_sequence_next(sig_seq)
+                            && asn1_sequence_next(sig_seq)) {
+                        uint8_t* sig_der_ptr;
+                        if (asn1_octet_string_get(sig_seq, &sig_der_ptr, sig_der_length)) {
+                            *sig_der = (uint8_t*) malloc(*sig_der_length);
+                            if (*sig_der != NULL) {
+                                memcpy(*sig_der, sig_der_ptr, *sig_der_length);
+                            }
+                        }
+                        asn1_context_free(sig_seq);
+                    }
+                    asn1_context_free(sig_set);
+                }
+                asn1_context_free(signed_data_seq);
+            }
+            asn1_context_free(signed_data_app);
+        }
+        asn1_context_free(pkcs7_seq);
+    }
+    asn1_context_free(ctx);
+
+    return *sig_der != NULL;
+}
+
 // Look for an RSA signature embedded in the .ZIP file comment given
 // the path to the zip.  Verify it matches one of the given public
 // keys.
 //
 // Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered
 // or no key matches the signature).
-int verify_file(const char* path) {
+int verify_file(unsigned char* addr, size_t length) {
     //ui->SetProgress(0.0);
 
     int numKeys;
@@ -47,12 +123,6 @@
     }
     LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);
 
-    FILE* f = fopen(path, "rb");
-    if (f == NULL) {
-        LOGE("failed to open %s (%s)\n", path, strerror(errno));
-        return VERIFY_FAILURE;
-    }
-
     // An archive with a whole-file signature will end in six bytes:
     //
     //   (2-byte signature start) $ff $ff (2-byte comment size)
@@ -64,34 +134,25 @@
 
 #define FOOTER_SIZE 6
 
-    if (fseek(f, -FOOTER_SIZE, SEEK_END) != 0) {
-        LOGE("failed to seek in %s (%s)\n", path, strerror(errno));
-        fclose(f);
+    if (length < FOOTER_SIZE) {
+        LOGE("not big enough to contain footer\n");
         return VERIFY_FAILURE;
     }
 
-    unsigned char footer[FOOTER_SIZE];
-    if (fread(footer, 1, FOOTER_SIZE, f) != FOOTER_SIZE) {
-        LOGE("failed to read footer from %s (%s)\n", path, strerror(errno));
-        fclose(f);
-        return VERIFY_FAILURE;
-    }
+    unsigned char* footer = addr + length - FOOTER_SIZE;
 
     if (footer[2] != 0xff || footer[3] != 0xff) {
         LOGE("footer is wrong\n");
-        fclose(f);
         return VERIFY_FAILURE;
     }
 
     size_t comment_size = footer[4] + (footer[5] << 8);
     size_t signature_start = footer[0] + (footer[1] << 8);
-    LOGI("comment is %d bytes; signature %d bytes from end\n",
+    LOGI("comment is %zu bytes; signature %zu bytes from end\n",
          comment_size, signature_start);
 
-    if (signature_start - FOOTER_SIZE < RSANUMBYTES) {
-        // "signature" block isn't big enough to contain an RSA block.
-        LOGE("signature is too short\n");
-        fclose(f);
+    if (signature_start <= FOOTER_SIZE) {
+        LOGE("Signature start is in the footer");
         return VERIFY_FAILURE;
     }
 
@@ -101,9 +162,8 @@
     // comment length.
     size_t eocd_size = comment_size + EOCD_HEADER_SIZE;
 
-    if (fseek(f, -eocd_size, SEEK_END) != 0) {
-        LOGE("failed to seek in %s (%s)\n", path, strerror(errno));
-        fclose(f);
+    if (length < eocd_size) {
+        LOGE("not big enough to contain EOCD\n");
         return VERIFY_FAILURE;
     }
 
@@ -111,26 +171,15 @@
     // This is everything except the signature data and length, which
     // includes all of the EOCD except for the comment length field (2
     // bytes) and the comment data.
-    size_t signed_len = ftell(f) + EOCD_HEADER_SIZE - 2;
+    size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2;
 
-    unsigned char* eocd = (unsigned char*)malloc(eocd_size);
-    if (eocd == NULL) {
-        LOGE("malloc for EOCD record failed\n");
-        fclose(f);
-        return VERIFY_FAILURE;
-    }
-    if (fread(eocd, 1, eocd_size, f) != eocd_size) {
-        LOGE("failed to read eocd from %s (%s)\n", path, strerror(errno));
-        fclose(f);
-        return VERIFY_FAILURE;
-    }
+    unsigned char* eocd = addr + length - eocd_size;
 
     // If this is really is the EOCD record, it will begin with the
     // magic number $50 $4b $05 $06.
     if (eocd[0] != 0x50 || eocd[1] != 0x4b ||
         eocd[2] != 0x05 || eocd[3] != 0x06) {
         LOGE("signature length doesn't match EOCD marker\n");
-        fclose(f);
         return VERIFY_FAILURE;
     }
 
@@ -143,7 +192,6 @@
             // which could be exploitable.  Fail verification if
             // this sequence occurs anywhere after the real one.
             LOGE("EOCD marker occurs after start of EOCD\n");
-            fclose(f);
             return VERIFY_FAILURE;
         }
     }
@@ -163,39 +211,42 @@
     SHA256_CTX sha256_ctx;
     SHA_init(&sha1_ctx);
     SHA256_init(&sha256_ctx);
-    unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE);
-    if (buffer == NULL) {
-        LOGE("failed to alloc memory for sha1 buffer\n");
-        fclose(f);
-        return VERIFY_FAILURE;
-    }
 
     double frac = -1.0;
     size_t so_far = 0;
-    fseek(f, 0, SEEK_SET);
     while (so_far < signed_len) {
-        size_t size = BUFFER_SIZE;
-        if (signed_len - so_far < size) size = signed_len - so_far;
-        if (fread(buffer, 1, size, f) != size) {
-            LOGE("failed to read data from %s (%s)\n", path, strerror(errno));
-            fclose(f);
-            return VERIFY_FAILURE;
-        }
-        if (need_sha1) SHA_update(&sha1_ctx, buffer, size);
-        if (need_sha256) SHA256_update(&sha256_ctx, buffer, size);
+        size_t size = signed_len - so_far;
+        if (size > BUFFER_SIZE) size = BUFFER_SIZE;
+
+        if (need_sha1) SHA_update(&sha1_ctx, addr + so_far, size);
+        if (need_sha256) SHA256_update(&sha256_ctx, addr + so_far, size);
         so_far += size;
+
         double f = so_far / (double)signed_len;
         if (f > frac + 0.02 || size == so_far) {
             //ui->SetProgress(f);
             frac = f;
         }
     }
-    fclose(f);
-    free(buffer);
 
     const uint8_t* sha1 = SHA_final(&sha1_ctx);
     const uint8_t* sha256 = SHA256_final(&sha256_ctx);
 
+    uint8_t* sig_der = NULL;
+    size_t sig_der_length = 0;
+
+    size_t signature_size = signature_start - FOOTER_SIZE;
+    if (!read_pkcs7(eocd + eocd_size - signature_start, signature_size, &sig_der,
+            &sig_der_length)) {
+        LOGE("Could not find signature DER block\n");
+        return VERIFY_FAILURE;
+    }
+
+    /*
+     * Check to make sure at least one of the keys matches the signature. Since
+     * any key can match, we need to try each before determining a verification
+     * failure has happened.
+     */
     for (i = 0; i < numKeys; ++i) {
         const uint8_t* hash;
         switch (pKeys[i].hash_len) {
@@ -206,17 +257,47 @@
 
         // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that
         // the signing tool appends after the signature itself.
-        if (RSA_verify(pKeys[i].public_key, eocd + eocd_size - 6 - RSANUMBYTES,
-                       RSANUMBYTES, hash, pKeys[i].hash_len)) {
-            LOGI("whole-file signature verified against key %d\n", i);
-            free(eocd);
+        if (pKeys[i].key_type == Certificate::RSA) {
+            if (sig_der_length < RSANUMBYTES) {
+                // "signature" block isn't big enough to contain an RSA block.
+                LOGI("signature is too short for RSA key %zu\n", i);
+                continue;
+            }
+
+            if (!RSA_verify(pKeys[i].rsa, sig_der, RSANUMBYTES,
+                            hash, pKeys[i].hash_len)) {
+                LOGI("failed to verify against RSA key %zu\n", i);
+                continue;
+            }
+
+            LOGI("whole-file signature verified against RSA key %zu\n", i);
+            free(sig_der);
+            return VERIFY_SUCCESS;
+        } else if (pKeys[i].key_type == Certificate::EC
+                && pKeys[i].hash_len == SHA256_DIGEST_SIZE) {
+            p256_int r, s;
+            if (!dsa_sig_unpack(sig_der, sig_der_length, &r, &s)) {
+                LOGI("Not a DSA signature block for EC key %zu\n", i);
+                continue;
+            }
+
+            p256_int p256_hash;
+            p256_from_bin(hash, &p256_hash);
+            if (!p256_ecdsa_verify(&(pKeys[i].ec->x), &(pKeys[i].ec->y),
+                                   &p256_hash, &r, &s)) {
+                LOGI("failed to verify against EC key %zu\n", i);
+                continue;
+            }
+
+            LOGI("whole-file signature verified against EC key %zu\n", i);
+            free(sig_der);
             return VERIFY_SUCCESS;
         } else {
-            LOGI("failed to verify against key %d\n", i);
+            LOGI("Unknown key type %d\n", pKeys[i].key_type);
         }
 		LOGI("i: %i, eocd_size: %i, RSANUMBYTES: %i\n", i, eocd_size, RSANUMBYTES);
     }
-    free(eocd);
+    free(sig_der);
     LOGE("failed to verify whole-file signature\n");
     return VERIFY_FAILURE;
 }
@@ -248,6 +329,7 @@
 //       2: 2048-bit RSA key with e=65537 and SHA-1 hash
 //       3: 2048-bit RSA key with e=3 and SHA-256 hash
 //       4: 2048-bit RSA key with e=65537 and SHA-256 hash
+//       5: 256-bit EC key using the NIST P-256 curve parameters and SHA-256 hash
 //
 // Returns NULL if the file failed to parse, or if it contain zero keys.
 Certificate*
@@ -268,28 +350,41 @@
             ++*numKeys;
             out = (Certificate*)realloc(out, *numKeys * sizeof(Certificate));
             Certificate* cert = out + (*numKeys - 1);
-            cert->public_key = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+            memset(cert, '\0', sizeof(Certificate));
 
             char start_char;
             if (fscanf(f, " %c", &start_char) != 1) goto exit;
             if (start_char == '{') {
                 // a version 1 key has no version specifier.
-                cert->public_key->exponent = 3;
+                cert->key_type = Certificate::RSA;
+                cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                cert->rsa->exponent = 3;
                 cert->hash_len = SHA_DIGEST_SIZE;
             } else if (start_char == 'v') {
                 int version;
                 if (fscanf(f, "%d {", &version) != 1) goto exit;
                 switch (version) {
                     case 2:
-                        cert->public_key->exponent = 65537;
+                        cert->key_type = Certificate::RSA;
+                        cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                        cert->rsa->exponent = 65537;
                         cert->hash_len = SHA_DIGEST_SIZE;
                         break;
                     case 3:
-                        cert->public_key->exponent = 3;
+                        cert->key_type = Certificate::RSA;
+                        cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                        cert->rsa->exponent = 3;
                         cert->hash_len = SHA256_DIGEST_SIZE;
                         break;
                     case 4:
-                        cert->public_key->exponent = 65537;
+                        cert->key_type = Certificate::RSA;
+                        cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                        cert->rsa->exponent = 65537;
+                        cert->hash_len = SHA256_DIGEST_SIZE;
+                        break;
+                    case 5:
+                        cert->key_type = Certificate::EC;
+                        cert->ec = (ECPublicKey*)calloc(1, sizeof(ECPublicKey));
                         cert->hash_len = SHA256_DIGEST_SIZE;
                         break;
                     default:
@@ -297,23 +392,55 @@
                 }
             }
 
-            RSAPublicKey* key = cert->public_key;
-            if (fscanf(f, " %i , 0x%x , { %u",
-                       &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
+            if (cert->key_type == Certificate::RSA) {
+                RSAPublicKey* key = cert->rsa;
+                if (fscanf(f, " %i , 0x%x , { %u",
+                           &(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, " , %u", &(key->n[i])) != 1) goto exit;
+                }
+                if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
+                for (i = 1; i < key->len; ++i) {
+                    if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
+                }
+                fscanf(f, " } } ");
+
+                LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len);
+            } else if (cert->key_type == Certificate::EC) {
+                ECPublicKey* key = cert->ec;
+                int key_len;
+                unsigned int byte;
+                uint8_t x_bytes[P256_NBYTES];
+                uint8_t y_bytes[P256_NBYTES];
+                if (fscanf(f, " %i , { %u", &key_len, &byte) != 2) goto exit;
+                if (key_len != P256_NBYTES) {
+                    LOGE("Key length (%d) does not match expected size %d\n", key_len, P256_NBYTES);
+                    goto exit;
+                }
+                x_bytes[P256_NBYTES - 1] = byte;
+                for (i = P256_NBYTES - 2; i >= 0; --i) {
+                    if (fscanf(f, " , %u", &byte) != 1) goto exit;
+                    x_bytes[i] = byte;
+                }
+                if (fscanf(f, " } , { %u", &byte) != 1) goto exit;
+                y_bytes[P256_NBYTES - 1] = byte;
+                for (i = P256_NBYTES - 2; i >= 0; --i) {
+                    if (fscanf(f, " , %u", &byte) != 1) goto exit;
+                    y_bytes[i] = byte;
+                }
+                fscanf(f, " } } ");
+                p256_from_bin(x_bytes, &key->x);
+                p256_from_bin(y_bytes, &key->y);
+            } else {
+                LOGE("Unknown key type %d\n", cert->key_type);
                 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, " , %u", &(key->n[i])) != 1) goto exit;
-            }
-            if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
-            for (i = 1; i < key->len; ++i) {
-                if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
-            }
-            fscanf(f, " } } ");
 
             // if the line ends in a comma, this file has more keys.
             switch (fgetc(f)) {
@@ -329,7 +456,10 @@
                 LOGE("unexpected character between keys\n");
                 goto exit;
             }
+<<<<<<< HEAD
             LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len);
+=======
+>>>>>>> cddb68b5eafbeba696d5276bda1f1a9f70bbde42
         }
     }
 
diff --git a/verifier.h b/verifier.h
index d704173..43fd5ad 100644
--- a/verifier.h
+++ b/verifier.h
@@ -17,6 +17,7 @@
 #ifndef _RECOVERY_VERIFIER_H
 #define _RECOVERY_VERIFIER_H
 
+#include "mincrypt/p256.h"
 #include "mincrypt/rsa.h"
 
 #define ASSUMED_UPDATE_BINARY_NAME  "META-INF/com/google/android/update-binary"
@@ -26,14 +27,30 @@
 static const float VERIFICATION_PROGRESS_FRACTION = 0.25;
 
 typedef struct Certificate {
+
+typedef struct {
+    p256_int x;
+    p256_int y;
+} ECPublicKey;
+
+typedef struct {
+    typedef enum {
+        RSA,
+        EC,
+    } KeyType;
+
     int hash_len;  // SHA_DIGEST_SIZE (SHA-1) or SHA256_DIGEST_SIZE (SHA-256)
-    RSAPublicKey* public_key;
+    KeyType key_type;
+    RSAPublicKey* rsa;
+    ECPublicKey* ec;
 } Certificate;
 
-/* Look in the file for a signature footer, and verify that it
- * matches one of the given keys.  Return one of the constants below.
+/* addr and length define a an update package file that has been
+ * loaded (or mmap'ed, or whatever) into memory.  Verify that the file
+ * is signed and the signature matches one of the given keys.  Return
+ * one of the constants below.
  */
-int verify_file(const char* path);
+int verify_file(unsigned char* addr, size_t length);
 
 Certificate* load_keys(const char* filename, int* numKeys);
 
diff --git a/verifier_test.cpp b/verifier_test.cpp
index 20aa3d1..3ba270d 100644
--- a/verifier_test.cpp
+++ b/verifier_test.cpp
@@ -17,6 +17,9 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 
 /*
 #include "common.h"
@@ -25,6 +28,7 @@
 #include "ui.h"
 #include "mincrypt/sha.h"
 #include "mincrypt/sha256.h"
+#include "minzip/SysUtil.h"
 
 // This is build/target/product/security/testkey.x509.pem after being
 // dumped out by dumpkey.jar.
@@ -102,6 +106,18 @@
       65537
     };
 
+ECPublicKey test_ec_key =
+    {
+       {
+         {0xd656fa24u, 0x931416cau, 0x1c0278c6u, 0x174ebe4cu,
+          0x6018236au, 0x45ba1656u, 0xe8c05d84u, 0x670ed500u}
+      },
+      {
+        {0x0d179adeu, 0x4c16827du, 0x9f8cb992u, 0x8f69ff8au,
+         0x481b1020u, 0x798d91afu, 0x184db8e9u, 0xb5848dd9u}
+      }
+    };
+
 RecoveryUI* ui = NULL;
 
 // verifier expects to find a UI object; we provide one that does
@@ -138,37 +154,93 @@
     va_end(ap);
 }
 
+static Certificate* add_certificate(Certificate** certsp, int* num_keys,
+        Certificate::KeyType key_type) {
+    int i = *num_keys;
+    *num_keys = *num_keys + 1;
+    *certsp = (Certificate*) realloc(*certsp, *num_keys * sizeof(Certificate));
+    Certificate* certs = *certsp;
+    certs[i].rsa = NULL;
+    certs[i].ec = NULL;
+    certs[i].key_type = key_type;
+    certs[i].hash_len = SHA_DIGEST_SIZE;
+    return &certs[i];
+}
+
 int main(int argc, char **argv) {
-    if (argc < 2 || argc > 4) {
-        fprintf(stderr, "Usage: %s [-sha256] [-f4 | -file <keys>] <package>\n", argv[0]);
+    if (argc < 2) {
+        fprintf(stderr, "Usage: %s [-sha256] [-ec | -f4 | -file <keys>] <package>\n", argv[0]);
+        return 2;
+    }
+    Certificate* certs = NULL;
+    int num_keys = 0;
+
+    int argn = 1;
+    while (argn < argc) {
+        if (strcmp(argv[argn], "-sha256") == 0) {
+            if (num_keys == 0) {
+                fprintf(stderr, "May only specify -sha256 after key type\n");
+                return 2;
+            }
+            ++argn;
+            Certificate* cert = &certs[num_keys - 1];
+            cert->hash_len = SHA256_DIGEST_SIZE;
+        } else if (strcmp(argv[argn], "-ec") == 0) {
+            ++argn;
+            Certificate* cert = add_certificate(&certs, &num_keys, Certificate::EC);
+            cert->ec = &test_ec_key;
+        } else if (strcmp(argv[argn], "-e3") == 0) {
+            ++argn;
+            Certificate* cert = add_certificate(&certs, &num_keys, Certificate::RSA);
+            cert->rsa = &test_key;
+        } else if (strcmp(argv[argn], "-f4") == 0) {
+            ++argn;
+            Certificate* cert = add_certificate(&certs, &num_keys, Certificate::RSA);
+            cert->rsa = &test_f4_key;
+        } else if (strcmp(argv[argn], "-file") == 0) {
+            if (certs != NULL) {
+                fprintf(stderr, "Cannot specify -file with other certs specified\n");
+                return 2;
+            }
+            ++argn;
+            certs = load_keys(argv[argn], &num_keys);
+            ++argn;
+        } else if (argv[argn][0] == '-') {
+            fprintf(stderr, "Unknown argument %s\n", argv[argn]);
+            return 2;
+        } else {
+            break;
+        }
+    }
+
+    if (argn == argc) {
+        fprintf(stderr, "Must specify package to verify\n");
         return 2;
     }
 
-    Certificate default_cert;
-    Certificate* cert = &default_cert;
-    cert->public_key = &test_key;
-    cert->hash_len = SHA_DIGEST_SIZE;
-    int num_keys = 1;
-    ++argv;
-    if (strcmp(argv[0], "-sha256") == 0) {
-        ++argv;
-        cert->hash_len = SHA256_DIGEST_SIZE;
-    }
-    if (strcmp(argv[0], "-f4") == 0) {
-        ++argv;
-        cert->public_key = &test_f4_key;
-    } else if (strcmp(argv[0], "-file") == 0) {
-        ++argv;
-        cert = load_keys(argv[0], &num_keys);
-        ++argv;
+    if (num_keys == 0) {
+        certs = (Certificate*) calloc(1, sizeof(Certificate));
+        if (certs == NULL) {
+            fprintf(stderr, "Failure allocating memory for default certificate\n");
+            return 1;
+        }
+        certs->key_type = Certificate::RSA;
+        certs->rsa = &test_key;
+        certs->ec = NULL;
+        certs->hash_len = SHA_DIGEST_SIZE;
+        num_keys = 1;
     }
 
     ui = new FakeUI();
 
-/*
-    int result = verify_file(*argv, cert, num_keys);
-*/
-    int result = verify_file(*argv);
+    MemMapping map;
+    if (sysMapFile(argv[argn], &map) != 0) {
+        fprintf(stderr, "failed to mmap %s: %s\n", argv[argn], strerror(errno));
+        return 4;
+    }
+
+    int result = verify_file(map.addr, map.length);
+
     if (result == VERIFY_SUCCESS) {
         printf("VERIFIED\n");
         return 0;
diff --git a/verifier_test.sh b/verifier_test.sh
index 65f77f4..4761cef 100755
--- a/verifier_test.sh
+++ b/verifier_test.sh
@@ -81,20 +81,30 @@
 expect_fail jarsigned.zip
 
 # success cases
-expect_succeed otasigned.zip
+expect_succeed otasigned.zip -e3
 expect_succeed otasigned_f4.zip -f4
-expect_succeed otasigned_sha256.zip -sha256
-expect_succeed otasigned_f4_sha256.zip -sha256 -f4
+expect_succeed otasigned_sha256.zip -e3 -sha256
+expect_succeed otasigned_f4_sha256.zip -f4 -sha256
+expect_succeed otasigned_ecdsa_sha256.zip -ec -sha256
+
+# success with multiple keys
+expect_succeed otasigned.zip -f4 -e3
+expect_succeed otasigned_f4.zip -ec -f4
+expect_succeed otasigned_sha256.zip -ec -e3 -e3 -sha256
+expect_succeed otasigned_f4_sha256.zip -ec -sha256 -e3 -f4 -sha256
+expect_succeed otasigned_ecdsa_sha256.zip -f4 -sha256 -e3 -ec -sha256
 
 # verified against different key
 expect_fail otasigned.zip -f4
-expect_fail otasigned_f4.zip
+expect_fail otasigned_f4.zip -e3
+expect_fail otasigned_ecdsa_sha256.zip -e3 -sha256
 
 # verified against right key but wrong hash algorithm
-expect_fail otasigned.zip -sha256
-expect_fail otasigned_f4.zip -sha256 -f4
+expect_fail otasigned.zip -e3 -sha256
+expect_fail otasigned_f4.zip -f4 -sha256
 expect_fail otasigned_sha256.zip
 expect_fail otasigned_f4_sha256.zip -f4
+expect_fail otasigned_ecdsa_sha256.zip
 
 # various other cases
 expect_fail random.zip