DO NOT MERGE: Add a checker for signature boundary in verifier am: f616da1726 am: 5346da0220  -s ours
am: a41cc29447  -s ours

Change-Id: Ib3a827173380145bf7a3fcd7e4f8386571228699
diff --git a/Android.mk b/Android.mk
index cbc9a8e..1a91f00 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,6 +17,18 @@
 
 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_STATIC_LIBRARIES := libcutils libc libmincrypt
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
 LOCAL_SRC_FILES := \
     recovery.cpp \
     bootloader.cpp \
@@ -26,15 +38,21 @@
     screen_ui.cpp \
     asn1_decoder.cpp \
     verifier.cpp \
-    adb_install.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 \
@@ -44,21 +62,20 @@
     libmtdutils \
     libmincrypt \
     libminadbd \
+    libfusesideload \
     libminui \
-    libpixelflinger_static \
     libpng \
     libfs_mgr \
     libcutils \
     liblog \
     libselinux \
     libstdc++ \
-    libutils \
     libm \
     libc
 
 ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
     LOCAL_CFLAGS += -DUSE_EXT4
-    LOCAL_C_INCLUDES += system/extras/ext4_utils
+    LOCAL_C_INCLUDES += system/extras/ext4_utils system/vold
     LOCAL_STATIC_LIBRARIES += libext4_utils_static libz
 endif
 
@@ -75,6 +92,7 @@
 endif
 
 LOCAL_C_INCLUDES += system/extras/ext4_utils
+LOCAL_C_INCLUDES += external/openssl/include
 
 include $(BUILD_EXECUTABLE)
 
@@ -90,6 +108,8 @@
 LOCAL_MODULE := verifier_test
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 LOCAL_MODULE_TAGS := tests
+LOCAL_CFLAGS += -DNO_RECOVERY_MOUNT
+LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_SRC_FILES := \
     verifier_test.cpp \
     asn1_decoder.cpp \
@@ -98,6 +118,7 @@
 LOCAL_STATIC_LIBRARIES := \
     libmincrypt \
     libminui \
+    libminzip \
     libcutils \
     libstdc++ \
     libc
@@ -105,12 +126,12 @@
 
 
 include $(LOCAL_PATH)/minui/Android.mk \
-    $(LOCAL_PATH)/minelf/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 a226ea5..be3b9a0 100644
--- a/adb_install.cpp
+++ b/adb_install.cpp
@@ -31,7 +31,8 @@
 #include "common.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;
@@ -69,6 +70,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(RecoveryUI* ui_, int* wipe_cache, const char* install_file) {
     ui = ui_;
@@ -84,27 +89,58 @@
         execl("/sbin/recovery", "recovery", "--adbd", NULL);
         _exit(-1);
     }
+
+    // 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) {
-        ui->Print("status %d\n", WEXITSTATUS(status));
+        if (WEXITSTATUS(status) == 3) {
+            ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n");
+        } else if (!WIFSIGNALED(status)) {
+            ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status));
+        }
     }
 
     set_usb_driver(false);
     maybe_restart_adbd();
 
-    struct stat st;
-    if (stat(ADB_SIDELOAD_FILENAME, &st) != 0) {
-        if (errno == ENOENT) {
-            ui->Print("No package received.\n");
-        } else {
-            ui->Print("Error reading package:\n  %s\n", strerror(errno));
-        }
-        return INSTALL_ERROR;
-    }
-    return install_package(ADB_SIDELOAD_FILENAME, wipe_cache, install_file);
+    return result;
 }
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
index ef57f24..4984093 100644
--- a/applypatch/Android.mk
+++ b/applypatch/Android.mk
@@ -28,7 +28,7 @@
 LOCAL_SRC_FILES := main.c
 LOCAL_MODULE := applypatch
 LOCAL_C_INCLUDES += bootable/recovery
-LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz libminelf
+LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz
 LOCAL_SHARED_LIBRARIES += libz libcutils libstdc++ libc
 
 include $(BUILD_EXECUTABLE)
@@ -40,7 +40,7 @@
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 LOCAL_MODULE_TAGS := eng
 LOCAL_C_INCLUDES += bootable/recovery
-LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz libminelf
+LOCAL_STATIC_LIBRARIES += libapplypatch libmtdutils libmincrypt libbz
 LOCAL_STATIC_LIBRARIES += libz libcutils libstdc++ libc
 
 include $(BUILD_EXECUTABLE)
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
index cb9bc23..2c86e09 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"
@@ -31,7 +32,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,
@@ -44,14 +45,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
@@ -87,20 +85,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;
 }
@@ -247,7 +231,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;
@@ -274,7 +258,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;
         }
@@ -325,7 +309,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));
@@ -340,8 +324,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));
@@ -402,7 +392,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;
@@ -424,7 +414,7 @@
         {
             size_t start = 0;
             int success = 0;
-            int fd = open(partition, O_RDWR);
+            int fd = open(partition, O_RDWR | O_SYNC);
             if (fd < 0) {
                 printf("failed to open %s: %s\n", partition, strerror(errno));
                 return -1;
@@ -449,7 +439,22 @@
                     }
                     start += written;
                 }
-                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.
@@ -476,20 +481,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;
                     }
@@ -579,7 +584,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 "
@@ -594,7 +599,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;
         }
@@ -615,7 +620,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;
@@ -636,7 +641,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;
@@ -730,8 +735,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.
@@ -750,8 +754,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) {
@@ -767,8 +770,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;
@@ -938,7 +940,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));
@@ -969,8 +972,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/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 6e0595b..8ff7280 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -23,11 +23,15 @@
     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
+    write /proc/sys/vm/max_map_count 1000000
+
 on fs
     mkdir /dev/usb-ffs 0770 shell shell
     mkdir /dev/usb-ffs/adb 0770 shell shell
@@ -44,13 +48,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}
 
@@ -58,7 +80,7 @@
     critical
     seclabel u:r:ueventd:s0
 
-service healthd /sbin/healthd -n
+service healthd /sbin/healthd -r
     critical
     seclabel u:r:healthd:s0
 
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 2ada529..53c0d31 100644
--- a/install.h
+++ b/install.h
@@ -28,7 +28,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);
 
 #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 5a4de68..04956d8 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 \
@@ -22,11 +23,10 @@
 
 LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter
 LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
+LOCAL_C_INCLUDES += bootable/recovery
 
 LOCAL_MODULE := libminadbd
 
-LOCAL_STATIC_LIBRARIES := libcutils libc
+LOCAL_STATIC_LIBRARIES := libfusesideload libcutils libc
+
 include $(BUILD_STATIC_LIBRARY)
-
-
-
diff --git a/minadbd/adb.c b/minadbd/adb.c
index 948f4b7..127d072 100644
--- a/minadbd/adb.c
+++ b/minadbd/adb.c
@@ -387,21 +387,11 @@
     init_transport_registration();
 
     // The minimal version of adbd only uses USB.
-    if (access("/dev/android_adb", F_OK) == 0) {
+    if (access(USB_ADB_PATH, F_OK) == 0 || access(USB_FFS_ADB_EP0, F_OK) == 0) {
         // listen on USB
         usb_init();
     }
 
-    if (setgid(AID_SHELL) != 0) {
-        fprintf(stderr, "failed to setgid to shell\n");
-        exit(1);
-    }
-    if (setuid(AID_SHELL) != 0) {
-        fprintf(stderr, "failed to setuid to shell\n");
-        exit(1);
-    }
-    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 688a6f2..714868f 100644
--- a/minadbd/adb.h
+++ b/minadbd/adb.h
@@ -400,15 +400,25 @@
 #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;
 
 #define CHUNK_SIZE (64*1024)
 
+#if !ADB_HOST
+#define USB_ADB_PATH     "/dev/android_adb"
+
+#define USB_FFS_ADB_PATH  "/dev/usb-ffs/adb/"
+#define USB_FFS_ADB_EP(x) USB_FFS_ADB_PATH#x
+
+#define USB_FFS_ADB_EP0   USB_FFS_ADB_EP(ep0)
+#define USB_FFS_ADB_OUT   USB_FFS_ADB_EP(ep1)
+#define USB_FFS_ADB_IN    USB_FFS_ADB_EP(ep2)
+#endif
+
 int sendfailmsg(int fd, const char *reason);
 int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s);
 
-#define ADB_SIDELOAD_FILENAME "/tmp/update.zip"
-
 #endif
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 4c0c97f..92679f5 100644
--- a/minadbd/transport.c
+++ b/minadbd/transport.c
@@ -713,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) {
@@ -734,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 635fa4b..29bab15 100644
--- a/minadbd/usb_linux_client.c
+++ b/minadbd/usb_linux_client.c
@@ -19,6 +19,8 @@
 #include <unistd.h>
 #include <string.h>
 
+#include <linux/usb/ch9.h>
+#include <linux/usb/functionfs.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <dirent.h>
@@ -29,12 +31,114 @@
 #define   TRACE_TAG  TRACE_USB
 #include "adb.h"
 
+#define MAX_PACKET_SIZE_FS	64
+#define MAX_PACKET_SIZE_HS	512
+
+#define cpu_to_le16(x)  htole16(x)
+#define cpu_to_le32(x)  htole32(x)
 
 struct usb_handle
 {
     int fd;
     adb_cond_t notify;
     adb_mutex_t lock;
+
+    int (*write)(usb_handle *h, const void *data, int len);
+    int (*read)(usb_handle *h, void *data, int len);
+    void (*kick)(usb_handle *h);
+
+    int control;
+    int bulk_out; /* "out" from the host's perspective => source for adbd */
+    int bulk_in;  /* "in" from the host's perspective => sink for adbd */
+};
+
+static const struct {
+    struct usb_functionfs_descs_head header;
+    struct {
+        struct usb_interface_descriptor intf;
+        struct usb_endpoint_descriptor_no_audio source;
+        struct usb_endpoint_descriptor_no_audio sink;
+    } __attribute__((packed)) fs_descs, hs_descs;
+} __attribute__((packed)) descriptors = {
+    .header = {
+        .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+        .length = cpu_to_le32(sizeof(descriptors)),
+        .fs_count = 3,
+        .hs_count = 3,
+    },
+    .fs_descs = {
+        .intf = {
+            .bLength = sizeof(descriptors.fs_descs.intf),
+            .bDescriptorType = USB_DT_INTERFACE,
+            .bInterfaceNumber = 0,
+            .bNumEndpoints = 2,
+            .bInterfaceClass = ADB_CLASS,
+            .bInterfaceSubClass = ADB_SUBCLASS,
+            .bInterfaceProtocol = ADB_PROTOCOL,
+            .iInterface = 1, /* first string from the provided table */
+        },
+        .source = {
+            .bLength = sizeof(descriptors.fs_descs.source),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 1 | USB_DIR_OUT,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+        },
+        .sink = {
+            .bLength = sizeof(descriptors.fs_descs.sink),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 2 | USB_DIR_IN,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+        },
+    },
+    .hs_descs = {
+        .intf = {
+            .bLength = sizeof(descriptors.hs_descs.intf),
+            .bDescriptorType = USB_DT_INTERFACE,
+            .bInterfaceNumber = 0,
+            .bNumEndpoints = 2,
+            .bInterfaceClass = ADB_CLASS,
+            .bInterfaceSubClass = ADB_SUBCLASS,
+            .bInterfaceProtocol = ADB_PROTOCOL,
+            .iInterface = 1, /* first string from the provided table */
+        },
+        .source = {
+            .bLength = sizeof(descriptors.hs_descs.source),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 1 | USB_DIR_OUT,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+        },
+        .sink = {
+            .bLength = sizeof(descriptors.hs_descs.sink),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 2 | USB_DIR_IN,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+        },
+    },
+};
+
+#define STR_INTERFACE_ "ADB Interface"
+
+static const struct {
+    struct usb_functionfs_strings_head header;
+    struct {
+        __le16 code;
+        const char str1[sizeof(STR_INTERFACE_)];
+    } __attribute__((packed)) lang0;
+} __attribute__((packed)) strings = {
+    .header = {
+        .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC),
+        .length = cpu_to_le32(sizeof(strings)),
+        .str_count = cpu_to_le32(1),
+        .lang_count = cpu_to_le32(1),
+    },
+    .lang0 = {
+        cpu_to_le16(0x0409), /* en-us */
+        STR_INTERFACE_,
+    },
 };
 
 void usb_cleanup()
@@ -42,7 +146,7 @@
     // nothing to do here
 }
 
-static void *usb_open_thread(void *x)
+static void *usb_adb_open_thread(void *x)
 {
     struct usb_handle *usb = (struct usb_handle *)x;
     int fd;
@@ -61,6 +165,7 @@
             if (fd < 0) {
                 // to support older kernels
                 fd = unix_open("/dev/android", O_RDWR);
+                fprintf(stderr, "usb_adb_open_thread: %d\n", fd );
             }
             if (fd < 0) {
                 adb_sleep_ms(1000);
@@ -79,7 +184,7 @@
     return 0;
 }
 
-int usb_write(usb_handle *h, const void *data, int len)
+static int usb_adb_write(usb_handle *h, const void *data, int len)
 {
     int n;
 
@@ -94,7 +199,7 @@
     return 0;
 }
 
-int usb_read(usb_handle *h, void *data, int len)
+static int usb_adb_read(usb_handle *h, void *data, int len)
 {
     int n;
 
@@ -109,23 +214,42 @@
     return 0;
 }
 
-void usb_init()
+static void usb_adb_kick(usb_handle *h)
+{
+    D("usb_kick\n");
+    adb_mutex_lock(&h->lock);
+    adb_close(h->fd);
+    h->fd = -1;
+
+    // notify usb_adb_open_thread that we are disconnected
+    adb_cond_signal(&h->notify);
+    adb_mutex_unlock(&h->lock);
+}
+
+static void usb_adb_init()
 {
     usb_handle *h;
     adb_thread_t tid;
     int fd;
 
     h = calloc(1, sizeof(usb_handle));
+
+    h->write = usb_adb_write;
+    h->read = usb_adb_read;
+    h->kick = usb_adb_kick;
     h->fd = -1;
+
     adb_cond_init(&h->notify, 0);
     adb_mutex_init(&h->lock, 0);
 
+    fprintf(stderr, "Starting to open usb_init()\n");
     // Open the file /dev/android_adb_enable to trigger 
     // the enabling of the adb USB function in the kernel.
     // We never touch this file again - just leave it open
     // indefinitely so the kernel will know when we are running
     // and when we are not.
     fd = unix_open("/dev/android_adb_enable", O_RDWR);
+    fprintf(stderr, "unix_open to open usb_init(): %d\n", fd);
     if (fd < 0) {
        D("failed to open /dev/android_adb_enable\n");
     } else {
@@ -133,25 +257,237 @@
     }
 
     D("[ usb_init - starting thread ]\n");
-    if(adb_thread_create(&tid, usb_open_thread, h)){
+    if(adb_thread_create(&tid, usb_adb_open_thread, h)){
         fatal_errno("cannot create usb thread");
+        fprintf(stderr, "cannot create the usb thread()\n");
     }
 }
 
-void usb_kick(usb_handle *h)
-{
-    D("usb_kick\n");
-    adb_mutex_lock(&h->lock);
-    adb_close(h->fd);
-    h->fd = -1;
 
-    // notify usb_open_thread that we are disconnected
+static void init_functionfs(struct usb_handle *h)
+{
+    ssize_t ret;
+
+    D("OPENING %s\n", USB_FFS_ADB_EP0);
+    h->control = adb_open(USB_FFS_ADB_EP0, O_RDWR);
+    if (h->control < 0) {
+        D("[ %s: cannot open control endpoint: errno=%d]\n", USB_FFS_ADB_EP0, errno);
+        goto err;
+    }
+
+    ret = adb_write(h->control, &descriptors, sizeof(descriptors));
+    if (ret < 0) {
+        D("[ %s: write descriptors failed: errno=%d ]\n", USB_FFS_ADB_EP0, errno);
+        goto err;
+    }
+
+    ret = adb_write(h->control, &strings, sizeof(strings));
+    if (ret < 0) {
+        D("[ %s: writing strings failed: errno=%d]\n", USB_FFS_ADB_EP0, errno);
+        goto err;
+    }
+
+    h->bulk_out = adb_open(USB_FFS_ADB_OUT, O_RDWR);
+    if (h->bulk_out < 0) {
+        D("[ %s: cannot open bulk-out ep: errno=%d ]\n", USB_FFS_ADB_OUT, errno);
+        goto err;
+    }
+
+    h->bulk_in = adb_open(USB_FFS_ADB_IN, O_RDWR);
+    if (h->bulk_in < 0) {
+        D("[ %s: cannot open bulk-in ep: errno=%d ]\n", USB_FFS_ADB_IN, errno);
+        goto err;
+    }
+
+    return;
+
+err:
+    if (h->bulk_in > 0) {
+        adb_close(h->bulk_in);
+        h->bulk_in = -1;
+    }
+    if (h->bulk_out > 0) {
+        adb_close(h->bulk_out);
+        h->bulk_out = -1;
+    }
+    if (h->control > 0) {
+        adb_close(h->control);
+        h->control = -1;
+    }
+    return;
+}
+
+static void *usb_ffs_open_thread(void *x)
+{
+    struct usb_handle *usb = (struct usb_handle *)x;
+
+    while (1) {
+        // wait until the USB device needs opening
+        adb_mutex_lock(&usb->lock);
+        while (usb->control != -1)
+            adb_cond_wait(&usb->notify, &usb->lock);
+        adb_mutex_unlock(&usb->lock);
+
+        while (1) {
+            init_functionfs(usb);
+
+            if (usb->control >= 0)
+                break;
+
+            adb_sleep_ms(1000);
+        }
+
+        D("[ usb_thread - registering device ]\n");
+        register_usb_transport(usb, 0, 1);
+    }
+
+    // never gets here
+    return 0;
+}
+
+static int bulk_write(int bulk_in, const char *buf, size_t length)
+{
+    size_t count = 0;
+    int ret;
+
+    do {
+        ret = adb_write(bulk_in, buf + count, length - count);
+        if (ret < 0) {
+            if (errno != EINTR)
+                return ret;
+        } else {
+            count += ret;
+        }
+    } while (count < length);
+
+    D("[ bulk_write done fd=%d ]\n", bulk_in);
+    return count;
+}
+
+static int usb_ffs_write(usb_handle *h, const void *data, int len)
+{
+    int n;
+
+    D("about to write (fd=%d, len=%d)\n", h->bulk_in, len);
+    n = bulk_write(h->bulk_in, data, len);
+    if (n != len) {
+        D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+            h->bulk_in, n, errno, strerror(errno));
+        return -1;
+    }
+    D("[ done fd=%d ]\n", h->bulk_in);
+    return 0;
+}
+
+static int bulk_read(int bulk_out, char *buf, size_t length)
+{
+    size_t count = 0;
+    int ret;
+
+    do {
+        ret = adb_read(bulk_out, buf + count, length - count);
+        if (ret < 0) {
+            if (errno != EINTR) {
+                D("[ bulk_read failed fd=%d length=%zu count=%zu ]\n",
+                                           bulk_out, length, count);
+                return ret;
+            }
+        } else {
+            count += ret;
+        }
+    } while (count < length);
+
+    return count;
+}
+
+static int usb_ffs_read(usb_handle *h, void *data, int len)
+{
+    int n;
+
+    D("about to read (fd=%d, len=%d)\n", h->bulk_out, len);
+    n = bulk_read(h->bulk_out, data, len);
+    if (n != len) {
+        D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+            h->bulk_out, n, errno, strerror(errno));
+        return -1;
+    }
+    D("[ done fd=%d ]\n", h->bulk_out);
+    return 0;
+}
+
+static void usb_ffs_kick(usb_handle *h)
+{
+    int err;
+
+    err = ioctl(h->bulk_in, FUNCTIONFS_CLEAR_HALT);
+    if (err < 0)
+        D("[ kick: source (fd=%d) clear halt failed (%d) ]", h->bulk_in, errno);
+
+    err = ioctl(h->bulk_out, FUNCTIONFS_CLEAR_HALT);
+    if (err < 0)
+        D("[ kick: sink (fd=%d) clear halt failed (%d) ]", h->bulk_out, errno);
+
+    adb_mutex_lock(&h->lock);
+    adb_close(h->control);
+    adb_close(h->bulk_out);
+    adb_close(h->bulk_in);
+    h->control = h->bulk_out = h->bulk_in = -1;
+
+    // notify usb_ffs_open_thread that we are disconnected
     adb_cond_signal(&h->notify);
     adb_mutex_unlock(&h->lock);
 }
 
+static void usb_ffs_init()
+{
+    usb_handle *h;
+    adb_thread_t tid;
+
+    D("[ usb_init - using FunctionFS ]\n");
+
+    h = calloc(1, sizeof(usb_handle));
+
+    h->write = usb_ffs_write;
+    h->read = usb_ffs_read;
+    h->kick = usb_ffs_kick;
+
+    h->control  = -1;
+    h->bulk_out = -1;
+    h->bulk_out = -1;
+
+    adb_cond_init(&h->notify, 0);
+    adb_mutex_init(&h->lock, 0);
+
+    D("[ usb_init - starting thread ]\n");
+    if (adb_thread_create(&tid, usb_ffs_open_thread, h)){
+        fatal_errno("[ cannot create usb thread ]\n");
+    }
+}
+
+void usb_init()
+{
+    if (access(USB_FFS_ADB_EP0, F_OK) == 0)
+        usb_ffs_init();
+    else
+        usb_adb_init();
+}
+
+int usb_write(usb_handle *h, const void *data, int len)
+{
+    return h->write(h, data, len);
+}
+
+int usb_read(usb_handle *h, void *data, int len)
+{
+    return h->read(h, data, len);
+}
 int usb_close(usb_handle *h)
 {
     // nothing to do here
     return 0;
 }
+
+void usb_kick(usb_handle *h)
+{
+    h->kick(h);
+}
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 43e0ad3..df4aac1 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -1,12 +1,15 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := graphics.c events.c resources.c
+LOCAL_SRC_FILES := graphics.c graphics_adf.c graphics_fbdev.c events.c \
+	resources.c
 
 LOCAL_C_INCLUDES +=\
     external/libpng\
     external/zlib
 
+LOCAL_WHOLE_STATIC_LIBRARIES += libadf
+
 LOCAL_MODULE := libminui
 
 # This used to compare against values in double-quotes (which are just
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/graphics.c b/minui/graphics.c
index d757165..6049d85 100644
--- a/minui/graphics.c
+++ b/minui/graphics.c
@@ -28,188 +28,37 @@
 #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 struct fb_var_screeninfo vi;
-static struct fb_fix_screeninfo fi;
+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 int get_framebuffer(GGLSurface *fb)
+static GRSurface* gr_draw = NULL;
+
+static bool outside(int x, int y)
 {
-    int fd;
-    void *bits;
-
-    fd = open("/dev/graphics/fb0", O_RDWR);
-    if (fd < 0) {
-        perror("cannot open fb0");
-        return -1;
-    }
-
-    if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
-        perror("failed to get fb0 info");
-        close(fd);
-        return -1;
-    }
-
-    vi.bits_per_pixel = PIXEL_SIZE * 8;
-    if (PIXEL_FORMAT == GGL_PIXEL_FORMAT_BGRA_8888) {
-      vi.red.offset     = 8;
-      vi.red.length     = 8;
-      vi.green.offset   = 16;
-      vi.green.length   = 8;
-      vi.blue.offset    = 24;
-      vi.blue.length    = 8;
-      vi.transp.offset  = 0;
-      vi.transp.length  = 8;
-    } else if (PIXEL_FORMAT == GGL_PIXEL_FORMAT_RGBX_8888) {
-      vi.red.offset     = 24;
-      vi.red.length     = 8;
-      vi.green.offset   = 16;
-      vi.green.length   = 8;
-      vi.blue.offset    = 8;
-      vi.blue.length    = 8;
-      vi.transp.offset  = 0;
-      vi.transp.length  = 8;
-    } else { /* RGB565*/
-      vi.red.offset     = 11;
-      vi.red.length     = 5;
-      vi.green.offset   = 5;
-      vi.green.length   = 6;
-      vi.blue.offset    = 0;
-      vi.blue.length    = 5;
-      vi.transp.offset  = 0;
-      vi.transp.length  = 0;
-    }
-    if (ioctl(fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
-        perror("failed to put fb0 info");
-        close(fd);
-        return -1;
-    }
-
-    if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
-        perror("failed to get fb0 info");
-        close(fd);
-        return -1;
-    }
-
-    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 -1;
-    }
-
-    overscan_offset_x = vi.xres * overscan_percent / 100;
-    overscan_offset_y = vi.yres * overscan_percent / 100;
-
-    fb->version = sizeof(*fb);
-    fb->width = vi.xres;
-    fb->height = vi.yres;
-    fb->stride = fi.line_length/PIXEL_SIZE;
-    fb->data = bits;
-    fb->format = PIXEL_FORMAT;
-    memset(fb->data, 0, vi.yres * fi.line_length);
-
-    fb++;
-
-    /* check if we can use double buffering */
-    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;
-    fb->stride = fi.line_length/PIXEL_SIZE;
-    fb->data = (void*) (((unsigned) bits) + vi.yres * fi.line_length);
-    fb->format = PIXEL_FORMAT;
-    memset(fb->data, 0, vi.yres * fi.line_length);
-
-    return fd;
-}
-
-static void get_memory_surface(GGLSurface* ms) {
-  ms->version = sizeof(*ms);
-  ms->width = vi.xres;
-  ms->height = vi.yres;
-  ms->stride = fi.line_length/PIXEL_SIZE;
-  ms->data = malloc(fi.line_length * vi.yres);
-  ms->format = PIXEL_FORMAT;
-}
-
-static void set_active_framebuffer(unsigned n)
-{
-    if (n > 1 || !double_buffering) return;
-    vi.yres_virtual = vi.yres * NUM_BUFFERS;
-    vi.yoffset = n * vi.yres;
-    vi.bits_per_pixel = PIXEL_SIZE * 8;
-    if (ioctl(gr_fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
-        perror("active fb swap failed");
-    }
-}
-
-void gr_flip(void)
-{
-    GGLContext *gl = gr_context;
-
-    /* swap front and back buffers */
-    if (double_buffering)
-        gr_active_fb = (gr_active_fb + 1) & 1;
-
-    /* copy data from the in-memory surface to the buffer we're about
-     * to make active. */
-    memcpy(gr_framebuffer[gr_active_fb].data, gr_mem_surface.data,
-           fi.line_length * vi.yres);
-
-    /* inform the display driver */
-    set_active_framebuffer(gr_active_fb);
-}
-
-void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
-{
-    GGLContext *gl = gr_context;
-    GGLint color[4];
-    color[0] = ((r << 8) | r) + 1;
-    color[1] = ((g << 8) | g) + 1;
-    color[2] = ((b << 8) | b) + 1;
-    color[3] = ((a << 8) | a) + 1;
-    gl->color4xv(gl, color);
+    return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height;
 }
 
 int gr_measure(const char *s)
@@ -223,58 +72,118 @@
     *y = gr_font->cheight;
 }
 
-int gr_text(int x, int y, const char *s, int bold)
+static void text_blend(unsigned char* src_p, int src_row_bytes,
+                       unsigned char* dst_p, int dst_row_bytes,
+                       int width, int height)
 {
-    GGLContext *gl = gr_context;
+    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;
+    }
+}
+
+
+void gr_text(int x, int y, const char *s, int bold)
+{
     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)
@@ -285,48 +194,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
@@ -340,7 +283,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;
@@ -355,17 +299,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.
@@ -377,38 +369,34 @@
         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);
+    if (!gr_draw) {
+        gr_backend = open_fbdev();
+        gr_draw = gr_backend->init(gr_backend);
+        if (gr_draw == NULL) {
+            return -1;
+        }
+    }
 
-    printf("framebuffer: fd %d (%d x %d)\n",
-           gr_fb_fd, gr_framebuffer[0].width, gr_framebuffer[0].height);
+    overscan_offset_x = gr_draw->width * overscan_percent / 100;
+    overscan_offset_y = gr_draw->height * overscan_percent / 100;
 
-        /* start with 0 as front (displayed) and 1 as back (drawing) */
-    gr_active_fb = 0;
-    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);
-
-    gr_fb_blank(true);
-    gr_fb_blank(false);
+    gr_flip();
+    gr_flip();
 
     return 0;
 }
 
 void gr_exit(void)
 {
-    close(gr_fb_fd);
-    gr_fb_fd = -1;
-
-    free(gr_mem_surface.data);
+    gr_backend->exit(gr_backend);
 
     ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT);
     close(gr_vt_fd);
@@ -417,24 +405,15 @@
 
 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)
 {
-    int ret;
-
-    ret = ioctl(gr_fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
-    if (ret < 0)
-        perror("ioctl(): blank");
+    gr_backend->blank(gr_backend, blank);
 }
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 1b8dd05..733b675 100644
--- a/minui/minui.h
+++ b/minui/minui.h
@@ -17,28 +17,38 @@
 #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);
-int gr_text(int x, int y, const char *s, int bold);
- void gr_texticon(int x, int y, gr_surface icon);
+void gr_text(int x, int y, const char *s, int bold);
+void gr_texticon(int x, int y, gr_surface icon);
 int gr_measure(const char *s);
 void gr_font_size(int *x, int *y);
 
@@ -50,7 +60,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);
@@ -65,14 +75,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);
 
 #ifdef __cplusplus
diff --git a/minui/resources.c b/minui/resources.c
index c0a9cca..2bae4de 100644
--- a/minui/resources.c
+++ b/minui/resources.c
@@ -27,31 +27,28 @@
 #include <linux/fb.h>
 #include <linux/kd.h>
 
-#include <pixelflinger/pixelflinger.h>
-
 #include <png.h>
 
 #include "minui.h"
 
 extern char* locale;
 
-// 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';
@@ -72,116 +69,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, bit_depth;
-    size_t width, height;
-    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
+    png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth,
             &color_type, NULL, NULL, NULL);
 
-    int channels = png_get_channels(png_ptr, info_ptr);
+    *channels = png_get_channels(*png_ptr, *info_ptr);
 
-    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;
+    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 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;
@@ -198,98 +365,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);
-
-    int color_type, bit_depth;
-    size_t width, height;
-    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
-            &color_type, NULL, NULL, NULL);
-    int channels = png_get_channels(png_ptr, info_ptr);
-
-    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;
@@ -304,21 +434,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/SysUtil.c b/minzip/SysUtil.c
index 31c76d6..8601591 100644
--- a/minzip/SysUtil.c
+++ b/minzip/SysUtil.c
@@ -3,47 +3,24 @@
  *
  * System utilities.
  */
+#include <stdbool.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <stdio.h>
 #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 +51,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 +68,185 @@
     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));
+    if (pMap->ranges == NULL) {
+        LOGE("malloc failed: %s\n", strerror(errno));
+        munmap(memPtr, length);
+        return -1;
+    }
+    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;
+    size_t 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;
+    }
+    if (blksize != 0) {
+        blocks = ((size-1) / blksize) + 1;
+    }
+    if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0) {
+        LOGE("invalid data in block map file: size %zu, blksize %u, range_count %u\n",
+             size, blksize, range_count);
         return -1;
     }
 
-    /* adjust to be page-aligned */
-    adjust = start % DEFAULT_PAGE_SIZE;
-    actualStart = start - adjust;
-    actualLength = length + adjust;
-
-    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 = calloc(range_count, sizeof(MappedRange));
+    if (pMap->ranges == NULL) {
+        LOGE("calloc(%u, %zu) failed: %s\n", range_count, sizeof(MappedRange), strerror(errno));
         return -1;
     }
 
-    pMap->baseAddr = memPtr;
-    pMap->baseLength = actualLength;
-    pMap->addr = (char*)memPtr + adjust;
-    pMap->length = length;
+    // 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));
+        free(pMap->ranges);
+        return -1;
+    }
 
-    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));
+        munmap(reserve, blocks * blksize);
+        free(pMap->ranges);
+        return -1;
+    }
 
+    unsigned char* next = reserve;
+    size_t remaining_size = blocks * blksize;
+    bool success = true;
+    for (i = 0; i < range_count; ++i) {
+        size_t start, end;
+        if (fscanf(mapf, "%zu %zu\n", &start, &end) != 2) {
+            LOGW("failed to parse range %d in block map\n", i);
+            success = false;
+            break;
+        }
+        size_t length = (end - start) * blksize;
+        if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) {
+          LOGE("unexpected range in block map: %zu %zu\n", start, end);
+          success = false;
+          break;
+        }
+
+        void* addr = mmap64(next, length, 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));
+            success = false;
+            break;
+        }
+        pMap->ranges[i].addr = addr;
+        pMap->ranges[i].length = length;
+
+        next += length;
+        remaining_size -= length;
+    }
+    if (success && remaining_size != 0) {
+      LOGE("ranges in block map are invalid: remaining_size = %zu\n", remaining_size);
+      success = false;
+    }
+    if (!success) {
+      close(fd);
+      munmap(reserve, blocks * blksize);
+      free(pMap->ranges);
+      return -1;
+    }
+
+    close(fd);
+    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);
+            fclose(mapf);
+            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 fdb9095..e1a2a96 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -44,6 +44,8 @@
 #include "adb_install.h"
 extern "C" {
 #include "minadbd/adb.h"
+#include "fuse_sideload.h"
+#include "fuse_sdcard_provider.h"
 }
 
 struct selabel_handle *sehandle;
@@ -58,6 +60,7 @@
   { "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 },
 };
 
@@ -73,12 +76,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.
@@ -158,6 +163,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) {
@@ -178,11 +189,11 @@
     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
@@ -439,96 +450,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
@@ -604,9 +525,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:",
@@ -618,10 +539,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);
@@ -677,58 +595,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;
 }
 
@@ -768,10 +669,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());
 
@@ -795,27 +759,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")) {
@@ -824,39 +809,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:
@@ -867,7 +839,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");
                     }
@@ -923,9 +895,7 @@
 main(int argc, char **argv) {
     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
@@ -939,11 +909,11 @@
         return 0;
     }
 
-    printf("Starting recovery on %s", ctime(&start));
+    printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
 
     load_volume_table();
     ensure_path_mounted(LAST_LOG_FILE);
-    rotate_last_logs(10);
+    rotate_last_logs(KEEP_LOG_COUNT);
     get_args(&argc, &argv);
 
     const char *send_intent = NULL;
@@ -971,6 +941,7 @@
             break;
         }
         case 'p': shutdown_after = true; break;
+        case 'r': reason = optarg; break;
         case '?':
             LOGE("Invalid command argument\n");
             continue;
@@ -981,12 +952,14 @@
         load_locale_from_cache();
     }
     printf("locale is [%s]\n", locale);
-    printf("stage is [%s]\n", stage, stage);
+    printf("stage is [%s]\n", stage);
+    printf("reason is [%s]\n", reason);
 
     Device* device = make_device();
     ui = device->GetUI();
     gCurrentUI = ui;
 
+    ui->SetLocale(locale);
     ui->Init();
 
     int st_cur, st_max;
@@ -994,7 +967,6 @@
         ui->SetStage(st_cur, st_max);
     }
 
-    ui->SetLocale(locale);
     ui->SetBackground(RecoveryUI::NONE);
     if (show_text) ui->ShowText(true);
 
@@ -1039,7 +1011,7 @@
     int status = INSTALL_SUCCESS;
 
     if (update_package != NULL) {
-        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.");
@@ -1061,6 +1033,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) {
         if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
@@ -1074,18 +1047,31 @@
         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);
-    if (shutdown_after) {
-        ui->Print("Shutting down...\n");
-        property_set(ANDROID_RB_PROPERTY, "shutdown,");
-    } else {
-        ui->Print("Rebooting...\n");
-        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/images/icon_error.png b/res-hdpi/images/icon_error.png
similarity index 100%
rename from res/images/icon_error.png
rename to 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/images/progress_empty.png b/res-hdpi/images/progress_empty.png
similarity index 100%
rename from res/images/progress_empty.png
rename to res-hdpi/images/progress_empty.png
Binary files differ
diff --git a/res/images/progress_fill.png b/res-hdpi/images/progress_fill.png
similarity index 100%
rename from res/images/progress_fill.png
rename to res-hdpi/images/progress_fill.png
Binary files differ
diff --git a/res/images/stage_empty.png b/res-hdpi/images/stage_empty.png
similarity index 100%
rename from res/images/stage_empty.png
rename to res-hdpi/images/stage_empty.png
Binary files differ
diff --git a/res/images/stage_fill.png b/res-hdpi/images/stage_fill.png
similarity index 100%
rename from res/images/stage_fill.png
rename to 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/images/icon_error.png b/res-mdpi/images/icon_error.png
similarity index 100%
copy from res/images/icon_error.png
copy to 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/images/progress_empty.png b/res-mdpi/images/progress_empty.png
similarity index 100%
copy from res/images/progress_empty.png
copy to res-mdpi/images/progress_empty.png
Binary files differ
diff --git a/res/images/progress_fill.png b/res-mdpi/images/progress_fill.png
similarity index 100%
copy from res/images/progress_fill.png
copy to res-mdpi/images/progress_fill.png
Binary files differ
diff --git a/res/images/stage_empty.png b/res-mdpi/images/stage_empty.png
similarity index 100%
copy from res/images/stage_empty.png
copy to res-mdpi/images/stage_empty.png
Binary files differ
diff --git a/res/images/stage_fill.png b/res-mdpi/images/stage_fill.png
similarity index 100%
copy from res/images/stage_fill.png
copy to 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/images/icon_error.png b/res-xhdpi/images/icon_error.png
similarity index 100%
copy from res/images/icon_error.png
copy to 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/images/progress_empty.png b/res-xhdpi/images/progress_empty.png
similarity index 100%
copy from res/images/progress_empty.png
copy to res-xhdpi/images/progress_empty.png
Binary files differ
diff --git a/res/images/progress_fill.png b/res-xhdpi/images/progress_fill.png
similarity index 100%
copy from res/images/progress_fill.png
copy to res-xhdpi/images/progress_fill.png
Binary files differ
diff --git a/res/images/stage_empty.png b/res-xhdpi/images/stage_empty.png
similarity index 100%
copy from res/images/stage_empty.png
copy to res-xhdpi/images/stage_empty.png
Binary files differ
diff --git a/res/images/stage_fill.png b/res-xhdpi/images/stage_fill.png
similarity index 100%
copy from res/images/stage_fill.png
copy to 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/images/icon_error.png b/res-xxhdpi/images/icon_error.png
similarity index 100%
copy from res/images/icon_error.png
copy to 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/images/progress_empty.png b/res-xxhdpi/images/progress_empty.png
similarity index 100%
copy from res/images/progress_empty.png
copy to res-xxhdpi/images/progress_empty.png
Binary files differ
diff --git a/res/images/progress_fill.png b/res-xxhdpi/images/progress_fill.png
similarity index 100%
copy from res/images/progress_fill.png
copy to res-xxhdpi/images/progress_fill.png
Binary files differ
diff --git a/res/images/stage_empty.png b/res-xxhdpi/images/stage_empty.png
similarity index 100%
copy from res/images/stage_empty.png
copy to res-xxhdpi/images/stage_empty.png
Binary files differ
diff --git a/res/images/stage_fill.png b/res-xxhdpi/images/stage_fill.png
similarity index 100%
copy from res/images/stage_fill.png
copy to 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/images/icon_error.png b/res-xxxhdpi/images/icon_error.png
similarity index 100%
copy from res/images/icon_error.png
copy to 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/images/progress_empty.png b/res-xxxhdpi/images/progress_empty.png
similarity index 100%
copy from res/images/progress_empty.png
copy to res-xxxhdpi/images/progress_empty.png
Binary files differ
diff --git a/res/images/progress_fill.png b/res-xxxhdpi/images/progress_fill.png
similarity index 100%
copy from res/images/progress_fill.png
copy to res-xxxhdpi/images/progress_fill.png
Binary files differ
diff --git a/res/images/stage_empty.png b/res-xxxhdpi/images/stage_empty.png
similarity index 100%
copy from res/images/stage_empty.png
copy to res-xxxhdpi/images/stage_empty.png
Binary files differ
diff --git a/res/images/stage_fill.png b/res-xxxhdpi/images/stage_fill.png
similarity index 100%
copy from res/images/stage_fill.png
copy to res-xxxhdpi/images/stage_fill.png
Binary files differ
diff --git a/res/images/erasing_text.png b/res/images/erasing_text.png
deleted file mode 100644
index 441768a..0000000
--- a/res/images/erasing_text.png
+++ /dev/null
Binary files differ
diff --git a/res/images/error_text.png b/res/images/error_text.png
deleted file mode 100644
index 4ac6391..0000000
--- a/res/images/error_text.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing.png b/res/images/icon_installing.png
deleted file mode 100644
index 571eb8b..0000000
--- a/res/images/icon_installing.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay01.png b/res/images/icon_installing_overlay01.png
deleted file mode 100644
index e762d6c..0000000
--- a/res/images/icon_installing_overlay01.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay02.png b/res/images/icon_installing_overlay02.png
deleted file mode 100644
index f7a8530..0000000
--- a/res/images/icon_installing_overlay02.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay03.png b/res/images/icon_installing_overlay03.png
deleted file mode 100644
index 1a1d738..0000000
--- a/res/images/icon_installing_overlay03.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay04.png b/res/images/icon_installing_overlay04.png
deleted file mode 100644
index a74903d..0000000
--- a/res/images/icon_installing_overlay04.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay05.png b/res/images/icon_installing_overlay05.png
deleted file mode 100644
index d17bdc0..0000000
--- a/res/images/icon_installing_overlay05.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay06.png b/res/images/icon_installing_overlay06.png
deleted file mode 100644
index 1200b75..0000000
--- a/res/images/icon_installing_overlay06.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay07.png b/res/images/icon_installing_overlay07.png
deleted file mode 100644
index 3838a85..0000000
--- a/res/images/icon_installing_overlay07.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate01.png b/res/images/indeterminate01.png
deleted file mode 100644
index 933528d..0000000
--- a/res/images/indeterminate01.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate02.png b/res/images/indeterminate02.png
deleted file mode 100644
index d760e2b..0000000
--- a/res/images/indeterminate02.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate03.png b/res/images/indeterminate03.png
deleted file mode 100644
index 0e97399..0000000
--- a/res/images/indeterminate03.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate04.png b/res/images/indeterminate04.png
deleted file mode 100644
index c7d5b4e..0000000
--- a/res/images/indeterminate04.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate05.png b/res/images/indeterminate05.png
deleted file mode 100644
index d6fb2a0..0000000
--- a/res/images/indeterminate05.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate06.png b/res/images/indeterminate06.png
deleted file mode 100644
index 4486761..0000000
--- a/res/images/indeterminate06.png
+++ /dev/null
Binary files differ
diff --git a/res/images/installing_text.png b/res/images/installing_text.png
deleted file mode 100644
index e1ac819..0000000
--- a/res/images/installing_text.png
+++ /dev/null
Binary files differ
diff --git a/res/images/no_command_text.png b/res/images/no_command_text.png
deleted file mode 100644
index a688f09..0000000
--- a/res/images/no_command_text.png
+++ /dev/null
Binary files differ
diff --git a/roots.cpp b/roots.cpp
index 113dba1..0d47577 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>
 
 #include <fs_mgr.h>
 #include "mtdutils/mtdutils.h"
@@ -28,11 +30,17 @@
 #include "roots.h"
 #include "common.h"
 #include "make_ext4fs.h"
+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;
@@ -44,7 +52,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);
@@ -146,6 +154,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) {
     Volume* v = volume_for_path(volume);
     if (v == NULL) {
@@ -190,10 +212,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;
@@ -203,6 +266,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");
@@ -213,10 +311,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 27d0a24..03ef049 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -52,6 +52,7 @@
 ScreenRecoveryUI::ScreenRecoveryUI() :
     currentIcon(NONE),
     installingFrame(0),
+    locale(NULL),
     rtl_locale(false),
     progressBarType(EMPTY),
     progressScopeStart(0),
@@ -69,53 +70,33 @@
     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);
@@ -126,8 +107,8 @@
 
         int sh = (max_stage >= 0) ? stageHeight : 0;
 
-        int iconX = (gr_fb_width() - iconWidth) / 2;
-        int iconY = (gr_fb_height() - (iconHeight+textHeight+40+sh)) / 2;
+        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+sh)) / 2) + iconHeight + 40;
@@ -144,10 +125,6 @@
             }
         }
 
-        if (icon == INSTALLING_UPDATE || icon == ERASING) {
-            draw_install_overlay_locked(installingFrame);
-        }
-
         gr_color(255, 255, 255, 255);
         gr_texticon(textX, textY, text_surface);
     }
@@ -160,7 +137,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) {
@@ -197,18 +175,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;
-            }
-        }
     }
 }
 
@@ -240,12 +206,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;
@@ -335,12 +301,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) {
@@ -365,14 +325,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);
     }
@@ -392,7 +359,9 @@
     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];
@@ -407,38 +376,14 @@
     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 == '_') {
@@ -457,6 +402,8 @@
             rtl_locale = true;
         }
         free(lang);
+    } else {
+        new_locale = NULL;
     }
 }
 
@@ -464,19 +411,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 +
-                               ((max_stage >= 0) ? gr_get_height(stageMarkerEmpty) : 0))) / 2;
-    }
-
     currentIcon = icon;
     update_screen_locked();
 
@@ -516,7 +450,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;
diff --git a/screen_ui.h b/screen_ui.h
index 5c4366d..01a33bf 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -63,13 +63,13 @@
   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;
@@ -101,16 +101,14 @@
     pthread_t progress_t;
 
     int animation_fps;
-    int indeterminate_frames;
     int installing_frames;
   protected:
-    int install_overlay_offset_x, install_overlay_offset_y;
   private:
-    int overlay_offset_x, overlay_offset_y;
+
+    int iconX, iconY;
 
     int stage, max_stage;
 
-    void draw_install_overlay_locked(int frame);
     void draw_background_locked(Icon icon);
     void draw_progress_locked();
     void draw_screen_locked();
@@ -120,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/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/ui.cpp b/ui.cpp
index cece02d..c8f08cd 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -31,6 +31,7 @@
 #include <cutils/android_reboot.h>
 
 #include "common.h"
+#include "roots.h"
 #include "device.h"
 #include "minui/minui.h"
 #include "screen_ui.h"
@@ -47,10 +48,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() {
@@ -59,12 +65,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;
 
@@ -112,6 +118,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;
@@ -133,6 +140,7 @@
         }
         key_last_down = -1;
     }
+    reboot_enabled = enable_reboot;
     pthread_mutex_unlock(&key_queue_mutex);
 
     if (register_key) {
@@ -146,12 +154,21 @@
             break;
 
           case RecoveryUI::REBOOT:
-            android_reboot(ANDROID_RB_RESTART, 0, 0);
+            if (reboot_enabled) {
+                android_reboot(ANDROID_RB_RESTART, 0, 0);
+            }
             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;
         }
     }
 }
@@ -258,8 +275,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) {
@@ -267,3 +323,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 0757260..31a8a7f 100644
--- a/ui.h
+++ b/ui.h
@@ -79,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
@@ -93,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
@@ -121,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;
@@ -132,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 74%
rename from minelf/Android.mk
rename to uncrypt/Android.mk
index 0f41ff5..878d275 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,13 @@
 # limitations under the License.
 
 LOCAL_PATH := $(call my-dir)
+
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := \
-	Retouch.c
+LOCAL_SRC_FILES := uncrypt.c
 
-LOCAL_C_INCLUDES += bootable/recovery
+LOCAL_MODULE := uncrypt
 
-LOCAL_MODULE := libminelf
+LOCAL_STATIC_LIBRARIES := libfs_mgr liblog libcutils
 
-LOCAL_CFLAGS += -Wall
-
-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 67e98ec..a3a900a 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_static \
@@ -30,7 +32,6 @@
 LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UPDATER_LIBS) $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS)
 LOCAL_STATIC_LIBRARIES += libapplypatch libedify libmtdutils libminzip libz
 LOCAL_STATIC_LIBRARIES += libmincrypt libbz
-LOCAL_STATIC_LIBRARIES += libminelf
 LOCAL_STATIC_LIBRARIES += libcutils liblog libstdc++ libc
 LOCAL_STATIC_LIBRARIES += libselinux
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
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 aebd4f3..ff7de47 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -45,27 +45,73 @@
 #include "mtdutils/mounts.h"
 #include "mtdutils/mtdutils.h"
 #include "updater.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) {
@@ -105,13 +151,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;
@@ -119,8 +165,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 {
@@ -133,6 +180,7 @@
     free(partition_type);
     free(location);
     if (result != mount_point) free(mount_point);
+    if (has_mount_options) free(mount_options);
     return StringValue(result);
 }
 
@@ -183,10 +231,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;
     }
 
@@ -195,14 +247,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) {
@@ -274,6 +341,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\"",
@@ -304,13 +389,17 @@
         goto done;
     }
     if (strlen(dst_name) == 0) {
-        ErrorAbort(state, "dst_name argument to %s() can't be empty",
-                   name);
+        ErrorAbort(state, "dst_name argument to %s() can't be empty", name);
         goto done;
     }
-
-    if (rename(src_name, dst_name) != 0) {
-        ErrorAbort(state, "Rename of %s() to %s() failed, error %s()",
+    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;
@@ -421,19 +510,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);
@@ -502,7 +595,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
@@ -577,7 +670,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;
@@ -592,7 +685,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;
@@ -603,7 +696,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;
@@ -614,7 +707,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;
@@ -625,7 +718,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;
@@ -636,7 +729,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;
@@ -647,7 +740,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;
@@ -657,7 +750,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;
@@ -674,71 +767,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++;
             }
@@ -751,8 +844,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++;
             }
         }
@@ -764,10 +857,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[]) {
@@ -792,14 +886,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:
@@ -838,8 +934,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;
@@ -866,7 +962,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;
     }
 
@@ -879,7 +975,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;
     }
@@ -897,9 +993,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 '='
@@ -1053,8 +1147,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 "
@@ -1173,15 +1267,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);
 }
 
@@ -1239,19 +1325,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).
@@ -1322,7 +1395,7 @@
     v->type = VAL_BLOB;
 
     FileContents fc;
-    if (LoadFileContents(filename, &fc, RETOUCH_DONT_MASK) != 0) {
+    if (LoadFileContents(filename, &fc) != 0) {
         free(filename);
         v->size = -1;
         v->data = NULL;
@@ -1419,7 +1492,7 @@
 // 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 != 2) {
+    if (argc != 1) {
         return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
     }
 
@@ -1436,6 +1509,36 @@
     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);
@@ -1469,6 +1572,8 @@
     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);
@@ -1482,4 +1587,6 @@
     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 c7009fe..465e123 100644
--- a/updater/updater.c
+++ b/updater/updater.c
@@ -21,7 +21,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
@@ -65,19 +67,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;
     }
 
@@ -92,6 +99,7 @@
 
     RegisterBuiltins();
     RegisterInstallFunctions();
+    RegisterBlockImageFunctions();
     RegisterDeviceExtensions();
     FinishRegistration();
 
@@ -99,8 +107,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;
@@ -122,6 +129,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;
@@ -152,6 +161,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 0930fbd..3d4f603 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -111,15 +111,10 @@
 // Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered
 // or no key matches the signature).
 
-int verify_file(const char* path, const Certificate* pKeys, unsigned int numKeys) {
+int verify_file(unsigned char* addr, size_t length,
+                const Certificate* pKeys, unsigned int numKeys) {
     ui->SetProgress(0.0);
 
-    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)
@@ -131,33 +126,31 @@
 
 #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 > comment_size) {
+        LOGE("signature start: %zu is larger than comment size: %zu\n", signature_start,
+             comment_size);
+        return VERIFY_FAILURE;
+    }
+
     if (signature_start <= FOOTER_SIZE) {
         LOGE("Signature start is in the footer");
-        fclose(f);
         return VERIFY_FAILURE;
     }
 
@@ -167,9 +160,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;
     }
 
@@ -177,26 +169,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;
     }
 
@@ -209,7 +190,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;
         }
     }
@@ -229,35 +209,23 @@
     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);
@@ -269,10 +237,8 @@
     if (!read_pkcs7(eocd + eocd_size - signature_start, signature_size, &sig_der,
             &sig_der_length)) {
         LOGE("Could not find signature DER block\n");
-        free(eocd);
         return VERIFY_FAILURE;
     }
-    free(eocd);
 
     /*
      * Check to make sure at least one of the keys matches the signature. Since
@@ -292,24 +258,24 @@
         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 %d\n", i);
+                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 %d\n", i);
+                LOGI("failed to verify against RSA key %zu\n", i);
                 continue;
             }
 
-            LOGI("whole-file signature verified against RSA key %d\n", i);
+            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 %d\n", i);
+                LOGI("Not a DSA signature block for EC key %zu\n", i);
                 continue;
             }
 
@@ -317,11 +283,11 @@
             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 %d\n", i);
+                LOGI("failed to verify against EC key %zu\n", i);
                 continue;
             }
 
-            LOGI("whole-file signature verified against EC key %d\n", i);
+            LOGI("whole-file signature verified against EC key %zu\n", i);
             free(sig_der);
             return VERIFY_SUCCESS;
         } else {
diff --git a/verifier.h b/verifier.h
index 023d3bf..15f8d98 100644
--- a/verifier.h
+++ b/verifier.h
@@ -37,10 +37,13 @@
     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, const Certificate *pKeys, unsigned int numKeys);
+int verify_file(unsigned char* addr, size_t length,
+                const Certificate *pKeys, unsigned int numKeys);
 
 Certificate* load_keys(const char* filename, int* numKeys);
 
diff --git a/verifier_test.cpp b/verifier_test.cpp
index 88fcad4..10a5dda 100644
--- a/verifier_test.cpp
+++ b/verifier_test.cpp
@@ -17,12 +17,16 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 
 #include "common.h"
 #include "verifier.h"
 #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.
@@ -227,7 +231,13 @@
 
     ui = new FakeUI();
 
-    int result = verify_file(argv[argn], certs, num_keys);
+    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, certs, num_keys);
     if (result == VERIFY_SUCCESS) {
         printf("VERIFIED\n");
         return 0;