DO NOT MERGE: resolve merge conflicts of 5346da02 to klp-modular-dev am: 4eb997d52b  -s ours am: 3591cb48f5  -s ours am: 8bdb13dd9a
am: 0d750f900b  -s ours

Change-Id: Iaab9c0e9d8e5a5198bcad373d229915c6bc62c79
diff --git a/Android.mk b/Android.mk
index 1a91f00..0484065 100644
--- a/Android.mk
+++ b/Android.mk
@@ -30,16 +30,17 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
-    recovery.cpp \
-    bootloader.cpp \
-    install.cpp \
-    roots.cpp \
-    ui.cpp \
-    screen_ui.cpp \
-    asn1_decoder.cpp \
-    verifier.cpp \
     adb_install.cpp \
-    fuse_sdcard_provider.c
+    asn1_decoder.cpp \
+    bootloader.cpp \
+    device.cpp \
+    fuse_sdcard_provider.c \
+    install.cpp \
+    recovery.cpp \
+    roots.cpp \
+    screen_ui.cpp \
+    ui.cpp \
+    verifier.cpp \
 
 LOCAL_MODULE := recovery
 
@@ -54,6 +55,11 @@
 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
 LOCAL_CFLAGS += -Wno-unused-parameter
 
+LOCAL_C_INCLUDES += \
+    system/vold \
+    system/extras/ext4_utils \
+    system/core/adb \
+
 LOCAL_STATIC_LIBRARIES := \
     libext4_utils_static \
     libsparse_static \
@@ -66,6 +72,7 @@
     libminui \
     libpng \
     libfs_mgr \
+    libbase \
     libcutils \
     liblog \
     libselinux \
@@ -75,15 +82,11 @@
 
 ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
     LOCAL_CFLAGS += -DUSE_EXT4
-    LOCAL_C_INCLUDES += system/extras/ext4_utils system/vold
+    LOCAL_C_INCLUDES += system/extras/ext4_utils
     LOCAL_STATIC_LIBRARIES += libext4_utils_static libz
 endif
 
-# This binary is in the recovery ramdisk, which is otherwise a copy of root.
-# It gets copied there in config/Makefile.  LOCAL_MODULE_TAGS suppresses
-# a (redundant) copy of the binary in /system/bin for user builds.
-# TODO: Build the ramdisk image in a more principled way.
-LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
 
 ifeq ($(TARGET_RECOVERY_UI_LIB),)
   LOCAL_SRC_FILES += default_device.cpp
@@ -91,9 +94,6 @@
   LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB)
 endif
 
-LOCAL_C_INCLUDES += system/extras/ext4_utils
-LOCAL_C_INCLUDES += external/openssl/include
-
 include $(BUILD_EXECUTABLE)
 
 # All the APIs for testing
@@ -108,7 +108,6 @@
 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 \
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bab7e87
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+The Recovery Image
+==================
+
+Quick turn-around testing
+-------------------------
+
+    mm -j && m ramdisk-nodeps && m recoveryimage-nodeps
+
+    # To boot into the new recovery image
+    # without flashing the recovery partition:
+    adb reboot bootloader
+    fastboot boot $ANDROID_PRODUCT_OUT/recovery.img
diff --git a/adb_install.cpp b/adb_install.cpp
index be3b9a0..e3b94ea 100644
--- a/adb_install.cpp
+++ b/adb_install.cpp
@@ -30,10 +30,8 @@
 #include "install.h"
 #include "common.h"
 #include "adb_install.h"
-extern "C" {
 #include "minadbd/fuse_adb_provider.h"
 #include "fuse_sideload.h"
-}
 
 static RecoveryUI* ui = NULL;
 
@@ -44,7 +42,7 @@
         ui->Print("failed to open driver control: %s\n", strerror(errno));
         return;
     }
-    if (write(fd, enabled ? "1" : "0", 1) < 0) {
+    if (TEMP_FAILURE_RETRY(write(fd, enabled ? "1" : "0", 1)) == -1) {
         ui->Print("failed to set driver control: %s\n", strerror(errno));
     }
     if (close(fd) < 0) {
@@ -61,9 +59,7 @@
 
 static void
 maybe_restart_adbd() {
-    char value[PROPERTY_VALUE_MAX+1];
-    int len = property_get("ro.debuggable", value, NULL);
-    if (len == 1 && value[0] == '1') {
+    if (is_ro_debuggable()) {
         ui->Print("Restarting adbd...\n");
         set_usb_driver(true);
         property_set("ctl.start", "adbd");
@@ -75,7 +71,9 @@
 #define ADB_INSTALL_TIMEOUT 300
 
 int
-apply_from_adb(RecoveryUI* ui_, int* wipe_cache, const char* install_file) {
+apply_from_adb(RecoveryUI* ui_, bool* wipe_cache, const char* install_file) {
+    modified_flash = true;
+
     ui = ui_;
 
     stop_adbd();
@@ -109,7 +107,7 @@
                 sleep(1);
                 continue;
             } else {
-                ui->Print("\nTimed out waiting for package.\n\n", strerror(errno));
+                ui->Print("\nTimed out waiting for package.\n\n");
                 result = INSTALL_ERROR;
                 kill(child, SIGKILL);
                 break;
diff --git a/adb_install.h b/adb_install.h
index a18b712..efad436 100644
--- a/adb_install.h
+++ b/adb_install.h
@@ -19,6 +19,6 @@
 
 class RecoveryUI;
 
-int apply_from_adb(RecoveryUI* h, int* wipe_cache, const char* install_file);
+int apply_from_adb(RecoveryUI* h, bool* wipe_cache, const char* install_file);
 
 #endif
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
index 2c86e09..2358d42 100644
--- a/applypatch/applypatch.c
+++ b/applypatch/applypatch.c
@@ -422,20 +422,19 @@
             int attempt;
 
             for (attempt = 0; attempt < 2; ++attempt) {
-                lseek(fd, start, SEEK_SET);
+                if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) {
+                    printf("failed seek on %s: %s\n",
+                           partition, strerror(errno));
+                    return -1;
+                }
                 while (start < len) {
                     size_t to_write = len - start;
                     if (to_write > 1<<20) to_write = 1<<20;
 
-                    ssize_t written = write(fd, data+start, to_write);
-                    if (written < 0) {
-                        if (errno == EINTR) {
-                            written = 0;
-                        } else {
-                            printf("failed write writing to %s (%s)\n",
-                                   partition, strerror(errno));
-                            return -1;
-                        }
+                    ssize_t written = TEMP_FAILURE_RETRY(write(fd, data+start, to_write));
+                    if (written == -1) {
+                        printf("failed write writing to %s: %s\n", partition, strerror(errno));
+                        return -1;
                     }
                     start += written;
                 }
@@ -460,13 +459,20 @@
                 // won't just be reading the cache.
                 sync();
                 int dc = open("/proc/sys/vm/drop_caches", O_WRONLY);
-                write(dc, "3\n", 2);
+                if (TEMP_FAILURE_RETRY(write(dc, "3\n", 2)) == -1) {
+                    printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno));
+                } else {
+                    printf("  caches dropped\n");
+                }
                 close(dc);
                 sleep(1);
-                printf("  caches dropped\n");
 
                 // verify
-                lseek(fd, 0, SEEK_SET);
+                if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) {
+                    printf("failed to seek back to beginning of %s: %s\n",
+                           partition, strerror(errno));
+                    return -1;
+                }
                 unsigned char buffer[4096];
                 start = len;
                 size_t p;
@@ -476,15 +482,12 @@
 
                     size_t so_far = 0;
                     while (so_far < to_read) {
-                        ssize_t read_count = read(fd, buffer+so_far, to_read-so_far);
-                        if (read_count < 0) {
-                            if (errno == EINTR) {
-                                read_count = 0;
-                            } else {
-                                printf("verify read error %s at %zu: %s\n",
-                                       partition, p, strerror(errno));
-                                return -1;
-                            }
+                        ssize_t read_count =
+                                TEMP_FAILURE_RETRY(read(fd, buffer+so_far, to_read-so_far));
+                        if (read_count == -1) {
+                            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 %zu: %zd %zu %s\n",
@@ -625,8 +628,8 @@
     ssize_t done = 0;
     ssize_t wrote;
     while (done < (ssize_t) len) {
-        wrote = write(fd, data+done, len-done);
-        if (wrote <= 0) {
+        wrote = TEMP_FAILURE_RETRY(write(fd, data+done, len-done));
+        if (wrote == -1) {
             printf("error writing %d bytes: %s\n", (int)(len-done), strerror(errno));
             return done;
         }
@@ -659,7 +662,7 @@
         printf("failed to statfs %s: %s\n", filename, strerror(errno));
         return -1;
     }
-    return sf.f_bsize * sf.f_bfree;
+    return sf.f_bsize * sf.f_bavail;
 }
 
 int CacheSizeCheck(size_t bytes) {
diff --git a/applypatch/bspatch.c b/applypatch/bspatch.c
index b34ec2a..b57760e 100644
--- a/applypatch/bspatch.c
+++ b/applypatch/bspatch.c
@@ -23,6 +23,7 @@
 #include <stdio.h>
 #include <sys/stat.h>
 #include <errno.h>
+#include <malloc.h>
 #include <unistd.h>
 #include <string.h>
 
diff --git a/applypatch/imgdiff.c b/applypatch/imgdiff.c
index 05c4f25..3bac8be 100644
--- a/applypatch/imgdiff.c
+++ b/applypatch/imgdiff.c
@@ -408,6 +408,7 @@
         p[2] == 0x08 &&    // deflate compression
         p[3] == 0x00) {    // no header flags
       // 'pos' is the offset of the start of a gzip chunk.
+      size_t chunk_offset = pos;
 
       *num_chunks += 3;
       *chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk));
@@ -453,6 +454,14 @@
         strm.avail_out = allocated - curr->len;
         strm.next_out = curr->data + curr->len;
         ret = inflate(&strm, Z_NO_FLUSH);
+        if (ret < 0) {
+            printf("Error: inflate failed [%s] at file offset [%zu]\n"
+                    "imgdiff only supports gzip kernel compression,"
+                    " did you try CONFIG_KERNEL_LZO?\n",
+                    strm.msg, chunk_offset);
+            free(img);
+            return NULL;
+        }
         curr->len = allocated - strm.avail_out;
         if (strm.avail_out == 0) {
           allocated *= 2;
diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c
index 33c4487..09b0a73 100644
--- a/applypatch/imgpatch.c
+++ b/applypatch/imgpatch.c
@@ -21,6 +21,7 @@
 #include <sys/cdefs.h>
 #include <sys/stat.h>
 #include <errno.h>
+#include <malloc.h>
 #include <unistd.h>
 #include <string.h>
 
diff --git a/asn1_decoder.cpp b/asn1_decoder.cpp
index 7280f74..e7aef78 100644
--- a/asn1_decoder.cpp
+++ b/asn1_decoder.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <malloc.h>
 #include <stdint.h>
 #include <string.h>
 
diff --git a/common.h b/common.h
index 768f499..b818ceb 100644
--- a/common.h
+++ b/common.h
@@ -17,6 +17,7 @@
 #ifndef RECOVERY_COMMON_H
 #define RECOVERY_COMMON_H
 
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdarg.h>
 
@@ -39,6 +40,7 @@
 #define STRINGIFY(x) #x
 #define EXPAND(x) STRINGIFY(x)
 
+extern bool modified_flash;
 typedef struct fstab_rec Volume;
 
 // fopen a file, mounting volumes and making parent dirs as necessary.
@@ -46,6 +48,8 @@
 
 void ui_print(const char* format, ...);
 
+bool is_ro_debuggable();
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/default_device.cpp b/default_device.cpp
index 97806ac..a971866 100644
--- a/default_device.cpp
+++ b/default_device.cpp
@@ -14,74 +14,9 @@
  * limitations under the License.
  */
 
-#include <linux/input.h>
-
-#include "common.h"
 #include "device.h"
 #include "screen_ui.h"
 
-static const char* HEADERS[] = { "Volume up/down to move highlight;",
-                                 "enter button to select.",
-                                 "",
-                                 NULL };
-
-static const char* ITEMS[] =  {"reboot system now",
-                               "apply update from ADB",
-                               "wipe data/factory reset",
-                               "wipe cache partition",
-                               "reboot to bootloader",
-                               "power down",
-                               "view recovery logs",
-                               NULL };
-
-class DefaultDevice : public Device {
-  public:
-    DefaultDevice() :
-        ui(new ScreenRecoveryUI) {
-    }
-
-    RecoveryUI* GetUI() { return ui; }
-
-    int HandleMenuKey(int key, int visible) {
-        if (visible) {
-            switch (key) {
-              case KEY_DOWN:
-              case KEY_VOLUMEDOWN:
-                return kHighlightDown;
-
-              case KEY_UP:
-              case KEY_VOLUMEUP:
-                return kHighlightUp;
-
-              case KEY_ENTER:
-              case KEY_POWER:
-                return kInvokeItem;
-            }
-        }
-
-        return kNoAction;
-    }
-
-    BuiltinAction InvokeMenuItem(int menu_position) {
-        switch (menu_position) {
-          case 0: return REBOOT;
-          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;
-        }
-    }
-
-    const char* const* GetMenuHeaders() { return HEADERS; }
-    const char* const* GetMenuItems() { return ITEMS; }
-
-  private:
-    RecoveryUI* ui;
-};
-
 Device* make_device() {
-    return new DefaultDevice();
+  return new Device(new ScreenRecoveryUI);
 }
diff --git a/device.cpp b/device.cpp
new file mode 100644
index 0000000..fd1a987
--- /dev/null
+++ b/device.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 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 "device.h"
+
+static const char* MENU_ITEMS[] = {
+    "Reboot system now",
+    "Reboot to bootloader",
+    "Apply update from ADB",
+    "Apply update from SD card",
+    "Wipe data/factory reset",
+    "Wipe cache partition",
+    "Mount /system",
+    "View recovery logs",
+    "Power off",
+    NULL
+};
+
+const char* const* Device::GetMenuItems() {
+  return MENU_ITEMS;
+}
+
+Device::BuiltinAction Device::InvokeMenuItem(int menu_position) {
+  switch (menu_position) {
+    case 0: return REBOOT;
+    case 1: return REBOOT_BOOTLOADER;
+    case 2: return APPLY_ADB_SIDELOAD;
+    case 3: return APPLY_SDCARD;
+    case 4: return WIPE_DATA;
+    case 5: return WIPE_CACHE;
+    case 6: return MOUNT_SYSTEM;
+    case 7: return VIEW_RECOVERY_LOGS;
+    case 8: return SHUTDOWN;
+    default: return NO_ACTION;
+  }
+}
+
+int Device::HandleMenuKey(int key, int visible) {
+  if (!visible) {
+    return kNoAction;
+  }
+
+  switch (key) {
+    case KEY_DOWN:
+    case KEY_VOLUMEDOWN:
+      return kHighlightDown;
+
+    case KEY_UP:
+    case KEY_VOLUMEUP:
+      return kHighlightUp;
+
+    case KEY_ENTER:
+    case KEY_POWER:
+      return kInvokeItem;
+
+    default:
+      // If you have all of the above buttons, any other buttons
+      // are ignored. Otherwise, any button cycles the highlight.
+      return ui_->HasThreeButtons() ? kNoAction : kHighlightDown;
+  }
+}
diff --git a/device.h b/device.h
index 8ff4ec0..f74b6b0 100644
--- a/device.h
+++ b/device.h
@@ -21,29 +21,20 @@
 
 class Device {
   public:
+    Device(RecoveryUI* ui) : ui_(ui) { }
     virtual ~Device() { }
 
     // Called to obtain the UI object that should be used to display
     // the recovery user interface for this device.  You should not
     // have called Init() on the UI object already, the caller will do
     // that after this method returns.
-    virtual RecoveryUI* GetUI() = 0;
+    virtual RecoveryUI* GetUI() { return ui_; }
 
     // Called when recovery starts up (after the UI has been obtained
     // and initialized and after the arguments have been parsed, but
     // before anything else).
     virtual void StartRecovery() { };
 
-    // enum KeyAction { NONE, TOGGLE, REBOOT };
-
-    // // Called in the input thread when a new key (key_code) is
-    // // pressed.  *key_pressed is an array of KEY_MAX+1 bytes
-    // // indicating which other keys are already pressed.  Return a
-    // // KeyAction to indicate action should be taken immediately.
-    // // These actions happen when recovery is not waiting for input
-    // // (eg, in the midst of installing a package).
-    // virtual KeyAction CheckImmediateKeyAction(volatile char* key_pressed, int key_code) = 0;
-
     // Called from the main thread when recovery is at the main menu
     // and waiting for input, and a key is pressed.  (Note that "at"
     // the main menu does not necessarily mean the menu is visible;
@@ -63,12 +54,26 @@
     //   - invoke the highlighted item (kInvokeItem)
     //   - do nothing (kNoAction)
     //   - invoke a specific action (a menu position: any non-negative number)
-    virtual int HandleMenuKey(int key, int visible) = 0;
+    virtual int HandleMenuKey(int key, int visible);
 
-    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 };
+    enum BuiltinAction {
+        NO_ACTION = 0,
+        REBOOT = 1,
+        APPLY_SDCARD = 2,
+        // APPLY_CACHE was 3.
+        APPLY_ADB_SIDELOAD = 4,
+        WIPE_DATA = 5,
+        WIPE_CACHE = 6,
+        REBOOT_BOOTLOADER = 7,
+        SHUTDOWN = 8,
+        VIEW_RECOVERY_LOGS = 9,
+        MOUNT_SYSTEM = 10,
+    };
+
+    // Return the list of menu items (an array of strings,
+    // NULL-terminated).  The menu_position passed to InvokeMenuItem
+    // will correspond to the indexes into this array.
+    virtual const char* const* GetMenuItems();
 
     // Perform a recovery action selected from the menu.
     // 'menu_position' will be the item number of the selected menu
@@ -79,31 +84,26 @@
     // builtin actions, you can just return the corresponding enum
     // value.  If it is an action specific to your device, you
     // actually perform it here and return NO_ACTION.
-    virtual BuiltinAction InvokeMenuItem(int menu_position) = 0;
+    virtual BuiltinAction InvokeMenuItem(int menu_position);
 
     static const int kNoAction = -1;
     static const int kHighlightUp = -2;
     static const int kHighlightDown = -3;
     static const int kInvokeItem = -4;
 
-    // Called when we do a wipe data/factory reset operation (either via a
-    // reboot from the main system with the --wipe_data flag, or when the
-    // user boots into recovery manually and selects the option from the
-    // menu.)  Can perform whatever device-specific wiping actions are
-    // needed.  Return 0 on success.  The userdata and cache partitions
-    // are erased AFTER this returns (whether it returns success or not).
-    virtual int WipeData() { return 0; }
+    // Called before and after we do a wipe data/factory reset operation,
+    // either via a reboot from the main system with the --wipe_data flag,
+    // or when the user boots into recovery image manually and selects the
+    // option from the menu, to perform whatever device-specific wiping
+    // actions are needed.
+    // Return true on success; returning false from PreWipeData will prevent
+    // the regular wipe, and returning false from PostWipeData will cause
+    // the wipe to be considered a failure.
+    virtual bool PreWipeData() { return true; }
+    virtual bool PostWipeData() { return true; }
 
-    // Return the headers (an array of strings, one per line,
-    // NULL-terminated) for the main menu.  Typically these tell users
-    // what to push to move the selection and invoke the selected
-    // item.
-    virtual const char* const* GetMenuHeaders() = 0;
-
-    // Return the list of menu items (an array of strings,
-    // NULL-terminated).  The menu_position passed to InvokeMenuItem
-    // will correspond to the indexes into this array.
-    virtual const char* const* GetMenuItems() = 0;
+  private:
+    RecoveryUI* ui_;
 };
 
 // The device-specific library must define this function (or the
diff --git a/edify/Android.mk b/edify/Android.mk
index 61ed6fa..03c04e4 100644
--- a/edify/Android.mk
+++ b/edify/Android.mk
@@ -7,9 +7,10 @@
 	parser.y \
 	expr.c
 
-# "-x c" forces the lex/yacc files to be compiled as c;
-# the build system otherwise forces them to be c++.
-edify_cflags := -x c
+# "-x c" forces the lex/yacc files to be compiled as c the build system
+# otherwise forces them to be c++. Need to also add an explicit -std because the
+# build system will soon default C++ to -std=c++11.
+edify_cflags := -x c -std=gnu89
 
 #
 # Build the host-side command line tool
diff --git a/edify/main.c b/edify/main.c
index b3fad53..b1baa0b 100644
--- a/edify/main.c
+++ b/edify/main.c
@@ -25,13 +25,12 @@
 
 int expect(const char* expr_str, const char* expected, int* errors) {
     Expr* e;
-    int error;
     char* result;
 
     printf(".");
 
     int error_count = parse_string(expr_str, &e, &error_count);
-    if (error > 0 || error_count > 0) {
+    if (error_count > 0) {
         printf("error parsing \"%s\" (%d errors)\n",
                expr_str, error_count);
         ++*errors;
diff --git a/etc/init.rc b/etc/init.rc
index c78a44a..6c07c60 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -1,13 +1,6 @@
 import /init.recovery.${ro.hardware}.rc
 
 on early-init
-    # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls.
-    write /sys/fs/selinux/checkreqprot 0
-
-    # Set the security context for the init process.
-    # This should occur before anything else (e.g. ueventd) is started.
-    setcon u:r:init:s0
-
     start ueventd
     start healthd
 
diff --git a/fuse_sdcard_provider.c b/fuse_sdcard_provider.c
index 19fb52d..4565c7b 100644
--- a/fuse_sdcard_provider.c
+++ b/fuse_sdcard_provider.c
@@ -16,6 +16,7 @@
 
 #include <stdlib.h>
 #include <stdio.h>
+#include <string.h>
 #include <errno.h>
 #include <pthread.h>
 #include <sys/mount.h>
@@ -35,19 +36,17 @@
 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));
+    off64_t offset = ((off64_t) block) * fd->block_size;
+    if (TEMP_FAILURE_RETRY(lseek64(fd->fd, offset, SEEK_SET)) == -1) {
+        fprintf(stderr, "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;
+        ssize_t r = TEMP_FAILURE_RETRY(read(fd->fd, buffer, fetch_size));
+        if (r == -1) {
+            fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno));
+            return -EIO;
         }
         fetch_size -= r;
         buffer += r;
diff --git a/fuse_sdcard_provider.h b/fuse_sdcard_provider.h
index dc2982c..dbfbcd5 100644
--- a/fuse_sdcard_provider.h
+++ b/fuse_sdcard_provider.h
@@ -17,7 +17,13 @@
 #ifndef __FUSE_SDCARD_PROVIDER_H
 #define __FUSE_SDCARD_PROVIDER_H
 
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
 void* start_sdcard_fuse(const char* path);
 void finish_sdcard_fuse(void* token);
 
+__END_DECLS
+
 #endif
diff --git a/fuse_sideload.c b/fuse_sideload.c
index ab91def..48e6cc5 100644
--- a/fuse_sideload.c
+++ b/fuse_sideload.c
@@ -53,6 +53,7 @@
 #include <string.h>
 #include <sys/inotify.h>
 #include <sys/mount.h>
+#include <sys/param.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/statfs.h>
@@ -105,27 +106,52 @@
 
     vec[0].iov_base = &hdr;
     vec[0].iov_len = sizeof(hdr);
-    vec[1].iov_base = data;
+    vec[1].iov_base = /* const_cast */(void*)(data);
     vec[1].iov_len = len;
 
     res = writev(fd->ffd, vec, 2);
     if (res < 0) {
-        printf("*** REPLY FAILED *** %d\n", errno);
+        printf("*** REPLY FAILED *** %s\n", strerror(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;
+    size_t fuse_struct_size;
+
+
+    /* Kernel 2.6.16 is the first stable kernel with struct fuse_init_out
+     * defined (fuse version 7.6). The structure is the same from 7.6 through
+     * 7.22. Beginning with 7.23, the structure increased in size and added
+     * new parameters.
+     */
+    if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) {
+        printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6",
+               req->major, req->minor, FUSE_KERNEL_VERSION);
+        return -1;
+    }
+
+    out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION);
+    fuse_struct_size = sizeof(out);
+#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
+    /* FUSE_KERNEL_VERSION >= 23. */
+
+    /* If the kernel only works on minor revs older than or equal to 22,
+     * then use the older structure size since this code only uses the 7.22
+     * version of the structure. */
+    if (req->minor <= 22) {
+        fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
+    }
+#endif
 
     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));
+    fuse_reply(fd, hdr->unique, &out, fuse_struct_size);
 
     return NO_STATUS;
 }
@@ -404,7 +430,7 @@
 
     char opts[256];
     snprintf(opts, sizeof(opts),
-             ("fd=%d,user_id=%d,group_id=%d,max_read=%zu,"
+             ("fd=%d,user_id=%d,group_id=%d,max_read=%u,"
               "allow_other,rootmode=040000"),
              fd.ffd, fd.uid, fd.gid, block_size);
 
@@ -416,14 +442,12 @@
     }
     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;
-                }
+        ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer)));
+        if (len == -1) {
+            perror("read request");
+            if (errno == ENODEV) {
+                result = -1;
+                break;
             }
             continue;
         }
@@ -482,7 +506,7 @@
             outhdr.len = sizeof(outhdr);
             outhdr.error = result;
             outhdr.unique = hdr->unique;
-            write(fd.ffd, &outhdr, sizeof(outhdr));
+            TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr)));
         }
     }
 
diff --git a/fuse_sideload.h b/fuse_sideload.h
index c0b16ef..f9e3bf0 100644
--- a/fuse_sideload.h
+++ b/fuse_sideload.h
@@ -17,6 +17,10 @@
 #ifndef __FUSE_SIDELOAD_H
 #define __FUSE_SIDELOAD_H
 
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
 // define the filenames created by the sideload FUSE filesystem
 #define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload"
 #define FUSE_SIDELOAD_HOST_FILENAME "package.zip"
@@ -35,4 +39,6 @@
 int run_fuse_sideload(struct provider_vtab* vtab, void* cookie,
                       uint64_t file_size, uint32_t block_size);
 
+__END_DECLS
+
 #endif
diff --git a/install.cpp b/install.cpp
index 9db5640..c7d382f 100644
--- a/install.cpp
+++ b/install.cpp
@@ -18,6 +18,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <string.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -47,7 +48,7 @@
 
 // If the package contains an update binary, extract it and run it.
 static int
-try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {
+try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
     const ZipEntry* binary_entry =
             mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
     if (binary_entry == NULL) {
@@ -87,7 +88,7 @@
     //            fill up the next <frac> part of of the progress bar
     //            over <secs> seconds.  If <secs> is zero, use
     //            set_progress commands to manually control the
-    //            progress of this segment of the bar
+    //            progress of this segment of the bar.
     //
     //        set_progress <frac>
     //            <frac> should be between 0.0 and 1.0; sets the
@@ -106,6 +107,18 @@
     //        ui_print <string>
     //            display <string> on the screen.
     //
+    //        wipe_cache
+    //            a wipe of cache will be performed following a successful
+    //            installation.
+    //
+    //        clear_display
+    //            turn off the text display.
+    //
+    //        enable_reboot
+    //            packages can explicitly request that they want the user
+    //            to be able to reboot during installation (useful for
+    //            debugging packages that don't exit).
+    //
     //   - the name of the package zip file.
     //
 
@@ -128,7 +141,7 @@
     }
     close(pipefd[1]);
 
-    *wipe_cache = 0;
+    *wipe_cache = false;
 
     char buffer[1024];
     FILE* from_child = fdopen(pipefd[0], "r");
@@ -157,7 +170,7 @@
             }
             fflush(stdout);
         } else if (strcmp(command, "wipe_cache") == 0) {
-            *wipe_cache = 1;
+            *wipe_cache = true;
         } else if (strcmp(command, "clear_display") == 0) {
             ui->SetBackground(RecoveryUI::NONE);
         } else if (strcmp(command, "enable_reboot") == 0) {
@@ -182,7 +195,7 @@
 }
 
 static int
-really_install_package(const char *path, int* wipe_cache, bool needs_mount)
+really_install_package(const char *path, bool* wipe_cache, bool needs_mount)
 {
     ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
     ui->Print("Finding update package...\n");
@@ -252,9 +265,11 @@
 }
 
 int
-install_package(const char* path, int* wipe_cache, const char* install_file,
+install_package(const char* path, bool* wipe_cache, const char* install_file,
                 bool needs_mount)
 {
+    modified_flash = true;
+
     FILE* install_log = fopen_path(install_file, "w");
     if (install_log) {
         fputs(path, install_log);
diff --git a/install.h b/install.h
index 53c0d31..680499d 100644
--- a/install.h
+++ b/install.h
@@ -27,7 +27,7 @@
 // Install the package specified by root_path.  If INSTALL_SUCCESS is
 // returned and *wipe_cache is true on exit, caller should wipe the
 // cache partition.
-int install_package(const char *root_path, int* wipe_cache,
+int install_package(const char* root_path, bool* wipe_cache,
                     const char* install_file, bool needs_mount);
 
 #ifdef __cplusplus
diff --git a/minadbd/Android.mk b/minadbd/Android.mk
index 04956d8..a7a3e08 100644
--- a/minadbd/Android.mk
+++ b/minadbd/Android.mk
@@ -1,32 +1,37 @@
 # Copyright 2005 The Android Open Source Project
-#
-# Android.mk for adb
-#
 
 LOCAL_PATH:= $(call my-dir)
 
-# minadbd library
-# =========================================================
+minadbd_cflags := \
+    -Wall -Werror \
+    -Wno-unused-parameter \
+    -Wno-missing-field-initializers \
+    -DADB_HOST=0 \
 
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
-	adb.c \
-	fdevent.c \
-	fuse_adb_provider.c \
-	transport.c \
-	transport_usb.c \
-	sockets.c \
-	services.c \
-	usb_linux_client.c \
-	utils.c
-
-LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter
-LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
-LOCAL_C_INCLUDES += bootable/recovery
+    adb_main.cpp \
+    fuse_adb_provider.cpp \
+    services.cpp \
 
 LOCAL_MODULE := libminadbd
-
-LOCAL_STATIC_LIBRARIES := libfusesideload libcutils libc
+LOCAL_CFLAGS := $(minadbd_cflags)
+LOCAL_CONLY_FLAGS := -Wimplicit-function-declaration
+LOCAL_C_INCLUDES := bootable/recovery system/core/adb
+LOCAL_WHOLE_STATIC_LIBRARIES := libadbd
+LOCAL_STATIC_LIBRARIES := libbase
 
 include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_CLANG := true
+LOCAL_MODULE := minadbd_test
+LOCAL_SRC_FILES := fuse_adb_provider_test.cpp
+LOCAL_CFLAGS := $(minadbd_cflags)
+LOCAL_C_INCLUDES := $(LOCAL_PATH) system/core/adb
+LOCAL_STATIC_LIBRARIES := libminadbd
+LOCAL_SHARED_LIBRARIES := liblog libbase libcutils
+
+include $(BUILD_NATIVE_TEST)
diff --git a/minadbd/README.txt b/minadbd/README.txt
index c9df484..e69dc87 100644
--- a/minadbd/README.txt
+++ b/minadbd/README.txt
@@ -1,39 +1,8 @@
-The contents of this directory are copied from system/core/adb, with
-the following changes:
+minadbd is now mostly built from libadbd. The fuse features are unique to
+minadbd, and services.c has been modified as follows:
 
-adb.c
-  - much support for host mode and non-linux OS's stripped out; this
-    version only runs as adbd on the device.
-  - always setuid/setgid's itself to the shell user
-  - only uses USB transport
-  - references to JDWP removed
-  - main() removed
-  - all ADB_HOST and win32 code removed
-  - removed listeners, logging code, background server (for host)
-
-adb.h
-  - minor changes to match adb.c changes
-
-sockets.c
-  - references to JDWP removed
-  - ADB_HOST code removed
-
-services.c
-  - all services except echo_service (which is commented out) removed
+  - all services removed
   - all host mode support removed
   - sideload_service() added; this is the only service supported.  It
     receives a single blob of data, writes it to a fixed filename, and
     makes the process exit.
-
-Android.mk
-  - only builds in adbd mode; builds as static library instead of a
-    standalone executable.
-
-sysdeps.h
-  - changes adb_creat() to use O_NOFOLLOW
-
-transport.c
-  - removed ADB_HOST code
-
-transport_usb.c
-  - removed ADB_HOST code
diff --git a/minadbd/adb.c b/minadbd/adb.c
deleted file mode 100644
index 127d072..0000000
--- a/minadbd/adb.c
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define  TRACE_TAG   TRACE_ADB
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <string.h>
-#include <time.h>
-#include <sys/time.h>
-
-#include "sysdeps.h"
-#include "adb.h"
-
-#include <private/android_filesystem_config.h>
-
-#if ADB_TRACE
-ADB_MUTEX_DEFINE( D_lock );
-#endif
-
-int HOST = 0;
-
-static const char *adb_device_banner = "sideload";
-
-void fatal(const char *fmt, ...)
-{
-    va_list ap;
-    va_start(ap, fmt);
-    fprintf(stderr, "error: ");
-    vfprintf(stderr, fmt, ap);
-    fprintf(stderr, "\n");
-    va_end(ap);
-    exit(-1);
-}
-
-void fatal_errno(const char *fmt, ...)
-{
-    va_list ap;
-    va_start(ap, fmt);
-    fprintf(stderr, "error: %s: ", strerror(errno));
-    vfprintf(stderr, fmt, ap);
-    fprintf(stderr, "\n");
-    va_end(ap);
-    exit(-1);
-}
-
-int   adb_trace_mask;
-
-/* read a comma/space/colum/semi-column separated list of tags
- * from the ADB_TRACE environment variable and build the trace
- * mask from it. note that '1' and 'all' are special cases to
- * enable all tracing
- */
-void  adb_trace_init(void)
-{
-    const char*  p = getenv("ADB_TRACE");
-    const char*  q;
-
-    static const struct {
-        const char*  tag;
-        int           flag;
-    } tags[] = {
-        { "1", 0 },
-        { "all", 0 },
-        { "adb", TRACE_ADB },
-        { "sockets", TRACE_SOCKETS },
-        { "packets", TRACE_PACKETS },
-        { "rwx", TRACE_RWX },
-        { "usb", TRACE_USB },
-        { "sync", TRACE_SYNC },
-        { "sysdeps", TRACE_SYSDEPS },
-        { "transport", TRACE_TRANSPORT },
-        { "jdwp", TRACE_JDWP },
-        { "services", TRACE_SERVICES },
-        { NULL, 0 }
-    };
-
-    if (p == NULL)
-            return;
-
-    /* use a comma/column/semi-colum/space separated list */
-    while (*p) {
-        int  len, tagn;
-
-        q = strpbrk(p, " ,:;");
-        if (q == NULL) {
-            q = p + strlen(p);
-        }
-        len = q - p;
-
-        for (tagn = 0; tags[tagn].tag != NULL; tagn++)
-        {
-            int  taglen = strlen(tags[tagn].tag);
-
-            if (len == taglen && !memcmp(tags[tagn].tag, p, len) )
-            {
-                int  flag = tags[tagn].flag;
-                if (flag == 0) {
-                    adb_trace_mask = ~0;
-                    return;
-                }
-                adb_trace_mask |= (1 << flag);
-                break;
-            }
-        }
-        p = q;
-        if (*p)
-            p++;
-    }
-}
-
-
-apacket *get_apacket(void)
-{
-    apacket *p = malloc(sizeof(apacket));
-    if(p == 0) fatal("failed to allocate an apacket");
-    memset(p, 0, sizeof(apacket) - MAX_PAYLOAD);
-    return p;
-}
-
-void put_apacket(apacket *p)
-{
-    free(p);
-}
-
-void handle_online(void)
-{
-    D("adb: online\n");
-}
-
-void handle_offline(atransport *t)
-{
-    D("adb: offline\n");
-    //Close the associated usb
-    run_transport_disconnects(t);
-}
-
-#if TRACE_PACKETS
-#define DUMPMAX 32
-void print_packet(const char *label, apacket *p)
-{
-    char *tag;
-    char *x;
-    unsigned count;
-
-    switch(p->msg.command){
-    case A_SYNC: tag = "SYNC"; break;
-    case A_CNXN: tag = "CNXN" ; break;
-    case A_OPEN: tag = "OPEN"; break;
-    case A_OKAY: tag = "OKAY"; break;
-    case A_CLSE: tag = "CLSE"; break;
-    case A_WRTE: tag = "WRTE"; break;
-    default: tag = "????"; break;
-    }
-
-    fprintf(stderr, "%s: %s %08x %08x %04x \"",
-            label, tag, p->msg.arg0, p->msg.arg1, p->msg.data_length);
-    count = p->msg.data_length;
-    x = (char*) p->data;
-    if(count > DUMPMAX) {
-        count = DUMPMAX;
-        tag = "\n";
-    } else {
-        tag = "\"\n";
-    }
-    while(count-- > 0){
-        if((*x >= ' ') && (*x < 127)) {
-            fputc(*x, stderr);
-        } else {
-            fputc('.', stderr);
-        }
-        x++;
-    }
-    fprintf(stderr, tag);
-}
-#endif
-
-static void send_ready(unsigned local, unsigned remote, atransport *t)
-{
-    D("Calling send_ready \n");
-    apacket *p = get_apacket();
-    p->msg.command = A_OKAY;
-    p->msg.arg0 = local;
-    p->msg.arg1 = remote;
-    send_packet(p, t);
-}
-
-static void send_close(unsigned local, unsigned remote, atransport *t)
-{
-    D("Calling send_close \n");
-    apacket *p = get_apacket();
-    p->msg.command = A_CLSE;
-    p->msg.arg0 = local;
-    p->msg.arg1 = remote;
-    send_packet(p, t);
-}
-
-static void send_connect(atransport *t)
-{
-    D("Calling send_connect \n");
-    apacket *cp = get_apacket();
-    cp->msg.command = A_CNXN;
-    cp->msg.arg0 = A_VERSION;
-    cp->msg.arg1 = MAX_PAYLOAD;
-    snprintf((char*) cp->data, sizeof cp->data, "%s::",
-            HOST ? "host" : adb_device_banner);
-    cp->msg.data_length = strlen((char*) cp->data) + 1;
-    send_packet(cp, t);
-}
-
-void parse_banner(char *banner, atransport *t)
-{
-    char *type, *product, *end;
-
-    D("parse_banner: %s\n", banner);
-    type = banner;
-    product = strchr(type, ':');
-    if(product) {
-        *product++ = 0;
-    } else {
-        product = "";
-    }
-
-        /* remove trailing ':' */
-    end = strchr(product, ':');
-    if(end) *end = 0;
-
-        /* save product name in device structure */
-    if (t->product == NULL) {
-        t->product = strdup(product);
-    } else if (strcmp(product, t->product) != 0) {
-        free(t->product);
-        t->product = strdup(product);
-    }
-
-    if(!strcmp(type, "bootloader")){
-        D("setting connection_state to CS_BOOTLOADER\n");
-        t->connection_state = CS_BOOTLOADER;
-        update_transports();
-        return;
-    }
-
-    if(!strcmp(type, "device")) {
-        D("setting connection_state to CS_DEVICE\n");
-        t->connection_state = CS_DEVICE;
-        update_transports();
-        return;
-    }
-
-    if(!strcmp(type, "recovery")) {
-        D("setting connection_state to CS_RECOVERY\n");
-        t->connection_state = CS_RECOVERY;
-        update_transports();
-        return;
-    }
-
-    if(!strcmp(type, "sideload")) {
-        D("setting connection_state to CS_SIDELOAD\n");
-        t->connection_state = CS_SIDELOAD;
-        update_transports();
-        return;
-    }
-
-    t->connection_state = CS_HOST;
-}
-
-void handle_packet(apacket *p, atransport *t)
-{
-    asocket *s;
-
-    D("handle_packet() %c%c%c%c\n", ((char*) (&(p->msg.command)))[0],
-            ((char*) (&(p->msg.command)))[1],
-            ((char*) (&(p->msg.command)))[2],
-            ((char*) (&(p->msg.command)))[3]);
-    print_packet("recv", p);
-
-    switch(p->msg.command){
-    case A_SYNC:
-        if(p->msg.arg0){
-            send_packet(p, t);
-            if(HOST) send_connect(t);
-        } else {
-            t->connection_state = CS_OFFLINE;
-            handle_offline(t);
-            send_packet(p, t);
-        }
-        return;
-
-    case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */
-            /* XXX verify version, etc */
-        if(t->connection_state != CS_OFFLINE) {
-            t->connection_state = CS_OFFLINE;
-            handle_offline(t);
-        }
-        parse_banner((char*) p->data, t);
-        handle_online();
-        if(!HOST) send_connect(t);
-        break;
-
-    case A_OPEN: /* OPEN(local-id, 0, "destination") */
-        if(t->connection_state != CS_OFFLINE) {
-            char *name = (char*) p->data;
-            name[p->msg.data_length > 0 ? p->msg.data_length - 1 : 0] = 0;
-            s = create_local_service_socket(name);
-            if(s == 0) {
-                send_close(0, p->msg.arg0, t);
-            } else {
-                s->peer = create_remote_socket(p->msg.arg0, t);
-                s->peer->peer = s;
-                send_ready(s->id, s->peer->id, t);
-                s->ready(s);
-            }
-        }
-        break;
-
-    case A_OKAY: /* READY(local-id, remote-id, "") */
-        if(t->connection_state != CS_OFFLINE) {
-            if((s = find_local_socket(p->msg.arg1))) {
-                if(s->peer == 0) {
-                    s->peer = create_remote_socket(p->msg.arg0, t);
-                    s->peer->peer = s;
-                }
-                s->ready(s);
-            }
-        }
-        break;
-
-    case A_CLSE: /* CLOSE(local-id, remote-id, "") */
-        if(t->connection_state != CS_OFFLINE) {
-            if((s = find_local_socket(p->msg.arg1))) {
-                s->close(s);
-            }
-        }
-        break;
-
-    case A_WRTE:
-        if(t->connection_state != CS_OFFLINE) {
-            if((s = find_local_socket(p->msg.arg1))) {
-                unsigned rid = p->msg.arg0;
-                p->len = p->msg.data_length;
-
-                if(s->enqueue(s, p) == 0) {
-                    D("Enqueue the socket\n");
-                    send_ready(s->id, rid, t);
-                }
-                return;
-            }
-        }
-        break;
-
-    default:
-        printf("handle_packet: what is %08x?!\n", p->msg.command);
-    }
-
-    put_apacket(p);
-}
-
-static void adb_cleanup(void)
-{
-    usb_cleanup();
-}
-
-int adb_main()
-{
-    atexit(adb_cleanup);
-#if defined(HAVE_FORKEXEC)
-    // No SIGCHLD. Let the service subproc handle its children.
-    signal(SIGPIPE, SIG_IGN);
-#endif
-
-    init_transport_registration();
-
-    // The minimal version of adbd only uses USB.
-    if (access(USB_ADB_PATH, F_OK) == 0 || access(USB_FFS_ADB_EP0, F_OK) == 0) {
-        // listen on USB
-        usb_init();
-    }
-
-    D("Event loop starting\n");
-
-    fdevent_loop();
-
-    usb_cleanup();
-
-    return 0;
-}
diff --git a/minadbd/adb.h b/minadbd/adb.h
deleted file mode 100644
index 714868f..0000000
--- a/minadbd/adb.h
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __ADB_H
-#define __ADB_H
-
-#include <limits.h>
-
-#include "transport.h"  /* readx(), writex() */
-#include "fdevent.h"
-
-#define MAX_PAYLOAD 4096
-
-#define A_SYNC 0x434e5953
-#define A_CNXN 0x4e584e43
-#define A_OPEN 0x4e45504f
-#define A_OKAY 0x59414b4f
-#define A_CLSE 0x45534c43
-#define A_WRTE 0x45545257
-
-#define A_VERSION 0x01000000        // ADB protocol version
-
-#define ADB_VERSION_MAJOR 1         // Used for help/version information
-#define ADB_VERSION_MINOR 0         // Used for help/version information
-
-#define ADB_SERVER_VERSION    29    // Increment this when we want to force users to start a new adb server
-
-typedef struct amessage amessage;
-typedef struct apacket apacket;
-typedef struct asocket asocket;
-typedef struct aservice aservice;
-typedef struct atransport atransport;
-typedef struct adisconnect  adisconnect;
-typedef struct usb_handle usb_handle;
-
-struct amessage {
-    unsigned command;       /* command identifier constant      */
-    unsigned arg0;          /* first argument                   */
-    unsigned arg1;          /* second argument                  */
-    unsigned data_length;   /* length of payload (0 is allowed) */
-    unsigned data_check;    /* checksum of data payload         */
-    unsigned magic;         /* command ^ 0xffffffff             */
-};
-
-struct apacket
-{
-    apacket *next;
-
-    unsigned len;
-    unsigned char *ptr;
-
-    amessage msg;
-    unsigned char data[MAX_PAYLOAD];
-};
-
-/* An asocket represents one half of a connection between a local and
-** remote entity.  A local asocket is bound to a file descriptor.  A
-** remote asocket is bound to the protocol engine.
-*/
-struct asocket {
-        /* chain pointers for the local/remote list of
-        ** asockets that this asocket lives in
-        */
-    asocket *next;
-    asocket *prev;
-
-        /* the unique identifier for this asocket
-        */
-    unsigned id;
-
-        /* flag: set when the socket's peer has closed
-        ** but packets are still queued for delivery
-        */
-    int    closing;
-
-        /* the asocket we are connected to
-        */
-
-    asocket *peer;
-
-        /* For local asockets, the fde is used to bind
-        ** us to our fd event system.  For remote asockets
-        ** these fields are not used.
-        */
-    fdevent fde;
-    int fd;
-
-        /* queue of apackets waiting to be written
-        */
-    apacket *pkt_first;
-    apacket *pkt_last;
-
-        /* enqueue is called by our peer when it has data
-        ** for us.  It should return 0 if we can accept more
-        ** data or 1 if not.  If we return 1, we must call
-        ** peer->ready() when we once again are ready to
-        ** receive data.
-        */
-    int (*enqueue)(asocket *s, apacket *pkt);
-
-        /* ready is called by the peer when it is ready for
-        ** us to send data via enqueue again
-        */
-    void (*ready)(asocket *s);
-
-        /* close is called by the peer when it has gone away.
-        ** we are not allowed to make any further calls on the
-        ** peer once our close method is called.
-        */
-    void (*close)(asocket *s);
-
-        /* socket-type-specific extradata */
-    void *extra;
-
-    	/* A socket is bound to atransport */
-    atransport *transport;
-};
-
-
-/* the adisconnect structure is used to record a callback that
-** will be called whenever a transport is disconnected (e.g. by the user)
-** this should be used to cleanup objects that depend on the
-** transport (e.g. remote sockets, etc...)
-*/
-struct  adisconnect
-{
-    void        (*func)(void*  opaque, atransport*  t);
-    void*         opaque;
-    adisconnect*  next;
-    adisconnect*  prev;
-};
-
-
-/* a transport object models the connection to a remote device or emulator
-** there is one transport per connected device/emulator. a "local transport"
-** connects through TCP (for the emulator), while a "usb transport" through
-** USB (for real devices)
-**
-** note that kTransportHost doesn't really correspond to a real transport
-** object, it's a special value used to indicate that a client wants to
-** connect to a service implemented within the ADB server itself.
-*/
-typedef enum transport_type {
-        kTransportUsb,
-        kTransportLocal,
-        kTransportAny,
-        kTransportHost,
-} transport_type;
-
-struct atransport
-{
-    atransport *next;
-    atransport *prev;
-
-    int (*read_from_remote)(apacket *p, atransport *t);
-    int (*write_to_remote)(apacket *p, atransport *t);
-    void (*close)(atransport *t);
-    void (*kick)(atransport *t);
-
-    int fd;
-    int transport_socket;
-    fdevent transport_fde;
-    int ref_count;
-    unsigned sync_token;
-    int connection_state;
-    transport_type type;
-
-        /* usb handle or socket fd as needed */
-    usb_handle *usb;
-    int sfd;
-
-        /* used to identify transports for clients */
-    char *serial;
-    char *product;
-    int adb_port; // Use for emulators (local transport)
-
-        /* a list of adisconnect callbacks called when the transport is kicked */
-    int          kicked;
-    adisconnect  disconnects;
-};
-
-
-void print_packet(const char *label, apacket *p);
-
-asocket *find_local_socket(unsigned id);
-void install_local_socket(asocket *s);
-void remove_socket(asocket *s);
-void close_all_sockets(atransport *t);
-
-#define  LOCAL_CLIENT_PREFIX  "emulator-"
-
-asocket *create_local_socket(int fd);
-asocket *create_local_service_socket(const char *destination);
-
-asocket *create_remote_socket(unsigned id, atransport *t);
-void connect_to_remote(asocket *s, const char *destination);
-void connect_to_smartsocket(asocket *s);
-
-void fatal(const char *fmt, ...);
-void fatal_errno(const char *fmt, ...);
-
-void handle_packet(apacket *p, atransport *t);
-void send_packet(apacket *p, atransport *t);
-
-void get_my_path(char *s, size_t maxLen);
-int launch_server(int server_port);
-int adb_main();
-
-
-/* transports are ref-counted
-** get_device_transport does an acquire on your behalf before returning
-*/
-void init_transport_registration(void);
-int  list_transports(char *buf, size_t  bufsize);
-void update_transports(void);
-
-asocket*  create_device_tracker(void);
-
-/* Obtain a transport from the available transports.
-** If state is != CS_ANY, only transports in that state are considered.
-** If serial is non-NULL then only the device with that serial will be chosen.
-** If no suitable transport is found, error is set.
-*/
-atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char **error_out);
-void   add_transport_disconnect( atransport*  t, adisconnect*  dis );
-void   remove_transport_disconnect( atransport*  t, adisconnect*  dis );
-void   run_transport_disconnects( atransport*  t );
-void   kick_transport( atransport*  t );
-
-/* initialize a transport object's func pointers and state */
-#if ADB_HOST
-int get_available_local_transport_index();
-#endif
-void init_usb_transport(atransport *t, usb_handle *usb, int state);
-
-/* for MacOS X cleanup */
-void close_usb_devices();
-
-/* these should only be used for the "adb disconnect" command */
-void unregister_transport(atransport *t);
-void unregister_all_tcp_transports();
-
-void register_usb_transport(usb_handle *h, const char *serial, unsigned writeable);
-
-/* this should only be used for transports with connection_state == CS_NOPERM */
-void unregister_usb_transport(usb_handle *usb);
-
-atransport *find_transport(const char *serial);
-#if ADB_HOST
-atransport* find_emulator_transport_by_adb_port(int adb_port);
-#endif
-
-int service_to_fd(const char *name);
-#if ADB_HOST
-asocket *host_service_to_socket(const char*  name, const char *serial);
-#endif
-
-#if !ADB_HOST
-typedef enum {
-    BACKUP,
-    RESTORE
-} BackupOperation;
-int backup_service(BackupOperation operation, char* args);
-void framebuffer_service(int fd, void *cookie);
-void log_service(int fd, void *cookie);
-void remount_service(int fd, void *cookie);
-char * get_log_file_path(const char * log_name);
-#endif
-
-/* packet allocator */
-apacket *get_apacket(void);
-void put_apacket(apacket *p);
-
-int check_header(apacket *p);
-int check_data(apacket *p);
-
-/* define ADB_TRACE to 1 to enable tracing support, or 0 to disable it */
-
-#define  ADB_TRACE    1
-
-/* IMPORTANT: if you change the following list, don't
- * forget to update the corresponding 'tags' table in
- * the adb_trace_init() function implemented in adb.c
- */
-typedef enum {
-    TRACE_ADB = 0,   /* 0x001 */
-    TRACE_SOCKETS,
-    TRACE_PACKETS,
-    TRACE_TRANSPORT,
-    TRACE_RWX,       /* 0x010 */
-    TRACE_USB,
-    TRACE_SYNC,
-    TRACE_SYSDEPS,
-    TRACE_JDWP,      /* 0x100 */
-    TRACE_SERVICES,
-} AdbTrace;
-
-#if ADB_TRACE
-
-  extern int     adb_trace_mask;
-  extern unsigned char    adb_trace_output_count;
-  void    adb_trace_init(void);
-
-#  define ADB_TRACING  ((adb_trace_mask & (1 << TRACE_TAG)) != 0)
-
-  /* you must define TRACE_TAG before using this macro */
-#  define  D(...)                                      \
-        do {                                           \
-            if (ADB_TRACING) {                         \
-                int save_errno = errno;                \
-                adb_mutex_lock(&D_lock);               \
-                fprintf(stderr, "%s::%s():",           \
-                        __FILE__, __FUNCTION__);       \
-                errno = save_errno;                    \
-                fprintf(stderr, __VA_ARGS__ );         \
-                fflush(stderr);                        \
-                adb_mutex_unlock(&D_lock);             \
-                errno = save_errno;                    \
-           }                                           \
-        } while (0)
-#  define  DR(...)                                     \
-        do {                                           \
-            if (ADB_TRACING) {                         \
-                int save_errno = errno;                \
-                adb_mutex_lock(&D_lock);               \
-                errno = save_errno;                    \
-                fprintf(stderr, __VA_ARGS__ );         \
-                fflush(stderr);                        \
-                adb_mutex_unlock(&D_lock);             \
-                errno = save_errno;                    \
-           }                                           \
-        } while (0)
-#else
-#  define  D(...)          ((void)0)
-#  define  DR(...)         ((void)0)
-#  define  ADB_TRACING     0
-#endif
-
-
-#if !TRACE_PACKETS
-#define print_packet(tag,p) do {} while (0)
-#endif
-
-#if ADB_HOST_ON_TARGET
-/* adb and adbd are coexisting on the target, so use 5038 for adb
- * to avoid conflicting with adbd's usage of 5037
- */
-#  define DEFAULT_ADB_PORT 5038
-#else
-#  define DEFAULT_ADB_PORT 5037
-#endif
-
-#define DEFAULT_ADB_LOCAL_TRANSPORT_PORT 5555
-
-#define ADB_CLASS              0xff
-#define ADB_SUBCLASS           0x42
-#define ADB_PROTOCOL           0x1
-
-
-void local_init(int port);
-int  local_connect(int  port);
-int  local_connect_arbitrary_ports(int console_port, int adb_port);
-
-/* usb host/client interface */
-void usb_init();
-void usb_cleanup();
-int usb_write(usb_handle *h, const void *data, int len);
-int usb_read(usb_handle *h, void *data, int len);
-int usb_close(usb_handle *h);
-void usb_kick(usb_handle *h);
-
-/* used for USB device detection */
-#if ADB_HOST
-int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol);
-#endif
-
-unsigned host_to_le32(unsigned n);
-int adb_commandline(int argc, char **argv);
-
-int connection_state(atransport *t);
-
-#define CS_ANY       -1
-#define CS_OFFLINE    0
-#define CS_BOOTLOADER 1
-#define CS_DEVICE     2
-#define CS_HOST       3
-#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);
-
-#endif
diff --git a/minadbd/adb_main.cpp b/minadbd/adb_main.cpp
new file mode 100644
index 0000000..7fae99a
--- /dev/null
+++ b/minadbd/adb_main.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 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 <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define TRACE_TAG TRACE_ADB
+
+#include "sysdeps.h"
+
+#include "adb.h"
+#include "adb_auth.h"
+#include "transport.h"
+
+int adb_main(int is_daemon, int server_port)
+{
+    atexit(usb_cleanup);
+
+    adb_device_banner = "sideload";
+
+    // No SIGCHLD. Let the service subproc handle its children.
+    signal(SIGPIPE, SIG_IGN);
+
+    // We can't require authentication for sideloading. http://b/22025550.
+    auth_required = false;
+
+    init_transport_registration();
+    usb_init();
+
+    D("Event loop starting\n");
+    fdevent_loop();
+
+    return 0;
+}
diff --git a/minadbd/fdevent.c b/minadbd/fdevent.c
deleted file mode 100644
index 5c374a7..0000000
--- a/minadbd/fdevent.c
+++ /dev/null
@@ -1,695 +0,0 @@
-/* http://frotznet.googlecode.com/svn/trunk/utils/fdevent.c
-**
-** Copyright 2006, Brian Swetland <swetland@frotz.net>
-**
-** 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 <sys/ioctl.h>
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <errno.h>
-
-#include <fcntl.h>
-
-#include <stdarg.h>
-#include <stddef.h>
-
-#include "fdevent.h"
-#include "transport.h"
-#include "sysdeps.h"
-
-
-/* !!! Do not enable DEBUG for the adb that will run as the server:
-** both stdout and stderr are used to communicate between the client
-** and server. Any extra output will cause failures.
-*/
-#define DEBUG 0   /* non-0 will break adb server */
-
-// This socket is used when a subproc shell service exists.
-// It wakes up the fdevent_loop() and cause the correct handling
-// of the shell's pseudo-tty master. I.e. force close it.
-int SHELL_EXIT_NOTIFY_FD = -1;
-
-static void fatal(const char *fn, const char *fmt, ...)
-{
-    va_list ap;
-    va_start(ap, fmt);
-    fprintf(stderr, "%s:", fn);
-    vfprintf(stderr, fmt, ap);
-    va_end(ap);
-    abort();
-}
-
-#define FATAL(x...) fatal(__FUNCTION__, x)
-
-#if DEBUG
-#define D(...) \
-    do { \
-        adb_mutex_lock(&D_lock);               \
-        int save_errno = errno;                \
-        fprintf(stderr, "%s::%s():", __FILE__, __FUNCTION__);  \
-        errno = save_errno;                    \
-        fprintf(stderr, __VA_ARGS__);          \
-        adb_mutex_unlock(&D_lock);             \
-        errno = save_errno;                    \
-    } while(0)
-static void dump_fde(fdevent *fde, const char *info)
-{
-    adb_mutex_lock(&D_lock);
-    fprintf(stderr,"FDE #%03d %c%c%c %s\n", fde->fd,
-            fde->state & FDE_READ ? 'R' : ' ',
-            fde->state & FDE_WRITE ? 'W' : ' ',
-            fde->state & FDE_ERROR ? 'E' : ' ',
-            info);
-    adb_mutex_unlock(&D_lock);
-}
-#else
-#define D(...) ((void)0)
-#define dump_fde(fde, info) do { } while(0)
-#endif
-
-#define FDE_EVENTMASK  0x00ff
-#define FDE_STATEMASK  0xff00
-
-#define FDE_ACTIVE     0x0100
-#define FDE_PENDING    0x0200
-#define FDE_CREATED    0x0400
-
-static void fdevent_plist_enqueue(fdevent *node);
-static void fdevent_plist_remove(fdevent *node);
-static fdevent *fdevent_plist_dequeue(void);
-static void fdevent_subproc_event_func(int fd, unsigned events, void *userdata);
-
-static fdevent list_pending = {
-    .next = &list_pending,
-    .prev = &list_pending,
-};
-
-static fdevent **fd_table = 0;
-static int fd_table_max = 0;
-
-#ifdef CRAPTASTIC
-//HAVE_EPOLL
-
-#include <sys/epoll.h>
-
-static int epoll_fd = -1;
-
-static void fdevent_init()
-{
-        /* XXX: what's a good size for the passed in hint? */
-    epoll_fd = epoll_create(256);
-
-    if(epoll_fd < 0) {
-        perror("epoll_create() failed");
-        exit(1);
-    }
-
-        /* mark for close-on-exec */
-    fcntl(epoll_fd, F_SETFD, FD_CLOEXEC);
-}
-
-static void fdevent_connect(fdevent *fde)
-{
-    struct epoll_event ev;
-
-    memset(&ev, 0, sizeof(ev));
-    ev.events = 0;
-    ev.data.ptr = fde;
-
-#if 0
-    if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fde->fd, &ev)) {
-        perror("epoll_ctl() failed\n");
-        exit(1);
-    }
-#endif
-}
-
-static void fdevent_disconnect(fdevent *fde)
-{
-    struct epoll_event ev;
-
-    memset(&ev, 0, sizeof(ev));
-    ev.events = 0;
-    ev.data.ptr = fde;
-
-        /* technically we only need to delete if we
-        ** were actively monitoring events, but let's
-        ** be aggressive and do it anyway, just in case
-        ** something's out of sync
-        */
-    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fde->fd, &ev);
-}
-
-static void fdevent_update(fdevent *fde, unsigned events)
-{
-    struct epoll_event ev;
-    int active;
-
-    active = (fde->state & FDE_EVENTMASK) != 0;
-
-    memset(&ev, 0, sizeof(ev));
-    ev.events = 0;
-    ev.data.ptr = fde;
-
-    if(events & FDE_READ) ev.events |= EPOLLIN;
-    if(events & FDE_WRITE) ev.events |= EPOLLOUT;
-    if(events & FDE_ERROR) ev.events |= (EPOLLERR | EPOLLHUP);
-
-    fde->state = (fde->state & FDE_STATEMASK) | events;
-
-    if(active) {
-            /* we're already active. if we're changing to *no*
-            ** events being monitored, we need to delete, otherwise
-            ** we need to just modify
-            */
-        if(ev.events) {
-            if(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fde->fd, &ev)) {
-                perror("epoll_ctl() failed\n");
-                exit(1);
-            }
-        } else {
-            if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fde->fd, &ev)) {
-                perror("epoll_ctl() failed\n");
-                exit(1);
-            }
-        }
-    } else {
-            /* we're not active.  if we're watching events, we need
-            ** to add, otherwise we can just do nothing
-            */
-        if(ev.events) {
-            if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fde->fd, &ev)) {
-                perror("epoll_ctl() failed\n");
-                exit(1);
-            }
-        }
-    }
-}
-
-static void fdevent_process()
-{
-    struct epoll_event events[256];
-    fdevent *fde;
-    int i, n;
-
-    n = epoll_wait(epoll_fd, events, 256, -1);
-
-    if(n < 0) {
-        if(errno == EINTR) return;
-        perror("epoll_wait");
-        exit(1);
-    }
-
-    for(i = 0; i < n; i++) {
-        struct epoll_event *ev = events + i;
-        fde = ev->data.ptr;
-
-        if(ev->events & EPOLLIN) {
-            fde->events |= FDE_READ;
-        }
-        if(ev->events & EPOLLOUT) {
-            fde->events |= FDE_WRITE;
-        }
-        if(ev->events & (EPOLLERR | EPOLLHUP)) {
-            fde->events |= FDE_ERROR;
-        }
-        if(fde->events) {
-            if(fde->state & FDE_PENDING) continue;
-            fde->state |= FDE_PENDING;
-            fdevent_plist_enqueue(fde);
-        }
-    }
-}
-
-#else /* USE_SELECT */
-
-#ifdef HAVE_WINSOCK
-#include <winsock2.h>
-#else
-#include <sys/select.h>
-#endif
-
-static fd_set read_fds;
-static fd_set write_fds;
-static fd_set error_fds;
-
-static int select_n = 0;
-
-static void fdevent_init(void)
-{
-    FD_ZERO(&read_fds);
-    FD_ZERO(&write_fds);
-    FD_ZERO(&error_fds);
-}
-
-static void fdevent_connect(fdevent *fde)
-{
-    if(fde->fd >= select_n) {
-        select_n = fde->fd + 1;
-    }
-}
-
-static void fdevent_disconnect(fdevent *fde)
-{
-    int i, n;
-
-    FD_CLR(fde->fd, &read_fds);
-    FD_CLR(fde->fd, &write_fds);
-    FD_CLR(fde->fd, &error_fds);
-
-    for(n = 0, i = 0; i < select_n; i++) {
-        if(fd_table[i] != 0) n = i;
-    }
-    select_n = n + 1;
-}
-
-static void fdevent_update(fdevent *fde, unsigned events)
-{
-    if(events & FDE_READ) {
-        FD_SET(fde->fd, &read_fds);
-    } else {
-        FD_CLR(fde->fd, &read_fds);
-    }
-    if(events & FDE_WRITE) {
-        FD_SET(fde->fd, &write_fds);
-    } else {
-        FD_CLR(fde->fd, &write_fds);
-    }
-    if(events & FDE_ERROR) {
-        FD_SET(fde->fd, &error_fds);
-    } else {
-        FD_CLR(fde->fd, &error_fds);
-    }
-
-    fde->state = (fde->state & FDE_STATEMASK) | events;
-}
-
-/* Looks at fd_table[] for bad FDs and sets bit in fds.
-** Returns the number of bad FDs.
-*/
-static int fdevent_fd_check(fd_set *fds)
-{
-    int i, n = 0;
-    fdevent *fde;
-
-    for(i = 0; i < select_n; i++) {
-        fde = fd_table[i];
-        if(fde == 0) continue;
-        if(fcntl(i, F_GETFL, NULL) < 0) {
-            FD_SET(i, fds);
-            n++;
-            // fde->state |= FDE_DONT_CLOSE;
-
-        }
-    }
-    return n;
-}
-
-#if !DEBUG
-static inline void dump_all_fds(const char *extra_msg) {}
-#else
-static void dump_all_fds(const char *extra_msg)
-{
-int i;
-    fdevent *fde;
-    // per fd: 4 digits (but really: log10(FD_SETSIZE)), 1 staus, 1 blank
-    char msg_buff[FD_SETSIZE*6 + 1], *pb=msg_buff;
-    size_t max_chars = FD_SETSIZE * 6 + 1;
-    int printed_out;
-#define SAFE_SPRINTF(...)                                                    \
-    do {                                                                     \
-        printed_out = snprintf(pb, max_chars, __VA_ARGS__);                  \
-        if (printed_out <= 0) {                                              \
-            D("... snprintf failed.\n");                                     \
-            return;                                                          \
-        }                                                                    \
-        if (max_chars < (unsigned int)printed_out) {                         \
-            D("... snprintf out of space.\n");                               \
-            return;                                                          \
-        }                                                                    \
-        pb += printed_out;                                                   \
-        max_chars -= printed_out;                                            \
-    } while(0)
-
-    for(i = 0; i < select_n; i++) {
-        fde = fd_table[i];
-        SAFE_SPRINTF("%d", i);
-        if(fde == 0) {
-            SAFE_SPRINTF("? ");
-            continue;
-        }
-        if(fcntl(i, F_GETFL, NULL) < 0) {
-            SAFE_SPRINTF("b");
-        }
-        SAFE_SPRINTF(" ");
-    }
-    D("%s fd_table[]->fd = {%s}\n", extra_msg, msg_buff);
-}
-#endif
-
-static void fdevent_process()
-{
-    int i, n;
-    fdevent *fde;
-    unsigned events;
-    fd_set rfd, wfd, efd;
-
-    memcpy(&rfd, &read_fds, sizeof(fd_set));
-    memcpy(&wfd, &write_fds, sizeof(fd_set));
-    memcpy(&efd, &error_fds, sizeof(fd_set));
-
-    dump_all_fds("pre select()");
-
-    n = select(select_n, &rfd, &wfd, &efd, NULL);
-    int saved_errno = errno;
-    D("select() returned n=%d, errno=%d\n", n, n<0?saved_errno:0);
-
-    dump_all_fds("post select()");
-
-    if(n < 0) {
-        switch(saved_errno) {
-        case EINTR: return;
-        case EBADF:
-            // Can't trust the FD sets after an error.
-            FD_ZERO(&wfd);
-            FD_ZERO(&efd);
-            FD_ZERO(&rfd);
-            break;
-        default:
-            D("Unexpected select() error=%d\n", saved_errno);
-            return;
-        }
-    }
-    if(n <= 0) {
-        // We fake a read, as the rest of the code assumes
-        // that errors will be detected at that point.
-        n = fdevent_fd_check(&rfd);
-    }
-
-    for(i = 0; (i < select_n) && (n > 0); i++) {
-        events = 0;
-        if(FD_ISSET(i, &rfd)) { events |= FDE_READ; n--; }
-        if(FD_ISSET(i, &wfd)) { events |= FDE_WRITE; n--; }
-        if(FD_ISSET(i, &efd)) { events |= FDE_ERROR; n--; }
-
-        if(events) {
-            fde = fd_table[i];
-            if(fde == 0)
-              FATAL("missing fde for fd %d\n", i);
-
-            fde->events |= events;
-
-            D("got events fde->fd=%d events=%04x, state=%04x\n",
-                fde->fd, fde->events, fde->state);
-            if(fde->state & FDE_PENDING) continue;
-            fde->state |= FDE_PENDING;
-            fdevent_plist_enqueue(fde);
-        }
-    }
-}
-
-#endif
-
-static void fdevent_register(fdevent *fde)
-{
-    if(fde->fd < 0) {
-        FATAL("bogus negative fd (%d)\n", fde->fd);
-    }
-
-    if(fde->fd >= fd_table_max) {
-        int oldmax = fd_table_max;
-        if(fde->fd > 32000) {
-            FATAL("bogus huuuuge fd (%d)\n", fde->fd);
-        }
-        if(fd_table_max == 0) {
-            fdevent_init();
-            fd_table_max = 256;
-        }
-        while(fd_table_max <= fde->fd) {
-            fd_table_max *= 2;
-        }
-        fd_table = realloc(fd_table, sizeof(fdevent*) * fd_table_max);
-        if(fd_table == 0) {
-            FATAL("could not expand fd_table to %d entries\n", fd_table_max);
-        }
-        memset(fd_table + oldmax, 0, sizeof(int) * (fd_table_max - oldmax));
-    }
-
-    fd_table[fde->fd] = fde;
-}
-
-static void fdevent_unregister(fdevent *fde)
-{
-    if((fde->fd < 0) || (fde->fd >= fd_table_max)) {
-        FATAL("fd out of range (%d)\n", fde->fd);
-    }
-
-    if(fd_table[fde->fd] != fde) {
-        FATAL("fd_table out of sync [%d]\n", fde->fd);
-    }
-
-    fd_table[fde->fd] = 0;
-
-    if(!(fde->state & FDE_DONT_CLOSE)) {
-        dump_fde(fde, "close");
-        adb_close(fde->fd);
-    }
-}
-
-static void fdevent_plist_enqueue(fdevent *node)
-{
-    fdevent *list = &list_pending;
-
-    node->next = list;
-    node->prev = list->prev;
-    node->prev->next = node;
-    list->prev = node;
-}
-
-static void fdevent_plist_remove(fdevent *node)
-{
-    node->prev->next = node->next;
-    node->next->prev = node->prev;
-    node->next = 0;
-    node->prev = 0;
-}
-
-static fdevent *fdevent_plist_dequeue(void)
-{
-    fdevent *list = &list_pending;
-    fdevent *node = list->next;
-
-    if(node == list) return 0;
-
-    list->next = node->next;
-    list->next->prev = list;
-    node->next = 0;
-    node->prev = 0;
-
-    return node;
-}
-
-static void fdevent_call_fdfunc(fdevent* fde)
-{
-    unsigned events = fde->events;
-    fde->events = 0;
-    if(!(fde->state & FDE_PENDING)) return;
-    fde->state &= (~FDE_PENDING);
-    dump_fde(fde, "callback");
-    fde->func(fde->fd, events, fde->arg);
-}
-
-static void fdevent_subproc_event_func(int fd, unsigned ev, void *userdata)
-{
-
-    D("subproc handling on fd=%d ev=%04x\n", fd, ev);
-
-    // Hook oneself back into the fde's suitable for select() on read.
-    if((fd < 0) || (fd >= fd_table_max)) {
-        FATAL("fd %d out of range for fd_table \n", fd);
-    }
-    fdevent *fde = fd_table[fd];
-    fdevent_add(fde, FDE_READ);
-
-    if(ev & FDE_READ){
-      int subproc_fd;
-
-      if(readx(fd, &subproc_fd, sizeof(subproc_fd))) {
-          FATAL("Failed to read the subproc's fd from fd=%d\n", fd);
-      }
-      if((subproc_fd < 0) || (subproc_fd >= fd_table_max)) {
-          D("subproc_fd %d out of range 0, fd_table_max=%d\n",
-            subproc_fd, fd_table_max);
-          return;
-      }
-      fdevent *subproc_fde = fd_table[subproc_fd];
-      if(!subproc_fde) {
-          D("subproc_fd %d cleared from fd_table\n", subproc_fd);
-          return;
-      }
-      if(subproc_fde->fd != subproc_fd) {
-          // Already reallocated?
-          D("subproc_fd %d != fd_table[].fd %d\n", subproc_fd, subproc_fde->fd);
-          return;
-      }
-
-      subproc_fde->force_eof = 1;
-
-      int rcount = 0;
-      ioctl(subproc_fd, FIONREAD, &rcount);
-      D("subproc with fd=%d  has rcount=%d err=%d\n",
-        subproc_fd, rcount, errno);
-
-      if(rcount) {
-        // If there is data left, it will show up in the select().
-        // This works because there is no other thread reading that
-        // data when in this fd_func().
-        return;
-      }
-
-      D("subproc_fde.state=%04x\n", subproc_fde->state);
-      subproc_fde->events |= FDE_READ;
-      if(subproc_fde->state & FDE_PENDING) {
-        return;
-      }
-      subproc_fde->state |= FDE_PENDING;
-      fdevent_call_fdfunc(subproc_fde);
-    }
-}
-
-fdevent *fdevent_create(int fd, fd_func func, void *arg)
-{
-    fdevent *fde = (fdevent*) malloc(sizeof(fdevent));
-    if(fde == 0) return 0;
-    fdevent_install(fde, fd, func, arg);
-    fde->state |= FDE_CREATED;
-    return fde;
-}
-
-void fdevent_destroy(fdevent *fde)
-{
-    if(fde == 0) return;
-    if(!(fde->state & FDE_CREATED)) {
-        FATAL("fde %p not created by fdevent_create()\n", fde);
-    }
-    fdevent_remove(fde);
-}
-
-void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg)
-{
-    memset(fde, 0, sizeof(fdevent));
-    fde->state = FDE_ACTIVE;
-    fde->fd = fd;
-    fde->force_eof = 0;
-    fde->func = func;
-    fde->arg = arg;
-
-#ifndef HAVE_WINSOCK
-    fcntl(fd, F_SETFL, O_NONBLOCK);
-#endif
-    fdevent_register(fde);
-    dump_fde(fde, "connect");
-    fdevent_connect(fde);
-    fde->state |= FDE_ACTIVE;
-}
-
-void fdevent_remove(fdevent *fde)
-{
-    if(fde->state & FDE_PENDING) {
-        fdevent_plist_remove(fde);
-    }
-
-    if(fde->state & FDE_ACTIVE) {
-        fdevent_disconnect(fde);
-        dump_fde(fde, "disconnect");
-        fdevent_unregister(fde);
-    }
-
-    fde->state = 0;
-    fde->events = 0;
-}
-
-
-void fdevent_set(fdevent *fde, unsigned events)
-{
-    events &= FDE_EVENTMASK;
-
-    if((fde->state & FDE_EVENTMASK) == events) return;
-
-    if(fde->state & FDE_ACTIVE) {
-        fdevent_update(fde, events);
-        dump_fde(fde, "update");
-    }
-
-    fde->state = (fde->state & FDE_STATEMASK) | events;
-
-    if(fde->state & FDE_PENDING) {
-            /* if we're pending, make sure
-            ** we don't signal an event that
-            ** is no longer wanted.
-            */
-        fde->events &= (~events);
-        if(fde->events == 0) {
-            fdevent_plist_remove(fde);
-            fde->state &= (~FDE_PENDING);
-        }
-    }
-}
-
-void fdevent_add(fdevent *fde, unsigned events)
-{
-    fdevent_set(
-        fde, (fde->state & FDE_EVENTMASK) | (events & FDE_EVENTMASK));
-}
-
-void fdevent_del(fdevent *fde, unsigned events)
-{
-    fdevent_set(
-        fde, (fde->state & FDE_EVENTMASK) & (~(events & FDE_EVENTMASK)));
-}
-
-void fdevent_subproc_setup()
-{
-    int s[2];
-
-    if(adb_socketpair(s)) {
-        FATAL("cannot create shell-exit socket-pair\n");
-    }
-    SHELL_EXIT_NOTIFY_FD = s[0];
-    fdevent *fde;
-    fde = fdevent_create(s[1], fdevent_subproc_event_func, NULL);
-    if(!fde)
-      FATAL("cannot create fdevent for shell-exit handler\n");
-    fdevent_add(fde, FDE_READ);
-}
-
-void fdevent_loop()
-{
-    fdevent *fde;
-    fdevent_subproc_setup();
-
-    for(;;) {
-        D("--- ---- waiting for events\n");
-
-        fdevent_process();
-
-        while((fde = fdevent_plist_dequeue())) {
-            fdevent_call_fdfunc(fde);
-        }
-    }
-}
diff --git a/minadbd/fdevent.h b/minadbd/fdevent.h
deleted file mode 100644
index a0ebe2a..0000000
--- a/minadbd/fdevent.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2006 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 __FDEVENT_H
-#define __FDEVENT_H
-
-#include <stdint.h>  /* for int64_t */
-
-/* events that may be observed */
-#define FDE_READ              0x0001
-#define FDE_WRITE             0x0002
-#define FDE_ERROR             0x0004
-#define FDE_TIMEOUT           0x0008
-
-/* features that may be set (via the events set/add/del interface) */
-#define FDE_DONT_CLOSE        0x0080
-
-typedef struct fdevent fdevent;
-
-typedef void (*fd_func)(int fd, unsigned events, void *userdata);
-
-/* Allocate and initialize a new fdevent object
- * Note: use FD_TIMER as 'fd' to create a fd-less object
- * (used to implement timers).
-*/
-fdevent *fdevent_create(int fd, fd_func func, void *arg);
-
-/* Uninitialize and deallocate an fdevent object that was
-** created by fdevent_create()
-*/
-void fdevent_destroy(fdevent *fde);
-
-/* Initialize an fdevent object that was externally allocated
-*/
-void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg);
-
-/* Uninitialize an fdevent object that was initialized by
-** fdevent_install()
-*/
-void fdevent_remove(fdevent *item);
-
-/* Change which events should cause notifications
-*/
-void fdevent_set(fdevent *fde, unsigned events);
-void fdevent_add(fdevent *fde, unsigned events);
-void fdevent_del(fdevent *fde, unsigned events);
-
-void fdevent_set_timeout(fdevent *fde, int64_t  timeout_ms);
-
-/* loop forever, handling events.
-*/
-void fdevent_loop();
-
-struct fdevent 
-{
-    fdevent *next;
-    fdevent *prev;
-
-    int fd;
-    int force_eof;
-
-    unsigned short state;
-    unsigned short events;
-
-    fd_func func;
-    void *arg;
-};
-
-
-#endif
diff --git a/minadbd/fuse_adb_provider.c b/minadbd/fuse_adb_provider.cpp
similarity index 66%
rename from minadbd/fuse_adb_provider.c
rename to minadbd/fuse_adb_provider.cpp
index f80533a..d71807d 100644
--- a/minadbd/fuse_adb_provider.c
+++ b/minadbd/fuse_adb_provider.cpp
@@ -16,29 +16,25 @@
 
 #include <stdlib.h>
 #include <stdio.h>
+#include <string.h>
 #include <errno.h>
 
+#include "sysdeps.h"
+
 #include "adb.h"
+#include "adb_io.h"
+#include "fuse_adb_provider.h"
 #include "fuse_sideload.h"
 
-struct adb_data {
-    int sfd;  // file descriptor for the adb channel
+int read_block_adb(void* data, uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
+    adb_data* ad = reinterpret_cast<adb_data*>(data);
 
-    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) {
+    if (!WriteFdFmt(ad->sfd, "%08u", block)) {
         fprintf(stderr, "failed to write to adb host: %s\n", strerror(errno));
         return -EIO;
     }
 
-    if (readx(ad->sfd, buffer, fetch_size) < 0) {
+    if (!ReadFdExactly(ad->sfd, buffer, fetch_size)) {
         fprintf(stderr, "failed to read from adb host: %s\n", strerror(errno));
         return -EIO;
     }
@@ -46,20 +42,18 @@
     return 0;
 }
 
-static void close_adb(void* cookie) {
-    struct adb_data* ad = (struct adb_data*)cookie;
-
-    writex(ad->sfd, "DONEDONE", 8);
+static void close_adb(void* data) {
+    adb_data* ad = reinterpret_cast<adb_data*>(data);
+    WriteFdExactly(ad->sfd, "DONEDONE");
 }
 
 int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size) {
-    struct adb_data ad;
-    struct provider_vtab vtab;
-
+    adb_data ad;
     ad.sfd = sfd;
     ad.file_size = file_size;
     ad.block_size = block_size;
 
+    provider_vtab vtab;
     vtab.read_block = read_block_adb;
     vtab.close = close_adb;
 
diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h
index 0eb1f79..9941709 100644
--- a/minadbd/fuse_adb_provider.h
+++ b/minadbd/fuse_adb_provider.h
@@ -17,6 +17,16 @@
 #ifndef __FUSE_ADB_PROVIDER_H
 #define __FUSE_ADB_PROVIDER_H
 
+#include <stdint.h>
+
+struct adb_data {
+    int sfd;  // file descriptor for the adb channel
+
+    uint64_t file_size;
+    uint32_t block_size;
+};
+
+int read_block_adb(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size);
 int run_adb_fuse(int sfd, uint64_t file_size, uint32_t block_size);
 
 #endif
diff --git a/minadbd/fuse_adb_provider_test.cpp b/minadbd/fuse_adb_provider_test.cpp
new file mode 100644
index 0000000..0f2e881
--- /dev/null
+++ b/minadbd/fuse_adb_provider_test.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 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 "fuse_adb_provider.h"
+
+#include <gtest/gtest.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+
+#include <string>
+
+#include "adb_io.h"
+
+TEST(fuse_adb_provider, read_block_adb) {
+  adb_data data = {};
+  int sockets[2];
+
+  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets));
+  data.sfd = sockets[0];
+
+  int host_socket = sockets[1];
+  fcntl(host_socket, F_SETFL, O_NONBLOCK);
+
+  const char expected_data[] = "foobar";
+  char block_data[sizeof(expected_data)] = {};
+
+  // If we write the result of read_block_adb's request before the request is
+  // actually made we can avoid needing an extra thread for this test.
+  ASSERT_TRUE(WriteFdExactly(host_socket, expected_data,
+                             strlen(expected_data)));
+
+  uint32_t block = 1234U;
+  const char expected_block[] = "00001234";
+  ASSERT_EQ(0, read_block_adb(reinterpret_cast<void*>(&data), block,
+                              reinterpret_cast<uint8_t*>(block_data),
+                              sizeof(expected_data) - 1));
+
+  // Check that read_block_adb requested the right block.
+  char block_req[sizeof(expected_block)] = {};
+  ASSERT_TRUE(ReadFdExactly(host_socket, block_req, 8));
+  ASSERT_EQ(0, block_req[8]);
+  ASSERT_EQ(8U, strlen(block_req));
+  ASSERT_STREQ(expected_block, block_req);
+
+  // Check that read_block_adb returned the right data.
+  ASSERT_EQ(0, block_req[8]);
+  ASSERT_STREQ(expected_data, block_data);
+
+  // Check that nothing else was written to the socket.
+  char tmp;
+  errno = 0;
+  ASSERT_EQ(-1, read(host_socket, &tmp, 1));
+  ASSERT_EQ(EWOULDBLOCK, errno);
+
+  close(sockets[0]);
+  close(sockets[1]);
+}
+
+TEST(fuse_adb_provider, read_block_adb_fail_write) {
+  adb_data data = {};
+  int sockets[2];
+
+  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sockets));
+  data.sfd = sockets[0];
+
+  ASSERT_EQ(0, close(sockets[1]));
+
+  char buf[1];
+  ASSERT_EQ(-EIO, read_block_adb(reinterpret_cast<void*>(&data), 0,
+                                 reinterpret_cast<uint8_t*>(buf), 1));
+
+  close(sockets[0]);
+}
diff --git a/minadbd/mutex_list.h b/minadbd/mutex_list.h
deleted file mode 100644
index 652dd73..0000000
--- a/minadbd/mutex_list.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/* the list of mutexes used by adb */
-/* #ifndef __MUTEX_LIST_H
- * Do not use an include-guard. This file is included once to declare the locks
- * and once in win32 to actually do the runtime initialization.
- */
-#ifndef ADB_MUTEX
-#error ADB_MUTEX not defined when including this file
-#endif
-ADB_MUTEX(dns_lock)
-ADB_MUTEX(socket_list_lock)
-ADB_MUTEX(transport_lock)
-#if ADB_HOST
-ADB_MUTEX(local_transports_lock)
-#endif
-ADB_MUTEX(usb_lock)
-
-// Sadly logging to /data/adb/adb-... is not thread safe.
-//  After modifying adb.h::D() to count invocations:
-//   DEBUG(jpa):0:Handling main()
-//   DEBUG(jpa):1:[ usb_init - starting thread ]
-// (Oopsies, no :2:, and matching message is also gone.)
-//   DEBUG(jpa):3:[ usb_thread - opening device ]
-//   DEBUG(jpa):4:jdwp control socket started (10)
-ADB_MUTEX(D_lock)
-
-#undef ADB_MUTEX
diff --git a/minadbd/services.c b/minadbd/services.cpp
similarity index 63%
rename from minadbd/services.c
rename to minadbd/services.cpp
index 218b84a..dd1fd7c 100644
--- a/minadbd/services.c
+++ b/minadbd/services.cpp
@@ -14,18 +14,19 @@
  * limitations under the License.
  */
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
 #include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
 #include "sysdeps.h"
-#include "fdevent.h"
-#include "fuse_adb_provider.h"
 
 #define  TRACE_TAG  TRACE_SERVICES
 #include "adb.h"
+#include "fdevent.h"
+#include "fuse_adb_provider.h"
 
 typedef struct stinfo stinfo;
 
@@ -35,24 +36,23 @@
     void *cookie;
 };
 
-
-void *service_bootstrap_func(void *x)
-{
-    stinfo *sti = x;
+void* service_bootstrap_func(void* x) {
+    stinfo* sti = reinterpret_cast<stinfo*>(x);
     sti->func(sti->fd, sti->cookie);
     free(sti);
     return 0;
 }
 
-static void sideload_host_service(int sfd, void* cookie)
-{
-    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);
+static void sideload_host_service(int sfd, void* data) {
+    const char* args = reinterpret_cast<const char*>(data);
+    int file_size;
+    int block_size;
+    if (sscanf(args, "%d:%d", &file_size, &block_size) != 2) {
+        printf("bad sideload-host arguments: %s\n", args);
+        exit(1);
+    }
 
-    printf("sideload-host file size %llu block size %lu\n", file_size, block_size);
+    printf("sideload-host file size %d block size %d\n", file_size, block_size);
 
     int result = run_adb_fuse(sfd, file_size, block_size);
 
@@ -61,58 +61,22 @@
     exit(result == 0 ? 0 : 1);
 }
 
-#if 0
-static void echo_service(int fd, void *cookie)
-{
-    char buf[4096];
-    int r;
-    char *p;
-    int c;
-
-    for(;;) {
-        r = read(fd, buf, 4096);
-        if(r == 0) goto done;
-        if(r < 0) {
-            if(errno == EINTR) continue;
-            else goto done;
-        }
-
-        c = r;
-        p = buf;
-        while(c > 0) {
-            r = write(fd, p, c);
-            if(r > 0) {
-                c -= r;
-                p += r;
-                continue;
-            }
-            if((r < 0) && (errno == EINTR)) continue;
-            goto done;
-        }
-    }
-done:
-    close(fd);
-}
-#endif
-
 static int create_service_thread(void (*func)(int, void *), void *cookie)
 {
-    stinfo *sti;
-    adb_thread_t t;
     int s[2];
-
     if(adb_socketpair(s)) {
         printf("cannot create service socket pair\n");
         return -1;
     }
 
-    sti = malloc(sizeof(stinfo));
+    stinfo* sti = reinterpret_cast<stinfo*>(malloc(sizeof(stinfo)));
     if(sti == 0) fatal("cannot allocate stinfo");
     sti->func = func;
     sti->cookie = cookie;
     sti->fd = s[1];
 
-    if(adb_thread_create( &t, service_bootstrap_func, sti)){
+    adb_thread_t t;
+    if (adb_thread_create( &t, service_bootstrap_func, sti)){
         free(sti);
         adb_close(s[0]);
         adb_close(s[1]);
@@ -124,8 +88,7 @@
     return s[0];
 }
 
-int service_to_fd(const char *name)
-{
+int service_to_fd(const char* name) {
     int ret = -1;
 
     if (!strncmp(name, "sideload:", 9)) {
@@ -135,10 +98,6 @@
         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);
-#endif
     }
     if (ret >= 0) {
         close_on_exec(ret);
diff --git a/minadbd/sockets.c b/minadbd/sockets.c
deleted file mode 100644
index 817410d..0000000
--- a/minadbd/sockets.c
+++ /dev/null
@@ -1,731 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "sysdeps.h"
-
-#define  TRACE_TAG  TRACE_SOCKETS
-#include "adb.h"
-
-ADB_MUTEX_DEFINE( socket_list_lock );
-
-static void local_socket_close_locked(asocket *s);
-
-int sendfailmsg(int fd, const char *reason)
-{
-    char buf[9];
-    int len;
-    len = strlen(reason);
-    if(len > 0xffff) len = 0xffff;
-    snprintf(buf, sizeof buf, "FAIL%04x", len);
-    if(writex(fd, buf, 8)) return -1;
-    return writex(fd, reason, len);
-}
-
-//extern int online;
-
-static unsigned local_socket_next_id = 1;
-
-static asocket local_socket_list = {
-    .next = &local_socket_list,
-    .prev = &local_socket_list,
-};
-
-/* the the list of currently closing local sockets.
-** these have no peer anymore, but still packets to
-** write to their fd.
-*/
-static asocket local_socket_closing_list = {
-    .next = &local_socket_closing_list,
-    .prev = &local_socket_closing_list,
-};
-
-asocket *find_local_socket(unsigned id)
-{
-    asocket *s;
-    asocket *result = NULL;
-
-    adb_mutex_lock(&socket_list_lock);
-    for (s = local_socket_list.next; s != &local_socket_list; s = s->next) {
-        if (s->id == id) {
-            result = s;
-            break;
-        }
-    }
-    adb_mutex_unlock(&socket_list_lock);
-
-    return result;
-}
-
-static void
-insert_local_socket(asocket*  s, asocket*  list)
-{
-    s->next       = list;
-    s->prev       = s->next->prev;
-    s->prev->next = s;
-    s->next->prev = s;
-}
-
-
-void install_local_socket(asocket *s)
-{
-    adb_mutex_lock(&socket_list_lock);
-
-    s->id = local_socket_next_id++;
-    insert_local_socket(s, &local_socket_list);
-
-    adb_mutex_unlock(&socket_list_lock);
-}
-
-void remove_socket(asocket *s)
-{
-    // socket_list_lock should already be held
-    if (s->prev && s->next)
-    {
-        s->prev->next = s->next;
-        s->next->prev = s->prev;
-        s->next = 0;
-        s->prev = 0;
-        s->id = 0;
-    }
-}
-
-void close_all_sockets(atransport *t)
-{
-    asocket *s;
-
-        /* this is a little gross, but since s->close() *will* modify
-        ** the list out from under you, your options are limited.
-        */
-    adb_mutex_lock(&socket_list_lock);
-restart:
-    for(s = local_socket_list.next; s != &local_socket_list; s = s->next){
-        if(s->transport == t || (s->peer && s->peer->transport == t)) {
-            local_socket_close_locked(s);
-            goto restart;
-        }
-    }
-    adb_mutex_unlock(&socket_list_lock);
-}
-
-static int local_socket_enqueue(asocket *s, apacket *p)
-{
-    D("LS(%d): enqueue %d\n", s->id, p->len);
-
-    p->ptr = p->data;
-
-        /* if there is already data queue'd, we will receive
-        ** events when it's time to write.  just add this to
-        ** the tail
-        */
-    if(s->pkt_first) {
-        goto enqueue;
-    }
-
-        /* write as much as we can, until we
-        ** would block or there is an error/eof
-        */
-    while(p->len > 0) {
-        int r = adb_write(s->fd, p->ptr, p->len);
-        if(r > 0) {
-            p->len -= r;
-            p->ptr += r;
-            continue;
-        }
-        if((r == 0) || (errno != EAGAIN)) {
-            D( "LS(%d): not ready, errno=%d: %s\n", s->id, errno, strerror(errno) );
-            s->close(s);
-            return 1; /* not ready (error) */
-        } else {
-            break;
-        }
-    }
-
-    if(p->len == 0) {
-        put_apacket(p);
-        return 0; /* ready for more data */
-    }
-
-enqueue:
-    p->next = 0;
-    if(s->pkt_first) {
-        s->pkt_last->next = p;
-    } else {
-        s->pkt_first = p;
-    }
-    s->pkt_last = p;
-
-        /* make sure we are notified when we can drain the queue */
-    fdevent_add(&s->fde, FDE_WRITE);
-
-    return 1; /* not ready (backlog) */
-}
-
-static void local_socket_ready(asocket *s)
-{
-        /* far side is ready for data, pay attention to
-           readable events */
-    fdevent_add(&s->fde, FDE_READ);
-//    D("LS(%d): ready()\n", s->id);
-}
-
-static void local_socket_close(asocket *s)
-{
-    adb_mutex_lock(&socket_list_lock);
-    local_socket_close_locked(s);
-    adb_mutex_unlock(&socket_list_lock);
-}
-
-// be sure to hold the socket list lock when calling this
-static void local_socket_destroy(asocket  *s)
-{
-    apacket *p, *n;
-    D("LS(%d): destroying fde.fd=%d\n", s->id, s->fde.fd);
-
-        /* IMPORTANT: the remove closes the fd
-        ** that belongs to this socket
-        */
-    fdevent_remove(&s->fde);
-
-        /* dispose of any unwritten data */
-    for(p = s->pkt_first; p; p = n) {
-        D("LS(%d): discarding %d bytes\n", s->id, p->len);
-        n = p->next;
-        put_apacket(p);
-    }
-    remove_socket(s);
-    free(s);
-}
-
-
-static void local_socket_close_locked(asocket *s)
-{
-    D("entered. LS(%d) fd=%d\n", s->id, s->fd);
-    if(s->peer) {
-        D("LS(%d): closing peer. peer->id=%d peer->fd=%d\n",
-          s->id, s->peer->id, s->peer->fd);
-        s->peer->peer = 0;
-        // tweak to avoid deadlock
-        if (s->peer->close == local_socket_close) {
-            local_socket_close_locked(s->peer);
-        } else {
-            s->peer->close(s->peer);
-        }
-        s->peer = 0;
-    }
-
-        /* If we are already closing, or if there are no
-        ** pending packets, destroy immediately
-        */
-    if (s->closing || s->pkt_first == NULL) {
-        int   id = s->id;
-        local_socket_destroy(s);
-        D("LS(%d): closed\n", id);
-        return;
-    }
-
-        /* otherwise, put on the closing list
-        */
-    D("LS(%d): closing\n", s->id);
-    s->closing = 1;
-    fdevent_del(&s->fde, FDE_READ);
-    remove_socket(s);
-    D("LS(%d): put on socket_closing_list fd=%d\n", s->id, s->fd);
-    insert_local_socket(s, &local_socket_closing_list);
-}
-
-static void local_socket_event_func(int fd, unsigned ev, void *_s)
-{
-    asocket *s = _s;
-
-    D("LS(%d): event_func(fd=%d(==%d), ev=%04x)\n", s->id, s->fd, fd, ev);
-
-    /* put the FDE_WRITE processing before the FDE_READ
-    ** in order to simplify the code.
-    */
-    if(ev & FDE_WRITE){
-        apacket *p;
-
-        while((p = s->pkt_first) != 0) {
-            while(p->len > 0) {
-                int r = adb_write(fd, p->ptr, p->len);
-                if(r > 0) {
-                    p->ptr += r;
-                    p->len -= r;
-                    continue;
-                }
-                if(r < 0) {
-                    /* returning here is ok because FDE_READ will
-                    ** be processed in the next iteration loop
-                    */
-                    if(errno == EAGAIN) return;
-                    if(errno == EINTR) continue;
-                }
-                D(" closing after write because r=%d and errno is %d\n", r, errno);
-                s->close(s);
-                return;
-            }
-
-            if(p->len == 0) {
-                s->pkt_first = p->next;
-                if(s->pkt_first == 0) s->pkt_last = 0;
-                put_apacket(p);
-            }
-        }
-
-            /* if we sent the last packet of a closing socket,
-            ** we can now destroy it.
-            */
-        if (s->closing) {
-            D(" closing because 'closing' is set after write\n");
-            s->close(s);
-            return;
-        }
-
-            /* no more packets queued, so we can ignore
-            ** writable events again and tell our peer
-            ** to resume writing
-            */
-        fdevent_del(&s->fde, FDE_WRITE);
-        s->peer->ready(s->peer);
-    }
-
-
-    if(ev & FDE_READ){
-        apacket *p = get_apacket();
-        unsigned char *x = p->data;
-        size_t avail = MAX_PAYLOAD;
-        int r;
-        int is_eof = 0;
-
-        while(avail > 0) {
-            r = adb_read(fd, x, 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;
-                continue;
-            }
-            if(r < 0) {
-                if(errno == EAGAIN) break;
-                if(errno == EINTR) continue;
-            }
-
-                /* r = 0 or unhandled error */
-            is_eof = 1;
-            break;
-        }
-        D("LS(%d): fd=%d post avail loop. r=%d is_eof=%d forced_eof=%d\n",
-          s->id, s->fd, r, is_eof, s->fde.force_eof);
-        if((avail == MAX_PAYLOAD) || (s->peer == 0)) {
-            put_apacket(p);
-        } else {
-            p->len = MAX_PAYLOAD - avail;
-
-            r = s->peer->enqueue(s->peer, p);
-            D("LS(%d): fd=%d post peer->enqueue(). r=%d\n", s->id, s->fd, r);
-
-            if(r < 0) {
-                    /* error return means they closed us as a side-effect
-                    ** and we must return immediately.
-                    **
-                    ** note that if we still have buffered packets, the
-                    ** socket will be placed on the closing socket list.
-                    ** this handler function will be called again
-                    ** to process FDE_WRITE events.
-                    */
-                return;
-            }
-
-            if(r > 0) {
-                    /* if the remote cannot accept further events,
-                    ** we disable notification of READs.  They'll
-                    ** be enabled again when we get a call to ready()
-                    */
-                fdevent_del(&s->fde, FDE_READ);
-            }
-        }
-        /* Don't allow a forced eof if data is still there */
-        if((s->fde.force_eof && !r) || is_eof) {
-            D(" closing because is_eof=%d r=%d s->fde.force_eof=%d\n", is_eof, r, s->fde.force_eof);
-            s->close(s);
-        }
-    }
-
-    if(ev & FDE_ERROR){
-            /* this should be caught be the next read or write
-            ** catching it here means we may skip the last few
-            ** bytes of readable data.
-            */
-//        s->close(s);
-        D("LS(%d): FDE_ERROR (fd=%d)\n", s->id, s->fd);
-
-        return;
-    }
-}
-
-asocket *create_local_socket(int fd)
-{
-    asocket *s = calloc(1, sizeof(asocket));
-    if (s == NULL) fatal("cannot allocate socket");
-    s->fd = fd;
-    s->enqueue = local_socket_enqueue;
-    s->ready = local_socket_ready;
-    s->close = local_socket_close;
-    install_local_socket(s);
-
-    fdevent_install(&s->fde, fd, local_socket_event_func, s);
-/*    fdevent_add(&s->fde, FDE_ERROR); */
-    //fprintf(stderr, "Created local socket in create_local_socket \n");
-    D("LS(%d): created (fd=%d)\n", s->id, s->fd);
-    return s;
-}
-
-asocket *create_local_service_socket(const char *name)
-{
-    asocket *s;
-    int fd;
-
-    fd = service_to_fd(name);
-    if(fd < 0) return 0;
-
-    s = create_local_socket(fd);
-    D("LS(%d): bound to '%s' via %d\n", s->id, name, fd);
-    return s;
-}
-
-/* a Remote socket is used to send/receive data to/from a given transport object
-** it needs to be closed when the transport is forcibly destroyed by the user
-*/
-typedef struct aremotesocket {
-    asocket      socket;
-    adisconnect  disconnect;
-} aremotesocket;
-
-static int remote_socket_enqueue(asocket *s, apacket *p)
-{
-    D("entered remote_socket_enqueue RS(%d) WRITE fd=%d peer.fd=%d\n",
-      s->id, s->fd, s->peer->fd);
-    p->msg.command = A_WRTE;
-    p->msg.arg0 = s->peer->id;
-    p->msg.arg1 = s->id;
-    p->msg.data_length = p->len;
-    send_packet(p, s->transport);
-    return 1;
-}
-
-static void remote_socket_ready(asocket *s)
-{
-    D("entered remote_socket_ready RS(%d) OKAY fd=%d peer.fd=%d\n",
-      s->id, s->fd, s->peer->fd);
-    apacket *p = get_apacket();
-    p->msg.command = A_OKAY;
-    p->msg.arg0 = s->peer->id;
-    p->msg.arg1 = s->id;
-    send_packet(p, s->transport);
-}
-
-static void remote_socket_close(asocket *s)
-{
-    D("entered remote_socket_close RS(%d) CLOSE fd=%d peer->fd=%d\n",
-      s->id, s->fd, s->peer?s->peer->fd:-1);
-    apacket *p = get_apacket();
-    p->msg.command = A_CLSE;
-    if(s->peer) {
-        p->msg.arg0 = s->peer->id;
-        s->peer->peer = 0;
-        D("RS(%d) peer->close()ing peer->id=%d peer->fd=%d\n",
-          s->id, s->peer->id, s->peer->fd);
-        s->peer->close(s->peer);
-    }
-    p->msg.arg1 = s->id;
-    send_packet(p, s->transport);
-    D("RS(%d): closed\n", s->id);
-    remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect );
-    free(s);
-}
-
-static void remote_socket_disconnect(void*  _s, atransport*  t)
-{
-    asocket*  s    = _s;
-    asocket*  peer = s->peer;
-
-    D("remote_socket_disconnect RS(%d)\n", s->id);
-    if (peer) {
-        peer->peer = NULL;
-        peer->close(peer);
-    }
-    remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect );
-    free(s);
-}
-
-asocket *create_remote_socket(unsigned id, atransport *t)
-{
-    asocket *s = calloc(1, sizeof(aremotesocket));
-    adisconnect*  dis = &((aremotesocket*)s)->disconnect;
-
-    if (s == NULL) fatal("cannot allocate socket");
-    s->id = id;
-    s->enqueue = remote_socket_enqueue;
-    s->ready = remote_socket_ready;
-    s->close = remote_socket_close;
-    s->transport = t;
-
-    dis->func   = remote_socket_disconnect;
-    dis->opaque = s;
-    add_transport_disconnect( t, dis );
-    D("RS(%d): created\n", s->id);
-    return s;
-}
-
-void connect_to_remote(asocket *s, const char *destination)
-{
-    D("Connect_to_remote call RS(%d) fd=%d\n", s->id, s->fd);
-    apacket *p = get_apacket();
-    int len = strlen(destination) + 1;
-
-    if(len > (MAX_PAYLOAD-1)) {
-        fatal("destination oversized");
-    }
-
-    D("LS(%d): connect('%s')\n", s->id, destination);
-    p->msg.command = A_OPEN;
-    p->msg.arg0 = s->id;
-    p->msg.data_length = len;
-    strcpy((char*) p->data, destination);
-    send_packet(p, s->transport);
-}
-
-
-/* this is used by magic sockets to rig local sockets to
-   send the go-ahead message when they connect */
-static void local_socket_ready_notify(asocket *s)
-{
-    s->ready = local_socket_ready;
-    s->close = local_socket_close;
-    adb_write(s->fd, "OKAY", 4);
-    s->ready(s);
-}
-
-/* this is used by magic sockets to rig local sockets to
-   send the failure message if they are closed before
-   connected (to avoid closing them without a status message) */
-static void local_socket_close_notify(asocket *s)
-{
-    s->ready = local_socket_ready;
-    s->close = local_socket_close;
-    sendfailmsg(s->fd, "closed");
-    s->close(s);
-}
-
-unsigned unhex(unsigned char *s, int len)
-{
-    unsigned n = 0, c;
-
-    while(len-- > 0) {
-        switch((c = *s++)) {
-        case '0': case '1': case '2':
-        case '3': case '4': case '5':
-        case '6': case '7': case '8':
-        case '9':
-            c -= '0';
-            break;
-        case 'a': case 'b': case 'c':
-        case 'd': case 'e': case 'f':
-            c = c - 'a' + 10;
-            break;
-        case 'A': case 'B': case 'C':
-        case 'D': case 'E': case 'F':
-            c = c - 'A' + 10;
-            break;
-        default:
-            return 0xffffffff;
-        }
-
-        n = (n << 4) | c;
-    }
-
-    return n;
-}
-
-/* skip_host_serial return the position in a string
-   skipping over the 'serial' parameter in the ADB protocol,
-   where parameter string may be a host:port string containing
-   the protocol delimiter (colon). */
-char *skip_host_serial(char *service) {
-    char *first_colon, *serial_end;
-
-    first_colon = strchr(service, ':');
-    if (!first_colon) {
-        /* No colon in service string. */
-        return NULL;
-    }
-    serial_end = first_colon;
-    if (isdigit(serial_end[1])) {
-        serial_end++;
-        while ((*serial_end) && isdigit(*serial_end)) {
-            serial_end++;
-        }
-        if ((*serial_end) != ':') {
-            // Something other than numbers was found, reset the end.
-            serial_end = first_colon;
-        }
-    }
-    return serial_end;
-}
-
-static int smart_socket_enqueue(asocket *s, apacket *p)
-{
-    unsigned len;
-
-    D("SS(%d): enqueue %d\n", s->id, p->len);
-
-    if(s->pkt_first == 0) {
-        s->pkt_first = p;
-        s->pkt_last = p;
-    } else {
-        if((s->pkt_first->len + p->len) > MAX_PAYLOAD) {
-            D("SS(%d): overflow\n", s->id);
-            put_apacket(p);
-            goto fail;
-        }
-
-        memcpy(s->pkt_first->data + s->pkt_first->len,
-               p->data, p->len);
-        s->pkt_first->len += p->len;
-        put_apacket(p);
-
-        p = s->pkt_first;
-    }
-
-        /* don't bother if we can't decode the length */
-    if(p->len < 4) return 0;
-
-    len = unhex(p->data, 4);
-    if((len < 1) ||  (len > 1024)) {
-        D("SS(%d): bad size (%d)\n", s->id, len);
-        goto fail;
-    }
-
-    D("SS(%d): len is %d\n", s->id, len );
-        /* can't do anything until we have the full header */
-    if((len + 4) > p->len) {
-        D("SS(%d): waiting for %d more bytes\n", s->id, len+4 - p->len);
-        return 0;
-    }
-
-    p->data[len + 4] = 0;
-
-    D("SS(%d): '%s'\n", s->id, (char*) (p->data + 4));
-
-    if (s->transport == NULL) {
-        char* error_string = "unknown failure";
-        s->transport = acquire_one_transport (CS_ANY,
-                kTransportAny, NULL, &error_string);
-
-        if (s->transport == NULL) {
-            sendfailmsg(s->peer->fd, error_string);
-            goto fail;
-        }
-    }
-
-    if(!(s->transport) || (s->transport->connection_state == CS_OFFLINE)) {
-           /* if there's no remote we fail the connection
-            ** right here and terminate it
-            */
-        sendfailmsg(s->peer->fd, "device offline (x)");
-        goto fail;
-    }
-
-
-        /* instrument our peer to pass the success or fail
-        ** message back once it connects or closes, then
-        ** detach from it, request the connection, and
-        ** tear down
-        */
-    s->peer->ready = local_socket_ready_notify;
-    s->peer->close = local_socket_close_notify;
-    s->peer->peer = 0;
-        /* give him our transport and upref it */
-    s->peer->transport = s->transport;
-
-    connect_to_remote(s->peer, (char*) (p->data + 4));
-    s->peer = 0;
-    s->close(s);
-    return 1;
-
-fail:
-        /* we're going to close our peer as a side-effect, so
-        ** return -1 to signal that state to the local socket
-        ** who is enqueueing against us
-        */
-    s->close(s);
-    return -1;
-}
-
-static void smart_socket_ready(asocket *s)
-{
-    D("SS(%d): ready\n", s->id);
-}
-
-static void smart_socket_close(asocket *s)
-{
-    D("SS(%d): closed\n", s->id);
-    if(s->pkt_first){
-        put_apacket(s->pkt_first);
-    }
-    if(s->peer) {
-        s->peer->peer = 0;
-        s->peer->close(s->peer);
-        s->peer = 0;
-    }
-    free(s);
-}
-
-asocket *create_smart_socket(void (*action_cb)(asocket *s, const char *act))
-{
-    D("Creating smart socket \n");
-    asocket *s = calloc(1, sizeof(asocket));
-    if (s == NULL) fatal("cannot allocate socket");
-    s->enqueue = smart_socket_enqueue;
-    s->ready = smart_socket_ready;
-    s->close = smart_socket_close;
-    s->extra = action_cb;
-
-    D("SS(%d): created %p\n", s->id, action_cb);
-    return s;
-}
-
-void smart_socket_action(asocket *s, const char *act)
-{
-
-}
-
-void connect_to_smartsocket(asocket *s)
-{
-    D("Connecting to smart socket \n");
-    asocket *ss = create_smart_socket(smart_socket_action);
-    s->peer = ss;
-    ss->peer = s;
-    s->ready(s);
-}
diff --git a/minadbd/sysdeps.h b/minadbd/sysdeps.h
deleted file mode 100644
index 800ddb7..0000000
--- a/minadbd/sysdeps.h
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* this file contains system-dependent definitions used by ADB
- * they're related to threads, sockets and file descriptors
- */
-#ifndef _ADB_SYSDEPS_H
-#define _ADB_SYSDEPS_H
-
-#ifdef __CYGWIN__
-#  undef _WIN32
-#endif
-
-#ifdef _WIN32
-
-#include <windows.h>
-#include <winsock2.h>
-#include <ws2tcpip.h>
-#include <process.h>
-#include <fcntl.h>
-#include <io.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <ctype.h>
-
-#define OS_PATH_SEPARATOR '\\'
-#define OS_PATH_SEPARATOR_STR "\\"
-
-typedef CRITICAL_SECTION          adb_mutex_t;
-
-#define  ADB_MUTEX_DEFINE(x)     adb_mutex_t   x
-
-/* declare all mutexes */
-/* For win32, adb_sysdeps_init() will do the mutex runtime initialization. */
-#define  ADB_MUTEX(x)   extern adb_mutex_t  x;
-#include "mutex_list.h"
-
-extern void  adb_sysdeps_init(void);
-
-static __inline__ void adb_mutex_lock( adb_mutex_t*  lock )
-{
-    EnterCriticalSection( lock );
-}
-
-static __inline__ void  adb_mutex_unlock( adb_mutex_t*  lock )
-{
-    LeaveCriticalSection( lock );
-}
-
-typedef struct { unsigned  tid; }  adb_thread_t;
-
-typedef  void*  (*adb_thread_func_t)(void*  arg);
-
-typedef  void (*win_thread_func_t)(void*  arg);
-
-static __inline__ int  adb_thread_create( adb_thread_t  *thread, adb_thread_func_t  func, void*  arg)
-{
-    thread->tid = _beginthread( (win_thread_func_t)func, 0, arg );
-    if (thread->tid == (unsigned)-1L) {
-        return -1;
-    }
-    return 0;
-}
-
-static __inline__ void  close_on_exec(int  fd)
-{
-    /* nothing really */
-}
-
-extern void  disable_tcp_nagle(int  fd);
-
-#define  lstat    stat   /* no symlinks on Win32 */
-
-#define  S_ISLNK(m)   0   /* no symlinks on Win32 */
-
-static __inline__  int    adb_unlink(const char*  path)
-{
-    int  rc = unlink(path);
-
-    if (rc == -1 && errno == EACCES) {
-        /* unlink returns EACCES when the file is read-only, so we first */
-        /* try to make it writable, then unlink again...                  */
-        rc = chmod(path, _S_IREAD|_S_IWRITE );
-        if (rc == 0)
-            rc = unlink(path);
-    }
-    return rc;
-}
-#undef  unlink
-#define unlink  ___xxx_unlink
-
-static __inline__ int  adb_mkdir(const char*  path, int mode)
-{
-	return _mkdir(path);
-}
-#undef   mkdir
-#define  mkdir  ___xxx_mkdir
-
-extern int  adb_open(const char*  path, int  options);
-extern int  adb_creat(const char*  path, int  mode);
-extern int  adb_read(int  fd, void* buf, int len);
-extern int  adb_write(int  fd, const void*  buf, int  len);
-extern int  adb_lseek(int  fd, int  pos, int  where);
-extern int  adb_shutdown(int  fd);
-extern int  adb_close(int  fd);
-
-static __inline__ int  unix_close(int fd)
-{
-    return close(fd);
-}
-#undef   close
-#define  close   ____xxx_close
-
-static __inline__  int  unix_read(int  fd, void*  buf, size_t  len)
-{
-    return read(fd, buf, len);
-}
-#undef   read
-#define  read  ___xxx_read
-
-static __inline__  int  unix_write(int  fd, const void*  buf, size_t  len)
-{
-    return write(fd, buf, len);
-}
-#undef   write
-#define  write  ___xxx_write
-
-static __inline__ int  adb_open_mode(const char* path, int options, int mode)
-{
-    return adb_open(path, options);
-}
-
-static __inline__ int  unix_open(const char*  path, int options,...)
-{
-    if ((options & O_CREAT) == 0)
-    {
-        return  open(path, options);
-    }
-    else
-    {
-        int      mode;
-        va_list  args;
-        va_start( args, options );
-        mode = va_arg( args, int );
-        va_end( args );
-        return open(path, options, mode);
-    }
-}
-#define  open    ___xxx_unix_open
-
-
-/* normally provided by <cutils/misc.h> */
-extern void*  load_file(const char*  pathname, unsigned*  psize);
-
-/* normally provided by <cutils/sockets.h> */
-extern int socket_loopback_client(int port, int type);
-extern int socket_network_client(const char *host, int port, int type);
-extern int socket_loopback_server(int port, int type);
-extern int socket_inaddr_any_server(int port, int type);
-
-/* normally provided by "fdevent.h" */
-
-#define FDE_READ              0x0001
-#define FDE_WRITE             0x0002
-#define FDE_ERROR             0x0004
-#define FDE_DONT_CLOSE        0x0080
-
-typedef struct fdevent fdevent;
-
-typedef void (*fd_func)(int fd, unsigned events, void *userdata);
-
-fdevent *fdevent_create(int fd, fd_func func, void *arg);
-void     fdevent_destroy(fdevent *fde);
-void     fdevent_install(fdevent *fde, int fd, fd_func func, void *arg);
-void     fdevent_remove(fdevent *item);
-void     fdevent_set(fdevent *fde, unsigned events);
-void     fdevent_add(fdevent *fde, unsigned events);
-void     fdevent_del(fdevent *fde, unsigned events);
-void     fdevent_loop();
-
-struct fdevent {
-    fdevent *next;
-    fdevent *prev;
-
-    int fd;
-    int force_eof;
-
-    unsigned short state;
-    unsigned short events;
-
-    fd_func func;
-    void *arg;
-};
-
-static __inline__ void  adb_sleep_ms( int  mseconds )
-{
-    Sleep( mseconds );
-}
-
-extern int  adb_socket_accept(int  serverfd, struct sockaddr*  addr, socklen_t  *addrlen);
-
-#undef   accept
-#define  accept  ___xxx_accept
-
-static __inline__  int  adb_socket_setbufsize( int   fd, int  bufsize )
-{
-    int opt = bufsize;
-    return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char*)&opt, sizeof(opt));
-}
-
-extern int  adb_socketpair( int  sv[2] );
-
-static __inline__  char*  adb_dirstart( const char*  path )
-{
-    char*  p  = strchr(path, '/');
-    char*  p2 = strchr(path, '\\');
-
-    if ( !p )
-        p = p2;
-    else if ( p2 && p2 > p )
-        p = p2;
-
-    return p;
-}
-
-static __inline__  char*  adb_dirstop( const char*  path )
-{
-    char*  p  = strrchr(path, '/');
-    char*  p2 = strrchr(path, '\\');
-
-    if ( !p )
-        p = p2;
-    else if ( p2 && p2 > p )
-        p = p2;
-
-    return p;
-}
-
-static __inline__  int  adb_is_absolute_host_path( const char*  path )
-{
-    return isalpha(path[0]) && path[1] == ':' && path[2] == '\\';
-}
-
-#else /* !_WIN32 a.k.a. Unix */
-
-#include "fdevent.h"
-#include <cutils/sockets.h>
-#include <cutils/properties.h>
-#include <cutils/misc.h>
-#include <signal.h>
-#include <sys/wait.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#include <pthread.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <stdarg.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <string.h>
-
-#define OS_PATH_SEPARATOR '/'
-#define OS_PATH_SEPARATOR_STR "/"
-
-typedef  pthread_mutex_t          adb_mutex_t;
-
-#define  ADB_MUTEX_INITIALIZER    PTHREAD_MUTEX_INITIALIZER
-#define  adb_mutex_init           pthread_mutex_init
-#define  adb_mutex_lock           pthread_mutex_lock
-#define  adb_mutex_unlock         pthread_mutex_unlock
-#define  adb_mutex_destroy        pthread_mutex_destroy
-
-#define  ADB_MUTEX_DEFINE(m)      adb_mutex_t   m = PTHREAD_MUTEX_INITIALIZER
-
-#define  adb_cond_t               pthread_cond_t
-#define  adb_cond_init            pthread_cond_init
-#define  adb_cond_wait            pthread_cond_wait
-#define  adb_cond_broadcast       pthread_cond_broadcast
-#define  adb_cond_signal          pthread_cond_signal
-#define  adb_cond_destroy         pthread_cond_destroy
-
-/* declare all mutexes */
-#define  ADB_MUTEX(x)   extern adb_mutex_t  x;
-#include "mutex_list.h"
-
-static __inline__ void  close_on_exec(int  fd)
-{
-    fcntl( fd, F_SETFD, FD_CLOEXEC );
-}
-
-static __inline__ int  unix_open(const char*  path, int options,...)
-{
-    if ((options & O_CREAT) == 0)
-    {
-        return  open(path, options);
-    }
-    else
-    {
-        int      mode;
-        va_list  args;
-        va_start( args, options );
-        mode = va_arg( args, int );
-        va_end( args );
-        return open(path, options, mode);
-    }
-}
-
-static __inline__ int  adb_open_mode( const char*  pathname, int  options, int  mode )
-{
-    return open( pathname, options, mode );
-}
-
-static __inline__  int  adb_creat(const char*  path, int  mode)
-{
-    int  fd = open(path, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW, mode);
-
-    if ( fd < 0 )
-        return -1;
-
-    close_on_exec(fd);
-    return fd;
-}
-#undef   creat
-#define  creat  ___xxx_creat
-
-static __inline__ int  adb_open( const char*  pathname, int  options )
-{
-    int  fd = open( pathname, options );
-    if (fd < 0)
-        return -1;
-    close_on_exec( fd );
-    return fd;
-}
-#undef   open
-#define  open    ___xxx_open
-
-static __inline__ int  adb_shutdown(int fd)
-{
-    return shutdown(fd, SHUT_RDWR);
-}
-#undef   shutdown
-#define  shutdown   ____xxx_shutdown
-
-static __inline__ int  adb_close(int fd)
-{
-    return close(fd);
-}
-#undef   close
-#define  close   ____xxx_close
-
-
-static __inline__  int  adb_read(int  fd, void*  buf, size_t  len)
-{
-    return read(fd, buf, len);
-}
-
-#undef   read
-#define  read  ___xxx_read
-
-static __inline__  int  adb_write(int  fd, const void*  buf, size_t  len)
-{
-    return write(fd, buf, len);
-}
-#undef   write
-#define  write  ___xxx_write
-
-static __inline__ int   adb_lseek(int  fd, int  pos, int  where)
-{
-    return lseek(fd, pos, where);
-}
-#undef   lseek
-#define  lseek   ___xxx_lseek
-
-static __inline__  int    adb_unlink(const char*  path)
-{
-    return  unlink(path);
-}
-#undef  unlink
-#define unlink  ___xxx_unlink
-
-static __inline__ int  adb_socket_accept(int  serverfd, struct sockaddr*  addr, socklen_t  *addrlen)
-{
-    int fd;
-
-    fd = accept(serverfd, addr, addrlen);
-    if (fd >= 0)
-        close_on_exec(fd);
-
-    return fd;
-}
-
-#undef   accept
-#define  accept  ___xxx_accept
-
-#define  unix_read   adb_read
-#define  unix_write  adb_write
-#define  unix_close  adb_close
-
-typedef  pthread_t                 adb_thread_t;
-
-typedef void*  (*adb_thread_func_t)( void*  arg );
-
-static __inline__ int  adb_thread_create( adb_thread_t  *pthread, adb_thread_func_t  start, void*  arg )
-{
-    pthread_attr_t   attr;
-
-    pthread_attr_init (&attr);
-    pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
-
-    return pthread_create( pthread, &attr, start, arg );
-}
-
-static __inline__  int  adb_socket_setbufsize( int   fd, int  bufsize )
-{
-    int opt = bufsize;
-    return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt));
-}
-
-static __inline__ void  disable_tcp_nagle(int fd)
-{
-    int  on = 1;
-    setsockopt( fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on) );
-}
-
-
-static __inline__ int  unix_socketpair( int  d, int  type, int  protocol, int sv[2] )
-{
-    return socketpair( d, type, protocol, sv );
-}
-
-static __inline__ int  adb_socketpair( int  sv[2] )
-{
-    int  rc;
-
-    rc = unix_socketpair( AF_UNIX, SOCK_STREAM, 0, sv );
-    if (rc < 0)
-        return -1;
-
-    close_on_exec( sv[0] );
-    close_on_exec( sv[1] );
-    return 0;
-}
-
-#undef   socketpair
-#define  socketpair   ___xxx_socketpair
-
-static __inline__ void  adb_sleep_ms( int  mseconds )
-{
-    usleep( mseconds*1000 );
-}
-
-static __inline__ int  adb_mkdir(const char*  path, int mode)
-{
-    return mkdir(path, mode);
-}
-#undef   mkdir
-#define  mkdir  ___xxx_mkdir
-
-static __inline__ void  adb_sysdeps_init(void)
-{
-}
-
-static __inline__ char*  adb_dirstart(const char*  path)
-{
-    return strchr(path, '/');
-}
-
-static __inline__ char*  adb_dirstop(const char*  path)
-{
-    return strrchr(path, '/');
-}
-
-static __inline__  int  adb_is_absolute_host_path( const char*  path )
-{
-    return path[0] == '/';
-}
-
-#endif /* !_WIN32 */
-
-#endif /* _ADB_SYSDEPS_H */
diff --git a/minadbd/transport.c b/minadbd/transport.c
deleted file mode 100644
index 92679f5..0000000
--- a/minadbd/transport.c
+++ /dev/null
@@ -1,803 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-
-#include "sysdeps.h"
-
-#define   TRACE_TAG  TRACE_TRANSPORT
-#include "adb.h"
-
-static void transport_unref(atransport *t);
-
-static atransport transport_list = {
-    .next = &transport_list,
-    .prev = &transport_list,
-};
-
-ADB_MUTEX_DEFINE( transport_lock );
-
-#if ADB_TRACE
-#define MAX_DUMP_HEX_LEN 16
-static void  dump_hex( const unsigned char*  ptr, size_t  len )
-{
-    int  nn, len2 = len;
-    // Build a string instead of logging each character.
-    // MAX chars in 2 digit hex, one space, MAX chars, one '\0'.
-    char buffer[MAX_DUMP_HEX_LEN *2 + 1 + MAX_DUMP_HEX_LEN + 1 ], *pb = buffer;
-
-    if (len2 > MAX_DUMP_HEX_LEN) len2 = MAX_DUMP_HEX_LEN;
-
-    for (nn = 0; nn < len2; nn++) {
-        sprintf(pb, "%02x", ptr[nn]);
-        pb += 2;
-    }
-    sprintf(pb++, " ");
-
-    for (nn = 0; nn < len2; nn++) {
-        int  c = ptr[nn];
-        if (c < 32 || c > 127)
-            c = '.';
-        *pb++ =  c;
-    }
-    *pb++ = '\0';
-    DR("%s\n", buffer);
-}
-#endif
-
-void
-kick_transport(atransport*  t)
-{
-    if (t && !t->kicked)
-    {
-        int  kicked;
-
-        adb_mutex_lock(&transport_lock);
-        kicked = t->kicked;
-        if (!kicked)
-            t->kicked = 1;
-        adb_mutex_unlock(&transport_lock);
-
-        if (!kicked)
-            t->kick(t);
-    }
-}
-
-void
-run_transport_disconnects(atransport*  t)
-{
-    adisconnect*  dis = t->disconnects.next;
-
-    D("%s: run_transport_disconnects\n", t->serial);
-    while (dis != &t->disconnects) {
-        adisconnect*  next = dis->next;
-        dis->func( dis->opaque, t );
-        dis = next;
-    }
-}
-
-#if ADB_TRACE
-static void
-dump_packet(const char* name, const char* func, apacket* p)
-{
-    unsigned  command = p->msg.command;
-    int       len     = p->msg.data_length;
-    char      cmd[9];
-    char      arg0[12], arg1[12];
-    int       n;
-
-    for (n = 0; n < 4; n++) {
-        int  b = (command >> (n*8)) & 255;
-        if (b < 32 || b >= 127)
-            break;
-        cmd[n] = (char)b;
-    }
-    if (n == 4) {
-        cmd[4] = 0;
-    } else {
-        /* There is some non-ASCII name in the command, so dump
-            * the hexadecimal value instead */
-        snprintf(cmd, sizeof cmd, "%08x", command);
-    }
-
-    if (p->msg.arg0 < 256U)
-        snprintf(arg0, sizeof arg0, "%d", p->msg.arg0);
-    else
-        snprintf(arg0, sizeof arg0, "0x%x", p->msg.arg0);
-
-    if (p->msg.arg1 < 256U)
-        snprintf(arg1, sizeof arg1, "%d", p->msg.arg1);
-    else
-        snprintf(arg1, sizeof arg1, "0x%x", p->msg.arg1);
-
-    D("%s: %s: [%s] arg0=%s arg1=%s (len=%d) ",
-        name, func, cmd, arg0, arg1, len);
-    dump_hex(p->data, len);
-}
-#endif /* ADB_TRACE */
-
-static int
-read_packet(int  fd, const char* name, apacket** ppacket)
-{
-    char *p = (char*)ppacket;  /* really read a packet address */
-    int   r;
-    int   len = sizeof(*ppacket);
-    char  buff[8];
-    if (!name) {
-        snprintf(buff, sizeof buff, "fd=%d", fd);
-        name = buff;
-    }
-    while(len > 0) {
-        r = adb_read(fd, p, len);
-        if(r > 0) {
-            len -= r;
-            p   += r;
-        } else {
-            D("%s: read_packet (fd=%d), error ret=%d errno=%d: %s\n", name, fd, r, errno, strerror(errno));
-            if((r < 0) && (errno == EINTR)) continue;
-            return -1;
-        }
-    }
-
-#if ADB_TRACE
-    if (ADB_TRACING) {
-        dump_packet(name, "from remote", *ppacket);
-    }
-#endif
-    return 0;
-}
-
-static int
-write_packet(int  fd, const char* name, apacket** ppacket)
-{
-    char *p = (char*) ppacket;  /* we really write the packet address */
-    int r, len = sizeof(ppacket);
-    char buff[8];
-    if (!name) {
-        snprintf(buff, sizeof buff, "fd=%d", fd);
-        name = buff;
-    }
-
-#if ADB_TRACE
-    if (ADB_TRACING) {
-        dump_packet(name, "to remote", *ppacket);
-    }
-#endif
-    len = sizeof(ppacket);
-    while(len > 0) {
-        r = adb_write(fd, p, len);
-        if(r > 0) {
-            len -= r;
-            p += r;
-        } else {
-            D("%s: write_packet (fd=%d) error ret=%d errno=%d: %s\n", name, fd, r, errno, strerror(errno));
-            if((r < 0) && (errno == EINTR)) continue;
-            return -1;
-        }
-    }
-    return 0;
-}
-
-static void transport_socket_events(int fd, unsigned events, void *_t)
-{
-    atransport *t = _t;
-    D("transport_socket_events(fd=%d, events=%04x,...)\n", fd, events);
-    if(events & FDE_READ){
-        apacket *p = 0;
-        if(read_packet(fd, t->serial, &p)){
-            D("%s: failed to read packet from transport socket on fd %d\n", t->serial, fd);
-        } else {
-            handle_packet(p, (atransport *) _t);
-        }
-    }
-}
-
-void send_packet(apacket *p, atransport *t)
-{
-    unsigned char *x;
-    unsigned sum;
-    unsigned count;
-
-    p->msg.magic = p->msg.command ^ 0xffffffff;
-
-    count = p->msg.data_length;
-    x = (unsigned char *) p->data;
-    sum = 0;
-    while(count-- > 0){
-        sum += *x++;
-    }
-    p->msg.data_check = sum;
-
-    print_packet("send", p);
-
-    if (t == NULL) {
-        D("Transport is null \n");
-        // Zap errno because print_packet() and other stuff have errno effect.
-        errno = 0;
-        fatal_errno("Transport is null");
-    }
-
-    if(write_packet(t->transport_socket, t->serial, &p)){
-        fatal_errno("cannot enqueue packet on transport socket");
-    }
-}
-
-/* The transport is opened by transport_register_func before
-** the input and output threads are started.
-**
-** The output thread issues a SYNC(1, token) message to let
-** the input thread know to start things up.  In the event
-** of transport IO failure, the output thread will post a
-** SYNC(0,0) message to ensure shutdown.
-**
-** The transport will not actually be closed until both
-** threads exit, but the input thread will kick the transport
-** on its way out to disconnect the underlying device.
-*/
-
-static void *output_thread(void *_t)
-{
-    atransport *t = _t;
-    apacket *p;
-
-    D("%s: starting transport output thread on fd %d, SYNC online (%d)\n",
-       t->serial, t->fd, t->sync_token + 1);
-    p = get_apacket();
-    p->msg.command = A_SYNC;
-    p->msg.arg0 = 1;
-    p->msg.arg1 = ++(t->sync_token);
-    p->msg.magic = A_SYNC ^ 0xffffffff;
-    if(write_packet(t->fd, t->serial, &p)) {
-        put_apacket(p);
-        D("%s: failed to write SYNC packet\n", t->serial);
-        goto oops;
-    }
-
-    D("%s: data pump started\n", t->serial);
-    for(;;) {
-        p = get_apacket();
-
-        if(t->read_from_remote(p, t) == 0){
-            D("%s: received remote packet, sending to transport\n",
-              t->serial);
-            if(write_packet(t->fd, t->serial, &p)){
-                put_apacket(p);
-                D("%s: failed to write apacket to transport\n", t->serial);
-                goto oops;
-            }
-        } else {
-            D("%s: remote read failed for transport\n", t->serial);
-            put_apacket(p);
-            break;
-        }
-    }
-
-    D("%s: SYNC offline for transport\n", t->serial);
-    p = get_apacket();
-    p->msg.command = A_SYNC;
-    p->msg.arg0 = 0;
-    p->msg.arg1 = 0;
-    p->msg.magic = A_SYNC ^ 0xffffffff;
-    if(write_packet(t->fd, t->serial, &p)) {
-        put_apacket(p);
-        D("%s: failed to write SYNC apacket to transport", t->serial);
-    }
-
-oops:
-    D("%s: transport output thread is exiting\n", t->serial);
-    kick_transport(t);
-    transport_unref(t);
-    return 0;
-}
-
-static void *input_thread(void *_t)
-{
-    atransport *t = _t;
-    apacket *p;
-    int active = 0;
-
-    D("%s: starting transport input thread, reading from fd %d\n",
-       t->serial, t->fd);
-
-    for(;;){
-        if(read_packet(t->fd, t->serial, &p)) {
-            D("%s: failed to read apacket from transport on fd %d\n",
-               t->serial, t->fd );
-            break;
-        }
-        if(p->msg.command == A_SYNC){
-            if(p->msg.arg0 == 0) {
-                D("%s: transport SYNC offline\n", t->serial);
-                put_apacket(p);
-                break;
-            } else {
-                if(p->msg.arg1 == t->sync_token) {
-                    D("%s: transport SYNC online\n", t->serial);
-                    active = 1;
-                } else {
-                    D("%s: transport ignoring SYNC %d != %d\n",
-                      t->serial, p->msg.arg1, t->sync_token);
-                }
-            }
-        } else {
-            if(active) {
-                D("%s: transport got packet, sending to remote\n", t->serial);
-                t->write_to_remote(p, t);
-            } else {
-                D("%s: transport ignoring packet while offline\n", t->serial);
-            }
-        }
-
-        put_apacket(p);
-    }
-
-    // this is necessary to avoid a race condition that occured when a transport closes
-    // while a client socket is still active.
-    close_all_sockets(t);
-
-    D("%s: transport input thread is exiting, fd %d\n", t->serial, t->fd);
-    kick_transport(t);
-    transport_unref(t);
-    return 0;
-}
-
-
-static int transport_registration_send = -1;
-static int transport_registration_recv = -1;
-static fdevent transport_registration_fde;
-
-void  update_transports(void)
-{
-    // nothing to do on the device side
-}
-
-typedef struct tmsg tmsg;
-struct tmsg
-{
-    atransport *transport;
-    int         action;
-};
-
-static int
-transport_read_action(int  fd, struct tmsg*  m)
-{
-    char *p   = (char*)m;
-    int   len = sizeof(*m);
-    int   r;
-
-    while(len > 0) {
-        r = adb_read(fd, p, len);
-        if(r > 0) {
-            len -= r;
-            p   += r;
-        } else {
-            if((r < 0) && (errno == EINTR)) continue;
-            D("transport_read_action: on fd %d, error %d: %s\n",
-              fd, errno, strerror(errno));
-            return -1;
-        }
-    }
-    return 0;
-}
-
-static int
-transport_write_action(int  fd, struct tmsg*  m)
-{
-    char *p   = (char*)m;
-    int   len = sizeof(*m);
-    int   r;
-
-    while(len > 0) {
-        r = adb_write(fd, p, len);
-        if(r > 0) {
-            len -= r;
-            p   += r;
-        } else {
-            if((r < 0) && (errno == EINTR)) continue;
-            D("transport_write_action: on fd %d, error %d: %s\n",
-              fd, errno, strerror(errno));
-            return -1;
-        }
-    }
-    return 0;
-}
-
-static void transport_registration_func(int _fd, unsigned ev, void *data)
-{
-    tmsg m;
-    adb_thread_t output_thread_ptr;
-    adb_thread_t input_thread_ptr;
-    int s[2];
-    atransport *t;
-
-    if(!(ev & FDE_READ)) {
-        return;
-    }
-
-    if(transport_read_action(_fd, &m)) {
-        fatal_errno("cannot read transport registration socket");
-    }
-
-    t = m.transport;
-
-    if(m.action == 0){
-        D("transport: %s removing and free'ing %d\n", t->serial, t->transport_socket);
-
-            /* IMPORTANT: the remove closes one half of the
-            ** socket pair.  The close closes the other half.
-            */
-        fdevent_remove(&(t->transport_fde));
-        adb_close(t->fd);
-
-        adb_mutex_lock(&transport_lock);
-        t->next->prev = t->prev;
-        t->prev->next = t->next;
-        adb_mutex_unlock(&transport_lock);
-
-        run_transport_disconnects(t);
-
-        if (t->product)
-            free(t->product);
-        if (t->serial)
-            free(t->serial);
-
-        memset(t,0xee,sizeof(atransport));
-        free(t);
-
-        update_transports();
-        return;
-    }
-
-    /* don't create transport threads for inaccessible devices */
-    if (t->connection_state != CS_NOPERM) {
-        /* initial references are the two threads */
-        t->ref_count = 2;
-
-        if(adb_socketpair(s)) {
-            fatal_errno("cannot open transport socketpair");
-        }
-
-        D("transport: %s (%d,%d) starting\n", t->serial, s[0], s[1]);
-
-        t->transport_socket = s[0];
-        t->fd = s[1];
-
-        fdevent_install(&(t->transport_fde),
-                        t->transport_socket,
-                        transport_socket_events,
-                        t);
-
-        fdevent_set(&(t->transport_fde), FDE_READ);
-
-        if(adb_thread_create(&input_thread_ptr, input_thread, t)){
-            fatal_errno("cannot create input thread");
-        }
-
-        if(adb_thread_create(&output_thread_ptr, output_thread, t)){
-            fatal_errno("cannot create output thread");
-        }
-    }
-
-        /* put us on the master device list */
-    adb_mutex_lock(&transport_lock);
-    t->next = &transport_list;
-    t->prev = transport_list.prev;
-    t->next->prev = t;
-    t->prev->next = t;
-    adb_mutex_unlock(&transport_lock);
-
-    t->disconnects.next = t->disconnects.prev = &t->disconnects;
-
-    update_transports();
-}
-
-void init_transport_registration(void)
-{
-    int s[2];
-
-    if(adb_socketpair(s)){
-        fatal_errno("cannot open transport registration socketpair");
-    }
-
-    transport_registration_send = s[0];
-    transport_registration_recv = s[1];
-
-    fdevent_install(&transport_registration_fde,
-                    transport_registration_recv,
-                    transport_registration_func,
-                    0);
-
-    fdevent_set(&transport_registration_fde, FDE_READ);
-}
-
-/* the fdevent select pump is single threaded */
-static void register_transport(atransport *transport)
-{
-    tmsg m;
-    m.transport = transport;
-    m.action = 1;
-    D("transport: %s registered\n", transport->serial);
-    if(transport_write_action(transport_registration_send, &m)) {
-        fatal_errno("cannot write transport registration socket\n");
-    }
-}
-
-static void remove_transport(atransport *transport)
-{
-    tmsg m;
-    m.transport = transport;
-    m.action = 0;
-    D("transport: %s removed\n", transport->serial);
-    if(transport_write_action(transport_registration_send, &m)) {
-        fatal_errno("cannot write transport registration socket\n");
-    }
-}
-
-
-static void transport_unref_locked(atransport *t)
-{
-    t->ref_count--;
-    if (t->ref_count == 0) {
-        D("transport: %s unref (kicking and closing)\n", t->serial);
-        if (!t->kicked) {
-            t->kicked = 1;
-            t->kick(t);
-        }
-        t->close(t);
-        remove_transport(t);
-    } else {
-        D("transport: %s unref (count=%d)\n", t->serial, t->ref_count);
-    }
-}
-
-static void transport_unref(atransport *t)
-{
-    if (t) {
-        adb_mutex_lock(&transport_lock);
-        transport_unref_locked(t);
-        adb_mutex_unlock(&transport_lock);
-    }
-}
-
-void add_transport_disconnect(atransport*  t, adisconnect*  dis)
-{
-    adb_mutex_lock(&transport_lock);
-    dis->next       = &t->disconnects;
-    dis->prev       = dis->next->prev;
-    dis->prev->next = dis;
-    dis->next->prev = dis;
-    adb_mutex_unlock(&transport_lock);
-}
-
-void remove_transport_disconnect(atransport*  t, adisconnect*  dis)
-{
-    dis->prev->next = dis->next;
-    dis->next->prev = dis->prev;
-    dis->next = dis->prev = dis;
-}
-
-
-atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char** error_out)
-{
-    atransport *t;
-    atransport *result = NULL;
-    int ambiguous = 0;
-
-retry:
-    if (error_out)
-        *error_out = "device not found";
-
-    adb_mutex_lock(&transport_lock);
-    for (t = transport_list.next; t != &transport_list; t = t->next) {
-        if (t->connection_state == CS_NOPERM) {
-        if (error_out)
-            *error_out = "insufficient permissions for device";
-            continue;
-        }
-
-        /* check for matching serial number */
-        if (serial) {
-            if (t->serial && !strcmp(serial, t->serial)) {
-                result = t;
-                break;
-            }
-        } else {
-            if (ttype == kTransportUsb && t->type == kTransportUsb) {
-                if (result) {
-                    if (error_out)
-                        *error_out = "more than one device";
-                    ambiguous = 1;
-                    result = NULL;
-                    break;
-                }
-                result = t;
-            } else if (ttype == kTransportLocal && t->type == kTransportLocal) {
-                if (result) {
-                    if (error_out)
-                        *error_out = "more than one emulator";
-                    ambiguous = 1;
-                    result = NULL;
-                    break;
-                }
-                result = t;
-            } else if (ttype == kTransportAny) {
-                if (result) {
-                    if (error_out)
-                        *error_out = "more than one device and emulator";
-                    ambiguous = 1;
-                    result = NULL;
-                    break;
-                }
-                result = t;
-            }
-        }
-    }
-    adb_mutex_unlock(&transport_lock);
-
-    if (result) {
-         /* offline devices are ignored -- they are either being born or dying */
-        if (result && result->connection_state == CS_OFFLINE) {
-            if (error_out)
-                *error_out = "device offline";
-            result = NULL;
-        }
-         /* check for required connection state */
-        if (result && state != CS_ANY && result->connection_state != state) {
-            if (error_out)
-                *error_out = "invalid device state";
-            result = NULL;
-        }
-    }
-
-    if (result) {
-        /* found one that we can take */
-        if (error_out)
-            *error_out = NULL;
-    } else if (state != CS_ANY && (serial || !ambiguous)) {
-        adb_sleep_ms(1000);
-        goto retry;
-    }
-
-    return result;
-}
-
-void register_usb_transport(usb_handle *usb, const char *serial, unsigned writeable)
-{
-    atransport *t = calloc(1, sizeof(atransport));
-    D("transport: %p init'ing for usb_handle %p (sn='%s')\n", t, usb,
-      serial ? serial : "");
-    init_usb_transport(t, usb, (writeable ? CS_OFFLINE : CS_NOPERM));
-    if(serial) {
-        t->serial = strdup(serial);
-    }
-    register_transport(t);
-}
-
-/* this should only be used for transports with connection_state == CS_NOPERM */
-void unregister_usb_transport(usb_handle *usb)
-{
-    atransport *t;
-    adb_mutex_lock(&transport_lock);
-    for(t = transport_list.next; t != &transport_list; t = t->next) {
-        if (t->usb == usb && t->connection_state == CS_NOPERM) {
-            t->next->prev = t->prev;
-            t->prev->next = t->next;
-            break;
-        }
-     }
-    adb_mutex_unlock(&transport_lock);
-}
-
-#undef TRACE_TAG
-#define TRACE_TAG  TRACE_RWX
-
-int readx(int fd, void *ptr, size_t len)
-{
-    char *p = ptr;
-    int r;
-#if ADB_TRACE
-    size_t len0 = len;
-#endif
-    D("readx: fd=%d wanted=%d\n", fd, (int)len);
-    while(len > 0) {
-        r = adb_read(fd, p, len);
-        if(r > 0) {
-            len -= r;
-            p += r;
-        } else {
-            if (r < 0) {
-                D("readx: fd=%d error %d: %s\n", fd, errno, strerror(errno));
-                if (errno == EINTR)
-                    continue;
-            } else {
-                D("readx: fd=%d disconnected\n", fd);
-            }
-            return -1;
-        }
-    }
-
-#if ADB_TRACE
-    D("readx: fd=%d wanted=%zu got=%zu\n", fd, len0, len0 - len);
-    dump_hex( ptr, len0 );
-#endif
-    return 0;
-}
-
-int writex(int fd, const void *ptr, size_t len)
-{
-    char *p = (char*) ptr;
-    int r;
-
-#if ADB_TRACE
-    D("writex: fd=%d len=%d: ", fd, (int)len);
-    dump_hex( ptr, len );
-#endif
-    while(len > 0) {
-        r = adb_write(fd, p, len);
-        if(r > 0) {
-            len -= r;
-            p += r;
-        } else {
-            if (r < 0) {
-                D("writex: fd=%d error %d: %s\n", fd, errno, strerror(errno));
-                if (errno == EINTR)
-                    continue;
-            } else {
-                D("writex: fd=%d disconnected\n", fd);
-            }
-            return -1;
-        }
-    }
-    return 0;
-}
-
-int check_header(apacket *p)
-{
-    if(p->msg.magic != (p->msg.command ^ 0xffffffff)) {
-        D("check_header(): invalid magic\n");
-        return -1;
-    }
-
-    if(p->msg.data_length > MAX_PAYLOAD) {
-        D("check_header(): %d > MAX_PAYLOAD\n", p->msg.data_length);
-        return -1;
-    }
-
-    return 0;
-}
-
-int check_data(apacket *p)
-{
-    unsigned count, sum;
-    unsigned char *x;
-
-    count = p->msg.data_length;
-    x = p->data;
-    sum = 0;
-    while(count-- > 0) {
-        sum += *x++;
-    }
-
-    if(sum != p->msg.data_check) {
-        return -1;
-    } else {
-        return 0;
-    }
-}
diff --git a/minadbd/transport.h b/minadbd/transport.h
deleted file mode 100644
index 992e052..0000000
--- a/minadbd/transport.h
+++ /dev/null
@@ -1,26 +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.
- */
-
-#ifndef __TRANSPORT_H
-#define __TRANSPORT_H
-
-/* convenience wrappers around read/write that will retry on
-** EINTR and/or short read/write.  Returns 0 on success, -1
-** on error or EOF.
-*/
-int readx(int fd, void *ptr, size_t len);
-int writex(int fd, const void *ptr, size_t len);
-#endif   /* __TRANSPORT_H */
diff --git a/minadbd/transport_usb.c b/minadbd/transport_usb.c
deleted file mode 100644
index 91cbf61..0000000
--- a/minadbd/transport_usb.c
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <sysdeps.h>
-
-#define  TRACE_TAG  TRACE_TRANSPORT
-#include "adb.h"
-
-#ifdef HAVE_BIG_ENDIAN
-#define H4(x)	(((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24)
-static inline void fix_endians(apacket *p)
-{
-    p->msg.command     = H4(p->msg.command);
-    p->msg.arg0        = H4(p->msg.arg0);
-    p->msg.arg1        = H4(p->msg.arg1);
-    p->msg.data_length = H4(p->msg.data_length);
-    p->msg.data_check  = H4(p->msg.data_check);
-    p->msg.magic       = H4(p->msg.magic);
-}
-unsigned host_to_le32(unsigned n)
-{
-    return H4(n);
-}
-#else
-#define fix_endians(p) do {} while (0)
-unsigned host_to_le32(unsigned n)
-{
-    return n;
-}
-#endif
-
-static int remote_read(apacket *p, atransport *t)
-{
-    if(usb_read(t->usb, &p->msg, sizeof(amessage))){
-        D("remote usb: read terminated (message)\n");
-        return -1;
-    }
-
-    fix_endians(p);
-
-    if(check_header(p)) {
-        D("remote usb: check_header failed\n");
-        return -1;
-    }
-
-    if(p->msg.data_length) {
-        if(usb_read(t->usb, p->data, p->msg.data_length)){
-            D("remote usb: terminated (data)\n");
-            return -1;
-        }
-    }
-
-    if(check_data(p)) {
-        D("remote usb: check_data failed\n");
-        return -1;
-    }
-
-    return 0;
-}
-
-static int remote_write(apacket *p, atransport *t)
-{
-    unsigned size = p->msg.data_length;
-
-    fix_endians(p);
-
-    if(usb_write(t->usb, &p->msg, sizeof(amessage))) {
-        D("remote usb: 1 - write terminated\n");
-        return -1;
-    }
-    if(p->msg.data_length == 0) return 0;
-    if(usb_write(t->usb, &p->data, size)) {
-        D("remote usb: 2 - write terminated\n");
-        return -1;
-    }
-
-    return 0;
-}
-
-static void remote_close(atransport *t)
-{
-    usb_close(t->usb);
-    t->usb = 0;
-}
-
-static void remote_kick(atransport *t)
-{
-    usb_kick(t->usb);
-}
-
-void init_usb_transport(atransport *t, usb_handle *h, int state)
-{
-    D("transport: usb\n");
-    t->close = remote_close;
-    t->kick = remote_kick;
-    t->read_from_remote = remote_read;
-    t->write_to_remote = remote_write;
-    t->sync_token = 1;
-    t->connection_state = state;
-    t->type = kTransportUsb;
-    t->usb = h;
-
-    HOST = 0;
-}
diff --git a/minadbd/usb_linux_client.c b/minadbd/usb_linux_client.c
deleted file mode 100644
index 29bab15..0000000
--- a/minadbd/usb_linux_client.c
+++ /dev/null
@@ -1,493 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#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>
-#include <errno.h>
-
-#include "sysdeps.h"
-
-#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()
-{
-    // nothing to do here
-}
-
-static void *usb_adb_open_thread(void *x)
-{
-    struct usb_handle *usb = (struct usb_handle *)x;
-    int fd;
-
-    while (1) {
-        // wait until the USB device needs opening
-        adb_mutex_lock(&usb->lock);
-        while (usb->fd != -1)
-            adb_cond_wait(&usb->notify, &usb->lock);
-        adb_mutex_unlock(&usb->lock);
-
-        D("[ usb_thread - opening device ]\n");
-        do {
-            /* XXX use inotify? */
-            fd = unix_open("/dev/android_adb", O_RDWR);
-            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);
-            }
-        } while (fd < 0);
-        D("[ opening device succeeded ]\n");
-
-        close_on_exec(fd);
-        usb->fd = fd;
-
-        D("[ usb_thread - registering device ]\n");
-        register_usb_transport(usb, 0, 1);
-    }
-
-    // never gets here
-    return 0;
-}
-
-static int usb_adb_write(usb_handle *h, const void *data, int len)
-{
-    int n;
-
-    D("about to write (fd=%d, len=%d)\n", h->fd, len);
-    n = adb_write(h->fd, data, len);
-    if(n != len) {
-        D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
-            h->fd, n, errno, strerror(errno));
-        return -1;
-    }
-    D("[ done fd=%d ]\n", h->fd);
-    return 0;
-}
-
-static int usb_adb_read(usb_handle *h, void *data, int len)
-{
-    int n;
-
-    D("about to read (fd=%d, len=%d)\n", h->fd, len);
-    n = adb_read(h->fd, data, len);
-    if(n != len) {
-        D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
-            h->fd, n, errno, strerror(errno));
-        return -1;
-    }
-    D("[ done fd=%d ]\n", h->fd);
-    return 0;
-}
-
-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 {
-        close_on_exec(fd);
-    }
-
-    D("[ usb_init - starting thread ]\n");
-    if(adb_thread_create(&tid, usb_adb_open_thread, h)){
-        fatal_errno("cannot create usb thread");
-        fprintf(stderr, "cannot create the usb thread()\n");
-    }
-}
-
-
-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/minadbd/utils.c b/minadbd/utils.c
deleted file mode 100644
index 91518ba..0000000
--- a/minadbd/utils.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2008 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 "utils.h"
-#include <stdarg.h>
-#include <stdio.h>
-#include <string.h>
-
-char*
-buff_addc (char*  buff, char*  buffEnd, int  c)
-{
-    int  avail = buffEnd - buff;
-
-    if (avail <= 0)  /* already in overflow mode */
-        return buff;
-
-    if (avail == 1) {  /* overflowing, the last byte is reserved for zero */
-        buff[0] = 0;
-        return buff + 1;
-    }
-
-    buff[0] = (char) c;  /* add char and terminating zero */
-    buff[1] = 0;
-    return buff + 1;
-}
-
-char*
-buff_adds (char*  buff, char*  buffEnd, const char*  s)
-{
-    int  slen = strlen(s);
-
-    return buff_addb(buff, buffEnd, s, slen);
-}
-
-char*
-buff_addb (char*  buff, char*  buffEnd, const void*  data, int  len)
-{
-    int  avail = (buffEnd - buff);
-
-    if (avail <= 0 || len <= 0)  /* already overflowing */
-        return buff;
-
-    if (len > avail)
-        len = avail;
-
-    memcpy(buff, data, len);
-
-    buff += len;
-
-    /* ensure there is a terminating zero */
-    if (buff >= buffEnd) {  /* overflow */
-        buff[-1] = 0;
-    } else
-        buff[0] = 0;
-
-    return buff;
-}
-
-char*
-buff_add  (char*  buff, char*  buffEnd, const char*  format, ... )
-{
-    int      avail;
-
-    avail = (buffEnd - buff);
-
-    if (avail > 0) {
-        va_list  args;
-        int      nn;
-
-        va_start(args, format);
-        nn = vsnprintf( buff, avail, format, args);
-        va_end(args);
-
-        if (nn < 0) {
-            /* some C libraries return -1 in case of overflow,
-             * but they will also do that if the format spec is
-             * invalid. We assume ADB is not buggy enough to
-             * trigger that last case. */
-            nn = avail;
-        }
-        else if (nn > avail) {
-            nn = avail;
-        }
-
-        buff += nn;
-
-        /* ensure that there is a terminating zero */
-        if (buff >= buffEnd)
-            buff[-1] = 0;
-        else
-            buff[0] = 0;
-    }
-    return buff;
-}
diff --git a/minadbd/utils.h b/minadbd/utils.h
deleted file mode 100644
index f70ecd2..0000000
--- a/minadbd/utils.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2008 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 _ADB_UTILS_H
-#define _ADB_UTILS_H
-
-/* bounded buffer functions */
-
-/* all these functions are used to append data to a bounded buffer.
- *
- * after each operation, the buffer is guaranteed to be zero-terminated,
- * even in the case of an overflow. they all return the new buffer position
- * which allows one to use them in succession, only checking for overflows
- * at the end. For example:
- *
- *    BUFF_DECL(temp,p,end,1024);
- *    char*    p;
- *
- *    p = buff_addc(temp, end, '"');
- *    p = buff_adds(temp, end, string);
- *    p = buff_addc(temp, end, '"');
- *
- *    if (p >= end) {
- *        overflow detected. note that 'temp' is
- *        zero-terminated for safety. 
- *    }
- *    return strdup(temp);
- */
-
-/* tries to add a character to the buffer, in case of overflow
- * this will only write a terminating zero and return buffEnd.
- */
-char*   buff_addc (char*  buff, char*  buffEnd, int  c);
-
-/* tries to add a string to the buffer */
-char*   buff_adds (char*  buff, char*  buffEnd, const char*  s);
-
-/* tries to add a bytes to the buffer. the input can contain zero bytes,
- * but a terminating zero will always be appended at the end anyway
- */
-char*   buff_addb (char*  buff, char*  buffEnd, const void*  data, int  len);
-
-/* tries to add a formatted string to a bounded buffer */
-char*   buff_add  (char*  buff, char*  buffEnd, const char*  format, ... );
-
-/* convenience macro used to define a bounded buffer, as well as
- * a 'cursor' and 'end' variables all in one go.
- *
- * note: this doesn't place an initial terminating zero in the buffer,
- * you need to use one of the buff_ functions for this. or simply
- * do _cursor[0] = 0 manually.
- */
-#define  BUFF_DECL(_buff,_cursor,_end,_size)   \
-    char   _buff[_size], *_cursor=_buff, *_end = _cursor + (_size)
-
-#endif /* _ADB_UTILS_H */
diff --git a/minui/Android.mk b/minui/Android.mk
index df4aac1..97724fb 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -1,21 +1,29 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := graphics.c graphics_adf.c graphics_fbdev.c events.c \
-	resources.c
-
-LOCAL_C_INCLUDES +=\
-    external/libpng\
-    external/zlib
+LOCAL_SRC_FILES := \
+    events.cpp \
+    graphics.cpp \
+    graphics_adf.cpp \
+    graphics_drm.cpp \
+    graphics_fbdev.cpp \
+    resources.cpp \
 
 LOCAL_WHOLE_STATIC_LIBRARIES += libadf
+LOCAL_WHOLE_STATIC_LIBRARIES += libdrm
+LOCAL_STATIC_LIBRARIES += libpng
 
 LOCAL_MODULE := libminui
 
+LOCAL_CLANG := true
+
 # This used to compare against values in double-quotes (which are just
 # ordinary characters in this context).  Strip double-quotes from the
 # value so that either will work.
 
+ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),ABGR_8888)
+  LOCAL_CFLAGS += -DRECOVERY_ABGR
+endif
 ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888)
   LOCAL_CFLAGS += -DRECOVERY_RGBX
 endif
@@ -30,3 +38,10 @@
 endif
 
 include $(BUILD_STATIC_LIBRARY)
+
+# Used by OEMs for factory test images.
+include $(CLEAR_VARS)
+LOCAL_MODULE := libminui
+LOCAL_WHOLE_STATIC_LIBRARIES += libminui
+LOCAL_SHARED_LIBRARIES := libpng
+include $(BUILD_SHARED_LIBRARY)
diff --git a/minui/events.c b/minui/events.c
deleted file mode 100644
index df7dad4..0000000
--- a/minui/events.c
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <sys/epoll.h>
-
-#include <linux/input.h>
-
-#include "minui.h"
-
-#define MAX_DEVICES 16
-#define MAX_MISC_FDS 16
-
-#define BITS_PER_LONG (sizeof(unsigned long) * 8)
-#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG)
-
-#define test_bit(bit, array) \
-    ((array)[(bit)/BITS_PER_LONG] & (1 << ((bit) % BITS_PER_LONG)))
-
-struct fd_info {
-    int fd;
-    ev_callback cb;
-    void *data;
-};
-
-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;
-static unsigned ev_dev_count = 0;
-static unsigned ev_misc_count = 0;
-
-int ev_init(ev_callback input_cb, void *data)
-{
-    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) {
-        while((de = readdir(dir))) {
-            unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];
-
-//            fprintf(stderr,"/dev/input/%s\n", de->d_name);
-            if(strncmp(de->d_name,"event",5)) continue;
-            fd = openat(dirfd(dir), de->d_name, O_RDONLY);
-            if(fd < 0) continue;
-
-            /* read the evbits of the input device */
-            if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) < 0) {
-                close(fd);
-                continue;
-            }
-
-            /* TODO: add ability to specify event masks. For now, just assume
-             * that only EV_KEY and EV_REL event types are ever needed. */
-            if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits)) {
-                close(fd);
-                continue;
-            }
-
-            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++;
-            ev_dev_count++;
-            if(ev_dev_count == MAX_DEVICES) break;
-        }
-    }
-
-    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.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_fdinfo[--ev_count].fd);
-    }
-    ev_misc_count = 0;
-    ev_dev_count = 0;
-    close(epollfd);
-}
-
-int ev_wait(int timeout)
-{
-    npolledevents = epoll_wait(epollfd, polledevents, ev_count, timeout);
-    if (npolledevents <= 0)
-        return -1;
-    return 0;
-}
-
-void ev_dispatch(void)
-{
-    int n;
-    int ret;
-
-    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, uint32_t epevents, struct input_event *ev)
-{
-    int r;
-
-    if (epevents & EPOLLIN) {
-        r = read(fd, ev, sizeof(*ev));
-        if (r == sizeof(*ev))
-            return 0;
-    }
-    return -1;
-}
-
-int ev_sync_key_state(ev_set_key_callback set_key_cb, void *data)
-{
-    unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];
-    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];
-    unsigned i;
-    int ret;
-
-    for (i = 0; i < ev_dev_count; i++) {
-        int code;
-
-        memset(key_bits, 0, sizeof(key_bits));
-        memset(ev_bits, 0, sizeof(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_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits);
-        if (ret < 0)
-            continue;
-
-        for (code = 0; code <= KEY_MAX; code++) {
-            if (test_bit(code, key_bits))
-                set_key_cb(code, 1, data);
-        }
-    }
-
-    return 0;
-}
diff --git a/minui/events.cpp b/minui/events.cpp
new file mode 100644
index 0000000..3b2262a
--- /dev/null
+++ b/minui/events.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <unistd.h>
+
+#include <linux/input.h>
+
+#include "minui.h"
+
+#define MAX_DEVICES 16
+#define MAX_MISC_FDS 16
+
+#define BITS_PER_LONG (sizeof(unsigned long) * 8)
+#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG)
+
+struct fd_info {
+    int fd;
+    ev_callback cb;
+    void* data;
+};
+
+static int g_epoll_fd;
+static epoll_event polledevents[MAX_DEVICES + MAX_MISC_FDS];
+static int npolledevents;
+
+static fd_info ev_fdinfo[MAX_DEVICES + MAX_MISC_FDS];
+
+static unsigned ev_count = 0;
+static unsigned ev_dev_count = 0;
+static unsigned ev_misc_count = 0;
+
+static bool test_bit(size_t bit, unsigned long* array) {
+    return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
+}
+
+int ev_init(ev_callback input_cb, void* data) {
+    bool epollctlfail = false;
+
+    g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
+    if (g_epoll_fd == -1) {
+        return -1;
+    }
+
+    DIR* dir = opendir("/dev/input");
+    if (dir != NULL) {
+        dirent* de;
+        while ((de = readdir(dir))) {
+            unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];
+
+//            fprintf(stderr,"/dev/input/%s\n", de->d_name);
+            if (strncmp(de->d_name, "event", 5)) continue;
+            int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
+            if (fd == -1) continue;
+
+            // Read the evbits of the input device.
+            if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+                close(fd);
+                continue;
+            }
+
+            // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed.
+            if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
+                close(fd);
+                continue;
+            }
+
+            epoll_event ev;
+            ev.events = EPOLLIN | EPOLLWAKEUP;
+            ev.data.ptr = &ev_fdinfo[ev_count];
+            if (epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
+                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++;
+            ev_dev_count++;
+            if (ev_dev_count == MAX_DEVICES) break;
+        }
+
+        closedir(dir);
+    }
+
+    if (epollctlfail && !ev_count) {
+        close(g_epoll_fd);
+        g_epoll_fd = -1;
+        return -1;
+    }
+
+    return 0;
+}
+
+int ev_get_epollfd(void) {
+    return g_epoll_fd;
+}
+
+int ev_add_fd(int fd, ev_callback cb, void* data) {
+    if (ev_misc_count == MAX_MISC_FDS || cb == NULL) {
+        return -1;
+    }
+
+    epoll_event ev;
+    ev.events = EPOLLIN | EPOLLWAKEUP;
+    ev.data.ptr = (void *)&ev_fdinfo[ev_count];
+    int ret = epoll_ctl(g_epoll_fd, 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;
+}
+
+void ev_exit(void) {
+    while (ev_count > 0) {
+        close(ev_fdinfo[--ev_count].fd);
+    }
+    ev_misc_count = 0;
+    ev_dev_count = 0;
+    close(g_epoll_fd);
+}
+
+int ev_wait(int timeout) {
+    npolledevents = epoll_wait(g_epoll_fd, polledevents, ev_count, timeout);
+    if (npolledevents <= 0) {
+        return -1;
+    }
+    return 0;
+}
+
+void ev_dispatch(void) {
+    for (int n = 0; n < npolledevents; n++) {
+        fd_info* fdi = reinterpret_cast<fd_info*>(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, uint32_t epevents, input_event* ev) {
+    if (epevents & EPOLLIN) {
+        ssize_t r = TEMP_FAILURE_RETRY(read(fd, ev, sizeof(*ev)));
+        if (r == sizeof(*ev)) {
+            return 0;
+        }
+    }
+    return -1;
+}
+
+int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data) {
+    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];
+    unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];
+
+    for (size_t i = 0; i < ev_dev_count; ++i) {
+        memset(ev_bits, 0, sizeof(ev_bits));
+        memset(key_bits, 0, sizeof(key_bits));
+
+        if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+            continue;
+        }
+        if (!test_bit(EV_KEY, ev_bits)) {
+            continue;
+        }
+        if (ioctl(ev_fdinfo[i].fd, EVIOCGKEY(sizeof(key_bits)), key_bits) == -1) {
+            continue;
+        }
+
+        for (int code = 0; code <= KEY_MAX; code++) {
+            if (test_bit(code, key_bits)) {
+                set_key_cb(code, 1, data);
+            }
+        }
+    }
+
+    return 0;
+}
+
+void ev_iterate_available_keys(std::function<void(int)> f) {
+    unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];
+    unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)];
+
+    for (size_t i = 0; i < ev_dev_count; ++i) {
+        memset(ev_bits, 0, sizeof(ev_bits));
+        memset(key_bits, 0, sizeof(key_bits));
+
+        // Does this device even have keys?
+        if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+            continue;
+        }
+        if (!test_bit(EV_KEY, ev_bits)) {
+            continue;
+        }
+
+        int rc = ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_KEY, KEY_MAX), key_bits);
+        if (rc == -1) {
+            continue;
+        }
+
+        for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
+            if (test_bit(key_code, key_bits)) {
+                f(key_code);
+            }
+        }
+    }
+}
diff --git a/minui/graphics.c b/minui/graphics.cpp
similarity index 84%
rename from minui/graphics.c
rename to minui/graphics.cpp
index 6049d85..c0eea9e 100644
--- a/minui/graphics.c
+++ b/minui/graphics.cpp
@@ -16,6 +16,7 @@
 
 #include <stdbool.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 
 #include <fcntl.h>
@@ -34,11 +35,11 @@
 #include "minui.h"
 #include "graphics.h"
 
-typedef struct {
+struct GRFont {
     GRSurface* texture;
     int cwidth;
     int cheight;
-} GRFont;
+};
 
 static GRFont* gr_font = NULL;
 static minui_backend* gr_backend = NULL;
@@ -47,8 +48,6 @@
 static int overscan_offset_x = 0;
 static int overscan_offset_y = 0;
 
-static int gr_vt_fd = -1;
-
 static unsigned char gr_current_r = 255;
 static unsigned char gr_current_g = 255;
 static unsigned char gr_current_b = 255;
@@ -76,11 +75,10 @@
                        unsigned char* dst_p, int dst_row_bytes,
                        int width, int height)
 {
-    int i, j;
-    for (j = 0; j < height; ++j) {
+    for (int j = 0; j < height; ++j) {
         unsigned char* sx = src_p;
         unsigned char* px = dst_p;
-        for (i = 0; i < width; ++i) {
+        for (int i = 0; i < width; ++i) {
             unsigned char a = *sx++;
             if (gr_current_a < 255) a = ((int)a * gr_current_a) / 255;
             if (a == 255) {
@@ -105,34 +103,33 @@
     }
 }
 
-
-void gr_text(int x, int y, const char *s, int bold)
+void gr_text(int x, int y, const char *s, bool bold)
 {
-    GRFont *font = gr_font;
-    unsigned off;
+    GRFont* font = gr_font;
 
-    if (!font->texture) return;
-    if (gr_current_a == 0) return;
+    if (!font->texture || gr_current_a == 0) return;
 
     bold = bold && (font->texture->height != font->cheight);
 
     x += overscan_offset_x;
     y += overscan_offset_y;
 
-    while((off = *s++)) {
-        off -= 32;
+    unsigned char ch;
+    while ((ch = *s++)) {
         if (outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) break;
-        if (off < 96) {
 
-            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);
-
+        if (ch < ' ' || ch > '~') {
+            ch = '?';
         }
+
+        unsigned char* src_p = font->texture->data + ((ch - ' ') * 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;
     }
 }
@@ -160,22 +157,27 @@
 
 void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
 {
+#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
+    gr_current_r = b;
+    gr_current_g = g;
+    gr_current_b = r;
+    gr_current_a = a;
+#else
     gr_current_r = r;
     gr_current_g = g;
     gr_current_b = b;
     gr_current_a = a;
+#endif
 }
 
 void gr_clear()
 {
-    if (gr_current_r == gr_current_g &&
-        gr_current_r == gr_current_b) {
+    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) {
+        for (int y = 0; y < gr_draw->height; ++y) {
+            for (int x = 0; x < gr_draw->width; ++x) {
                 *px++ = gr_current_r;
                 *px++ = gr_current_g;
                 *px++ = gr_current_b;
@@ -267,7 +269,7 @@
 
 static void gr_init_font(void)
 {
-    gr_font = calloc(sizeof(*gr_font), 1);
+    gr_font = reinterpret_cast<GRFont*>(calloc(sizeof(*gr_font), 1));
 
     int res = res_create_alpha_surface("font", &(gr_font->texture));
     if (res == 0) {
@@ -280,14 +282,14 @@
         printf("failed to read font: res=%d\n", res);
 
         // fall back to the compiled-in font.
-        gr_font->texture = malloc(sizeof(*gr_font->texture));
+        gr_font->texture = reinterpret_cast<GRSurface*>(malloc(sizeof(*gr_font->texture)));
         gr_font->texture->width = font.width;
         gr_font->texture->height = font.height;
         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;
+        unsigned char* bits = reinterpret_cast<unsigned char*>(malloc(font.width * font.height));
+        gr_font->texture->data = reinterpret_cast<unsigned char*>(bits);
 
         unsigned char data;
         unsigned char* in = font.rundata;
@@ -324,7 +326,7 @@
         gr_clear();
 
         gr_color(255, 0, 0, 255);
-        gr_surface frame = images[x%frames];
+        GRSurface* frame = images[x%frames];
         gr_blit(frame, 0, 0, frame->width, frame->height, x, 0);
 
         gr_color(255, 0, 0, 128);
@@ -358,17 +360,6 @@
 {
     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.
-        perror("can't open /dev/tty0");
-    } else if (ioctl(gr_vt_fd, KDSETMODE, (void*) KD_GRAPHICS)) {
-        // However, if we do open tty0, we expect the ioctl to work.
-        perror("failed KDSETMODE to KD_GRAPHICS on tty0");
-        gr_exit();
-        return -1;
-    }
-
     gr_backend = open_adf();
     if (gr_backend) {
         gr_draw = gr_backend->init(gr_backend);
@@ -378,6 +369,11 @@
     }
 
     if (!gr_draw) {
+        gr_backend = open_drm();
+        gr_draw = gr_backend->init(gr_backend);
+    }
+
+    if (!gr_draw) {
         gr_backend = open_fbdev();
         gr_draw = gr_backend->init(gr_backend);
         if (gr_draw == NULL) {
@@ -397,10 +393,6 @@
 void gr_exit(void)
 {
     gr_backend->exit(gr_backend);
-
-    ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT);
-    close(gr_vt_fd);
-    gr_vt_fd = -1;
 }
 
 int gr_fb_width(void)
diff --git a/minui/graphics.h b/minui/graphics.h
index 993e986..52968eb 100644
--- a/minui/graphics.h
+++ b/minui/graphics.h
@@ -17,34 +17,27 @@
 #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*);
+// TODO: lose the function pointers.
+struct minui_backend {
+    // Initializes the backend and returns a GRSurface* to draw into.
+    GRSurface* (*init)(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*);
+    GRSurface* (*flip)(minui_backend*);
 
     // Blank (or unblank) the screen.
-    void (*blank)(struct minui_backend*, bool);
+    void (*blank)(minui_backend*, bool);
 
     // Device cleanup when drawing is done.
-    void (*exit)(struct minui_backend*);
-} minui_backend;
+    void (*exit)(minui_backend*);
+};
 
 minui_backend* open_fbdev();
 minui_backend* open_adf();
-
-#ifdef __cplusplus
-}
-#endif
+minui_backend* open_drm();
 
 #endif
diff --git a/minui/graphics_adf.c b/minui/graphics_adf.cpp
similarity index 78%
rename from minui/graphics_adf.c
rename to minui/graphics_adf.cpp
index ac6d64e..5d0867f 100644
--- a/minui/graphics_adf.c
+++ b/minui/graphics_adf.cpp
@@ -19,6 +19,7 @@
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 
 #include <sys/cdefs.h>
@@ -43,15 +44,13 @@
 
     unsigned int current_surface;
     unsigned int n_surfaces;
-    struct adf_surface_pdata surfaces[2];
+    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 GRSurface* adf_flip(minui_backend *backend);
+static void adf_blank(minui_backend *backend, bool blank);
 
-static int adf_surface_init(struct adf_pdata *pdata,
-        struct drm_mode_modeinfo *mode, struct adf_surface_pdata *surf)
-{
+static int adf_surface_init(adf_pdata *pdata, drm_mode_modeinfo *mode, adf_surface_pdata *surf) {
     memset(surf, 0, sizeof(*surf));
 
     surf->fd = adf_interface_simple_buffer_alloc(pdata->intf_fd, mode->hdisplay,
@@ -64,8 +63,9 @@
     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);
+    surf->base.data = reinterpret_cast<uint8_t*>(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;
@@ -74,9 +74,9 @@
     return 0;
 }
 
-static int adf_interface_init(struct adf_pdata *pdata)
+static int adf_interface_init(adf_pdata *pdata)
 {
-    struct adf_interface_data intf_data;
+    adf_interface_data intf_data;
     int ret = 0;
     int err;
 
@@ -106,7 +106,7 @@
     return ret;
 }
 
-static int adf_device_init(struct adf_pdata *pdata, struct adf_device *dev)
+static int adf_device_init(adf_pdata *pdata, adf_device *dev)
 {
     adf_id_t intf_id;
     int intf_fd;
@@ -134,14 +134,16 @@
     return err;
 }
 
-static gr_surface adf_init(minui_backend *backend)
+static GRSurface* adf_init(minui_backend *backend)
 {
-    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    adf_pdata *pdata = (adf_pdata *)backend;
     adf_id_t *dev_ids = NULL;
     ssize_t n_dev_ids, i;
-    gr_surface ret;
+    GRSurface* ret;
 
-#if defined(RECOVERY_BGRA)
+#if defined(RECOVERY_ABGR)
+    pdata->format = DRM_FORMAT_ABGR8888;
+#elif defined(RECOVERY_BGRA)
     pdata->format = DRM_FORMAT_BGRA8888;
 #elif defined(RECOVERY_RGBX)
     pdata->format = DRM_FORMAT_RGBX8888;
@@ -161,7 +163,7 @@
     pdata->intf_fd = -1;
 
     for (i = 0; i < n_dev_ids && pdata->intf_fd < 0; i++) {
-        struct adf_device dev;
+        adf_device dev;
 
         int err = adf_device_open(dev_ids[i], O_RDWR, &dev);
         if (err < 0) {
@@ -191,10 +193,10 @@
     return ret;
 }
 
-static gr_surface adf_flip(struct minui_backend *backend)
+static GRSurface* adf_flip(minui_backend *backend)
 {
-    struct adf_pdata *pdata = (struct adf_pdata *)backend;
-    struct adf_surface_pdata *surf = &pdata->surfaces[pdata->current_surface];
+    adf_pdata *pdata = (adf_pdata *)backend;
+    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,
@@ -206,22 +208,22 @@
     return &pdata->surfaces[pdata->current_surface].base;
 }
 
-static void adf_blank(struct minui_backend *backend, bool blank)
+static void adf_blank(minui_backend *backend, bool blank)
 {
-    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    adf_pdata *pdata = (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)
+static void adf_surface_destroy(adf_surface_pdata *surf)
 {
     munmap(surf->base.data, surf->pitch * surf->base.height);
     close(surf->fd);
 }
 
-static void adf_exit(struct minui_backend *backend)
+static void adf_exit(minui_backend *backend)
 {
-    struct adf_pdata *pdata = (struct adf_pdata *)backend;
+    adf_pdata *pdata = (adf_pdata *)backend;
     unsigned int i;
 
     for (i = 0; i < pdata->n_surfaces; i++)
@@ -233,7 +235,7 @@
 
 minui_backend *open_adf()
 {
-    struct adf_pdata *pdata = calloc(1, sizeof(*pdata));
+    adf_pdata* pdata = reinterpret_cast<adf_pdata*>(calloc(1, sizeof(*pdata)));
     if (!pdata) {
         perror("allocating adf backend failed");
         return NULL;
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
new file mode 100644
index 0000000..03e33b7
--- /dev/null
+++ b/minui/graphics_drm.cpp
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2015 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 <drm_fourcc.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include "minui.h"
+#include "graphics.h"
+
+#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
+
+struct drm_surface {
+    GRSurface base;
+    uint32_t fb_id;
+    uint32_t handle;
+};
+
+static drm_surface *drm_surfaces[2];
+static int current_buffer;
+
+static drmModeCrtc *main_monitor_crtc;
+static drmModeConnector *main_monitor_connector;
+
+static int drm_fd = -1;
+
+static void drm_disable_crtc(int drm_fd, drmModeCrtc *crtc) {
+    if (crtc) {
+        drmModeSetCrtc(drm_fd, crtc->crtc_id,
+                       0, // fb_id
+                       0, 0,  // x,y
+                       NULL,  // connectors
+                       0,     // connector_count
+                       NULL); // mode
+    }
+}
+
+static void drm_enable_crtc(int drm_fd, drmModeCrtc *crtc,
+                            struct drm_surface *surface) {
+    int32_t ret;
+
+    ret = drmModeSetCrtc(drm_fd, crtc->crtc_id,
+                         surface->fb_id,
+                         0, 0,  // x,y
+                         &main_monitor_connector->connector_id,
+                         1,  // connector_count
+                         &main_monitor_crtc->mode);
+
+    if (ret)
+        printf("drmModeSetCrtc failed ret=%d\n", ret);
+}
+
+static void drm_blank(minui_backend* backend __unused, bool blank) {
+    if (blank)
+        drm_disable_crtc(drm_fd, main_monitor_crtc);
+    else
+        drm_enable_crtc(drm_fd, main_monitor_crtc,
+                        drm_surfaces[current_buffer]);
+}
+
+static void drm_destroy_surface(struct drm_surface *surface) {
+    struct drm_gem_close gem_close;
+    int ret;
+
+    if(!surface)
+        return;
+
+    if (surface->base.data)
+        munmap(surface->base.data,
+               surface->base.row_bytes * surface->base.height);
+
+    if (surface->fb_id) {
+        ret = drmModeRmFB(drm_fd, surface->fb_id);
+        if (ret)
+            printf("drmModeRmFB failed ret=%d\n", ret);
+    }
+
+    if (surface->handle) {
+        memset(&gem_close, 0, sizeof(gem_close));
+        gem_close.handle = surface->handle;
+
+        ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
+        if (ret)
+            printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret);
+    }
+
+    free(surface);
+}
+
+static int drm_format_to_bpp(uint32_t format) {
+    switch(format) {
+        case DRM_FORMAT_ABGR8888:
+        case DRM_FORMAT_BGRA8888:
+        case DRM_FORMAT_RGBX8888:
+        case DRM_FORMAT_BGRX8888:
+        case DRM_FORMAT_XBGR8888:
+        case DRM_FORMAT_XRGB8888:
+            return 32;
+        case DRM_FORMAT_RGB565:
+            return 16;
+        default:
+            printf("Unknown format %d\n", format);
+            return 32;
+    }
+}
+
+static drm_surface *drm_create_surface(int width, int height) {
+    struct drm_surface *surface;
+    struct drm_mode_create_dumb create_dumb;
+    uint32_t format;
+    int ret;
+
+    surface = (struct drm_surface*)calloc(1, sizeof(*surface));
+    if (!surface) {
+        printf("Can't allocate memory\n");
+        return NULL;
+    }
+
+#if defined(RECOVERY_ABGR)
+    format = DRM_FORMAT_RGBA8888;
+#elif defined(RECOVERY_BGRA)
+    format = DRM_FORMAT_ARGB8888;
+#elif defined(RECOVERY_RGBX)
+    format = DRM_FORMAT_XBGR8888;
+#else
+    format = DRM_FORMAT_RGB565;
+#endif
+
+    memset(&create_dumb, 0, sizeof(create_dumb));
+    create_dumb.height = height;
+    create_dumb.width = width;
+    create_dumb.bpp = drm_format_to_bpp(format);
+    create_dumb.flags = 0;
+
+    ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
+    if (ret) {
+        printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n",ret);
+        drm_destroy_surface(surface);
+        return NULL;
+    }
+    surface->handle = create_dumb.handle;
+
+    uint32_t handles[4], pitches[4], offsets[4];
+
+    handles[0] = surface->handle;
+    pitches[0] = create_dumb.pitch;
+    offsets[0] = 0;
+
+    ret = drmModeAddFB2(drm_fd, width, height,
+            format, handles, pitches, offsets,
+            &(surface->fb_id), 0);
+    if (ret) {
+        printf("drmModeAddFB2 failed ret=%d\n", ret);
+        drm_destroy_surface(surface);
+        return NULL;
+    }
+
+    struct drm_mode_map_dumb map_dumb;
+    memset(&map_dumb, 0, sizeof(map_dumb));
+    map_dumb.handle = create_dumb.handle;
+    ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
+    if (ret) {
+        printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n",ret);
+        drm_destroy_surface(surface);
+        return NULL;;
+    }
+
+    surface->base.height = height;
+    surface->base.width = width;
+    surface->base.row_bytes = create_dumb.pitch;
+    surface->base.pixel_bytes = create_dumb.bpp / 8;
+    surface->base.data = (unsigned char*)
+                         mmap(NULL,
+                              surface->base.height * surface->base.row_bytes,
+                              PROT_READ | PROT_WRITE, MAP_SHARED,
+                              drm_fd, map_dumb.offset);
+    if (surface->base.data == MAP_FAILED) {
+        perror("mmap() failed");
+        drm_destroy_surface(surface);
+        return NULL;
+    }
+
+    return surface;
+}
+
+static drmModeCrtc *find_crtc_for_connector(int fd,
+                            drmModeRes *resources,
+                            drmModeConnector *connector) {
+    int i, j;
+    drmModeEncoder *encoder;
+    int32_t crtc;
+
+    /*
+     * Find the encoder. If we already have one, just use it.
+     */
+    if (connector->encoder_id)
+        encoder = drmModeGetEncoder(fd, connector->encoder_id);
+    else
+        encoder = NULL;
+
+    if (encoder && encoder->crtc_id) {
+        crtc = encoder->crtc_id;
+        drmModeFreeEncoder(encoder);
+        return drmModeGetCrtc(fd, crtc);
+    }
+
+    /*
+     * Didn't find anything, try to find a crtc and encoder combo.
+     */
+    crtc = -1;
+    for (i = 0; i < connector->count_encoders; i++) {
+        encoder = drmModeGetEncoder(fd, connector->encoders[i]);
+
+        if (encoder) {
+            for (j = 0; j < resources->count_crtcs; j++) {
+                if (!(encoder->possible_crtcs & (1 << j)))
+                    continue;
+                crtc = resources->crtcs[j];
+                break;
+            }
+            if (crtc >= 0) {
+                drmModeFreeEncoder(encoder);
+                return drmModeGetCrtc(fd, crtc);
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static drmModeConnector *find_used_connector_by_type(int fd,
+                                 drmModeRes *resources,
+                                 unsigned type) {
+    int i;
+    for (i = 0; i < resources->count_connectors; i++) {
+        drmModeConnector *connector;
+
+        connector = drmModeGetConnector(fd, resources->connectors[i]);
+        if (connector) {
+            if ((connector->connector_type == type) &&
+                    (connector->connection == DRM_MODE_CONNECTED) &&
+                    (connector->count_modes > 0))
+                return connector;
+
+            drmModeFreeConnector(connector);
+        }
+    }
+    return NULL;
+}
+
+static drmModeConnector *find_first_connected_connector(int fd,
+                             drmModeRes *resources) {
+    int i;
+    for (i = 0; i < resources->count_connectors; i++) {
+        drmModeConnector *connector;
+
+        connector = drmModeGetConnector(fd, resources->connectors[i]);
+        if (connector) {
+            if ((connector->count_modes > 0) &&
+                    (connector->connection == DRM_MODE_CONNECTED))
+                return connector;
+
+            drmModeFreeConnector(connector);
+        }
+    }
+    return NULL;
+}
+
+static drmModeConnector *find_main_monitor(int fd, drmModeRes *resources,
+        uint32_t *mode_index) {
+    unsigned i = 0;
+    int modes;
+    /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */
+    unsigned kConnectorPriority[] = {
+        DRM_MODE_CONNECTOR_LVDS,
+        DRM_MODE_CONNECTOR_eDP,
+        DRM_MODE_CONNECTOR_DSI,
+    };
+
+    drmModeConnector *main_monitor_connector = NULL;
+    do {
+        main_monitor_connector = find_used_connector_by_type(fd,
+                                         resources,
+                                         kConnectorPriority[i]);
+        i++;
+    } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
+
+    /* If we didn't find a connector, grab the first one that is connected. */
+    if (!main_monitor_connector)
+        main_monitor_connector =
+                find_first_connected_connector(fd, resources);
+
+    /* If we still didn't find a connector, give up and return. */
+    if (!main_monitor_connector)
+        return NULL;
+
+    *mode_index = 0;
+    for (modes = 0; modes < main_monitor_connector->count_modes; modes++) {
+        if (main_monitor_connector->modes[modes].type &
+                DRM_MODE_TYPE_PREFERRED) {
+            *mode_index = modes;
+            break;
+        }
+    }
+
+    return main_monitor_connector;
+}
+
+static void disable_non_main_crtcs(int fd,
+                    drmModeRes *resources,
+                    drmModeCrtc* main_crtc) {
+    int i;
+    drmModeCrtc* crtc;
+
+    for (i = 0; i < resources->count_connectors; i++) {
+        drmModeConnector *connector;
+
+        connector = drmModeGetConnector(fd, resources->connectors[i]);
+        crtc = find_crtc_for_connector(fd, resources, connector);
+        if (crtc->crtc_id != main_crtc->crtc_id)
+            drm_disable_crtc(fd, crtc);
+        drmModeFreeCrtc(crtc);
+    }
+}
+
+static GRSurface* drm_init(minui_backend* backend __unused) {
+    drmModeRes *res = NULL;
+    uint32_t selected_mode;
+    char *dev_name;
+    int width, height;
+    int ret, i;
+
+    /* Consider DRM devices in order. */
+    for (i = 0; i < DRM_MAX_MINOR; i++) {
+        uint64_t cap = 0;
+
+        ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
+        if (ret < 0)
+            continue;
+
+        drm_fd = open(dev_name, O_RDWR, 0);
+        free(dev_name);
+        if (drm_fd < 0)
+            continue;
+
+        /* We need dumb buffers. */
+        ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
+        if (ret || cap == 0) {
+            close(drm_fd);
+            continue;
+        }
+
+        res = drmModeGetResources(drm_fd);
+        if (!res) {
+            close(drm_fd);
+            continue;
+        }
+
+        /* Use this device if it has at least one connected monitor. */
+        if (res->count_crtcs > 0 && res->count_connectors > 0)
+            if (find_first_connected_connector(drm_fd, res))
+                break;
+
+        drmModeFreeResources(res);
+        close(drm_fd);
+        res = NULL;
+    }
+
+    if (drm_fd < 0 || res == NULL) {
+        perror("cannot find/open a drm device");
+        return NULL;
+    }
+
+    main_monitor_connector = find_main_monitor(drm_fd,
+            res, &selected_mode);
+
+    if (!main_monitor_connector) {
+        printf("main_monitor_connector not found\n");
+        drmModeFreeResources(res);
+        close(drm_fd);
+        return NULL;
+    }
+
+    main_monitor_crtc = find_crtc_for_connector(drm_fd, res,
+                                                main_monitor_connector);
+
+    if (!main_monitor_crtc) {
+        printf("main_monitor_crtc not found\n");
+        drmModeFreeResources(res);
+        close(drm_fd);
+        return NULL;
+    }
+
+    disable_non_main_crtcs(drm_fd,
+                           res, main_monitor_crtc);
+
+    main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode];
+
+    width = main_monitor_crtc->mode.hdisplay;
+    height = main_monitor_crtc->mode.vdisplay;
+
+    drmModeFreeResources(res);
+
+    drm_surfaces[0] = drm_create_surface(width, height);
+    drm_surfaces[1] = drm_create_surface(width, height);
+    if (!drm_surfaces[0] || !drm_surfaces[1]) {
+        drm_destroy_surface(drm_surfaces[0]);
+        drm_destroy_surface(drm_surfaces[1]);
+        drmModeFreeResources(res);
+        close(drm_fd);
+        return NULL;
+    }
+
+    current_buffer = 0;
+
+    drm_enable_crtc(drm_fd, main_monitor_crtc, drm_surfaces[1]);
+
+    return &(drm_surfaces[0]->base);
+}
+
+static GRSurface* drm_flip(minui_backend* backend __unused) {
+    int ret;
+
+    ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
+                          drm_surfaces[current_buffer]->fb_id, 0, NULL);
+    if (ret < 0) {
+        printf("drmModePageFlip failed ret=%d\n", ret);
+        return NULL;
+    }
+    current_buffer = 1 - current_buffer;
+    return &(drm_surfaces[current_buffer]->base);
+}
+
+static void drm_exit(minui_backend* backend __unused) {
+    drm_disable_crtc(drm_fd, main_monitor_crtc);
+    drm_destroy_surface(drm_surfaces[0]);
+    drm_destroy_surface(drm_surfaces[1]);
+    drmModeFreeCrtc(main_monitor_crtc);
+    drmModeFreeConnector(main_monitor_connector);
+    close(drm_fd);
+    drm_fd = -1;
+}
+
+static minui_backend drm_backend = {
+    .init = drm_init,
+    .flip = drm_flip,
+    .blank = drm_blank,
+    .exit = drm_exit,
+};
+
+minui_backend* open_drm() {
+    return &drm_backend;
+}
diff --git a/minui/graphics_fbdev.c b/minui/graphics_fbdev.cpp
similarity index 85%
rename from minui/graphics_fbdev.c
rename to minui/graphics_fbdev.cpp
index 6df2726..997e9ca 100644
--- a/minui/graphics_fbdev.c
+++ b/minui/graphics_fbdev.cpp
@@ -16,6 +16,7 @@
 
 #include <stdbool.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 
 #include <fcntl.h>
@@ -32,8 +33,8 @@
 #include "minui.h"
 #include "graphics.h"
 
-static gr_surface fbdev_init(minui_backend*);
-static gr_surface fbdev_flip(minui_backend*);
+static GRSurface* fbdev_init(minui_backend*);
+static GRSurface* fbdev_flip(minui_backend*);
 static void fbdev_blank(minui_backend*, bool);
 static void fbdev_exit(minui_backend*);
 
@@ -42,7 +43,7 @@
 static GRSurface* gr_draw = NULL;
 static int displayed_buffer;
 
-static struct fb_var_screeninfo vi;
+static fb_var_screeninfo vi;
 static int fb_fd = -1;
 
 static minui_backend my_backend = {
@@ -78,18 +79,14 @@
     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) {
+static GRSurface* fbdev_init(minui_backend* backend) {
+    int fd = open("/dev/graphics/fb0", O_RDWR);
+    if (fd == -1) {
         perror("cannot open fb0");
         return NULL;
     }
 
+    fb_fix_screeninfo fi;
     if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
         perror("failed to get fb0 info");
         close(fd);
@@ -123,7 +120,7 @@
            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);
+    void* 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);
@@ -136,7 +133,7 @@
     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;
+    gr_framebuffer[0].data = reinterpret_cast<uint8_t*>(bits);
     memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
 
     /* check if we can use double buffering */
@@ -177,7 +174,7 @@
     return gr_draw;
 }
 
-static gr_surface fbdev_flip(minui_backend* backend __unused) {
+static GRSurface* fbdev_flip(minui_backend* backend __unused) {
     if (double_buffered) {
 #if defined(RECOVERY_BGRA)
         // In case of BGRA, do some byte swapping
@@ -198,21 +195,8 @@
         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;
 }
diff --git a/minui/minui.h b/minui/minui.h
index 733b675..bdde083 100644
--- a/minui/minui.h
+++ b/minui/minui.h
@@ -19,67 +19,70 @@
 
 #include <sys/types.h>
 
-#include <stdbool.h>
+#include <functional>
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+//
+// Graphics.
+//
 
-typedef struct {
+struct GRSurface {
     int width;
     int height;
     int row_bytes;
     int pixel_bytes;
     unsigned char* data;
-} GRSurface;
+};
 
-typedef GRSurface* gr_surface;
+int gr_init();
+void gr_exit();
 
-int gr_init(void);
-void gr_exit(void);
+int gr_fb_width();
+int gr_fb_height();
 
-int gr_fb_width(void);
-int gr_fb_height(void);
-
-void gr_flip(void);
+void gr_flip();
 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);
-void 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, bool bold);
+void gr_texticon(int x, int y, GRSurface* icon);
 int gr_measure(const char *s);
 void gr_font_size(int *x, int *y);
 
-void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy);
-unsigned int gr_get_width(gr_surface surface);
-unsigned int gr_get_height(gr_surface surface);
+void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy);
+unsigned int gr_get_width(GRSurface* surface);
+unsigned int gr_get_height(GRSurface* surface);
 
-// input event structure, include <linux/input.h> for the definition.
-// see http://www.mjmwired.net/kernel/Documentation/input/ for info.
+//
+// Input events.
+//
+
 struct input_event;
 
-typedef int (*ev_callback)(int fd, uint32_t epevents, void *data);
-typedef int (*ev_set_key_callback)(int code, int value, void *data);
+// TODO: move these over to std::function.
+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);
-void ev_exit(void);
-int ev_add_fd(int fd, ev_callback cb, void *data);
-int ev_sync_key_state(ev_set_key_callback set_key_cb, void *data);
+int ev_init(ev_callback input_cb, void* data);
+void ev_exit();
+int ev_add_fd(int fd, ev_callback cb, void* data);
+void ev_iterate_available_keys(std::function<void(int)> f);
+int ev_sync_key_state(ev_set_key_callback set_key_cb, void* data);
 
-/* timeout has the same semantics as for poll
- *    0 : don't block
- *  < 0 : block forever
- *  > 0 : block for 'timeout' milliseconds
- */
+// 'timeout' has the same semantics as poll(2).
+//    0 : don't block
+//  < 0 : block forever
+//  > 0 : block for 'timeout' milliseconds
 int ev_wait(int timeout);
 
-int ev_get_input(int fd, uint32_t epevents, struct input_event *ev);
-void ev_dispatch(void);
-int ev_get_epollfd(void);
+int ev_get_input(int fd, uint32_t epevents, input_event* ev);
+void ev_dispatch();
+int ev_get_epollfd();
 
+//
 // Resources
+//
 
 // res_create_*_surface() functions return 0 if no error, else
 // negative.
@@ -92,17 +95,17 @@
 // 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);
+int res_create_display_surface(const char* name, GRSurface** 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);
+                                     int* frames, GRSurface*** pSurface);
 
 // Load a single alpha surface from a grayscale PNG image.
-int res_create_alpha_surface(const char* name, gr_surface* pSurface);
+int res_create_alpha_surface(const char* name, GRSurface** 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
@@ -111,14 +114,10 @@
 // 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);
+                                       GRSurface** pSurface);
 
 // Free a surface allocated by any of the res_create_*_surface()
 // functions.
-void res_free_surface(gr_surface surface);
-
-#ifdef __cplusplus
-}
-#endif
+void res_free_surface(GRSurface* surface);
 
 #endif
diff --git a/minui/resources.c b/minui/resources.cpp
similarity index 86%
rename from minui/resources.c
rename to minui/resources.cpp
index 2bae4de..5e47892 100644
--- a/minui/resources.c
+++ b/minui/resources.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 
 #include <fcntl.h>
@@ -35,10 +36,11 @@
 
 #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);
+static GRSurface* malloc_surface(size_t data_size) {
+    size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT;
+    unsigned char* temp = reinterpret_cast<unsigned char*>(malloc(size));
     if (temp == NULL) return NULL;
-    gr_surface surface = (gr_surface) temp;
+    GRSurface* surface = reinterpret_cast<GRSurface*>(temp);
     surface->data = temp + sizeof(GRSurface) +
         (SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT));
     return surface;
@@ -49,6 +51,8 @@
     char resPath[256];
     unsigned char header[8];
     int result = 0;
+    int color_type, bit_depth;
+    size_t bytesRead;
 
     snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
     resPath[sizeof(resPath)-1] = '\0';
@@ -58,7 +62,7 @@
         goto exit;
     }
 
-    size_t bytesRead = fread(header, 1, sizeof(header), fp);
+    bytesRead = fread(header, 1, sizeof(header), fp);
     if (bytesRead != sizeof(header)) {
         result = -2;
         goto exit;
@@ -90,7 +94,6 @@
     png_set_sig_bytes(*png_ptr, sizeof(header));
     png_read_info(*png_ptr, *info_ptr);
 
-    int color_type, bit_depth;
     png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth,
             &color_type, NULL, NULL, NULL);
 
@@ -135,12 +138,10 @@
 // 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
+// Allocate and return a GRSurface* 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);
+static GRSurface* init_display_surface(png_uint_32 width, png_uint_32 height) {
+    GRSurface* surface = malloc_surface(width * height * 4);
     if (surface == NULL) return NULL;
 
     surface->width = width;
@@ -196,13 +197,15 @@
     }
 }
 
-int res_create_display_surface(const char* name, gr_surface* pSurface) {
-    gr_surface surface = NULL;
+int res_create_display_surface(const char* name, GRSurface** pSurface) {
+    GRSurface* surface = NULL;
     int result = 0;
     png_structp png_ptr = NULL;
     png_infop info_ptr = NULL;
     png_uint_32 width, height;
     png_byte channels;
+    unsigned char* p_row;
+    unsigned int y;
 
     *pSurface = NULL;
 
@@ -215,8 +218,11 @@
         goto exit;
     }
 
-    unsigned char* p_row = malloc(width * 4);
-    unsigned int y;
+#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
+    png_set_bgr(png_ptr);
+#endif
+
+    p_row = reinterpret_cast<unsigned char*>(malloc(width * 4));
     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);
@@ -231,14 +237,18 @@
     return result;
 }
 
-int res_create_multi_display_surface(const char* name, int* frames, gr_surface** pSurface) {
-    gr_surface* surface = NULL;
+int res_create_multi_display_surface(const char* name, int* frames, GRSurface*** pSurface) {
+    GRSurface** 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;
+    png_textp text;
+    int num_text;
+    unsigned char* p_row;
+    unsigned int y;
 
     *pSurface = NULL;
     *frames = -1;
@@ -247,8 +257,6 @@
     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) {
@@ -265,7 +273,7 @@
         goto exit;
     }
 
-    surface = malloc(*frames * sizeof(gr_surface));
+    surface = reinterpret_cast<GRSurface**>(malloc(*frames * sizeof(GRSurface*)));
     if (surface == NULL) {
         result = -8;
         goto exit;
@@ -278,8 +286,11 @@
         }
     }
 
-    unsigned char* p_row = malloc(width * 4);
-    unsigned int y;
+#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
+    png_set_bgr(png_ptr);
+#endif
+
+    p_row = reinterpret_cast<unsigned char*>(malloc(width * 4));
     for (y = 0; y < height; ++y) {
         png_read_row(png_ptr, p_row, NULL);
         int frame = y % *frames;
@@ -289,7 +300,7 @@
     }
     free(p_row);
 
-    *pSurface = (gr_surface*) surface;
+    *pSurface = reinterpret_cast<GRSurface**>(surface);
 
 exit:
     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
@@ -305,8 +316,8 @@
     return result;
 }
 
-int res_create_alpha_surface(const char* name, gr_surface* pSurface) {
-    gr_surface surface = NULL;
+int res_create_alpha_surface(const char* name, GRSurface** pSurface) {
+    GRSurface* surface = NULL;
     int result = 0;
     png_structp png_ptr = NULL;
     png_infop info_ptr = NULL;
@@ -333,6 +344,10 @@
     surface->row_bytes = width;
     surface->pixel_bytes = 1;
 
+#if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA)
+    png_set_bgr(png_ptr);
+#endif
+
     unsigned char* p_row;
     unsigned int y;
     for (y = 0; y < height; ++y) {
@@ -367,13 +382,15 @@
 
 int res_create_localized_alpha_surface(const char* name,
                                        const char* locale,
-                                       gr_surface* pSurface) {
-    gr_surface surface = NULL;
+                                       GRSurface** pSurface) {
+    GRSurface* surface = NULL;
     int result = 0;
     png_structp png_ptr = NULL;
     png_infop info_ptr = NULL;
     png_uint_32 width, height;
     png_byte channels;
+    unsigned char* row;
+    png_uint_32 y;
 
     *pSurface = NULL;
 
@@ -394,8 +411,7 @@
         goto exit;
     }
 
-    unsigned char* row = malloc(width);
-    png_uint_32 y;
+    row = reinterpret_cast<unsigned char*>(malloc(width));
     for (y = 0; y < height; ++y) {
         png_read_row(png_ptr, row, NULL);
         int w = (row[1] << 8) | row[0];
@@ -422,7 +438,7 @@
                 memcpy(surface->data + i*w, row, w);
             }
 
-            *pSurface = (gr_surface) surface;
+            *pSurface = reinterpret_cast<GRSurface*>(surface);
             break;
         } else {
             int i;
@@ -438,6 +454,6 @@
     return result;
 }
 
-void res_free_surface(gr_surface surface) {
+void res_free_surface(GRSurface* surface) {
     free(surface);
 }
diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c
index fe2c880..97cb2e0 100644
--- a/minzip/DirUtil.c
+++ b/minzip/DirUtil.c
@@ -85,7 +85,7 @@
             c--;
         }
         if (c == cpath) {
-//xxx test this path
+            //xxx test this path
             /* No directory component.  Act like the path was empty.
              */
             errno = ENOENT;
@@ -206,7 +206,7 @@
     /* recurse over components */
     errno = 0;
     while ((de = readdir(dir)) != NULL) {
-//TODO: don't blow the stack
+        //TODO: don't blow the stack
         char dn[PATH_MAX];
         if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
             continue;
diff --git a/minzip/Hash.c b/minzip/Hash.c
index 8c6ca9b..8f8ed68 100644
--- a/minzip/Hash.c
+++ b/minzip/Hash.c
@@ -140,7 +140,6 @@
     int i;
 
     assert(countTombStones(pHashTable) == pHashTable->numDeadEntries);
-    //LOGI("before: dead=%d\n", pHashTable->numDeadEntries);
 
     pNewEntries = (HashEntry*) calloc(newSize, sizeof(HashTable));
     if (pNewEntries == NULL)
@@ -196,7 +195,6 @@
             (*cmpFunc)(pEntry->data, item) == 0)
         {
             /* match */
-            //LOGD("+++ match on entry %d\n", pEntry - pHashTable->pEntries);
             break;
         }
 
@@ -206,8 +204,6 @@
                 break;      /* edge case - single-entry table */
             pEntry = pHashTable->pEntries;
         }
-
-        //LOGI("+++ look probing %d...\n", pEntry - pHashTable->pEntries);
     }
 
     if (pEntry->data == NULL) {
@@ -228,10 +224,6 @@
                     abort();
                 }
                 /* note "pEntry" is now invalid */
-            } else {
-                //LOGW("okay %d/%d/%d\n",
-                //    pHashTable->numEntries, pHashTable->tableSize,
-                //    (pHashTable->tableSize * LOAD_NUMER) / LOAD_DENOM);
             }
 
             /* full table is bad -- search for nonexistent never halts */
@@ -264,7 +256,6 @@
     pEnd = &pHashTable->pEntries[pHashTable->tableSize];
     while (pEntry->data != NULL) {
         if (pEntry->data == item) {
-            //LOGI("+++ stepping on entry %d\n", pEntry - pHashTable->pEntries);
             pEntry->data = HASH_TOMBSTONE;
             pHashTable->numEntries--;
             pHashTable->numDeadEntries++;
@@ -277,8 +268,6 @@
                 break;      /* edge case - single-entry table */
             pEntry = pHashTable->pEntries;
         }
-
-        //LOGI("+++ del probing %d...\n", pEntry - pHashTable->pEntries);
     }
 
     return false;
diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c
index 8601591..b1fb455 100644
--- a/minzip/SysUtil.c
+++ b/minzip/SysUtil.c
@@ -29,11 +29,13 @@
     assert(start_ != NULL);
     assert(length_ != NULL);
 
-    start = lseek(fd, 0L, SEEK_CUR);
-    end = lseek(fd, 0L, SEEK_END);
-    (void) lseek(fd, start, SEEK_SET);
+    // TODO: isn't start always 0 for the single call site? just use fstat instead?
 
-    if (start == (off_t) -1 || end == (off_t) -1) {
+    start = TEMP_FAILURE_RETRY(lseek(fd, 0L, SEEK_CUR));
+    end = TEMP_FAILURE_RETRY(lseek(fd, 0L, SEEK_END));
+
+    if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1 ||
+                start == (off_t) -1 || end == (off_t) -1) {
         LOGE("could not determine length of file\n");
         return -1;
     }
diff --git a/minzip/Zip.c b/minzip/Zip.c
index 70aff00..40712e0 100644
--- a/minzip/Zip.c
+++ b/minzip/Zip.c
@@ -327,10 +327,6 @@
 #else
         pEntry = &pArchive->pEntries[i];
 #endif
-
-        //LOGI("%d: localHdr=%d fnl=%d el=%d cl=%d\n",
-        //    i, localHdrOffset, fileNameLen, extraLen, commentLen);
-
         pEntry->fileNameLen = fileNameLen;
         pEntry->fileName = fileName;
 
@@ -488,7 +484,7 @@
 /*
  * Return true if the entry is a symbolic link.
  */
-bool mzIsZipEntrySymlink(const ZipEntry* pEntry)
+static bool mzIsZipEntrySymlink(const ZipEntry* pEntry)
 {
     if ((pEntry->versionMadeBy & 0xff00) == CENVEM_UNIX) {
         return S_ISLNK(pEntry->externalFileAttributes >> 16);
@@ -632,30 +628,6 @@
     return true;
 }
 
-/*
- * Check the CRC on this entry; return true if it is correct.
- * May do other internal checks as well.
- */
-bool mzIsZipEntryIntact(const ZipArchive *pArchive, const ZipEntry *pEntry)
-{
-    unsigned long crc;
-    bool ret;
-
-    crc = crc32(0L, Z_NULL, 0);
-    ret = mzProcessZipEntryContents(pArchive, pEntry, crcProcessFunction,
-            (void *)&crc);
-    if (!ret) {
-        LOGE("Can't calculate CRC for entry\n");
-        return false;
-    }
-    if (crc != (unsigned long)pEntry->crc32) {
-        LOGW("CRC for entry %.*s (0x%08lx) != expected (0x%08lx)\n",
-                pEntry->fileNameLen, pEntry->fileName, crc, pEntry->crc32);
-        return false;
-    }
-    return true;
-}
-
 typedef struct {
     char *buf;
     int bufLen;
@@ -703,13 +675,11 @@
     }
     ssize_t soFar = 0;
     while (true) {
-        ssize_t n = write(fd, data+soFar, dataLen-soFar);
+        ssize_t n = TEMP_FAILURE_RETRY(write(fd, data+soFar, dataLen-soFar));
         if (n <= 0) {
             LOGE("Error writing %zd bytes from zip file from %p: %s\n",
                  dataLen-soFar, data+soFar, strerror(errno));
-            if (errno != EINTR) {
-              return false;
-            }
+            return false;
         } else if (n > 0) {
             soFar += n;
             if (soFar == dataLen) return true;
@@ -737,23 +707,6 @@
     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;
@@ -873,7 +826,7 @@
  */
 bool mzExtractRecursive(const ZipArchive *pArchive,
                         const char *zipDir, const char *targetDir,
-                        int flags, const struct utimbuf *timestamp,
+                        const struct utimbuf *timestamp,
                         void (*callback)(const char *fn, void *), void *cookie,
                         struct selabel_handle *sehnd)
 {
@@ -923,8 +876,8 @@
 
     /* Walk through the entries and extract anything whose path begins
      * with zpath.
-//TODO: since the entries are sorted, binary search for the first match
-//      and stop after the first non-match.
+    //TODO: since the entries are sorted, binary search for the first match
+    //      and stop after the first non-match.
      */
     unsigned int i;
     bool seenMatch = false;
@@ -933,10 +886,10 @@
     for (i = 0; i < pArchive->numEntries; i++) {
         ZipEntry *pEntry = pArchive->pEntries + i;
         if (pEntry->fileNameLen < zipDirLen) {
-//TODO: look out for a single empty directory entry that matches zpath, but
-//      missing the trailing slash.  Most zip files seem to include
-//      the trailing slash, but I think it's legal to leave it off.
-//      e.g., zpath "a/b/", entry "a/b", with no children of the entry.
+       //TODO: look out for a single empty directory entry that matches zpath, but
+       //      missing the trailing slash.  Most zip files seem to include
+       //      the trailing slash, but I think it's legal to leave it off.
+       //      e.g., zpath "a/b/", entry "a/b", with no children of the entry.
             /* No chance of matching.
              */
 #if SORT_ENTRIES
@@ -977,30 +930,19 @@
             break;
         }
 
-        /* With DRY_RUN set, invoke the callback but don't do anything else.
-         */
-        if (flags & MZ_EXTRACT_DRY_RUN) {
-            if (callback != NULL) callback(targetFile, cookie);
-            continue;
-        }
-
-        /* Create the file or directory.
-         */
 #define UNZIP_DIRMODE 0755
 #define UNZIP_FILEMODE 0644
-        if (pEntry->fileName[pEntry->fileNameLen-1] == '/') {
-            if (!(flags & MZ_EXTRACT_FILES_ONLY)) {
-                int ret = dirCreateHierarchy(
-                        targetFile, UNZIP_DIRMODE, timestamp, false, sehnd);
-                if (ret != 0) {
-                    LOGE("Can't create containing directory for \"%s\": %s\n",
-                            targetFile, strerror(errno));
-                    ok = false;
-                    break;
-                }
-                LOGD("Extracted dir \"%s\"\n", targetFile);
-            }
-        } else {
+        /*
+         * Create the file or directory. We ignore directory entries
+         * because we recursively create paths to each file entry we encounter
+         * in the zip archive anyway.
+         *
+         * NOTE: A "directory entry" in a zip archive is just a zero length
+         * entry that ends in a "/". They're not mandatory and many tools get
+         * rid of them. We need to process them only if we want to preserve
+         * empty directories from the archive.
+         */
+        if (pEntry->fileName[pEntry->fileNameLen-1] != '/') {
             /* This is not a directory.  First, make sure that
              * the containing directory exists.
              */
@@ -1013,97 +955,62 @@
                 break;
             }
 
-            /* With FILES_ONLY set, we need to ignore metadata entirely,
-             * so treat symlinks as regular files.
+            /*
+             * The entry is a regular file or a symlink. Open the target for writing.
+             *
+             * TODO: This behavior for symlinks seems rather bizarre. For a
+             * symlink foo/bar/baz -> foo/tar/taz, we will create a file called
+             * "foo/bar/baz" whose contents are the literal "foo/tar/taz". We
+             * warn about this for now and preserve older behavior.
              */
-            if (!(flags & MZ_EXTRACT_FILES_ONLY) && mzIsZipEntrySymlink(pEntry)) {
-                /* The entry is a symbolic link.
-                 * The relative target of the symlink is in the
-                 * data section of this entry.
-                 */
-                if (pEntry->uncompLen == 0) {
-                    LOGE("Symlink entry \"%s\" has no target\n",
-                            targetFile);
-                    ok = false;
-                    break;
-                }
-                char *linkTarget = malloc(pEntry->uncompLen + 1);
-                if (linkTarget == NULL) {
-                    ok = false;
-                    break;
-                }
-                ok = mzReadZipEntry(pArchive, pEntry, linkTarget,
-                        pEntry->uncompLen);
-                if (!ok) {
-                    LOGE("Can't read symlink target for \"%s\"\n",
-                            targetFile);
-                    free(linkTarget);
-                    break;
-                }
-                linkTarget[pEntry->uncompLen] = '\0';
-
-                /* Make the link.
-                 */
-                ret = symlink(linkTarget, targetFile);
-                if (ret != 0) {
-                    LOGE("Can't symlink \"%s\" to \"%s\": %s\n",
-                            targetFile, linkTarget, strerror(errno));
-                    free(linkTarget);
-                    ok = false;
-                    break;
-                }
-                LOGD("Extracted symlink \"%s\" -> \"%s\"\n",
-                        targetFile, linkTarget);
-                free(linkTarget);
-            } else {
-                /* The entry is a regular file.
-                 * Open the target for writing.
-                 */
-
-                char *secontext = NULL;
-
-                if (sehnd) {
-                    selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE);
-                    setfscreatecon(secontext);
-                }
-
-                int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC
-                        , UNZIP_FILEMODE);
-
-                if (secontext) {
-                    freecon(secontext);
-                    setfscreatecon(NULL);
-                }
-
-                if (fd < 0) {
-                    LOGE("Can't create target file \"%s\": %s\n",
-                            targetFile, strerror(errno));
-                    ok = false;
-                    break;
-                }
-
-                bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd);
-                if (ok) {
-                    ok = (fsync(fd) == 0);
-                }
-                if (close(fd) != 0) {
-                    ok = false;
-                }
-                if (!ok) {
-                    LOGE("Error extracting \"%s\"\n", targetFile);
-                    ok = false;
-                    break;
-                }
-
-                if (timestamp != NULL && utime(targetFile, timestamp)) {
-                    LOGE("Error touching \"%s\"\n", targetFile);
-                    ok = false;
-                    break;
-                }
-
-                LOGV("Extracted file \"%s\"\n", targetFile);
-                ++extractCount;
+            if (mzIsZipEntrySymlink(pEntry)) {
+                LOGE("Symlink entry \"%.*s\" will be output as a regular file.",
+                     pEntry->fileNameLen, pEntry->fileName);
             }
+
+            char *secontext = NULL;
+
+            if (sehnd) {
+                selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE);
+                setfscreatecon(secontext);
+            }
+
+            int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC,
+                UNZIP_FILEMODE);
+
+            if (secontext) {
+                freecon(secontext);
+                setfscreatecon(NULL);
+            }
+
+            if (fd < 0) {
+                LOGE("Can't create target file \"%s\": %s\n",
+                        targetFile, strerror(errno));
+                ok = false;
+                break;
+            }
+
+            bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd);
+            if (ok) {
+                ok = (fsync(fd) == 0);
+            }
+            if (close(fd) != 0) {
+                ok = false;
+            }
+            if (!ok) {
+                LOGE("Error extracting \"%s\"\n", targetFile);
+                ok = false;
+                break;
+            }
+
+            if (timestamp != NULL && utime(targetFile, timestamp)) {
+                LOGE("Error touching \"%s\"\n", targetFile);
+                ok = false;
+                break;
+            }
+
+            LOGV("Extracted file \"%s\"\n", targetFile);
+            ++extractCount;
         }
 
         if (callback != NULL) callback(targetFile, cookie);
diff --git a/minzip/Zip.h b/minzip/Zip.h
index 2054b38..86d8db5 100644
--- a/minzip/Zip.h
+++ b/minzip/Zip.h
@@ -85,56 +85,12 @@
 const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
         const char* entryName);
 
-/*
- * Get the number of entries in the Zip archive.
- */
-INLINE unsigned int mzZipEntryCount(const ZipArchive* pArchive) {
-    return pArchive->numEntries;
-}
-
-/*
- * Get an entry by index.  Returns NULL if the index is out-of-bounds.
- */
-INLINE const ZipEntry*
-mzGetZipEntryAt(const ZipArchive* pArchive, unsigned int index)
-{
-    if (index < pArchive->numEntries) {
-        return pArchive->pEntries + index;
-    }
-    return NULL;
-}
-
-/*
- * Get the index number of an entry in the archive.
- */
-INLINE unsigned int
-mzGetZipEntryIndex(const ZipArchive *pArchive, const ZipEntry *pEntry) {
-    return pEntry - pArchive->pEntries;
-}
-
-/*
- * Simple accessors.
- */
-INLINE UnterminatedString mzGetZipEntryFileName(const ZipEntry* pEntry) {
-    UnterminatedString ret;
-    ret.str = pEntry->fileName;
-    ret.len = pEntry->fileNameLen;
-    return ret;
-}
 INLINE long mzGetZipEntryOffset(const ZipEntry* pEntry) {
     return pEntry->offset;
 }
 INLINE long mzGetZipEntryUncompLen(const ZipEntry* pEntry) {
     return pEntry->uncompLen;
 }
-INLINE long mzGetZipEntryModTime(const ZipEntry* pEntry) {
-    return pEntry->modTime;
-}
-INLINE long mzGetZipEntryCrc32(const ZipEntry* pEntry) {
-    return pEntry->crc32;
-}
-bool mzIsZipEntrySymlink(const ZipEntry* pEntry);
-
 
 /*
  * Type definition for the callback function used by
@@ -164,12 +120,6 @@
         char* buf, int bufLen);
 
 /*
- * Check the CRC on this entry; return true if it is correct.
- * May do other internal checks as well.
- */
-bool mzIsZipEntryIntact(const ZipArchive *pArchive, const ZipEntry *pEntry);
-
-/*
  * Inflate and write an entry to a file.
  */
 bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
@@ -183,20 +133,12 @@
     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
+ * Inflate all files under zipDir to the directory specified by
  * targetDir, which must exist and be a writable directory.
  *
+ * Directory entries and symlinks are not extracted.
+ *
+ *
  * The immediate children of zipDir will become the immediate
  * children of targetDir; e.g., if the archive contains the entries
  *
@@ -211,21 +153,15 @@
  *     /tmp/two
  *     /tmp/d/three
  *
- * flags is zero or more of the following:
- *
- *     MZ_EXTRACT_FILES_ONLY - only unpack files, not directories or symlinks
- *     MZ_EXTRACT_DRY_RUN - don't do anything, but do invoke the callback
- *
  * If timestamp is non-NULL, file timestamps will be set accordingly.
  *
  * If callback is non-NULL, it will be invoked with each unpacked file.
  *
  * Returns true on success, false on failure.
  */
-enum { MZ_EXTRACT_FILES_ONLY = 1, MZ_EXTRACT_DRY_RUN = 2 };
 bool mzExtractRecursive(const ZipArchive *pArchive,
         const char *zipDir, const char *targetDir,
-        int flags, const struct utimbuf *timestamp,
+        const struct utimbuf *timestamp,
         void (*callback)(const char *fn, void*), void *cookie,
         struct selabel_handle *sehnd);
 
diff --git a/mtdutils/flash_image.c b/mtdutils/flash_image.c
index 5657dfc..36ffa13 100644
--- a/mtdutils/flash_image.c
+++ b/mtdutils/flash_image.c
@@ -72,7 +72,7 @@
     if (fd < 0) die("error opening %s", argv[2]);
 
     char header[HEADER_SIZE];
-    int headerlen = read(fd, header, sizeof(header));
+    int headerlen = TEMP_FAILURE_RETRY(read(fd, header, sizeof(header)));
     if (headerlen <= 0) die("error reading %s header", argv[2]);
 
     MtdReadContext *in = mtd_read_partition(partition);
@@ -104,7 +104,7 @@
     if (wrote != headerlen) die("error writing %s", argv[1]);
 
     int len;
-    while ((len = read(fd, buf, sizeof(buf))) > 0) {
+    while ((len = TEMP_FAILURE_RETRY(read(fd, buf, sizeof(buf)))) > 0) {
         wrote = mtd_write_data(out, buf, len);
         if (wrote != len) die("error writing %s", argv[1]);
     }
@@ -125,13 +125,13 @@
     if (mtd_partition_info(partition, NULL, &block_size, NULL))
         die("error getting %s block size", argv[1]);
 
-    if (lseek(fd, headerlen, SEEK_SET) != headerlen)
+    if (TEMP_FAILURE_RETRY(lseek(fd, headerlen, SEEK_SET)) != headerlen)
         die("error rewinding %s", argv[2]);
 
     int left = block_size - headerlen;
     while (left < 0) left += block_size;
     while (left > 0) {
-        len = read(fd, buf, left > (int)sizeof(buf) ? (int)sizeof(buf) : left);
+        len = TEMP_FAILURE_RETRY(read(fd, buf, left > (int)sizeof(buf) ? (int)sizeof(buf) : left));
         if (len <= 0) die("error reading %s", argv[2]);
         if (mtd_write_data(out, buf, len) != len)
             die("error writing %s", argv[1]);
diff --git a/mtdutils/mounts.c b/mtdutils/mounts.c
index c90fc8a..6a9b03d 100644
--- a/mtdutils/mounts.c
+++ b/mtdutils/mounts.c
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <mntent.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -59,10 +60,8 @@
 int
 scan_mounted_volumes()
 {
-    char buf[2048];
-    const char *bufp;
-    int fd;
-    ssize_t nbytes;
+    FILE* fp;
+    struct mntent* mentry;
 
     if (g_mounts_state.volumes == NULL) {
         const int numv = 32;
@@ -84,80 +83,20 @@
     }
     g_mounts_state.volume_count = 0;
 
-    /* Open and read the file contents.
-     */
-    fd = open(PROC_MOUNTS_FILENAME, O_RDONLY);
-    if (fd < 0) {
-        goto bail;
+    /* Open and read mount table entries. */
+    fp = setmntent(PROC_MOUNTS_FILENAME, "r");
+    if (fp == NULL) {
+        return -1;
     }
-    nbytes = read(fd, buf, sizeof(buf) - 1);
-    close(fd);
-    if (nbytes < 0) {
-        goto bail;
+    while ((mentry = getmntent(fp)) != NULL) {
+        MountedVolume* v = &g_mounts_state.volumes[g_mounts_state.volume_count++];
+        v->device = strdup(mentry->mnt_fsname);
+        v->mount_point = strdup(mentry->mnt_dir);
+        v->filesystem = strdup(mentry->mnt_type);
+        v->flags = strdup(mentry->mnt_opts);
     }
-    buf[nbytes] = '\0';
-
-    /* Parse the contents of the file, which looks like:
-     *
-     *     # cat /proc/mounts
-     *     rootfs / rootfs rw 0 0
-     *     /dev/pts /dev/pts devpts rw 0 0
-     *     /proc /proc proc rw 0 0
-     *     /sys /sys sysfs rw 0 0
-     *     /dev/block/mtdblock4 /system yaffs2 rw,nodev,noatime,nodiratime 0 0
-     *     /dev/block/mtdblock5 /data yaffs2 rw,nodev,noatime,nodiratime 0 0
-     *     /dev/block/mmcblk0p1 /sdcard vfat rw,sync,dirsync,fmask=0000,dmask=0000,codepage=cp437,iocharset=iso8859-1,utf8 0 0
-     *
-     * The zeroes at the end are dummy placeholder fields to make the
-     * output match Linux's /etc/mtab, but don't represent anything here.
-     */
-    bufp = buf;
-    while (nbytes > 0) {
-        char device[64];
-        char mount_point[64];
-        char filesystem[64];
-        char flags[128];
-        int matches;
-
-        /* %as is a gnu extension that malloc()s a string for each field.
-         */
-        matches = sscanf(bufp, "%63s %63s %63s %127s",
-                device, mount_point, filesystem, flags);
-
-        if (matches == 4) {
-            device[sizeof(device)-1] = '\0';
-            mount_point[sizeof(mount_point)-1] = '\0';
-            filesystem[sizeof(filesystem)-1] = '\0';
-            flags[sizeof(flags)-1] = '\0';
-
-            MountedVolume *v =
-                    &g_mounts_state.volumes[g_mounts_state.volume_count++];
-            v->device = strdup(device);
-            v->mount_point = strdup(mount_point);
-            v->filesystem = strdup(filesystem);
-            v->flags = strdup(flags);
-        } else {
-printf("matches was %d on <<%.40s>>\n", matches, bufp);
-        }
-
-        /* Eat the line.
-         */
-        while (nbytes > 0 && *bufp != '\n') {
-            bufp++;
-            nbytes--;
-        }
-        if (nbytes > 0) {
-            bufp++;
-            nbytes--;
-        }
-    }
-
+    endmntent(fp);
     return 0;
-
-bail:
-//TODO: free the strings we've allocated.
-    g_mounts_state.volume_count = 0;
-    return -1;
 }
 
 const MountedVolume *
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
index d04b26e..cc30334 100644
--- a/mtdutils/mtdutils.c
+++ b/mtdutils/mtdutils.c
@@ -108,7 +108,7 @@
     if (fd < 0) {
         goto bail;
     }
-    nbytes = read(fd, buf, sizeof(buf) - 1);
+    nbytes = TEMP_FAILURE_RETRY(read(fd, buf, sizeof(buf) - 1));
     close(fd);
     if (nbytes < 0) {
         goto bail;
@@ -279,12 +279,6 @@
     return ctx;
 }
 
-// Seeks to a location in the partition.  Don't mix with reads of
-// anything other than whole blocks; unpredictable things will result.
-void mtd_read_skip_to(const MtdReadContext* ctx, size_t offset) {
-    lseek64(ctx->fd, offset, SEEK_SET);
-}
-
 static int read_block(const MtdPartition *partition, int fd, char *data)
 {
     struct mtd_ecc_stats before, after;
@@ -293,13 +287,18 @@
         return -1;
     }
 
-    loff_t pos = lseek64(fd, 0, SEEK_CUR);
+    loff_t pos = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR));
+    if (pos == -1) {
+        printf("mtd: read_block: couldn't SEEK_CUR: %s\n", strerror(errno));
+        return -1;
+    }
 
     ssize_t size = partition->erase_size;
     int mgbb;
 
     while (pos + size <= (int) partition->size) {
-        if (lseek64(fd, pos, SEEK_SET) != pos || read(fd, data, size) != size) {
+        if (TEMP_FAILURE_RETRY(lseek64(fd, pos, SEEK_SET)) != pos ||
+                    TEMP_FAILURE_RETRY(read(fd, data, size)) != size) {
             printf("mtd: read error at 0x%08llx (%s)\n",
                     pos, strerror(errno));
         } else if (ioctl(fd, ECCGETSTATS, &after)) {
@@ -313,8 +312,8 @@
             memcpy(&before, &after, sizeof(struct mtd_ecc_stats));
         } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) {
             fprintf(stderr,
-                    "mtd: MEMGETBADBLOCK returned %d at 0x%08llx (errno=%d)\n",
-                    mgbb, pos, errno);
+                    "mtd: MEMGETBADBLOCK returned %d at 0x%08llx: %s\n",
+                    mgbb, pos, strerror(errno));
         } else {
             return 0;  // Success!
         }
@@ -409,8 +408,11 @@
     const MtdPartition *partition = ctx->partition;
     int fd = ctx->fd;
 
-    off_t pos = lseek(fd, 0, SEEK_CUR);
-    if (pos == (off_t) -1) return 1;
+    off_t pos = TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_CUR));
+    if (pos == (off_t) -1) {
+        printf("mtd: write_block: couldn't SEEK_CUR: %s\n", strerror(errno));
+        return -1;
+    }
 
     ssize_t size = partition->erase_size;
     while (pos + size <= (int) partition->size) {
@@ -419,8 +421,8 @@
         if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) {
             add_bad_block_offset(ctx, pos);
             fprintf(stderr,
-                    "mtd: not writing bad block at 0x%08lx (ret %d errno %d)\n",
-                    pos, ret, errno);
+                    "mtd: not writing bad block at 0x%08lx (ret %d): %s\n",
+                    pos, ret, strerror(errno));
             pos += partition->erase_size;
             continue;  // Don't try to erase known factory-bad blocks.
         }
@@ -435,15 +437,15 @@
                         pos, strerror(errno));
                 continue;
             }
-            if (lseek(fd, pos, SEEK_SET) != pos ||
-                write(fd, data, size) != size) {
+            if (TEMP_FAILURE_RETRY(lseek(fd, pos, SEEK_SET)) != pos ||
+                TEMP_FAILURE_RETRY(write(fd, data, size)) != size) {
                 printf("mtd: write error at 0x%08lx (%s)\n",
                         pos, strerror(errno));
             }
 
             char verify[size];
-            if (lseek(fd, pos, SEEK_SET) != pos ||
-                read(fd, verify, size) != size) {
+            if (TEMP_FAILURE_RETRY(lseek(fd, pos, SEEK_SET)) != pos ||
+                TEMP_FAILURE_RETRY(read(fd, verify, size)) != size) {
                 printf("mtd: re-read error at 0x%08lx (%s)\n",
                         pos, strerror(errno));
                 continue;
@@ -512,8 +514,11 @@
         ctx->stored = 0;
     }
 
-    off_t pos = lseek(ctx->fd, 0, SEEK_CUR);
-    if ((off_t) pos == (off_t) -1) return pos;
+    off_t pos = TEMP_FAILURE_RETRY(lseek(ctx->fd, 0, SEEK_CUR));
+    if ((off_t) pos == (off_t) -1) {
+        printf("mtd_erase_blocks: couldn't SEEK_CUR: %s\n", strerror(errno));
+        return -1;
+    }
 
     const int total = (ctx->partition->size - pos) / ctx->partition->erase_size;
     if (blocks < 0) blocks = total;
@@ -554,18 +559,3 @@
     free(ctx);
     return r;
 }
-
-/* Return the offset of the first good block at or after pos (which
- * might be pos itself).
- */
-off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos) {
-    int i;
-    for (i = 0; i < ctx->bad_block_count; ++i) {
-        if (ctx->bad_block_offsets[i] == pos) {
-            pos += ctx->partition->erase_size;
-        } else if (ctx->bad_block_offsets[i] > pos) {
-            return pos;
-        }
-    }
-    return pos;
-}
diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h
index 2708c43..8059d6a 100644
--- a/mtdutils/mtdutils.h
+++ b/mtdutils/mtdutils.h
@@ -49,12 +49,10 @@
 MtdReadContext *mtd_read_partition(const MtdPartition *);
 ssize_t mtd_read_data(MtdReadContext *, char *data, size_t data_len);
 void mtd_read_close(MtdReadContext *);
-void mtd_read_skip_to(const MtdReadContext *, size_t offset);
 
 MtdWriteContext *mtd_write_partition(const MtdPartition *);
 ssize_t mtd_write_data(MtdWriteContext *, const char *data, size_t data_len);
 off_t mtd_erase_blocks(MtdWriteContext *, int blocks);  /* 0 ok, -1 for all */
-off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos);
 int mtd_write_close(MtdWriteContext *);
 
 #ifdef __cplusplus
diff --git a/recovery.cpp b/recovery.cpp
index 575e287..b7a5458 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -31,6 +31,9 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <base/file.h>
+#include <base/stringprintf.h>
+
 #include "bootloader.h"
 #include "common.h"
 #include "cutils/properties.h"
@@ -43,20 +46,20 @@
 #include "screen_ui.h"
 #include "device.h"
 #include "adb_install.h"
-extern "C" {
-#include "minadbd/adb.h"
+#include "adb.h"
 #include "fuse_sideload.h"
 #include "fuse_sdcard_provider.h"
-}
 
 struct selabel_handle *sehandle;
 
 static const struct option OPTIONS[] = {
-  { "send_intent", required_argument, NULL, 's' },
+  { "send_intent", required_argument, NULL, 'i' },
   { "update_package", required_argument, NULL, 'u' },
   { "wipe_data", no_argument, NULL, 'w' },
   { "wipe_cache", no_argument, NULL, 'c' },
   { "show_text", no_argument, NULL, 't' },
+  { "sideload", no_argument, NULL, 's' },
+  { "sideload_auto_reboot", no_argument, NULL, 'a' },
   { "just_exit", no_argument, NULL, 'x' },
   { "locale", required_argument, NULL, 'l' },
   { "stages", required_argument, NULL, 'g' },
@@ -65,8 +68,6 @@
   { NULL, 0, NULL, 0 },
 };
 
-#define LAST_LOG_FILE "/cache/recovery/last_log"
-
 static const char *CACHE_LOG_DIR = "/cache/recovery";
 static const char *COMMAND_FILE = "/cache/recovery/command";
 static const char *INTENT_FILE = "/cache/recovery/intent";
@@ -78,18 +79,14 @@
 static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
 static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
 static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
-#define KLOG_DEFAULT_LEN (64 * 1024)
-
-#define KEEP_LOG_COUNT 10
-
-// Number of lines per page when displaying a file on screen
-#define LINES_PER_PAGE 30
+static const char *LAST_LOG_FILE = "/cache/recovery/last_log";
+static const int KEEP_LOG_COUNT = 10;
 
 RecoveryUI* ui = NULL;
 char* locale = NULL;
-char recovery_version[PROPERTY_VALUE_MAX+1];
 char* stage = NULL;
 char* reason = NULL;
+bool modified_flash = false;
 
 /*
  * The recovery tool communicates with the main system through /cache files.
@@ -169,6 +166,11 @@
     return fp;
 }
 
+bool is_ro_debuggable() {
+    char value[PROPERTY_VALUE_MAX+1];
+    return (property_get("ro.debuggable", value, NULL) == 1 && value[0] == '1');
+}
+
 static void redirect_stdio(const char* filename) {
     // If these fail, there's not really anywhere to complain...
     freopen(filename, "a", stdout); setbuf(stdout, NULL);
@@ -265,87 +267,89 @@
     set_bootloader_message(&boot);
 }
 
-// read from kernel log into buffer and write out to file
-static void
-save_kernel_log(const char *destination) {
-    int n;
-    char *buffer;
-    int klog_buf_len;
-    FILE *log;
-
-    klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0);
+// Read from kernel log into buffer and write out to file.
+static void save_kernel_log(const char* destination) {
+    int klog_buf_len = klogctl(KLOG_SIZE_BUFFER, 0, 0);
     if (klog_buf_len <= 0) {
-        LOGE("Error getting klog size (%s), using default\n", strerror(errno));
-        klog_buf_len = KLOG_DEFAULT_LEN;
-    }
-
-    buffer = (char *)malloc(klog_buf_len);
-    if (!buffer) {
-        LOGE("Can't alloc %d bytes for klog buffer\n", klog_buf_len);
+        LOGE("Error getting klog size: %s\n", strerror(errno));
         return;
     }
 
-    n = klogctl(KLOG_READ_ALL, buffer, klog_buf_len);
-    if (n < 0) {
-        LOGE("Error in reading klog (%s)\n", strerror(errno));
-        free(buffer);
+    std::string buffer(klog_buf_len, 0);
+    int n = klogctl(KLOG_READ_ALL, &buffer[0], klog_buf_len);
+    if (n == -1) {
+        LOGE("Error in reading klog: %s\n", strerror(errno));
         return;
     }
-
-    log = fopen_path(destination, "w");
-    if (log == NULL) {
-        LOGE("Can't open %s\n", destination);
-        free(buffer);
-        return;
-    }
-    fwrite(buffer, n, 1, log);
-    check_and_fclose(log, destination);
-    free(buffer);
+    buffer.resize(n);
+    android::base::WriteStringToFile(buffer, destination);
 }
 
 // How much of the temp log we have copied to the copy in cache.
 static long tmplog_offset = 0;
 
-static void
-copy_log_file(const char* source, const char* destination, int append) {
-    FILE *log = fopen_path(destination, append ? "a" : "w");
-    if (log == NULL) {
+static void copy_log_file(const char* source, const char* destination, bool append) {
+    FILE* dest_fp = fopen_path(destination, append ? "a" : "w");
+    if (dest_fp == nullptr) {
         LOGE("Can't open %s\n", destination);
     } else {
-        FILE *tmplog = fopen(source, "r");
-        if (tmplog != NULL) {
+        FILE* source_fp = fopen(source, "r");
+        if (source_fp != nullptr) {
             if (append) {
-                fseek(tmplog, tmplog_offset, SEEK_SET);  // Since last write
+                fseek(source_fp, tmplog_offset, SEEK_SET);  // Since last write
             }
             char buf[4096];
-            while (fgets(buf, sizeof(buf), tmplog)) fputs(buf, log);
-            if (append) {
-                tmplog_offset = ftell(tmplog);
+            size_t bytes;
+            while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
+                fwrite(buf, 1, bytes, dest_fp);
             }
-            check_and_fclose(tmplog, source);
+            if (append) {
+                tmplog_offset = ftell(source_fp);
+            }
+            check_and_fclose(source_fp, source);
         }
-        check_and_fclose(log, destination);
+        check_and_fclose(dest_fp, destination);
     }
 }
 
-// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max
-// Overwrites any existing last_log.$max.
-static void
-rotate_last_logs(int max) {
-    char oldfn[256];
-    char newfn[256];
+// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max.
+// Similarly rename last_kmsg -> last_kmsg.1 -> ... -> last_kmsg.$max.
+// Overwrite any existing last_log.$max and last_kmsg.$max.
+static void rotate_logs(int max) {
+    // Logs should only be rotated once.
+    static bool rotated = false;
+    if (rotated) {
+        return;
+    }
+    rotated = true;
+    ensure_path_mounted(LAST_LOG_FILE);
+    ensure_path_mounted(LAST_KMSG_FILE);
 
-    int i;
-    for (i = max-1; i >= 0; --i) {
-        snprintf(oldfn, sizeof(oldfn), (i==0) ? LAST_LOG_FILE : (LAST_LOG_FILE ".%d"), i);
-        snprintf(newfn, sizeof(newfn), LAST_LOG_FILE ".%d", i+1);
-        // ignore errors
-        rename(oldfn, newfn);
+    for (int i = max-1; i >= 0; --i) {
+        std::string old_log = android::base::StringPrintf((i == 0) ? "%s" : "%s.%d",
+                LAST_LOG_FILE, i);
+        std::string new_log = android::base::StringPrintf("%s.%d", LAST_LOG_FILE, i+1);
+        // Ignore errors if old_log doesn't exist.
+        rename(old_log.c_str(), new_log.c_str());
+
+        std::string old_kmsg = android::base::StringPrintf((i == 0) ? "%s" : "%s.%d",
+                LAST_KMSG_FILE, i);
+        std::string new_kmsg = android::base::StringPrintf("%s.%d", LAST_KMSG_FILE, i+1);
+        rename(old_kmsg.c_str(), new_kmsg.c_str());
     }
 }
 
-static void
-copy_logs() {
+static void copy_logs() {
+    // We only rotate and record the log of the current session if there are
+    // actual attempts to modify the flash, such as wipes, installs from BCB
+    // or menu selections. This is to avoid unnecessary rotation (and
+    // possible deletion) of log files, if it does not do anything loggable.
+    if (!modified_flash) {
+        return;
+    }
+
+    rotate_logs(KEEP_LOG_COUNT);
+
     // Copy logs to cache so the system can find out what happened.
     copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
     copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
@@ -413,8 +417,7 @@
     struct _saved_log_file* next;
 } saved_log_file;
 
-static int
-erase_volume(const char *volume) {
+static bool erase_volume(const char* volume) {
     bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
 
     ui->SetBackground(RecoveryUI::ERASING);
@@ -423,9 +426,10 @@
     saved_log_file* head = NULL;
 
     if (is_cache) {
-        // If we're reformatting /cache, we load any
-        // "/cache/recovery/last*" files into memory, so we can restore
-        // them after the reformat.
+        // If we're reformatting /cache, we load any past logs
+        // (i.e. "/cache/recovery/last_*") and the current log
+        // ("/cache/recovery/log") into memory, so we can restore them after
+        // the reformat.
 
         ensure_path_mounted(volume);
 
@@ -438,7 +442,7 @@
             strcat(path, "/");
             int path_len = strlen(path);
             while ((de = readdir(d)) != NULL) {
-                if (strncmp(de->d_name, "last", 4) == 0) {
+                if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) {
                     saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file));
                     strcpy(path+path_len, de->d_name);
                     p->name = strdup(path);
@@ -494,26 +498,7 @@
         copy_logs();
     }
 
-    return result;
-}
-
-static const char**
-prepend_title(const char* const* headers) {
-    // count the number of lines in our title, plus the
-    // caller-provided headers.
-    int count = 3;   // our title has 3 lines
-    const char* const* p;
-    for (p = headers; *p; ++p, ++count);
-
-    const char** new_headers = (const char**)malloc((count+1) * sizeof(char*));
-    const char** h = new_headers;
-    *(h++) = "Android system recovery <" EXPAND(RECOVERY_API_VERSION) "e>";
-    *(h++) = recovery_version;
-    *(h++) = "";
-    for (p = headers; *p; ++p, ++h) *h = *p;
-    *h = NULL;
-
-    return new_headers;
+    return (result == 0);
 }
 
 static int
@@ -546,12 +531,10 @@
         if (action < 0) {
             switch (action) {
                 case Device::kHighlightUp:
-                    --selected;
-                    selected = ui->SelectMenu(selected);
+                    selected = ui->SelectMenu(--selected);
                     break;
                 case Device::kHighlightDown:
-                    ++selected;
-                    selected = ui->SelectMenu(selected);
+                    selected = ui->SelectMenu(++selected);
                     break;
                 case Device::kInvokeItem:
                     chosen_item = selected;
@@ -573,24 +556,15 @@
 }
 
 // Returns a malloc'd path, or NULL.
-static char*
-browse_directory(const char* path, Device* device) {
+static char* browse_directory(const char* path, Device* device) {
     ensure_path_mounted(path);
 
-    const char* MENU_HEADERS[] = { "Choose a package to install:",
-                                   path,
-                                   "",
-                                   NULL };
-    DIR* d;
-    struct dirent* de;
-    d = opendir(path);
+    DIR* d = opendir(path);
     if (d == NULL) {
         LOGE("error opening %s: %s\n", path, strerror(errno));
         return NULL;
     }
 
-    const char** headers = prepend_title(MENU_HEADERS);
-
     int d_size = 0;
     int d_alloc = 10;
     char** dirs = (char**)malloc(d_alloc * sizeof(char*));
@@ -599,6 +573,7 @@
     char** zips = (char**)malloc(z_alloc * sizeof(char*));
     zips[0] = strdup("../");
 
+    struct dirent* de;
     while ((de = readdir(d)) != NULL) {
         int name_len = strlen(de->d_name);
 
@@ -642,6 +617,8 @@
     z_size += d_size;
     zips[z_size] = NULL;
 
+    const char* headers[] = { "Choose a package to install:", path, NULL };
+
     char* result;
     int chosen_item = 0;
     while (true) {
@@ -672,160 +649,135 @@
         }
     }
 
-    int i;
-    for (i = 0; i < z_size; ++i) free(zips[i]);
+    for (int i = 0; i < z_size; ++i) free(zips[i]);
     free(zips);
-    free(headers);
 
     return result;
 }
 
-static void
-wipe_data(int confirm, Device* device) {
-    if (confirm) {
-        static const char** title_headers = NULL;
+static bool yes_no(Device* device, const char* question1, const char* question2) {
+    const char* headers[] = { question1, question2, NULL };
+    const char* items[] = { " No", " Yes", NULL };
 
-        if (title_headers == NULL) {
-            const char* headers[] = { "Confirm wipe of all user data?",
-                                      "  THIS CAN NOT BE UNDONE.",
-                                      "",
-                                      NULL };
-            title_headers = prepend_title((const char**)headers);
-        }
-
-        const char* items[] = { " No",
-                                " No",
-                                " No",
-                                " No",
-                                " No",
-                                " No",
-                                " No",
-                                " Yes -- delete all user data",   // [7]
-                                " No",
-                                " No",
-                                " No",
-                                NULL };
-
-        int chosen_item = get_menu_selection(title_headers, items, 1, 0, device);
-        if (chosen_item != 7) {
-            return;
-        }
-    }
-
-    ui->Print("\n-- Wiping data...\n");
-    device->WipeData();
-    erase_volume("/data");
-    erase_volume("/cache");
-    ui->Print("Data wipe complete.\n");
+    int chosen_item = get_menu_selection(headers, items, 1, 0, device);
+    return (chosen_item == 1);
 }
 
-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;
-    int key = 0;
-    redirect_stdio("/dev/null");
-    while(fgets(line, sizeof(line), fp) != NULL) {
-        ui->Print("%s", line);
-        ct++;
-        if (ct % LINES_PER_PAGE == 0) {
-            // give the user time to glance at the entries
-            key = ui->WaitKey();
-
-            if (key == KEY_POWER) {
-                break;
-            }
-
-            if (key == KEY_VOLUMEUP) {
-                // Go back by seeking to the beginning and dumping ct - n
-                // lines.  It's ugly, but this way we don't need to store
-                // the previous offsets.  The files we're dumping here aren't
-                // expected to be very large.
-                int i;
-
-                ct -= 2 * LINES_PER_PAGE;
-                if (ct < 0) {
-                    ct = 0;
-                }
-                fseek(fp, 0, SEEK_SET);
-                for (i = 0; i < ct; i++) {
-                    fgets(line, sizeof(line), fp);
-                }
-                ui->Print("^^^^^^^^^^\n");
-            }
-        }
+// Return true on success.
+static bool wipe_data(int should_confirm, Device* device) {
+    if (should_confirm && !yes_no(device, "Wipe all user data?", "  THIS CAN NOT BE UNDONE!")) {
+        return false;
     }
 
-    // If the user didn't abort, then give the user time to glance at
-    // the end of the log, sorry, no rewind here
-    if (key != KEY_POWER) {
-        ui->Print("\n--END-- (press any key)\n");
-        ui->WaitKey();
+    modified_flash = true;
+
+    ui->Print("\n-- Wiping data...\n");
+    bool success =
+        device->PreWipeData() &&
+        erase_volume("/data") &&
+        erase_volume("/cache") &&
+        device->PostWipeData();
+    ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
+    return success;
+}
+
+// Return true on success.
+static bool wipe_cache(bool should_confirm, Device* device) {
+    if (should_confirm && !yes_no(device, "Wipe cache?", "  THIS CAN NOT BE UNDONE!")) {
+        return false;
     }
 
-    redirect_stdio(TEMPORARY_LOG_FILE);
-    fclose(fp);
+    modified_flash = true;
+
+    ui->Print("\n-- Wiping cache...\n");
+    bool success = erase_volume("/cache");
+    ui->Print("Cache wipe %s.\n", success ? "complete" : "failed");
+    return success;
 }
 
 static void choose_recovery_file(Device* device) {
-    unsigned int i;
-    unsigned int n;
-    static const char** title_headers = NULL;
-    char *filename;
-    const char* headers[] = { "Select file to view",
-                              "",
-                              NULL };
-    // "Go back" + LAST_KMSG_FILE + KEEP_LOG_COUNT + terminating NULL entry
-    char* entries[KEEP_LOG_COUNT + 3];
+    // "Back" + KEEP_LOG_COUNT * 2 + terminating nullptr entry
+    char* entries[1 + KEEP_LOG_COUNT * 2 + 1];
     memset(entries, 0, sizeof(entries));
 
-    n = 0;
-    entries[n++] = strdup("Go back");
-
-    // Add kernel kmsg file if available
-    if ((ensure_path_mounted(LAST_KMSG_FILE) == 0) && (access(LAST_KMSG_FILE, R_OK) == 0)) {
-        entries[n++] = strdup(LAST_KMSG_FILE);
-    }
+    unsigned int n = 0;
 
     // Add LAST_LOG_FILE + LAST_LOG_FILE.x
-    for (i = 0; i < KEEP_LOG_COUNT; i++) {
-        char *filename;
-        if (asprintf(&filename, (i==0) ? LAST_LOG_FILE : (LAST_LOG_FILE ".%d"), i) == -1) {
+    // Add LAST_KMSG_FILE + LAST_KMSG_FILE.x
+    for (int i = 0; i < KEEP_LOG_COUNT; i++) {
+        char* log_file;
+        if (asprintf(&log_file, (i == 0) ? "%s" : "%s.%d", LAST_LOG_FILE, 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[n++] = NULL;
-            break;
+        if ((ensure_path_mounted(log_file) != 0) || (access(log_file, R_OK) == -1)) {
+            free(log_file);
+        } else {
+            entries[n++] = log_file;
         }
-        entries[n++] = filename;
+
+        char* kmsg_file;
+        if (asprintf(&kmsg_file, (i == 0) ? "%s" : "%s.%d", LAST_KMSG_FILE, i) == -1) {
+            // memory allocation failure - return early. Should never happen.
+            return;
+        }
+        if ((ensure_path_mounted(kmsg_file) != 0) || (access(kmsg_file, R_OK) == -1)) {
+            free(kmsg_file);
+        } else {
+            entries[n++] = kmsg_file;
+        }
     }
 
-    title_headers = prepend_title((const char**)headers);
+    entries[n++] = strdup("Back");
 
-    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]);
+    const char* headers[] = { "Select file to view", nullptr };
+
+    while (true) {
+        int chosen_item = get_menu_selection(headers, entries, 1, 0, device);
+        if (strcmp(entries[chosen_item], "Back") == 0) break;
+
+        // TODO: do we need to redirect? ShowFile could just avoid writing to stdio.
+        redirect_stdio("/dev/null");
+        ui->ShowFile(entries[chosen_item]);
+        redirect_stdio(TEMPORARY_LOG_FILE);
     }
 
-    for (i = 0; i < (sizeof(entries) / sizeof(*entries)); i++) {
+    for (size_t i = 0; i < (sizeof(entries) / sizeof(*entries)); i++) {
         free(entries[i]);
     }
 }
 
+static int apply_from_sdcard(Device* device, bool* wipe_cache) {
+    modified_flash = true;
+
+    if (ensure_path_mounted(SDCARD_ROOT) != 0) {
+        ui->Print("\n-- Couldn't mount %s.\n", SDCARD_ROOT);
+        return INSTALL_ERROR;
+    }
+
+    char* path = browse_directory(SDCARD_ROOT, device);
+    if (path == NULL) {
+        ui->Print("\n-- No package file selected.\n");
+        return INSTALL_ERROR;
+    }
+
+    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);
+    return status;
+}
+
 // 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());
-
     for (;;) {
         finish_recovery(NULL);
         switch (status) {
@@ -841,14 +793,14 @@
         }
         ui->SetProgressType(RecoveryUI::EMPTY);
 
-        int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device);
+        int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), 0, 0, device);
 
         // device-specific code may take some action here.  It may
         // return one of the core actions handled in the switch
         // statement below.
         Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item);
 
-        int wipe_cache = 0;
+        bool should_wipe_cache = false;
         switch (chosen_action) {
             case Device::NO_ACTION:
                 break;
@@ -864,63 +816,26 @@
                 break;
 
             case Device::WIPE_CACHE:
-                ui->Print("\n-- Wiping cache...\n");
-                erase_volume("/cache");
-                ui->Print("Cache wipe complete.\n");
+                wipe_cache(ui->IsTextVisible(), device);
                 if (!ui->IsTextVisible()) return Device::NO_ACTION;
                 break;
 
-            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")) {
-                        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 Device::NO_ACTION;  // reboot if logs aren't visible
-                    } else {
-                        ui->Print("\nInstall from sdcard complete.\n");
-                    }
-                }
-                break;
-            }
-
-            case Device::APPLY_CACHE:
-                ui->Print("\nAPPLY_CACHE is deprecated.\n");
-                break;
-
-            case Device::READ_RECOVERY_LASTLOG:
-                choose_recovery_file(device);
-                break;
-
             case Device::APPLY_ADB_SIDELOAD:
-                status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE);
-                if (status >= 0) {
+            case Device::APPLY_SDCARD:
+                {
+                    bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD);
+                    if (adb) {
+                        status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
+                    } else {
+                        status = apply_from_sdcard(device, &should_wipe_cache);
+                    }
+
+                    if (status == INSTALL_SUCCESS && should_wipe_cache) {
+                        if (!wipe_cache(false, device)) {
+                            status = INSTALL_ERROR;
+                        }
+                    }
+
                     if (status != INSTALL_SUCCESS) {
                         ui->SetBackground(RecoveryUI::ERROR);
                         ui->Print("Installation aborted.\n");
@@ -928,10 +843,20 @@
                     } else if (!ui->IsTextVisible()) {
                         return Device::NO_ACTION;  // reboot if logs aren't visible
                     } else {
-                        ui->Print("\nInstall from ADB complete.\n");
+                        ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card");
                     }
                 }
                 break;
+
+            case Device::VIEW_RECOVERY_LOGS:
+                choose_recovery_file(device);
+                break;
+
+            case Device::MOUNT_SYSTEM:
+                if (ensure_path_mounted("/system") != -1) {
+                    ui->Print("Mounted /system.\n");
+                }
+                break;
         }
     }
 }
@@ -992,31 +917,35 @@
     // only way recovery should be run with this argument is when it
     // starts a copy of itself from the apply_from_adb() function.
     if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
-        adb_main();
+        adb_main(0, DEFAULT_ADB_PORT);
         return 0;
     }
 
     printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
 
     load_volume_table();
-    ensure_path_mounted(LAST_LOG_FILE);
-    rotate_last_logs(KEEP_LOG_COUNT);
     get_args(&argc, &argv);
 
     const char *send_intent = NULL;
     const char *update_package = NULL;
-    int wipe_data = 0, wipe_cache = 0, show_text = 0;
+    bool should_wipe_data = false;
+    bool should_wipe_cache = false;
+    bool show_text = false;
+    bool sideload = false;
+    bool sideload_auto_reboot = false;
     bool just_exit = false;
     bool shutdown_after = false;
 
     int arg;
     while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
         switch (arg) {
-        case 's': send_intent = optarg; break;
+        case 'i': send_intent = optarg; break;
         case 'u': update_package = optarg; break;
-        case 'w': wipe_data = wipe_cache = 1; break;
-        case 'c': wipe_cache = 1; break;
-        case 't': show_text = 1; break;
+        case 'w': should_wipe_data = true; break;
+        case 'c': should_wipe_cache = true; break;
+        case 't': show_text = true; break;
+        case 's': sideload = true; break;
+        case 'a': sideload = true; sideload_auto_reboot = true; break;
         case 'x': just_exit = true; break;
         case 'l': locale = optarg; break;
         case 'g': {
@@ -1092,52 +1021,78 @@
     printf("\n");
 
     property_list(print_property, NULL);
-    property_get("ro.build.display.id", recovery_version, "");
     printf("\n");
 
+    ui->Print("Supported API: %d\n", RECOVERY_API_VERSION);
+
     int status = INSTALL_SUCCESS;
 
     if (update_package != NULL) {
-        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.");
-            }
+        status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true);
+        if (status == INSTALL_SUCCESS && should_wipe_cache) {
+            wipe_cache(false, device);
         }
         if (status != INSTALL_SUCCESS) {
             ui->Print("Installation aborted.\n");
-            ui->Print("OTA failed! Please power off the device to keep it in this state and file a bug report!\n");
 
             // If this is an eng or userdebug build, then automatically
             // turn the text display on if the script fails so the error
             // message is visible.
-            char buffer[PROPERTY_VALUE_MAX+1];
-            property_get("ro.build.fingerprint", buffer, "");
-            if (strstr(buffer, ":userdebug/") || strstr(buffer, ":eng/")) {
+            if (is_ro_debuggable()) {
                 ui->ShowText(true);
             }
         }
-    } else if (wipe_data) {
-        if (device->WipeData()) status = INSTALL_ERROR;
-        if (erase_volume("/data")) status = INSTALL_ERROR;
-        if (wipe_cache && erase_volume("/cache")) 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;
-        if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");
+    } else if (should_wipe_data) {
+        if (!wipe_data(false, device)) {
+            status = INSTALL_ERROR;
+        }
+    } else if (should_wipe_cache) {
+        if (!wipe_cache(false, device)) {
+            status = INSTALL_ERROR;
+        }
+    } else if (sideload) {
+        // 'adb reboot sideload' acts the same as user presses key combinations
+        // to enter the sideload mode. When 'sideload-auto-reboot' is used, text
+        // display will NOT be turned on by default. And it will reboot after
+        // sideload finishes even if there are errors. Unless one turns on the
+        // text display during the installation. This is to enable automated
+        // testing.
+        if (!sideload_auto_reboot) {
+            ui->ShowText(true);
+        }
+        status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
+        if (status == INSTALL_SUCCESS && should_wipe_cache) {
+            if (!wipe_cache(false, device)) {
+                status = INSTALL_ERROR;
+            }
+        }
+        ui->Print("\nInstall from ADB complete (status: %d).\n", status);
+        if (sideload_auto_reboot) {
+            ui->Print("Rebooting automatically.\n");
+        }
     } else if (!just_exit) {
         status = INSTALL_NONE;  // No command specified
         ui->SetBackground(RecoveryUI::NO_COMMAND);
+
+        // http://b/17489952
+        // If this is an eng or userdebug build, automatically turn on the
+        // text display if no command is specified.
+        if (is_ro_debuggable()) {
+            ui->ShowText(true);
+        }
     }
 
-    if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
+    if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {
         copy_logs();
         ui->SetBackground(RecoveryUI::ERROR);
     }
+
     Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
-    if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
+    if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) {
         Device::BuiltinAction temp = prompt_and_wait(device, status);
-        if (temp != Device::NO_ACTION) after = temp;
+        if (temp != Device::NO_ACTION) {
+            after = temp;
+        }
     }
 
     // Save logs and clean up before rebooting or shutting down.
diff --git a/roots.cpp b/roots.cpp
index ee14016..2bd457e 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -111,9 +111,10 @@
         }
         return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0);
     } else if (strcmp(v->fs_type, "ext4") == 0 ||
+               strcmp(v->fs_type, "squashfs") == 0 ||
                strcmp(v->fs_type, "vfat") == 0) {
         result = mount(v->blk_device, v->mount_point, v->fs_type,
-                       MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");
+                       v->flags, v->fs_options);
         if (result == 0) return 0;
 
         LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 03ef049..ff95915 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -28,6 +28,10 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <vector>
+
+#include "base/strings.h"
+#include "cutils/properties.h"
 #include "common.h"
 #include "device.h"
 #include "minui/minui.h"
@@ -37,67 +41,60 @@
 static int char_width;
 static int char_height;
 
-// There's only (at most) one of these objects, and global callbacks
-// (for pthread_create, and the input event system) need to find it,
-// so use a global variable.
-static ScreenRecoveryUI* self = NULL;
-
 // Return the current time as a double (including fractions of a second).
 static double now() {
     struct timeval tv;
-    gettimeofday(&tv, NULL);
+    gettimeofday(&tv, nullptr);
     return tv.tv_sec + tv.tv_usec / 1000000.0;
 }
 
 ScreenRecoveryUI::ScreenRecoveryUI() :
     currentIcon(NONE),
     installingFrame(0),
-    locale(NULL),
+    locale(nullptr),
     rtl_locale(false),
     progressBarType(EMPTY),
     progressScopeStart(0),
     progressScopeSize(0),
     progress(0),
     pagesIdentical(false),
-    text_cols(0),
-    text_rows(0),
-    text_col(0),
-    text_row(0),
-    text_top(0),
+    text_cols_(0),
+    text_rows_(0),
+    text_(nullptr),
+    text_col_(0),
+    text_row_(0),
+    text_top_(0),
     show_text(false),
     show_text_ever(false),
+    menu_(nullptr),
     show_menu(false),
-    menu_top(0),
     menu_items(0),
     menu_sel(0),
+    file_viewer_text_(nullptr),
     animation_fps(20),
     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;
+    for (int i = 0; i < 5; i++) {
+        backgroundIcon[i] = nullptr;
+    }
+    pthread_mutex_init(&updateMutex, nullptr);
 }
 
 // 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)
-{
+void ScreenRecoveryUI::draw_background_locked(Icon icon) {
     pagesIdentical = false;
     gr_color(0, 0, 0, 255);
     gr_clear();
 
     if (icon) {
-        gr_surface surface = backgroundIcon[icon];
+        GRSurface* surface = backgroundIcon[icon];
         if (icon == INSTALLING_UPDATE || icon == ERASING) {
             surface = installation[installingFrame];
         }
-        gr_surface text_surface = backgroundText[icon];
+        GRSurface* text_surface = backgroundText[icon];
 
         int iconWidth = gr_get_width(surface);
         int iconHeight = gr_get_height(surface);
@@ -132,12 +129,11 @@
 
 // Draw the progress bar (if any) on the screen.  Does not flip pages.
 // Should only be called with updateMutex locked.
-void ScreenRecoveryUI::draw_progress_locked()
-{
+void ScreenRecoveryUI::draw_progress_locked() {
     if (currentIcon == ERROR) return;
 
     if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
-        gr_surface icon = installation[installingFrame];
+        GRSurface* icon = installation[installingFrame];
         gr_blit(icon, 0, 0, gr_get_width(icon), gr_get_height(icon), iconX, iconY);
     }
 
@@ -180,6 +176,9 @@
 
 void ScreenRecoveryUI::SetColor(UIElement e) {
     switch (e) {
+        case INFO:
+            gr_color(249, 194, 0, 255);
+            break;
         case HEADER:
             gr_color(247, 0, 6, 255);
             break;
@@ -187,11 +186,14 @@
         case MENU_SEL_BG:
             gr_color(0, 106, 157, 255);
             break;
+        case MENU_SEL_BG_ACTIVE:
+            gr_color(0, 156, 100, 255);
+            break;
         case MENU_SEL_FG:
             gr_color(255, 255, 255, 255);
             break;
         case LOG:
-            gr_color(249, 194, 0, 255);
+            gr_color(196, 196, 196, 255);
             break;
         case TEXT_FILL:
             gr_color(0, 0, 0, 160);
@@ -202,10 +204,38 @@
     }
 }
 
+void ScreenRecoveryUI::DrawHorizontalRule(int* y) {
+    SetColor(MENU);
+    *y += 4;
+    gr_fill(0, *y, gr_fb_width(), *y + 2);
+    *y += 4;
+}
+
+void ScreenRecoveryUI::DrawTextLine(int* y, const char* line, bool bold) {
+    gr_text(4, *y, line, bold);
+    *y += char_height + 4;
+}
+
+void ScreenRecoveryUI::DrawTextLines(int* y, const char* const* lines) {
+    for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
+        DrawTextLine(y, lines[i], false);
+    }
+}
+
+static const char* REGULAR_HELP[] = {
+    "Use volume up/down and power.",
+    NULL
+};
+
+static const char* LONG_PRESS_HELP[] = {
+    "Any button cycles highlight.",
+    "Long-press activates.",
+    NULL
+};
+
 // Redraw everything on the screen.  Does not flip pages.
 // Should only be called with updateMutex locked.
-void ScreenRecoveryUI::draw_screen_locked()
-{
+void ScreenRecoveryUI::draw_screen_locked() {
     if (!show_text) {
         draw_background_locked(currentIcon);
         draw_progress_locked();
@@ -214,62 +244,66 @@
         gr_clear();
 
         int y = 0;
-        int i = 0;
         if (show_menu) {
+            char recovery_fingerprint[PROPERTY_VALUE_MAX];
+            property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, "");
+
+            SetColor(INFO);
+            DrawTextLine(&y, "Android Recovery", true);
+            for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) {
+                DrawTextLine(&y, chunk.c_str(), false);
+            }
+            DrawTextLines(&y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
+
             SetColor(HEADER);
+            DrawTextLines(&y, menu_headers_);
 
-            for (; i < menu_top + menu_items; ++i) {
-                if (i == menu_top) SetColor(MENU);
-
-                if (i == menu_top + menu_sel) {
-                    // draw the highlight bar
-                    SetColor(MENU_SEL_BG);
-                    gr_fill(0, y-2, gr_fb_width(), y+char_height+2);
-                    // white text of selected item
+            SetColor(MENU);
+            DrawHorizontalRule(&y);
+            y += 4;
+            for (int i = 0; i < menu_items; ++i) {
+                if (i == menu_sel) {
+                    // Draw the highlight bar.
+                    SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG);
+                    gr_fill(0, y - 2, gr_fb_width(), y + char_height + 2);
+                    // Bold white text for the selected item.
                     SetColor(MENU_SEL_FG);
-                    if (menu[i][0]) gr_text(4, y, menu[i], 1);
+                    gr_text(4, y, menu_[i], true);
                     SetColor(MENU);
                 } else {
-                    if (menu[i][0]) gr_text(4, y, menu[i], i < menu_top);
+                    gr_text(4, y, menu_[i], false);
                 }
-                y += char_height+4;
+                y += char_height + 4;
             }
-            SetColor(MENU);
-            y += 4;
-            gr_fill(0, y, gr_fb_width(), y+2);
-            y += 4;
-            ++i;
+            DrawHorizontalRule(&y);
         }
 
-        SetColor(LOG);
-
         // display from the bottom up, until we hit the top of the
         // screen, the bottom of the menu, or we've displayed the
         // entire text buffer.
-        int ty;
-        int row = (text_top+text_rows-1) % text_rows;
-        for (int ty = gr_fb_height() - char_height, count = 0;
-             ty > y+2 && count < text_rows;
+        SetColor(LOG);
+        int row = (text_top_ + text_rows_ - 1) % text_rows_;
+        size_t count = 0;
+        for (int ty = gr_fb_height() - char_height;
+             ty >= y && count < text_rows_;
              ty -= char_height, ++count) {
-            gr_text(4, ty, text[row], 0);
+            gr_text(0, ty, text_[row], false);
             --row;
-            if (row < 0) row = text_rows-1;
+            if (row < 0) row = text_rows_ - 1;
         }
     }
 }
 
 // Redraw everything on the screen and flip the screen (make it visible).
 // Should only be called with updateMutex locked.
-void ScreenRecoveryUI::update_screen_locked()
-{
+void ScreenRecoveryUI::update_screen_locked() {
     draw_screen_locked();
     gr_flip();
 }
 
 // Updates only the progress bar, if possible, otherwise redraws the screen.
 // Should only be called with updateMutex locked.
-void ScreenRecoveryUI::update_progress_locked()
-{
+void ScreenRecoveryUI::update_progress_locked() {
     if (show_text || !pagesIdentical) {
         draw_screen_locked();    // Must redraw the whole screen
         pagesIdentical = true;
@@ -280,14 +314,14 @@
 }
 
 // Keeps the progress bar updated, even when the process is otherwise busy.
-void* ScreenRecoveryUI::progress_thread(void *cookie) {
-    self->progress_loop();
-    return NULL;
+void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) {
+    reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop();
+    return nullptr;
 }
 
-void ScreenRecoveryUI::progress_loop() {
+void ScreenRecoveryUI::ProgressThreadLoop() {
     double interval = 1.0 / animation_fps;
-    for (;;) {
+    while (true) {
         double start = now();
         pthread_mutex_lock(&updateMutex);
 
@@ -324,44 +358,53 @@
     }
 }
 
-void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) {
+void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** 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) {
+void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, GRSurface*** 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) {
+void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) {
     int result = res_create_localized_alpha_surface(filename, locale, surface);
     if (result < 0) {
         LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
     }
 }
 
-void ScreenRecoveryUI::Init()
-{
+static char** Alloc2d(size_t rows, size_t cols) {
+    char** result = new char*[rows];
+    for (size_t i = 0; i < rows; ++i) {
+        result[i] = new char[cols];
+        memset(result[i], 0, cols);
+    }
+    return result;
+}
+
+void ScreenRecoveryUI::Init() {
     gr_init();
 
     gr_font_size(&char_width, &char_height);
+    text_rows_ = gr_fb_height() / char_height;
+    text_cols_ = gr_fb_width() / char_width;
 
-    text_col = text_row = 0;
-    text_rows = gr_fb_height() / char_height;
-    if (text_rows > kMaxRows) text_rows = kMaxRows;
-    text_top = 1;
+    text_ = Alloc2d(text_rows_, text_cols_ + 1);
+    file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
+    menu_ = Alloc2d(text_rows_, text_cols_ + 1);
 
-    text_cols = gr_fb_width() / char_width;
-    if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1;
+    text_col_ = text_row_ = 0;
+    text_top_ = 1;
 
-    backgroundIcon[NONE] = NULL;
+    backgroundIcon[NONE] = nullptr;
     LoadBitmapArray("icon_installing", &installing_frames, &installation);
-    backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : NULL;
+    backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : nullptr;
     backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
     LoadBitmap("icon_error", &backgroundIcon[ERROR]);
     backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
@@ -376,7 +419,7 @@
     LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]);
     LoadLocalizedBitmap("error_text", &backgroundText[ERROR]);
 
-    pthread_create(&progress_t, NULL, progress_thread, NULL);
+    pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this);
 
     RecoveryUI::Init();
 }
@@ -403,12 +446,11 @@
         }
         free(lang);
     } else {
-        new_locale = NULL;
+        new_locale = nullptr;
     }
 }
 
-void ScreenRecoveryUI::SetBackground(Icon icon)
-{
+void ScreenRecoveryUI::SetBackground(Icon icon) {
     pthread_mutex_lock(&updateMutex);
 
     currentIcon = icon;
@@ -417,8 +459,7 @@
     pthread_mutex_unlock(&updateMutex);
 }
 
-void ScreenRecoveryUI::SetProgressType(ProgressType type)
-{
+void ScreenRecoveryUI::SetProgressType(ProgressType type) {
     pthread_mutex_lock(&updateMutex);
     if (progressBarType != type) {
         progressBarType = type;
@@ -430,8 +471,7 @@
     pthread_mutex_unlock(&updateMutex);
 }
 
-void ScreenRecoveryUI::ShowProgress(float portion, float seconds)
-{
+void ScreenRecoveryUI::ShowProgress(float portion, float seconds) {
     pthread_mutex_lock(&updateMutex);
     progressBarType = DETERMINATE;
     progressScopeStart += progressScopeSize;
@@ -443,8 +483,7 @@
     pthread_mutex_unlock(&updateMutex);
 }
 
-void ScreenRecoveryUI::SetProgress(float fraction)
-{
+void ScreenRecoveryUI::SetProgress(float fraction) {
     pthread_mutex_lock(&updateMutex);
     if (fraction < 0.0) fraction = 0.0;
     if (fraction > 1.0) fraction = 1.0;
@@ -467,8 +506,7 @@
     pthread_mutex_unlock(&updateMutex);
 }
 
-void ScreenRecoveryUI::Print(const char *fmt, ...)
-{
+void ScreenRecoveryUI::Print(const char *fmt, ...) {
     char buf[256];
     va_list ap;
     va_start(ap, fmt);
@@ -477,43 +515,133 @@
 
     fputs(buf, stdout);
 
-    // This can get called before ui_init(), so be careful.
     pthread_mutex_lock(&updateMutex);
-    if (text_rows > 0 && text_cols > 0) {
-        char *ptr;
-        for (ptr = buf; *ptr != '\0'; ++ptr) {
-            if (*ptr == '\n' || text_col >= text_cols) {
-                text[text_row][text_col] = '\0';
-                text_col = 0;
-                text_row = (text_row + 1) % text_rows;
-                if (text_row == text_top) text_top = (text_top + 1) % text_rows;
+    if (text_rows_ > 0 && text_cols_ > 0) {
+        for (const char* ptr = buf; *ptr != '\0'; ++ptr) {
+            if (*ptr == '\n' || text_col_ >= text_cols_) {
+                text_[text_row_][text_col_] = '\0';
+                text_col_ = 0;
+                text_row_ = (text_row_ + 1) % text_rows_;
+                if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
             }
-            if (*ptr != '\n') text[text_row][text_col++] = *ptr;
+            if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr;
         }
-        text[text_row][text_col] = '\0';
+        text_[text_row_][text_col_] = '\0';
         update_screen_locked();
     }
     pthread_mutex_unlock(&updateMutex);
 }
 
+void ScreenRecoveryUI::PutChar(char ch) {
+    pthread_mutex_lock(&updateMutex);
+    if (ch != '\n') text_[text_row_][text_col_++] = ch;
+    if (ch == '\n' || text_col_ >= text_cols_) {
+        text_col_ = 0;
+        ++text_row_;
+
+        if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_;
+    }
+    pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::ClearText() {
+    pthread_mutex_lock(&updateMutex);
+    text_col_ = 0;
+    text_row_ = 0;
+    text_top_ = 1;
+    for (size_t i = 0; i < text_rows_; ++i) {
+        memset(text_[i], 0, text_cols_ + 1);
+    }
+    pthread_mutex_unlock(&updateMutex);
+}
+
+void ScreenRecoveryUI::ShowFile(FILE* fp) {
+    std::vector<long> offsets;
+    offsets.push_back(ftell(fp));
+    ClearText();
+
+    struct stat sb;
+    fstat(fileno(fp), &sb);
+
+    bool show_prompt = false;
+    while (true) {
+        if (show_prompt) {
+            Print("--(%d%% of %d bytes)--",
+                  static_cast<int>(100 * (double(ftell(fp)) / double(sb.st_size))),
+                  static_cast<int>(sb.st_size));
+            Redraw();
+            while (show_prompt) {
+                show_prompt = false;
+                int key = WaitKey();
+                if (key == KEY_POWER || key == KEY_ENTER) {
+                    return;
+                } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
+                    if (offsets.size() <= 1) {
+                        show_prompt = true;
+                    } else {
+                        offsets.pop_back();
+                        fseek(fp, offsets.back(), SEEK_SET);
+                    }
+                } else {
+                    if (feof(fp)) {
+                        return;
+                    }
+                    offsets.push_back(ftell(fp));
+                }
+            }
+            ClearText();
+        }
+
+        int ch = getc(fp);
+        if (ch == EOF) {
+            while (text_row_ < text_rows_ - 1) PutChar('\n');
+            show_prompt = true;
+        } else {
+            PutChar(ch);
+            if (text_col_ == 0 && text_row_ >= text_rows_ - 1) {
+                show_prompt = true;
+            }
+        }
+    }
+}
+
+void ScreenRecoveryUI::ShowFile(const char* filename) {
+    FILE* fp = fopen_path(filename, "re");
+    if (fp == nullptr) {
+        Print("  Unable to open %s: %s\n", filename, strerror(errno));
+        return;
+    }
+
+    char** old_text = text_;
+    size_t old_text_col = text_col_;
+    size_t old_text_row = text_row_;
+    size_t old_text_top = text_top_;
+
+    // Swap in the alternate screen and clear it.
+    text_ = file_viewer_text_;
+    ClearText();
+
+    ShowFile(fp);
+    fclose(fp);
+
+    text_ = old_text;
+    text_col_ = old_text_col;
+    text_row_ = old_text_row;
+    text_top_ = old_text_top;
+}
+
 void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items,
                                  int initial_selection) {
-    int i;
     pthread_mutex_lock(&updateMutex);
-    if (text_rows > 0 && text_cols > 0) {
-        for (i = 0; i < text_rows; ++i) {
-            if (headers[i] == NULL) break;
-            strncpy(menu[i], headers[i], text_cols-1);
-            menu[i][text_cols-1] = '\0';
+    if (text_rows_ > 0 && text_cols_ > 0) {
+        menu_headers_ = headers;
+        size_t i = 0;
+        for (; i < text_rows_ && items[i] != nullptr; ++i) {
+            strncpy(menu_[i], items[i], text_cols_ - 1);
+            menu_[i][text_cols_ - 1] = '\0';
         }
-        menu_top = i;
-        for (; i < text_rows; ++i) {
-            if (items[i-menu_top] == NULL) break;
-            strncpy(menu[i], items[i-menu_top], text_cols-1);
-            menu[i][text_cols-1] = '\0';
-        }
-        menu_items = i - menu_top;
-        show_menu = 1;
+        menu_items = i;
+        show_menu = true;
         menu_sel = initial_selection;
         update_screen_locked();
     }
@@ -521,13 +649,15 @@
 }
 
 int ScreenRecoveryUI::SelectMenu(int sel) {
-    int old_sel;
     pthread_mutex_lock(&updateMutex);
-    if (show_menu > 0) {
-        old_sel = menu_sel;
+    if (show_menu) {
+        int old_sel = menu_sel;
         menu_sel = sel;
-        if (menu_sel < 0) menu_sel = 0;
-        if (menu_sel >= menu_items) menu_sel = menu_items-1;
+
+        // Wrap at top and bottom.
+        if (menu_sel < 0) menu_sel = menu_items - 1;
+        if (menu_sel >= menu_items) menu_sel = 0;
+
         sel = menu_sel;
         if (menu_sel != old_sel) update_screen_locked();
     }
@@ -536,43 +666,44 @@
 }
 
 void ScreenRecoveryUI::EndMenu() {
-    int i;
     pthread_mutex_lock(&updateMutex);
-    if (show_menu > 0 && text_rows > 0 && text_cols > 0) {
-        show_menu = 0;
+    if (show_menu && text_rows_ > 0 && text_cols_ > 0) {
+        show_menu = false;
         update_screen_locked();
     }
     pthread_mutex_unlock(&updateMutex);
 }
 
-bool ScreenRecoveryUI::IsTextVisible()
-{
+bool ScreenRecoveryUI::IsTextVisible() {
     pthread_mutex_lock(&updateMutex);
     int visible = show_text;
     pthread_mutex_unlock(&updateMutex);
     return visible;
 }
 
-bool ScreenRecoveryUI::WasTextEverVisible()
-{
+bool ScreenRecoveryUI::WasTextEverVisible() {
     pthread_mutex_lock(&updateMutex);
     int ever_visible = show_text_ever;
     pthread_mutex_unlock(&updateMutex);
     return ever_visible;
 }
 
-void ScreenRecoveryUI::ShowText(bool visible)
-{
+void ScreenRecoveryUI::ShowText(bool visible) {
     pthread_mutex_lock(&updateMutex);
     show_text = visible;
-    if (show_text) show_text_ever = 1;
+    if (show_text) show_text_ever = true;
     update_screen_locked();
     pthread_mutex_unlock(&updateMutex);
 }
 
-void ScreenRecoveryUI::Redraw()
-{
+void ScreenRecoveryUI::Redraw() {
     pthread_mutex_lock(&updateMutex);
     update_screen_locked();
     pthread_mutex_unlock(&updateMutex);
 }
+
+void ScreenRecoveryUI::KeyLongPress(int) {
+    // Redraw so that if we're in the menu, the highlight
+    // will change color to indicate a successful long press.
+    Redraw();
+}
diff --git a/screen_ui.h b/screen_ui.h
index 01a33bf..ea05bf1 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -18,6 +18,7 @@
 #define RECOVERY_SCREEN_UI_H
 
 #include <pthread.h>
+#include <stdio.h>
 
 #include "ui.h"
 #include "minui/minui.h"
@@ -47,18 +48,23 @@
     bool WasTextEverVisible();
 
     // printing messages
-    void Print(const char* fmt, ...); // __attribute__((format(printf, 1, 2)));
+    void Print(const char* fmt, ...) __printflike(2, 3);
+    void ShowFile(const char* filename);
 
     // menu display
     void StartMenu(const char* const * headers, const char* const * items,
-                           int initial_selection);
+                   int initial_selection);
     int SelectMenu(int sel);
     void EndMenu();
 
+    void KeyLongPress(int);
+
     void Redraw();
 
-    enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL };
-    virtual void SetColor(UIElement e);
+    enum UIElement {
+        HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO
+    };
+    void SetColor(UIElement e);
 
   private:
     Icon currentIcon;
@@ -67,43 +73,43 @@
     bool rtl_locale;
 
     pthread_mutex_t updateMutex;
-    gr_surface backgroundIcon[5];
-    gr_surface backgroundText[5];
-    gr_surface *installation;
-    gr_surface progressBarEmpty;
-    gr_surface progressBarFill;
-    gr_surface stageMarkerEmpty;
-    gr_surface stageMarkerFill;
+    GRSurface* backgroundIcon[5];
+    GRSurface* backgroundText[5];
+    GRSurface** installation;
+    GRSurface* progressBarEmpty;
+    GRSurface* progressBarFill;
+    GRSurface* stageMarkerEmpty;
+    GRSurface* stageMarkerFill;
 
     ProgressType progressBarType;
 
     float progressScopeStart, progressScopeSize, progress;
     double progressScopeTime, progressScopeDuration;
 
-    // true when both graphics pages are the same (except for the
-    // progress bar)
+    // true when both graphics pages are the same (except for the progress bar).
     bool pagesIdentical;
 
-    static const int kMaxCols = 96;
-    static const int kMaxRows = 96;
+    size_t text_cols_, text_rows_;
 
-    // Log text overlay, displayed when a magic key is pressed
-    char text[kMaxRows][kMaxCols];
-    int text_cols, text_rows;
-    int text_col, text_row, text_top;
+    // Log text overlay, displayed when a magic key is pressed.
+    char** text_;
+    size_t text_col_, text_row_, text_top_;
+
     bool show_text;
     bool show_text_ever;   // has show_text ever been true?
 
-    char menu[kMaxRows][kMaxCols];
+    char** menu_;
+    const char* const* menu_headers_;
     bool show_menu;
-    int menu_top, menu_items, menu_sel;
+    int menu_items, menu_sel;
 
-    pthread_t progress_t;
+    // An alternate text screen, swapped with 'text_' when we're viewing a log file.
+    char** file_viewer_text_;
+
+    pthread_t progress_thread_;
 
     int animation_fps;
     int installing_frames;
-  protected:
-  private:
 
     int iconX, iconY;
 
@@ -114,12 +120,21 @@
     void draw_screen_locked();
     void update_screen_locked();
     void update_progress_locked();
-    static void* progress_thread(void* cookie);
-    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);
+    static void* ProgressThreadStartRoutine(void* data);
+    void ProgressThreadLoop();
+
+    void ShowFile(FILE*);
+    void PutChar(char);
+    void ClearText();
+
+    void DrawHorizontalRule(int* y);
+    void DrawTextLine(int* y, const char* line, bool bold);
+    void DrawTextLines(int* y, const char* const* lines);
+
+    void LoadBitmap(const char* filename, GRSurface** surface);
+    void LoadBitmapArray(const char* filename, int* frames, GRSurface*** surface);
+    void LoadLocalizedBitmap(const char* filename, GRSurface** surface);
 };
 
 #endif  // RECOVERY_UI_H
diff --git a/tests/Android.mk b/tests/Android.mk
index 4d99d52..02a272a 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -1,26 +1,25 @@
-# Build the unit tests.
+#
+# 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.
+#
+
 LOCAL_PATH := $(call my-dir)
+
 include $(CLEAR_VARS)
-
-# Build the unit tests.
-test_src_files := \
-    asn1_decoder_test.cpp
-
-shared_libraries := \
-    liblog \
-    libcutils
-
-static_libraries := \
-    libgtest \
-    libgtest_main \
-    libverifier
-
-$(foreach file,$(test_src_files), \
-    $(eval include $(CLEAR_VARS)) \
-    $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
-    $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
-    $(eval LOCAL_SRC_FILES := $(file)) \
-    $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
-    $(eval LOCAL_C_INCLUDES := $(LOCAL_PATH)/..) \
-    $(eval include $(BUILD_NATIVE_TEST)) \
-)
\ No newline at end of file
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+LOCAL_STATIC_LIBRARIES := libverifier
+LOCAL_SRC_FILES := asn1_decoder_test.cpp
+LOCAL_MODULE := asn1_decoder_test
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
+include $(BUILD_NATIVE_TEST)
diff --git a/tools/ota/check-lost+found.c b/tools/ota/check-lost+found.c
index da02f46..8ce12d3 100644
--- a/tools/ota/check-lost+found.c
+++ b/tools/ota/check-lost+found.c
@@ -26,6 +26,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <time.h>
+#include <unistd.h>
 
 #include "private/android_filesystem_config.h"
 
@@ -77,7 +78,7 @@
                 snprintf(fn, sizeof(fn), "%s/%s", kPartitions[i], "dirty");
                 fd = open(fn, O_WRONLY|O_CREAT, 0444);
                 if (fd >= 0) {  // Don't sweat it if we can't write the file.
-                    write(fd, fn, sizeof(fn));  // write, you know, some data
+                    TEMP_FAILURE_RETRY(write(fd, fn, sizeof(fn)));  // write, you know, some data
                     close(fd);
                     unlink(fn);
                 }
diff --git a/ui.cpp b/ui.cpp
index c8f08cd..1a0b079 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -39,40 +39,59 @@
 
 #define UI_WAIT_KEY_TIMEOUT_SEC    120
 
-// There's only (at most) one of these objects, and global callbacks
-// (for pthread_create, and the input event system) need to find it,
-// so use a global variable.
-static RecoveryUI* self = NULL;
-
-RecoveryUI::RecoveryUI() :
-    key_queue_len(0),
-    key_last_down(-1),
-    key_long_press(false),
-    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;
+RecoveryUI::RecoveryUI()
+        : key_queue_len(0),
+          key_last_down(-1),
+          key_long_press(false),
+          key_down_count(0),
+          enable_reboot(true),
+          consecutive_power_keys(0),
+          last_key(-1),
+          has_power_key(false),
+          has_up_key(false),
+          has_down_key(false) {
+    pthread_mutex_init(&key_queue_mutex, nullptr);
+    pthread_cond_init(&key_queue_cond, nullptr);
     memset(key_pressed, 0, sizeof(key_pressed));
 }
 
-void RecoveryUI::Init() {
-    ev_init(input_callback, NULL);
-    pthread_create(&input_t, NULL, input_thread, NULL);
+void RecoveryUI::OnKeyDetected(int key_code) {
+    if (key_code == KEY_POWER) {
+        has_power_key = true;
+    } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) {
+        has_down_key = true;
+    } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) {
+        has_up_key = true;
+    }
 }
 
+int RecoveryUI::InputCallback(int fd, uint32_t epevents, void* data) {
+    return reinterpret_cast<RecoveryUI*>(data)->OnInputEvent(fd, epevents);
+}
 
-int RecoveryUI::input_callback(int fd, uint32_t epevents, void* data)
-{
+// Reads input events, handles special hot keys, and adds to the key queue.
+static void* InputThreadLoop(void*) {
+    while (true) {
+        if (!ev_wait(-1)) {
+            ev_dispatch();
+        }
+    }
+    return nullptr;
+}
+
+void RecoveryUI::Init() {
+    ev_init(InputCallback, this);
+
+    ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+
+    pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);
+}
+
+int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
     struct input_event ev;
-    int ret;
-
-    ret = ev_get_input(fd, epevents, &ev);
-    if (ret)
+    if (ev_get_input(fd, epevents, &ev) == -1) {
         return -1;
+    }
 
     if (ev.type == EV_SYN) {
         return 0;
@@ -82,23 +101,24 @@
             // the trackball.  When it exceeds a threshold
             // (positive or negative), fake an up/down
             // key event.
-            self->rel_sum += ev.value;
-            if (self->rel_sum > 3) {
-                self->process_key(KEY_DOWN, 1);   // press down key
-                self->process_key(KEY_DOWN, 0);   // and release it
-                self->rel_sum = 0;
-            } else if (self->rel_sum < -3) {
-                self->process_key(KEY_UP, 1);     // press up key
-                self->process_key(KEY_UP, 0);     // and release it
-                self->rel_sum = 0;
+            rel_sum += ev.value;
+            if (rel_sum > 3) {
+                ProcessKey(KEY_DOWN, 1);   // press down key
+                ProcessKey(KEY_DOWN, 0);   // and release it
+                rel_sum = 0;
+            } else if (rel_sum < -3) {
+                ProcessKey(KEY_UP, 1);     // press up key
+                ProcessKey(KEY_UP, 0);     // and release it
+                rel_sum = 0;
             }
         }
     } else {
-        self->rel_sum = 0;
+        rel_sum = 0;
     }
 
-    if (ev.type == EV_KEY && ev.code <= KEY_MAX)
-        self->process_key(ev.code, ev.value);
+    if (ev.type == EV_KEY && ev.code <= KEY_MAX) {
+        ProcessKey(ev.code, ev.value);
+    }
 
     return 0;
 }
@@ -115,7 +135,7 @@
 // a key is registered.
 //
 // updown == 1 for key down events; 0 for key up events
-void RecoveryUI::process_key(int key_code, int updown) {
+void RecoveryUI::ProcessKey(int key_code, int updown) {
     bool register_key = false;
     bool long_press = false;
     bool reboot_enabled;
@@ -126,13 +146,13 @@
         ++key_down_count;
         key_last_down = key_code;
         key_long_press = false;
-        pthread_t th;
         key_timer_t* info = new key_timer_t;
         info->ui = this;
         info->key_code = key_code;
         info->count = key_down_count;
-        pthread_create(&th, NULL, &RecoveryUI::time_key_helper, info);
-        pthread_detach(th);
+        pthread_t thread;
+        pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info);
+        pthread_detach(thread);
     } else {
         if (key_last_down == key_code) {
             long_press = key_long_press;
@@ -144,8 +164,7 @@
     pthread_mutex_unlock(&key_queue_mutex);
 
     if (register_key) {
-        NextCheckKeyIsLong(long_press);
-        switch (CheckKey(key_code)) {
+        switch (CheckKey(key_code, long_press)) {
           case RecoveryUI::IGNORE:
             break;
 
@@ -162,13 +181,6 @@
           case RecoveryUI::ENQUEUE:
             EnqueueKey(key_code);
             break;
-
-          case RecoveryUI::MOUNT_SYSTEM:
-#ifndef NO_RECOVERY_MOUNT
-            ensure_path_mounted("/system");
-            Print("Mounted /system.");
-#endif
-            break;
         }
     }
 }
@@ -177,7 +189,7 @@
     key_timer_t* info = (key_timer_t*) cookie;
     info->ui->time_key(info->key_code, info->count);
     delete info;
-    return NULL;
+    return nullptr;
 }
 
 void RecoveryUI::time_key(int key_code, int count) {
@@ -201,19 +213,7 @@
     pthread_mutex_unlock(&key_queue_mutex);
 }
 
-
-// Reads input events, handles special hot keys, and adds to the key queue.
-void* RecoveryUI::input_thread(void *cookie)
-{
-    for (;;) {
-        if (!ev_wait(-1))
-            ev_dispatch();
-    }
-    return NULL;
-}
-
-int RecoveryUI::WaitKey()
-{
+int RecoveryUI::WaitKey() {
     pthread_mutex_lock(&key_queue_mutex);
 
     // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is
@@ -221,17 +221,16 @@
     do {
         struct timeval now;
         struct timespec timeout;
-        gettimeofday(&now, NULL);
+        gettimeofday(&now, nullptr);
         timeout.tv_sec = now.tv_sec;
         timeout.tv_nsec = now.tv_usec * 1000;
         timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;
 
         int rc = 0;
         while (key_queue_len == 0 && rc != ETIMEDOUT) {
-            rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex,
-                                        &timeout);
+            rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout);
         }
-    } while (usb_connected() && key_queue_len == 0);
+    } while (IsUsbConnected() && key_queue_len == 0);
 
     int key = -1;
     if (key_queue_len > 0) {
@@ -242,8 +241,7 @@
     return key;
 }
 
-// Return true if USB is connected.
-bool RecoveryUI::usb_connected() {
+bool RecoveryUI::IsUsbConnected() {
     int fd = open("/sys/class/android_usb/android0/state", O_RDONLY);
     if (fd < 0) {
         printf("failed to open /sys/class/android_usb/android0/state: %s\n",
@@ -252,8 +250,8 @@
     }
 
     char buf;
-    /* USB is connected if android_usb state is CONNECTED or CONFIGURED */
-    int connected = (read(fd, &buf, 1) == 1) && (buf == 'C');
+    // USB is connected if android_usb state is CONNECTED or CONFIGURED.
+    int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C');
     if (close(fd) < 0) {
         printf("failed to close /sys/class/android_usb/android0/state: %s\n",
                strerror(errno));
@@ -261,31 +259,55 @@
     return connected;
 }
 
-bool RecoveryUI::IsKeyPressed(int key)
-{
+bool RecoveryUI::IsKeyPressed(int key) {
     pthread_mutex_lock(&key_queue_mutex);
     int pressed = key_pressed[key];
     pthread_mutex_unlock(&key_queue_mutex);
     return pressed;
 }
 
+bool RecoveryUI::IsLongPress() {
+    pthread_mutex_lock(&key_queue_mutex);
+    bool result = key_long_press;
+    pthread_mutex_unlock(&key_queue_mutex);
+    return result;
+}
+
+bool RecoveryUI::HasThreeButtons() {
+    return has_power_key && has_up_key && has_down_key;
+}
+
 void RecoveryUI::FlushKeys() {
     pthread_mutex_lock(&key_queue_mutex);
     key_queue_len = 0;
     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) {
-    if ((IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) || key == KEY_HOME) {
-        return TOGGLE;
+RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
+    pthread_mutex_lock(&key_queue_mutex);
+    key_long_press = false;
+    pthread_mutex_unlock(&key_queue_mutex);
+
+    // If we have power and volume up keys, that chord is the signal to toggle the text display.
+    if (HasThreeButtons()) {
+        if (key == KEY_VOLUMEUP && IsKeyPressed(KEY_POWER)) {
+            return TOGGLE;
+        }
+    } else {
+        // Otherwise long press of any button toggles to the text display,
+        // and there's no way to toggle back (but that's pretty useless anyway).
+        if (is_long_press && !IsTextVisible()) {
+            return TOGGLE;
+        }
+
+        // Also, for button-limited devices, a long press is translated to KEY_ENTER.
+        if (is_long_press && IsTextVisible()) {
+            EnqueueKey(KEY_ENTER);
+            return IGNORE;
+        }
     }
 
+    // Press power seven times in a row to reboot.
     if (key == KEY_POWER) {
         pthread_mutex_lock(&key_queue_mutex);
         bool reboot_enabled = enable_reboot;
@@ -301,27 +323,11 @@
         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;
+    return IsTextVisible() ? ENQUEUE : IGNORE;
 }
 
-void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) {
-}
-
-void RecoveryUI::KeyLongPress(int key) {
+void RecoveryUI::KeyLongPress(int) {
 }
 
 void RecoveryUI::SetEnableReboot(bool enabled) {
diff --git a/ui.h b/ui.h
index 31a8a7f..4dcaa0f 100644
--- a/ui.h
+++ b/ui.h
@@ -31,10 +31,10 @@
     // Initialize the object; called before anything else.
     virtual void Init();
     // Show a stage indicator.  Call immediately after Init().
-    virtual void SetStage(int current, int max) { }
+    virtual void SetStage(int current, int max) = 0;
 
     // After calling Init(), you can tell the UI what locale it is operating in.
-    virtual void SetLocale(const char* locale) { }
+    virtual void SetLocale(const char* locale) = 0;
 
     // Set the overall recovery state ("background image").
     enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR };
@@ -63,34 +63,37 @@
 
     // Write a message to the on-screen log (shown if the user has
     // toggled on the text display).
-    virtual void Print(const char* fmt, ...) = 0; // __attribute__((format(printf, 1, 2))) = 0;
+    virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0;
+
+    virtual void ShowFile(const char* filename) = 0;
 
     // --- key handling ---
 
-    // Wait for keypress and return it.  May return -1 after timeout.
+    // Wait for a key and return it.  May return -1 after timeout.
     virtual int WaitKey();
 
     virtual bool IsKeyPressed(int key);
+    virtual bool IsLongPress();
+
+    // Returns true if you have the volume up/down and power trio typical
+    // of phones and tablets, false otherwise.
+    virtual bool HasThreeButtons();
 
     // Erase any queued-up keys.
     virtual void FlushKeys();
 
-    // Called on each keypress, even while operations are in progress.
+    // Called on each key press, even while operations are in progress.
     // 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, MOUNT_SYSTEM };
-    virtual KeyAction CheckKey(int key);
-
-    // Called immediately before each call to CheckKey(), tell you if
-    // the key was long-pressed.
-    virtual void NextCheckKeyIsLong(bool is_long_press);
+    enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE };
+    virtual KeyAction CheckKey(int key, bool is_long_press);
 
     // Called when a key is held down long enough to have been a
     // long-press (but before the key is released).  This means that
     // if the key is eventually registered (released without any other
-    // keys being pressed in the meantime), NextCheckKeyIsLong() will
-    // be called with "true".
+    // keys being pressed in the meantime), CheckKey will be called with
+    // 'is_long_press' true.
     virtual void KeyLongPress(int key);
 
     // Normally in recovery there's a key sequence that triggers
@@ -108,8 +111,8 @@
     virtual void StartMenu(const char* const * headers, const char* const * items,
                            int initial_selection) = 0;
 
-    // Set the menu highlight to the given index, and return it (capped to
-    // the range [0..numitems).
+    // Set the menu highlight to the given index, wrapping if necessary.
+    // Returns the actual item selected.
     virtual int SelectMenu(int sel) = 0;
 
     // End menu mode, resetting the text overlay so that ui_print()
@@ -132,21 +135,27 @@
     int rel_sum;
 
     int consecutive_power_keys;
-    int consecutive_alternate_keys;
     int last_key;
 
-    typedef struct {
+    bool has_power_key;
+    bool has_up_key;
+    bool has_down_key;
+
+    struct key_timer_t {
         RecoveryUI* ui;
         int key_code;
         int count;
-    } key_timer_t;
+    };
 
-    pthread_t input_t;
+    pthread_t input_thread_;
 
-    static void* input_thread(void* cookie);
-    static int input_callback(int fd, uint32_t epevents, void* data);
-    void process_key(int key_code, int updown);
-    bool usb_connected();
+    void OnKeyDetected(int key_code);
+
+    static int InputCallback(int fd, uint32_t epevents, void* data);
+    int OnInputEvent(int fd, uint32_t epevents);
+    void ProcessKey(int key_code, int updown);
+
+    bool IsUsbConnected();
 
     static void* time_key_helper(void* cookie);
     void time_key(int key_code, int count);
diff --git a/uncrypt/Android.mk b/uncrypt/Android.mk
index 878d275..c7d4d37 100644
--- a/uncrypt/Android.mk
+++ b/uncrypt/Android.mk
@@ -16,10 +16,10 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := uncrypt.c
+LOCAL_SRC_FILES := uncrypt.cpp
 
 LOCAL_MODULE := uncrypt
 
-LOCAL_STATIC_LIBRARIES := libfs_mgr liblog libcutils
+LOCAL_STATIC_LIBRARIES := libbase liblog libfs_mgr libcutils
 
 include $(BUILD_EXECUTABLE)
diff --git a/uncrypt/uncrypt.c b/uncrypt/uncrypt.cpp
similarity index 62%
rename from uncrypt/uncrypt.c
rename to uncrypt/uncrypt.cpp
index 7fb0989..1db3013 100644
--- a/uncrypt/uncrypt.c
+++ b/uncrypt/uncrypt.cpp
@@ -39,36 +39,43 @@
 // 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 <errno.h>
 #include <fcntl.h>
 #include <linux/fs.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 
-#define LOG_TAG "uncrypt"
-#include <log/log.h>
+#include <base/file.h>
+#include <base/strings.h>
 #include <cutils/properties.h>
 #include <fs_mgr.h>
+#define LOG_TAG "uncrypt"
+#include <log/log.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 const std::string cache_block_map = "/cache/recovery/block.map";
+static const std::string status_file = "/cache/recovery/uncrypt_status";
+static const std::string uncrypt_file = "/cache/recovery/uncrypt_file";
 
 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);
+static int write_at_offset(unsigned char* buffer, size_t size, int wfd, off64_t offset) {
+    if (TEMP_FAILURE_RETRY(lseek64(wfd, offset, SEEK_SET)) == -1) {
+        ALOGE("error seeking to offset %lld: %s\n", offset, strerror(errno));
+        return -1;
+    }
     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));
+        ssize_t wrote = TEMP_FAILURE_RETRY(write(wfd, buffer + written, size - written));
+        if (wrote == -1) {
+            ALOGE("error writing offset %lld: %s\n", (offset + written), strerror(errno));
             return -1;
         }
         written += wrote;
@@ -76,8 +83,7 @@
     return 0;
 }
 
-void add_block_to_ranges(int** ranges, int* range_alloc, int* range_used, int new_block)
-{
+static 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.)
@@ -96,7 +102,7 @@
         // 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));
+            *ranges = reinterpret_cast<int*>(realloc(*ranges, *range_alloc * 2 * sizeof(int)));
         }
 
         ++*range_used;
@@ -105,8 +111,7 @@
     }
 }
 
-static struct fstab* read_fstab()
-{
+static struct fstab* read_fstab() {
     fstab = NULL;
 
     // The fstab path is always "/fstab.${ro.hardware}".
@@ -125,26 +130,26 @@
     return fstab;
 }
 
-const char* find_block_device(const char* path, int* encryptable, int* encrypted)
-{
+static const char* find_block_device(const char* path, bool* encryptable, bool* 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) {
+    for (int i = 0; i < fstab->num_entries; ++i) {
         struct fstab_rec* v = &fstab->recs[i];
-        if (!v->mount_point) continue;
+        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;
+            *encrypted = false;
+            *encryptable = false;
             if (fs_mgr_is_encryptable(v)) {
-                *encryptable = 1;
+                *encryptable = true;
                 char buffer[PROPERTY_VALUE_MAX+1];
                 if (property_get("ro.crypto.state", buffer, "") &&
                     strcmp(buffer, "encrypted") == 0) {
-                    *encrypted = 1;
+                    *encrypted = true;
                 }
             }
             return v->blk_device;
@@ -154,56 +159,37 @@
     return NULL;
 }
 
-char* parse_recovery_command_file()
+// Parse uncrypt_file to find the update package name.
+static bool find_uncrypt_package(std::string& package_name)
 {
-    char* fn = NULL;
-    int count = 0;
-    char temp[1024];
+    if (!android::base::ReadFileToString(uncrypt_file, &package_name)) {
+        ALOGE("failed to open \"%s\": %s\n", uncrypt_file.c_str(), strerror(errno));
+        return false;
+    }
 
-    FILE* f = fopen(RECOVERY_COMMAND_FILE, "r");
-    if (f == NULL) {
-        return NULL;
-    }
-    int fd = open(RECOVERY_COMMAND_FILE_TMP, O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR);
-    if (fd < 0) {
-        ALOGE("failed to open %s\n", RECOVERY_COMMAND_FILE_TMP);
-        return NULL;
-    }
-    FILE* fo = fdopen(fd, "w");
+    // Remove the trailing '\n' if present.
+    package_name = android::base::Trim(package_name);
 
-    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);
-    fsync(fd);
-    fclose(fo);
-
-    if (fn) {
-        char* newline = strchr(fn, '\n');
-        if (newline) *newline = 0;
-    }
-    return fn;
+    return true;
 }
 
-int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
-                      int encrypted)
-{
-    struct stat sb;
-    int ret;
-
+static int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
+                             bool encrypted, int status_fd) {
     int mapfd = open(map_file, O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR);
-    if (mapfd < 0) {
+    if (mapfd == -1) {
         ALOGE("failed to open %s\n", map_file);
         return -1;
     }
     FILE* mapf = fdopen(mapfd, "w");
 
-    ret = stat(path, &sb);
+    // Make sure we can write to the status_file.
+    if (!android::base::WriteStringToFd("0\n", status_fd)) {
+        ALOGE("failed to update \"%s\"\n", status_file.c_str());
+        return -1;
+    }
+
+    struct stat sb;
+    int ret = stat(path, &sb);
     if (ret != 0) {
         ALOGE("failed to stat %s\n", path);
         return -1;
@@ -214,20 +200,18 @@
     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));
+    int* ranges = reinterpret_cast<int*>(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);
+        for (size_t i = 0; i < WINDOW_SIZE; ++i) {
+            buffers[i] = reinterpret_cast<unsigned char*>(malloc(sb.st_blksize));
         }
     }
     int head_block = 0;
@@ -239,7 +223,6 @@
         ALOGE("failed to open fd for reading: %s\n", strerror(errno));
         return -1;
     }
-    fsync(fd);
 
     int wfd = -1;
     if (encrypted) {
@@ -250,7 +233,15 @@
         }
     }
 
+    int last_progress = 0;
     while (pos < sb.st_size) {
+        // Update the status file, progress must be between [0, 99].
+        int progress = static_cast<int>(100 * (double(pos) / double(sb.st_size)));
+        if (progress > last_progress) {
+          last_progress = progress;
+          android::base::WriteStringToFd(std::to_string(progress) + "\n", status_fd);
+        }
+
         if ((tail+1) % WINDOW_SIZE == head) {
             // write out head buffer
             int block = head_block;
@@ -261,7 +252,8 @@
             }
             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) {
+                if (write_at_offset(buffers[head], sb.st_blksize, wfd,
+                        (off64_t)sb.st_blksize * block) != 0) {
                     return -1;
                 }
             }
@@ -273,8 +265,9 @@
         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) {
+                ssize_t this_read =
+                        TEMP_FAILURE_RETRY(read(fd, buffers[tail] + so_far, sb.st_blksize - so_far));
+                if (this_read == -1) {
                     ALOGE("failed to read: %s\n", strerror(errno));
                     return -1;
                 }
@@ -300,7 +293,8 @@
         }
         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) {
+            if (write_at_offset(buffers[head], sb.st_blksize, wfd,
+                    (off64_t)sb.st_blksize * block) != 0) {
                 return -1;
             }
         }
@@ -309,25 +303,30 @@
     }
 
     fprintf(mapf, "%d\n", range_used);
-    for (i = 0; i < range_used; ++i) {
+    for (int i = 0; i < range_used; ++i) {
         fprintf(mapf, "%d %d\n", ranges[i*2], ranges[i*2+1]);
     }
 
-    fsync(mapfd);
+    if (fsync(mapfd) == -1) {
+        ALOGE("failed to fsync \"%s\": %s\n", map_file, strerror(errno));
+        return -1;
+    }
     fclose(mapf);
     close(fd);
     if (encrypted) {
-        fsync(wfd);
+        if (fsync(wfd) == -1) {
+            ALOGE("failed to fsync \"%s\": %s\n", blk_dev, strerror(errno));
+            return -1;
+        }
         close(wfd);
     }
 
     return 0;
 }
 
-void wipe_misc() {
+static void wipe_misc() {
     ALOGI("removing old commands from misc");
-    int i;
-    for (i = 0; i < fstab->num_entries; ++i) {
+    for (int 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) {
@@ -338,72 +337,49 @@
             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) {
+                ssize_t w = TEMP_FAILURE_RETRY(write(fd, zeroes, size-written));
+                if (w == -1) {
                     ALOGE("zero write failed: %s\n", strerror(errno));
                     return;
                 } else {
                     written += w;
                 }
             }
-            fsync(fd);
+            if (fsync(fd) == -1) {
+                ALOGE("failed to fsync \"%s\": %s\n", v->blk_device, strerror(errno));
+                close(fd);
+                return;
+            }
             close(fd);
         }
     }
 }
 
-void reboot_to_recovery() {
+static 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;
+int uncrypt(const char* input_path, const char* map_file, int status_fd) {
 
-    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);
+    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));
+        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;
     }
+
+    bool encryptable;
+    bool encrypted;
     const char* blk_dev = find_block_device(path, &encryptable, &encrypted);
     if (blk_dev == NULL) {
         ALOGE("failed to find block device for %s", path);
@@ -423,18 +399,67 @@
     // 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 {
+    if (strncmp(path, "/data/", 6) == 0) {
         ALOGI("writing block map %s", map_file);
-        if (produce_block_map(path, map_file, blk_dev, encrypted) != 0) {
+        if (produce_block_map(path, map_file, blk_dev, encrypted, status_fd) != 0) {
             return 1;
         }
     }
 
-    wipe_misc();
-    rename(RECOVERY_COMMAND_FILE_TMP, RECOVERY_COMMAND_FILE);
-    if (do_reboot) reboot_to_recovery();
+    return 0;
+}
+
+int main(int argc, char** argv) {
+    const char* input_path;
+    const char* map_file;
+
+    if (argc != 3 && argc != 1 && (argc == 2 && strcmp(argv[1], "--reboot") != 0)) {
+        fprintf(stderr, "usage: %s [--reboot] [<transform_path> <map_file>]\n", argv[0]);
+        return 2;
+    }
+
+    // When uncrypt is started with "--reboot", it wipes misc and reboots.
+    // Otherwise it uncrypts the package and writes the block map.
+    if (argc == 2) {
+        if (read_fstab() == NULL) {
+            return 1;
+        }
+        wipe_misc();
+        reboot_to_recovery();
+    } else {
+        // The pipe has been created by the system server.
+        int status_fd = open(status_file.c_str(), O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR);
+        if (status_fd == -1) {
+            ALOGE("failed to open pipe \"%s\": %s\n", status_file.c_str(), strerror(errno));
+            return 1;
+        }
+
+        if (argc == 3) {
+            // when command-line args are given this binary is being used
+            // for debugging.
+            input_path = argv[1];
+            map_file = argv[2];
+        } else {
+            std::string package;
+            if (!find_uncrypt_package(package)) {
+                android::base::WriteStringToFd("-1\n", status_fd);
+                close(status_fd);
+                return 1;
+            }
+            input_path = package.c_str();
+            map_file = cache_block_map.c_str();
+        }
+
+        int status = uncrypt(input_path, map_file, status_fd);
+        if (status != 0) {
+            android::base::WriteStringToFd("-1\n", status_fd);
+            close(status_fd);
+            return 1;
+        }
+
+        android::base::WriteStringToFd("100\n", status_fd);
+        close(status_fd);
+    }
+
     return 0;
 }
diff --git a/updater/Android.mk b/updater/Android.mk
index 11e7bb8..ff02a33 100644
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -79,7 +79,7 @@
 	$(hide) $(foreach lib,$(libs),echo "  Register_$(lib)();" >> $@;)
 	$(hide) echo "}" >> $@
 
-$(call intermediates-dir-for,EXECUTABLES,updater)/updater.o : $(inc)
+$(call intermediates-dir-for,EXECUTABLES,updater,,,$(TARGET_PREFER_32_BIT))/updater.o : $(inc)
 LOCAL_C_INCLUDES += $(dir $(inc))
 
 inc :=
diff --git a/updater/MODULE_LICENSE_GPL b/updater/MODULE_LICENSE_GPL
deleted file mode 100644
index e69de29..0000000
--- a/updater/MODULE_LICENSE_GPL
+++ /dev/null
diff --git a/updater/NOTICE b/updater/NOTICE
deleted file mode 100644
index e77696a..0000000
--- a/updater/NOTICE
+++ /dev/null
@@ -1,339 +0,0 @@
-		    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
index 6060ac2..b006d10 100644
--- a/updater/blockimg.c
+++ b/updater/blockimg.c
@@ -16,13 +16,16 @@
 
 #include <ctype.h>
 #include <errno.h>
+#include <dirent.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <libgen.h>
 #include <pthread.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/ioctl.h>
@@ -32,7 +35,7 @@
 #include "applypatch/applypatch.h"
 #include "edify/expr.h"
 #include "mincrypt/sha.h"
-#include "minzip/DirUtil.h"
+#include "minzip/Hash.h"
 #include "updater.h"
 
 #define BLOCKSIZE 4096
@@ -46,6 +49,10 @@
 #define BLKDISCARD _IO(0x12,119)
 #endif
 
+#define STASH_DIRECTORY_BASE "/cache/recovery"
+#define STASH_DIRECTORY_MODE 0700
+#define STASH_FILE_MODE 0600
+
 char* PrintSha1(const uint8_t* digest);
 
 typedef struct {
@@ -80,44 +87,69 @@
     return out;
 }
 
-static void readblock(int fd, uint8_t* data, size_t size) {
+static int range_overlaps(RangeSet* r1, RangeSet* r2) {
+    int i, j, r1_0, r1_1, r2_0, r2_1;
+
+    if (!r1 || !r2) {
+        return 0;
+    }
+
+    for (i = 0; i < r1->count; ++i) {
+        r1_0 = r1->pos[i * 2];
+        r1_1 = r1->pos[i * 2 + 1];
+
+        for (j = 0; j < r2->count; ++j) {
+            r2_0 = r2->pos[j * 2];
+            r2_1 = r2->pos[j * 2 + 1];
+
+            if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) {
+                return 1;
+            }
+        }
+    }
+
+    return 0;
+}
+
+static int read_all(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) {
+        ssize_t r = TEMP_FAILURE_RETRY(read(fd, data+so_far, size-so_far));
+        if (r == -1) {
             fprintf(stderr, "read failed: %s\n", strerror(errno));
-            return;
-        } else {
-            so_far += r;
+            return -1;
         }
+        so_far += r;
     }
+    return 0;
 }
 
-static void writeblock(int fd, const uint8_t* data, size_t size) {
+static int write_all(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) {
+        ssize_t w = TEMP_FAILURE_RETRY(write(fd, data+written, size-written));
+        if (w == -1) {
             fprintf(stderr, "write failed: %s\n", strerror(errno));
-            return;
-        } else {
-            written += w;
+            return -1;
         }
+        written += w;
     }
+
+    if (fsync(fd) == -1) {
+        fprintf(stderr, "fsync failed: %s\n", strerror(errno));
+        return -1;
+    }
+
+    return 0;
 }
 
-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 bool check_lseek(int fd, off64_t offset, int whence) {
+    off64_t rc = TEMP_FAILURE_RETRY(lseek64(fd, offset, whence));
+    if (rc == -1) {
+        fprintf(stderr, "lseek64 failed: %s\n", strerror(errno));
+        return false;
     }
+    return true;
 }
 
 static void allocate(size_t size, uint8_t** buffer, size_t* buffer_alloc) {
@@ -146,14 +178,21 @@
 
     if (rss->p_remain <= 0) {
         fprintf(stderr, "range sink write overrun");
-        exit(1);
+        return 0;
     }
 
     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);
+
+        if (rss->p_remain < write_now) {
+            write_now = rss->p_remain;
+        }
+
+        if (write_all(rss->fd, data, write_now) == -1) {
+            break;
+        }
+
         data += write_now;
         size -= write_now;
 
@@ -164,12 +203,17 @@
             // 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);
+                rss->p_remain = (rss->tgt->pos[rss->p_block * 2 + 1] -
+                                 rss->tgt->pos[rss->p_block * 2]) * BLOCKSIZE;
+
+                if (!check_lseek(rss->fd, (off64_t)rss->tgt->pos[rss->p_block*2] * BLOCKSIZE,
+                                 SEEK_SET)) {
+                    break;
+                }
             } else {
                 // we can't write any more; return how many bytes have
                 // been written so far.
-                return written;
+                break;
             }
         }
     }
@@ -245,6 +289,58 @@
     return NULL;
 }
 
+static int ReadBlocks(RangeSet* src, uint8_t* buffer, int fd) {
+    int i;
+    size_t p = 0;
+    size_t size;
+
+    if (!src || !buffer) {
+        return -1;
+    }
+
+    for (i = 0; i < src->count; ++i) {
+        if (!check_lseek(fd, (off64_t) src->pos[i * 2] * BLOCKSIZE, SEEK_SET)) {
+            return -1;
+        }
+
+        size = (src->pos[i * 2 + 1] - src->pos[i * 2]) * BLOCKSIZE;
+
+        if (read_all(fd, buffer + p, size) == -1) {
+            return -1;
+        }
+
+        p += size;
+    }
+
+    return 0;
+}
+
+static int WriteBlocks(RangeSet* tgt, uint8_t* buffer, int fd) {
+    int i;
+    size_t p = 0;
+    size_t size;
+
+    if (!tgt || !buffer) {
+        return -1;
+    }
+
+    for (i = 0; i < tgt->count; ++i) {
+        if (!check_lseek(fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET)) {
+            return -1;
+        }
+
+        size = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * BLOCKSIZE;
+
+        if (write_all(fd, buffer + p, size) == -1) {
+            return -1;
+        }
+
+        p += size;
+    }
+
+    return 0;
+}
+
 // Do a source/target load for move/bsdiff/imgdiff in version 1.
 // 'wordsave' is the save_ptr of a strtok_r()-in-progress.  We expect
 // to parse the remainder of the string as:
@@ -255,30 +351,525 @@
 // it to make it larger if necessary.  The target ranges are returned
 // in *tgt, if tgt is non-NULL.
 
-static void LoadSrcTgtVersion1(char* wordsave, RangeSet** tgt, int* src_blocks,
+static int LoadSrcTgtVersion1(char** wordsave, RangeSet** tgt, int* src_blocks,
                                uint8_t** buffer, size_t* buffer_alloc, int fd) {
     char* word;
+    int rc;
 
-    word = strtok_r(NULL, " ", &wordsave);
+    word = strtok_r(NULL, " ", wordsave);
     RangeSet* src = parse_range(word);
 
     if (tgt != NULL) {
-        word = strtok_r(NULL, " ", &wordsave);
+        word = strtok_r(NULL, " ", wordsave);
         *tgt = parse_range(word);
     }
 
     allocate(src->size * BLOCKSIZE, buffer, buffer_alloc);
-    size_t p = 0;
-    int i;
-    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;
+    rc = ReadBlocks(src, *buffer, fd);
+    *src_blocks = src->size;
+
+    free(src);
+    return rc;
+}
+
+static int VerifyBlocks(const char *expected, const uint8_t *buffer,
+                        size_t blocks, int printerror) {
+    char* hexdigest = NULL;
+    int rc = -1;
+    uint8_t digest[SHA_DIGEST_SIZE];
+
+    if (!expected || !buffer) {
+        return rc;
     }
 
-    *src_blocks = src->size;
-    free(src);
+    SHA_hash(buffer, blocks * BLOCKSIZE, digest);
+    hexdigest = PrintSha1(digest);
+
+    if (hexdigest != NULL) {
+        rc = strcmp(expected, hexdigest);
+
+        if (rc != 0 && printerror) {
+            fprintf(stderr, "failed to verify blocks (expected %s, read %s)\n",
+                expected, hexdigest);
+        }
+
+        free(hexdigest);
+    }
+
+    return rc;
+}
+
+static char* GetStashFileName(const char* base, const char* id, const char* postfix) {
+    char* fn;
+    int len;
+    int res;
+
+    if (base == NULL) {
+        return NULL;
+    }
+
+    if (id == NULL) {
+        id = "";
+    }
+
+    if (postfix == NULL) {
+        postfix = "";
+    }
+
+    len = strlen(STASH_DIRECTORY_BASE) + 1 + strlen(base) + 1 + strlen(id) + strlen(postfix) + 1;
+    fn = malloc(len);
+
+    if (fn == NULL) {
+        fprintf(stderr, "failed to malloc %d bytes for fn\n", len);
+        return NULL;
+    }
+
+    res = snprintf(fn, len, STASH_DIRECTORY_BASE "/%s/%s%s", base, id, postfix);
+
+    if (res < 0 || res >= len) {
+        fprintf(stderr, "failed to format file name (return value %d)\n", res);
+        free(fn);
+        return NULL;
+    }
+
+    return fn;
+}
+
+typedef void (*StashCallback)(const char*, void*);
+
+// Does a best effort enumeration of stash files. Ignores possible non-file
+// items in the stash directory and continues despite of errors. Calls the
+// 'callback' function for each file and passes 'data' to the function as a
+// parameter.
+
+static void EnumerateStash(const char* dirname, StashCallback callback, void* data) {
+    char* fn;
+    DIR* directory;
+    int len;
+    int res;
+    struct dirent* item;
+
+    if (dirname == NULL || callback == NULL) {
+        return;
+    }
+
+    directory = opendir(dirname);
+
+    if (directory == NULL) {
+        if (errno != ENOENT) {
+            fprintf(stderr, "opendir \"%s\" failed: %s\n", dirname, strerror(errno));
+        }
+        return;
+    }
+
+    while ((item = readdir(directory)) != NULL) {
+        if (item->d_type != DT_REG) {
+            continue;
+        }
+
+        len = strlen(dirname) + 1 + strlen(item->d_name) + 1;
+        fn = malloc(len);
+
+        if (fn == NULL) {
+            fprintf(stderr, "failed to malloc %d bytes for fn\n", len);
+            continue;
+        }
+
+        res = snprintf(fn, len, "%s/%s", dirname, item->d_name);
+
+        if (res < 0 || res >= len) {
+            fprintf(stderr, "failed to format file name (return value %d)\n", res);
+            free(fn);
+            continue;
+        }
+
+        callback(fn, data);
+        free(fn);
+    }
+
+    if (closedir(directory) == -1) {
+        fprintf(stderr, "closedir \"%s\" failed: %s\n", dirname, strerror(errno));
+    }
+}
+
+static void UpdateFileSize(const char* fn, void* data) {
+    int* size = (int*) data;
+    struct stat st;
+
+    if (!fn || !data) {
+        return;
+    }
+
+    if (stat(fn, &st) == -1) {
+        fprintf(stderr, "stat \"%s\" failed: %s\n", fn, strerror(errno));
+        return;
+    }
+
+    *size += st.st_size;
+}
+
+// Deletes the stash directory and all files in it. Assumes that it only
+// contains files. There is nothing we can do about unlikely, but possible
+// errors, so they are merely logged.
+
+static void DeleteFile(const char* fn, void* data) {
+    if (fn) {
+        fprintf(stderr, "deleting %s\n", fn);
+
+        if (unlink(fn) == -1 && errno != ENOENT) {
+            fprintf(stderr, "unlink \"%s\" failed: %s\n", fn, strerror(errno));
+        }
+    }
+}
+
+static void DeletePartial(const char* fn, void* data) {
+    if (fn && strstr(fn, ".partial") != NULL) {
+        DeleteFile(fn, data);
+    }
+}
+
+static void DeleteStash(const char* base) {
+    char* dirname;
+
+    if (base == NULL) {
+        return;
+    }
+
+    dirname = GetStashFileName(base, NULL, NULL);
+
+    if (dirname == NULL) {
+        return;
+    }
+
+    fprintf(stderr, "deleting stash %s\n", base);
+    EnumerateStash(dirname, DeleteFile, NULL);
+
+    if (rmdir(dirname) == -1) {
+        if (errno != ENOENT && errno != ENOTDIR) {
+            fprintf(stderr, "rmdir \"%s\" failed: %s\n", dirname, strerror(errno));
+        }
+    }
+
+    free(dirname);
+}
+
+static int LoadStash(const char* base, const char* id, int verify, int* blocks, uint8_t** buffer,
+        size_t* buffer_alloc, int printnoent) {
+    char *fn = NULL;
+    int blockcount = 0;
+    int fd = -1;
+    int rc = -1;
+    int res;
+    struct stat st;
+
+    if (!base || !id || !buffer || !buffer_alloc) {
+        goto lsout;
+    }
+
+    if (!blocks) {
+        blocks = &blockcount;
+    }
+
+    fn = GetStashFileName(base, id, NULL);
+
+    if (fn == NULL) {
+        goto lsout;
+    }
+
+    res = stat(fn, &st);
+
+    if (res == -1) {
+        if (errno != ENOENT || printnoent) {
+            fprintf(stderr, "stat \"%s\" failed: %s\n", fn, strerror(errno));
+        }
+        goto lsout;
+    }
+
+    fprintf(stderr, " loading %s\n", fn);
+
+    if ((st.st_size % BLOCKSIZE) != 0) {
+        fprintf(stderr, "%s size %zd not multiple of block size %d", fn, st.st_size, BLOCKSIZE);
+        goto lsout;
+    }
+
+    fd = TEMP_FAILURE_RETRY(open(fn, O_RDONLY));
+
+    if (fd == -1) {
+        fprintf(stderr, "open \"%s\" failed: %s\n", fn, strerror(errno));
+        goto lsout;
+    }
+
+    allocate(st.st_size, buffer, buffer_alloc);
+
+    if (read_all(fd, *buffer, st.st_size) == -1) {
+        goto lsout;
+    }
+
+    *blocks = st.st_size / BLOCKSIZE;
+
+    if (verify && VerifyBlocks(id, *buffer, *blocks, 1) != 0) {
+        fprintf(stderr, "unexpected contents in %s\n", fn);
+        DeleteFile(fn, NULL);
+        goto lsout;
+    }
+
+    rc = 0;
+
+lsout:
+    if (fd != -1) {
+        close(fd);
+    }
+
+    if (fn) {
+        free(fn);
+    }
+
+    return rc;
+}
+
+static int WriteStash(const char* base, const char* id, int blocks, uint8_t* buffer,
+        int checkspace, int *exists) {
+    char *fn = NULL;
+    char *cn = NULL;
+    int fd = -1;
+    int rc = -1;
+    int dfd = -1;
+    int res;
+    struct stat st;
+
+    if (base == NULL || buffer == NULL) {
+        goto wsout;
+    }
+
+    if (checkspace && CacheSizeCheck(blocks * BLOCKSIZE) != 0) {
+        fprintf(stderr, "not enough space to write stash\n");
+        goto wsout;
+    }
+
+    fn = GetStashFileName(base, id, ".partial");
+    cn = GetStashFileName(base, id, NULL);
+
+    if (fn == NULL || cn == NULL) {
+        goto wsout;
+    }
+
+    if (exists) {
+        res = stat(cn, &st);
+
+        if (res == 0) {
+            // The file already exists and since the name is the hash of the contents,
+            // it's safe to assume the contents are identical (accidental hash collisions
+            // are unlikely)
+            fprintf(stderr, " skipping %d existing blocks in %s\n", blocks, cn);
+            *exists = 1;
+            rc = 0;
+            goto wsout;
+        }
+
+        *exists = 0;
+    }
+
+    fprintf(stderr, " writing %d blocks to %s\n", blocks, cn);
+
+    fd = TEMP_FAILURE_RETRY(open(fn, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, STASH_FILE_MODE));
+
+    if (fd == -1) {
+        fprintf(stderr, "failed to create \"%s\": %s\n", fn, strerror(errno));
+        goto wsout;
+    }
+
+    if (write_all(fd, buffer, blocks * BLOCKSIZE) == -1) {
+        goto wsout;
+    }
+
+    if (fsync(fd) == -1) {
+        fprintf(stderr, "fsync \"%s\" failed: %s\n", fn, strerror(errno));
+        goto wsout;
+    }
+
+    if (rename(fn, cn) == -1) {
+        fprintf(stderr, "rename(\"%s\", \"%s\") failed: %s\n", fn, cn, strerror(errno));
+        goto wsout;
+    }
+
+    const char* dname;
+    dname = dirname(cn);
+    dfd = TEMP_FAILURE_RETRY(open(dname, O_RDONLY | O_DIRECTORY));
+
+    if (dfd == -1) {
+        fprintf(stderr, "failed to open \"%s\" failed: %s\n", dname, strerror(errno));
+        goto wsout;
+    }
+
+    if (fsync(dfd) == -1) {
+        fprintf(stderr, "fsync \"%s\" failed: %s\n", dname, strerror(errno));
+        goto wsout;
+    }
+
+    rc = 0;
+
+wsout:
+    if (fd != -1) {
+        close(fd);
+    }
+
+    if (dfd != -1) {
+        close(dfd);
+    }
+
+    if (fn) {
+        free(fn);
+    }
+
+    if (cn) {
+        free(cn);
+    }
+
+    return rc;
+}
+
+// Creates a directory for storing stash files and checks if the /cache partition
+// hash enough space for the expected amount of blocks we need to store. Returns
+// >0 if we created the directory, zero if it existed already, and <0 of failure.
+
+static int CreateStash(State* state, int maxblocks, const char* blockdev, char** base) {
+    char* dirname = NULL;
+    const uint8_t* digest;
+    int rc = -1;
+    int res;
+    int size = 0;
+    SHA_CTX ctx;
+    struct stat st;
+
+    if (blockdev == NULL || base == NULL) {
+        goto csout;
+    }
+
+    // Stash directory should be different for each partition to avoid conflicts
+    // when updating multiple partitions at the same time, so we use the hash of
+    // the block device name as the base directory
+    SHA_init(&ctx);
+    SHA_update(&ctx, blockdev, strlen(blockdev));
+    digest = SHA_final(&ctx);
+    *base = PrintSha1(digest);
+
+    if (*base == NULL) {
+        goto csout;
+    }
+
+    dirname = GetStashFileName(*base, NULL, NULL);
+
+    if (dirname == NULL) {
+        goto csout;
+    }
+
+    res = stat(dirname, &st);
+
+    if (res == -1 && errno != ENOENT) {
+        ErrorAbort(state, "stat \"%s\" failed: %s\n", dirname, strerror(errno));
+        goto csout;
+    } else if (res != 0) {
+        fprintf(stderr, "creating stash %s\n", dirname);
+        res = mkdir(dirname, STASH_DIRECTORY_MODE);
+
+        if (res != 0) {
+            ErrorAbort(state, "mkdir \"%s\" failed: %s\n", dirname, strerror(errno));
+            goto csout;
+        }
+
+        if (CacheSizeCheck(maxblocks * BLOCKSIZE) != 0) {
+            ErrorAbort(state, "not enough space for stash\n");
+            goto csout;
+        }
+
+        rc = 1; // Created directory
+        goto csout;
+    }
+
+    fprintf(stderr, "using existing stash %s\n", dirname);
+
+    // If the directory already exists, calculate the space already allocated to
+    // stash files and check if there's enough for all required blocks. Delete any
+    // partially completed stash files first.
+
+    EnumerateStash(dirname, DeletePartial, NULL);
+    EnumerateStash(dirname, UpdateFileSize, &size);
+
+    size = (maxblocks * BLOCKSIZE) - size;
+
+    if (size > 0 && CacheSizeCheck(size) != 0) {
+        ErrorAbort(state, "not enough space for stash (%d more needed)\n", size);
+        goto csout;
+    }
+
+    rc = 0; // Using existing directory
+
+csout:
+    if (dirname) {
+        free(dirname);
+    }
+
+    return rc;
+}
+
+static int SaveStash(const char* base, char** wordsave, uint8_t** buffer, size_t* buffer_alloc,
+                      int fd, int usehash, int* isunresumable) {
+    char *id = NULL;
+    int res = -1;
+    int blocks = 0;
+
+    if (!wordsave || !buffer || !buffer_alloc || !isunresumable) {
+        return -1;
+    }
+
+    id = strtok_r(NULL, " ", wordsave);
+
+    if (id == NULL) {
+        fprintf(stderr, "missing id field in stash command\n");
+        return -1;
+    }
+
+    if (usehash && LoadStash(base, id, 1, &blocks, buffer, buffer_alloc, 0) == 0) {
+        // Stash file already exists and has expected contents. Do not
+        // read from source again, as the source may have been already
+        // overwritten during a previous attempt.
+        return 0;
+    }
+
+    if (LoadSrcTgtVersion1(wordsave, NULL, &blocks, buffer, buffer_alloc, fd) == -1) {
+        return -1;
+    }
+
+    if (usehash && VerifyBlocks(id, *buffer, blocks, 1) != 0) {
+        // Source blocks have unexpected contents. If we actually need this
+        // data later, this is an unrecoverable error. However, the command
+        // that uses the data may have already completed previously, so the
+        // possible failure will occur during source block verification.
+        fprintf(stderr, "failed to load source blocks for stash %s\n", id);
+        return 0;
+    }
+
+    fprintf(stderr, "stashing %d blocks to %s\n", blocks, id);
+    return WriteStash(base, id, blocks, *buffer, 0, NULL);
+}
+
+static int FreeStash(const char* base, const char* id) {
+    char *fn = NULL;
+
+    if (base == NULL || id == NULL) {
+        return -1;
+    }
+
+    fn = GetStashFileName(base, id, NULL);
+
+    if (fn == NULL) {
+        return -1;
+    }
+
+    DeleteFile(fn, NULL);
+    free(fn);
+
+    return 0;
 }
 
 static void MoveRange(uint8_t* dest, RangeSet* locs, const uint8_t* source) {
@@ -312,64 +903,613 @@
 // On return, buffer is filled with the loaded source data (rearranged
 // and combined with stashed data as necessary).  buffer may be
 // reallocated if needed to accommodate the source data.  *tgt is the
-// target RangeSet.  Any stashes required are taken from stash_table
-// and free()'d after being used.
+// target RangeSet.  Any stashes required are loaded using LoadStash.
 
-static void LoadSrcTgtVersion2(char* wordsave, RangeSet** tgt, int* src_blocks,
+static int LoadSrcTgtVersion2(char** wordsave, RangeSet** tgt, int* src_blocks,
                                uint8_t** buffer, size_t* buffer_alloc, int fd,
-                               uint8_t** stash_table) {
+                               const char* stashbase, int* overlap) {
     char* word;
+    char* colonsave;
+    char* colon;
+    int id;
+    int res;
+    RangeSet* locs;
+    size_t stashalloc = 0;
+    uint8_t* stash = NULL;
 
     if (tgt != NULL) {
-        word = strtok_r(NULL, " ", &wordsave);
+        word = strtok_r(NULL, " ", wordsave);
         *tgt = parse_range(word);
     }
 
-    word = strtok_r(NULL, " ", &wordsave);
+    word = strtok_r(NULL, " ", wordsave);
     *src_blocks = strtol(word, NULL, 0);
 
     allocate(*src_blocks * BLOCKSIZE, buffer, buffer_alloc);
 
-    word = strtok_r(NULL, " ", &wordsave);
+    word = strtok_r(NULL, " ", wordsave);
     if (word[0] == '-' && word[1] == '\0') {
         // no source ranges, only stashes
     } else {
         RangeSet* src = parse_range(word);
+        res = ReadBlocks(src, *buffer, fd);
 
-        size_t p = 0;
-        int i;
-        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;
+        if (overlap && tgt) {
+            *overlap = range_overlaps(src, *tgt);
         }
+
         free(src);
 
-        word = strtok_r(NULL, " ", &wordsave);
-        if (word == NULL) {
-            // no stashes, only source range
-            return;
+        if (res == -1) {
+            return -1;
         }
 
-        RangeSet* locs = parse_range(word);
+        word = strtok_r(NULL, " ", wordsave);
+        if (word == NULL) {
+            // no stashes, only source range
+            return 0;
+        }
+
+        locs = parse_range(word);
         MoveRange(*buffer, locs, *buffer);
+        free(locs);
     }
 
-    while ((word = strtok_r(NULL, " ", &wordsave)) != NULL) {
+    while ((word = strtok_r(NULL, " ", wordsave)) != NULL) {
         // Each word is a an index into the stash table, a colon, and
         // then a rangeset describing where in the source block that
         // stashed data should go.
-        char* colonsave = NULL;
-        char* colon = strtok_r(word, ":", &colonsave);
-        int stash_id = strtol(colon, NULL, 0);
+        colonsave = NULL;
+        colon = strtok_r(word, ":", &colonsave);
+
+        res = LoadStash(stashbase, colon, 0, NULL, &stash, &stashalloc, 1);
+
+        if (res == -1) {
+            // These source blocks will fail verification if used later, but we
+            // will let the caller decide if this is a fatal failure
+            fprintf(stderr, "failed to load stash %s\n", colon);
+            continue;
+        }
+
         colon = strtok_r(NULL, ":", &colonsave);
-        RangeSet* locs = parse_range(colon);
-        MoveRange(*buffer, locs, stash_table[stash_id]);
-        free(stash_table[stash_id]);
-        stash_table[stash_id] = NULL;
+        locs = parse_range(colon);
+
+        MoveRange(*buffer, locs, stash);
         free(locs);
     }
+
+    if (stash) {
+        free(stash);
+    }
+
+    return 0;
+}
+
+// Parameters for transfer list command functions
+typedef struct {
+    char* cmdname;
+    char* cpos;
+    char* freestash;
+    char* stashbase;
+    int canwrite;
+    int createdstash;
+    int fd;
+    int foundwrites;
+    int isunresumable;
+    int version;
+    int written;
+    NewThreadInfo nti;
+    pthread_t thread;
+    size_t bufsize;
+    uint8_t* buffer;
+    uint8_t* patch_start;
+} CommandParameters;
+
+// Do a source/target load for move/bsdiff/imgdiff in version 3.
+//
+// Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which
+// tells the function whether to expect separate source and targe block hashes, or
+// if they are both the same and only one hash should be expected, and
+// 'isunresumable', which receives a non-zero value if block verification fails in
+// a way that the update cannot be resumed anymore.
+//
+// If the function is unable to load the necessary blocks or their contents don't
+// match the hashes, the return value is -1 and the command should be aborted.
+//
+// If the return value is 1, the command has already been completed according to
+// the contents of the target blocks, and should not be performed again.
+//
+// If the return value is 0, source blocks have expected content and the command
+// can be performed.
+
+static int LoadSrcTgtVersion3(CommandParameters* params, RangeSet** tgt, int* src_blocks,
+                              int onehash, int* overlap) {
+    char* srchash = NULL;
+    char* tgthash = NULL;
+    int stash_exists = 0;
+    int overlap_blocks = 0;
+    int rc = -1;
+    uint8_t* tgtbuffer = NULL;
+
+    if (!params|| !tgt || !src_blocks || !overlap) {
+        goto v3out;
+    }
+
+    srchash = strtok_r(NULL, " ", &params->cpos);
+
+    if (srchash == NULL) {
+        fprintf(stderr, "missing source hash\n");
+        goto v3out;
+    }
+
+    if (onehash) {
+        tgthash = srchash;
+    } else {
+        tgthash = strtok_r(NULL, " ", &params->cpos);
+
+        if (tgthash == NULL) {
+            fprintf(stderr, "missing target hash\n");
+            goto v3out;
+        }
+    }
+
+    if (LoadSrcTgtVersion2(&params->cpos, tgt, src_blocks, &params->buffer, &params->bufsize,
+            params->fd, params->stashbase, overlap) == -1) {
+        goto v3out;
+    }
+
+    tgtbuffer = (uint8_t*) malloc((*tgt)->size * BLOCKSIZE);
+
+    if (tgtbuffer == NULL) {
+        fprintf(stderr, "failed to allocate %d bytes\n", (*tgt)->size * BLOCKSIZE);
+        goto v3out;
+    }
+
+    if (ReadBlocks(*tgt, tgtbuffer, params->fd) == -1) {
+        goto v3out;
+    }
+
+    if (VerifyBlocks(tgthash, tgtbuffer, (*tgt)->size, 0) == 0) {
+        // Target blocks already have expected content, command should be skipped
+        rc = 1;
+        goto v3out;
+    }
+
+    if (VerifyBlocks(srchash, params->buffer, *src_blocks, 1) == 0) {
+        // If source and target blocks overlap, stash the source blocks so we can
+        // resume from possible write errors
+        if (*overlap) {
+            fprintf(stderr, "stashing %d overlapping blocks to %s\n", *src_blocks,
+                srchash);
+
+            if (WriteStash(params->stashbase, srchash, *src_blocks, params->buffer, 1,
+                    &stash_exists) != 0) {
+                fprintf(stderr, "failed to stash overlapping source blocks\n");
+                goto v3out;
+            }
+
+            // Can be deleted when the write has completed
+            if (!stash_exists) {
+                params->freestash = srchash;
+            }
+        }
+
+        // Source blocks have expected content, command can proceed
+        rc = 0;
+        goto v3out;
+    }
+
+    if (*overlap && LoadStash(params->stashbase, srchash, 1, NULL, &params->buffer,
+                        &params->bufsize, 1) == 0) {
+        // Overlapping source blocks were previously stashed, command can proceed.
+        // We are recovering from an interrupted command, so we don't know if the
+        // stash can safely be deleted after this command.
+        rc = 0;
+        goto v3out;
+    }
+
+    // Valid source data not available, update cannot be resumed
+    fprintf(stderr, "partition has unexpected contents\n");
+    params->isunresumable = 1;
+
+v3out:
+    if (tgtbuffer) {
+        free(tgtbuffer);
+    }
+
+    return rc;
+}
+
+static int PerformCommandMove(CommandParameters* params) {
+    int blocks = 0;
+    int overlap = 0;
+    int rc = -1;
+    int status = 0;
+    RangeSet* tgt = NULL;
+
+    if (!params) {
+        goto pcmout;
+    }
+
+    if (params->version == 1) {
+        status = LoadSrcTgtVersion1(&params->cpos, &tgt, &blocks, &params->buffer,
+                    &params->bufsize, params->fd);
+    } else if (params->version == 2) {
+        status = LoadSrcTgtVersion2(&params->cpos, &tgt, &blocks, &params->buffer,
+                    &params->bufsize, params->fd, params->stashbase, NULL);
+    } else if (params->version >= 3) {
+        status = LoadSrcTgtVersion3(params, &tgt, &blocks, 1, &overlap);
+    }
+
+    if (status == -1) {
+        fprintf(stderr, "failed to read blocks for move\n");
+        goto pcmout;
+    }
+
+    if (status == 0) {
+        params->foundwrites = 1;
+    } else if (params->foundwrites) {
+        fprintf(stderr, "warning: commands executed out of order [%s]\n", params->cmdname);
+    }
+
+    if (params->canwrite) {
+        if (status == 0) {
+            fprintf(stderr, "  moving %d blocks\n", blocks);
+
+            if (WriteBlocks(tgt, params->buffer, params->fd) == -1) {
+                goto pcmout;
+            }
+        } else {
+            fprintf(stderr, "skipping %d already moved blocks\n", blocks);
+        }
+
+    }
+
+    if (params->freestash) {
+        FreeStash(params->stashbase, params->freestash);
+        params->freestash = NULL;
+    }
+
+    params->written += tgt->size;
+    rc = 0;
+
+pcmout:
+    if (tgt) {
+        free(tgt);
+    }
+
+    return rc;
+}
+
+static int PerformCommandStash(CommandParameters* params) {
+    if (!params) {
+        return -1;
+    }
+
+    return SaveStash(params->stashbase, &params->cpos, &params->buffer, &params->bufsize,
+                params->fd, (params->version >= 3), &params->isunresumable);
+}
+
+static int PerformCommandFree(CommandParameters* params) {
+    if (!params) {
+        return -1;
+    }
+
+    if (params->createdstash || params->canwrite) {
+        return FreeStash(params->stashbase, params->cpos);
+    }
+
+    return 0;
+}
+
+static int PerformCommandZero(CommandParameters* params) {
+    char* range = NULL;
+    int i;
+    int j;
+    int rc = -1;
+    RangeSet* tgt = NULL;
+
+    if (!params) {
+        goto pczout;
+    }
+
+    range = strtok_r(NULL, " ", &params->cpos);
+
+    if (range == NULL) {
+        fprintf(stderr, "missing target blocks for zero\n");
+        goto pczout;
+    }
+
+    tgt = parse_range(range);
+
+    fprintf(stderr, "  zeroing %d blocks\n", tgt->size);
+
+    allocate(BLOCKSIZE, &params->buffer, &params->bufsize);
+    memset(params->buffer, 0, BLOCKSIZE);
+
+    if (params->canwrite) {
+        for (i = 0; i < tgt->count; ++i) {
+            if (!check_lseek(params->fd, (off64_t) tgt->pos[i * 2] * BLOCKSIZE, SEEK_SET)) {
+                goto pczout;
+            }
+
+            for (j = tgt->pos[i * 2]; j < tgt->pos[i * 2 + 1]; ++j) {
+                if (write_all(params->fd, params->buffer, BLOCKSIZE) == -1) {
+                    goto pczout;
+                }
+            }
+        }
+    }
+
+    if (params->cmdname[0] == 'z') {
+        // Update only for the zero command, as the erase command will call
+        // this if DEBUG_ERASE is defined.
+        params->written += tgt->size;
+    }
+
+    rc = 0;
+
+pczout:
+    if (tgt) {
+        free(tgt);
+    }
+
+    return rc;
+}
+
+static int PerformCommandNew(CommandParameters* params) {
+    char* range = NULL;
+    int rc = -1;
+    RangeSet* tgt = NULL;
+    RangeSinkState rss;
+
+    if (!params) {
+        goto pcnout;
+    }
+
+    range = strtok_r(NULL, " ", &params->cpos);
+
+    if (range == NULL) {
+        goto pcnout;
+    }
+
+    tgt = parse_range(range);
+
+    if (params->canwrite) {
+        fprintf(stderr, " writing %d blocks of new data\n", tgt->size);
+
+        rss.fd = params->fd;
+        rss.tgt = tgt;
+        rss.p_block = 0;
+        rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE;
+
+        if (!check_lseek(params->fd, (off64_t) tgt->pos[0] * BLOCKSIZE, SEEK_SET)) {
+            goto pcnout;
+        }
+
+        pthread_mutex_lock(&params->nti.mu);
+        params->nti.rss = &rss;
+        pthread_cond_broadcast(&params->nti.cv);
+
+        while (params->nti.rss) {
+            pthread_cond_wait(&params->nti.cv, &params->nti.mu);
+        }
+
+        pthread_mutex_unlock(&params->nti.mu);
+    }
+
+    params->written += tgt->size;
+    rc = 0;
+
+pcnout:
+    if (tgt) {
+        free(tgt);
+    }
+
+    return rc;
+}
+
+static int PerformCommandDiff(CommandParameters* params) {
+    char* logparams = NULL;
+    char* value = NULL;
+    int blocks = 0;
+    int overlap = 0;
+    int rc = -1;
+    int status = 0;
+    RangeSet* tgt = NULL;
+    RangeSinkState rss;
+    size_t len = 0;
+    size_t offset = 0;
+    Value patch_value;
+
+    if (!params) {
+        goto pcdout;
+    }
+
+    logparams = strdup(params->cpos);
+    value = strtok_r(NULL, " ", &params->cpos);
+
+    if (value == NULL) {
+        fprintf(stderr, "missing patch offset for %s\n", params->cmdname);
+        goto pcdout;
+    }
+
+    offset = strtoul(value, NULL, 0);
+
+    value = strtok_r(NULL, " ", &params->cpos);
+
+    if (value == NULL) {
+        fprintf(stderr, "missing patch length for %s\n", params->cmdname);
+        goto pcdout;
+    }
+
+    len = strtoul(value, NULL, 0);
+
+    if (params->version == 1) {
+        status = LoadSrcTgtVersion1(&params->cpos, &tgt, &blocks, &params->buffer,
+                    &params->bufsize, params->fd);
+    } else if (params->version == 2) {
+        status = LoadSrcTgtVersion2(&params->cpos, &tgt, &blocks, &params->buffer,
+                    &params->bufsize, params->fd, params->stashbase, NULL);
+    } else if (params->version >= 3) {
+        status = LoadSrcTgtVersion3(params, &tgt, &blocks, 0, &overlap);
+    }
+
+    if (status == -1) {
+        fprintf(stderr, "failed to read blocks for diff\n");
+        goto pcdout;
+    }
+
+    if (status == 0) {
+        params->foundwrites = 1;
+    } else if (params->foundwrites) {
+        fprintf(stderr, "warning: commands executed out of order [%s]\n", params->cmdname);
+    }
+
+    if (params->canwrite) {
+        if (status == 0) {
+            fprintf(stderr, "patching %d blocks to %d\n", blocks, tgt->size);
+
+            patch_value.type = VAL_BLOB;
+            patch_value.size = len;
+            patch_value.data = (char*) (params->patch_start + offset);
+
+            rss.fd = params->fd;
+            rss.tgt = tgt;
+            rss.p_block = 0;
+            rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE;
+
+            if (!check_lseek(params->fd, (off64_t) tgt->pos[0] * BLOCKSIZE, SEEK_SET)) {
+                goto pcdout;
+            }
+
+            if (params->cmdname[0] == 'i') {      // imgdiff
+                ApplyImagePatch(params->buffer, blocks * BLOCKSIZE, &patch_value,
+                    &RangeSinkWrite, &rss, NULL, NULL);
+            } else {
+                ApplyBSDiffPatch(params->buffer, blocks * 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");
+            }
+        } else {
+            fprintf(stderr, "skipping %d blocks already patched to %d [%s]\n",
+                blocks, tgt->size, logparams);
+        }
+    }
+
+    if (params->freestash) {
+        FreeStash(params->stashbase, params->freestash);
+        params->freestash = NULL;
+    }
+
+    params->written += tgt->size;
+    rc = 0;
+
+pcdout:
+    if (logparams) {
+        free(logparams);
+    }
+
+    if (tgt) {
+        free(tgt);
+    }
+
+    return rc;
+}
+
+static int PerformCommandErase(CommandParameters* params) {
+    char* range = NULL;
+    int i;
+    int rc = -1;
+    RangeSet* tgt = NULL;
+    struct stat st;
+    uint64_t blocks[2];
+
+    if (DEBUG_ERASE) {
+        return PerformCommandZero(params);
+    }
+
+    if (!params) {
+        goto pceout;
+    }
+
+    if (fstat(params->fd, &st) == -1) {
+        fprintf(stderr, "failed to fstat device to erase: %s\n", strerror(errno));
+        goto pceout;
+    }
+
+    if (!S_ISBLK(st.st_mode)) {
+        fprintf(stderr, "not a block device; skipping erase\n");
+        goto pceout;
+    }
+
+    range = strtok_r(NULL, " ", &params->cpos);
+
+    if (range == NULL) {
+        fprintf(stderr, "missing target blocks for zero\n");
+        goto pceout;
+    }
+
+    tgt = parse_range(range);
+
+    if (params->canwrite) {
+        fprintf(stderr, " erasing %d blocks\n", tgt->size);
+
+        for (i = 0; i < tgt->count; ++i) {
+            // offset in bytes
+            blocks[0] = tgt->pos[i * 2] * (uint64_t) BLOCKSIZE;
+            // length in bytes
+            blocks[1] = (tgt->pos[i * 2 + 1] - tgt->pos[i * 2]) * (uint64_t) BLOCKSIZE;
+
+            if (ioctl(params->fd, BLKDISCARD, &blocks) == -1) {
+                fprintf(stderr, "BLKDISCARD ioctl failed: %s\n", strerror(errno));
+                goto pceout;
+            }
+        }
+    }
+
+    rc = 0;
+
+pceout:
+    if (tgt) {
+        free(tgt);
+    }
+
+    return rc;
+}
+
+// Definitions for transfer list command functions
+typedef int (*CommandFunction)(CommandParameters*);
+
+typedef struct {
+    const char* name;
+    CommandFunction f;
+} Command;
+
+// CompareCommands and CompareCommandNames are for the hash table
+
+static int CompareCommands(const void* c1, const void* c2) {
+    return strcmp(((const Command*) c1)->name, ((const Command*) c2)->name);
+}
+
+static int CompareCommandNames(const void* c1, const void* c2) {
+    return strcmp(((const Command*) c1)->name, (const char*) c2);
+}
+
+// HashString is used to hash command names for the hash table
+
+static unsigned int HashString(const char *s) {
+    unsigned int hash = 0;
+    if (s) {
+        while (*s) {
+            hash = hash * 33 + *s++;
+        }
+    }
+    return hash;
 }
 
 // args:
@@ -378,393 +1518,372 @@
 //    - 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;
+static Value* PerformBlockImageUpdate(const char* name, State* state, int argc, Expr* argv[],
+            const Command* commands, int cmdcount, int dryrun) {
+
+    char* line = NULL;
+    char* linesave = NULL;
+    char* logcmd = NULL;
     char* transfer_list = NULL;
-    Value* new_data_fn;
-    Value* patch_data_fn;
-    bool success = false;
+    CommandParameters params;
+    const Command* cmd = NULL;
+    const ZipEntry* new_entry = NULL;
+    const ZipEntry* patch_entry = NULL;
+    FILE* cmd_pipe = NULL;
+    HashTable* cmdht = NULL;
+    int i;
+    int res;
+    int rc = -1;
+    int stash_max_blocks = 0;
+    int total_blocks = 0;
+    pthread_attr_t attr;
+    unsigned int cmdhash;
+    UpdaterInfo* ui = NULL;
+    Value* blockdev_filename = NULL;
+    Value* new_data_fn = NULL;
+    Value* patch_data_fn = NULL;
+    Value* transfer_list_value = NULL;
+    ZipArchive* za = NULL;
+
+    memset(&params, 0, sizeof(params));
+    params.canwrite = !dryrun;
+
+    fprintf(stderr, "performing %s\n", dryrun ? "verification" : "update");
 
     if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value,
-                      &new_data_fn, &patch_data_fn) < 0) {
-        return NULL;
+            &new_data_fn, &patch_data_fn) < 0) {
+        goto pbiudone;
     }
 
     if (blockdev_filename->type != VAL_STRING) {
         ErrorAbort(state, "blockdev_filename argument to %s must be string", name);
-        goto done;
+        goto pbiudone;
     }
     if (transfer_list_value->type != VAL_BLOB) {
         ErrorAbort(state, "transfer_list argument to %s must be blob", name);
-        goto done;
+        goto pbiudone;
     }
     if (new_data_fn->type != VAL_STRING) {
         ErrorAbort(state, "new_data_fn argument to %s must be string", name);
-        goto done;
+        goto pbiudone;
     }
     if (patch_data_fn->type != VAL_STRING) {
         ErrorAbort(state, "patch_data_fn argument to %s must be string", name);
-        goto done;
+        goto pbiudone;
     }
 
-    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
-    FILE* cmd_pipe = ui->cmd_pipe;
+    ui = (UpdaterInfo*) state->cookie;
 
-    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
+    if (ui == NULL) {
+        goto pbiudone;
+    }
 
-    const ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data);
+    cmd_pipe = ui->cmd_pipe;
+    za = ui->package_zip;
+
+    if (cmd_pipe == NULL || za == NULL) {
+        goto pbiudone;
+    }
+
+    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;
+        fprintf(stderr, "%s(): no file \"%s\" in package", name, patch_data_fn->data);
+        goto pbiudone;
     }
 
-    uint8_t* patch_start = ((UpdaterInfo*)(state->cookie))->package_zip_addr +
-        mzGetZipEntryOffset(patch_entry);
+    params.patch_start = ui->package_zip_addr + mzGetZipEntryOffset(patch_entry);
+    new_entry = mzFindZipEntry(za, new_data_fn->data);
 
-    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;
+        fprintf(stderr, "%s(): no file \"%s\" in package", name, new_data_fn->data);
+        goto pbiudone;
     }
 
-    // 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
-    //
-    //    erase [rangeset]
-    //      - mark the given blocks as empty
-    //
-    //    move <...>
-    //    bsdiff <patchstart> <patchlen> <...>
-    //    imgdiff <patchstart> <patchlen> <...>
-    //      - read the source blocks, apply a patch (or not in the
-    //        case of move), write result to target blocks.  bsdiff or
-    //        imgdiff specifies the type of patch; move means no patch
-    //        at all.
-    //
-    //        The format of <...> differs between versions 1 and 2;
-    //        see the LoadSrcTgtVersion{1,2}() functions for a
-    //        description of what's expected.
-    //
-    //    stash <stash_id> <src_range>
-    //      - (version 2 only) load the given source range and stash
-    //        the data in the given slot of the stash table.
-    //
-    // 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.
-    //
-    // In version 2, the creator will guarantee that a given stash is
-    // loaded (with a stash command) before it's used in a
-    // move/bsdiff/imgdiff command.
-    //
-    // 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.)
+    params.fd = TEMP_FAILURE_RETRY(open(blockdev_filename->data, O_RDWR));
 
-    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;
+    if (params.fd == -1) {
+        fprintf(stderr, "open \"%s\" failed: %s\n", blockdev_filename->data, strerror(errno));
+        goto pbiudone;
     }
 
-    char* line;
-    char* word;
+    if (params.canwrite) {
+        params.nti.za = za;
+        params.nti.entry = new_entry;
 
-    // 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);
+        pthread_mutex_init(&params.nti.mu, NULL);
+        pthread_cond_init(&params.nti.cv, NULL);
+        pthread_attr_init(&attr);
+        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+        int error = pthread_create(&params.thread, &attr, unzip_new_data, &params.nti);
+        if (error != 0) {
+            fprintf(stderr, "pthread_create failed: %s\n", strerror(error));
+            goto pbiudone;
+        }
+    }
+
+    // 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);
+            transfer_list_value->size + 1);
+        goto pbiudone;
     }
+
     memcpy(transfer_list, transfer_list_value->data, transfer_list_value->size);
     transfer_list[transfer_list_value->size] = '\0';
 
+    // First line in transfer list is the version number
     line = strtok_r(transfer_list, "\n", &linesave);
+    params.version = strtol(line, NULL, 0);
 
-    int version;
-    // first line in transfer list is the version number; currently
-    // there's only version 1.
-    if (strcmp(line, "1") == 0) {
-        version = 1;
-    } else if (strcmp(line, "2") == 0) {
-        version = 2;
-    } else {
-        ErrorAbort(state, "unexpected transfer list version [%s]\n", line);
-        goto done;
+    if (params.version < 1 || params.version > 3) {
+        fprintf(stderr, "unexpected transfer list version [%s]\n", line);
+        goto pbiudone;
     }
-    printf("blockimg version is %d\n", version);
 
-    // second line in transfer list is the total number of blocks we
-    // expect to write.
+    fprintf(stderr, "blockimg version is %d\n", params.version);
+
+    // 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;
+    total_blocks = strtol(line, NULL, 0);
 
-    uint8_t** stash_table = NULL;
-    if (version >= 2) {
-        // Next line is how many stash entries are needed simultaneously.
+    if (total_blocks < 0) {
+        ErrorAbort(state, "unexpected block count [%s]\n", line);
+        goto pbiudone;
+    } else if (total_blocks == 0) {
+        rc = 0;
+        goto pbiudone;
+    }
+
+    if (params.version >= 2) {
+        // Third line is how many stash entries are needed simultaneously
         line = strtok_r(NULL, "\n", &linesave);
-        int stash_entries = strtol(line, NULL, 0);
+        fprintf(stderr, "maximum stash entries %s\n", line);
 
-        stash_table = (uint8_t**) calloc(stash_entries, sizeof(uint8_t*));
-        if (stash_table == NULL) {
-            fprintf(stderr, "failed to allocate %d-entry stash table\n", stash_entries);
-            exit(1);
+        // Fourth line is the maximum number of blocks that will be stashed simultaneously
+        line = strtok_r(NULL, "\n", &linesave);
+        stash_max_blocks = strtol(line, NULL, 0);
+
+        if (stash_max_blocks < 0) {
+            ErrorAbort(state, "unexpected maximum stash blocks [%s]\n", line);
+            goto pbiudone;
         }
 
-        // Next line is the maximum number of blocks that will be
-        // stashed simultaneously.  This could be used to verify that
-        // enough memory or scratch disk space is available.
-        line = strtok_r(NULL, "\n", &linesave);
-        int stash_max_blocks = strtol(line, NULL, 0);
+        if (stash_max_blocks >= 0) {
+            res = CreateStash(state, stash_max_blocks, blockdev_filename->data,
+                    &params.stashbase);
+
+            if (res == -1) {
+                goto pbiudone;
+            }
+
+            params.createdstash = res;
+        }
     }
 
-    uint8_t* buffer = NULL;
-    size_t buffer_alloc = 0;
+    // Build a hash table of the available commands
+    cmdht = mzHashTableCreate(cmdcount, NULL);
 
-    // third and subsequent lines are all individual transfer commands.
+    for (i = 0; i < cmdcount; ++i) {
+        cmdhash = HashString(commands[i].name);
+        mzHashTableLookup(cmdht, cmdhash, (void*) &commands[i], CompareCommands, true);
+    }
+
+    // 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);
+        logcmd = strdup(line);
+        params.cmdname = strtok_r(line, " ", &params.cpos);
 
-        if (strcmp("move", style) == 0) {
-            RangeSet* tgt;
-            int src_blocks;
-            if (version == 1) {
-                LoadSrcTgtVersion1(wordsave, &tgt, &src_blocks,
-                                   &buffer, &buffer_alloc, fd);
-            } else if (version == 2) {
-                LoadSrcTgtVersion2(wordsave, &tgt, &src_blocks,
-                                   &buffer, &buffer_alloc, fd, stash_table);
-            }
+        if (params.cmdname == NULL) {
+            fprintf(stderr, "missing command [%s]\n", line);
+            goto pbiudone;
+        }
 
-            printf("  moving %d blocks\n", src_blocks);
+        cmdhash = HashString(params.cmdname);
+        cmd = (const Command*) mzHashTableLookup(cmdht, cmdhash, params.cmdname,
+                                    CompareCommandNames, false);
 
-            size_t 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;
-            }
+        if (cmd == NULL) {
+            fprintf(stderr, "unexpected command [%s]\n", params.cmdname);
+            goto pbiudone;
+        }
 
-            blocks_so_far += tgt->size;
-            fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
+        if (cmd->f != NULL && cmd->f(&params) == -1) {
+            fprintf(stderr, "failed to execute command [%s]\n",
+                logcmd ? logcmd : params.cmdname);
+            goto pbiudone;
+        }
+
+        if (logcmd) {
+            free(logcmd);
+            logcmd = NULL;
+        }
+
+        if (params.canwrite) {
+            fprintf(cmd_pipe, "set_progress %.4f\n", (double) params.written / total_blocks);
             fflush(cmd_pipe);
-
-            free(tgt);
-
-        } else if (strcmp("stash", style) == 0) {
-            word = strtok_r(NULL, " ", &wordsave);
-            int stash_id = strtol(word, NULL, 0);
-            int src_blocks;
-            size_t stash_alloc = 0;
-
-            // Even though the "stash" style only appears in version
-            // 2, the version 1 source loader happens to do exactly
-            // what we want to read data into the stash_table.
-            LoadSrcTgtVersion1(wordsave, NULL, &src_blocks,
-                               stash_table + stash_id, &stash_alloc, fd);
-
-        } 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);
-
-            RangeSet* tgt;
-            int src_blocks;
-            if (version == 1) {
-                LoadSrcTgtVersion1(wordsave, &tgt, &src_blocks,
-                                   &buffer, &buffer_alloc, fd);
-            } else if (version == 2) {
-                LoadSrcTgtVersion2(wordsave, &tgt, &src_blocks,
-                                   &buffer, &buffer_alloc, fd, stash_table);
-            }
-
-            printf("  patching %d blocks to %d\n", src_blocks, tgt->size);
-
-            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);
-
-            int ret;
-            if (style[0] == 'i') {      // imgdiff
-                ret = ApplyImagePatch(buffer, src_blocks * BLOCKSIZE,
-                                      &patch_value,
-                                      &RangeSinkWrite, &rss, NULL, NULL);
-            } else {
-                ret = ApplyBSDiffPatch(buffer, src_blocks * BLOCKSIZE,
-                                       &patch_value, 0,
-                                       &RangeSinkWrite, &rss, NULL);
-            }
-
-            if (ret != 0) {
-                ErrorAbort(state, "patch failed\n");
-                goto done;
-            }
-
-            // We expect the output of the patcher to fill the tgt ranges exactly.
-            if (rss.p_block != tgt->count || rss.p_remain != 0) {
-                ErrorAbort(state, "range sink underrun?\n");
-                goto done;
-            }
-
-            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 (!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) {
-                        ErrorAbort(state, "    blkdiscard failed: %s\n", strerror(errno));
-                        goto done;
-                    }
-                }
-
-                free(tgt);
-            } else {
-                printf("  ignoring erase (not block device)\n");
-            }
-        } else {
-            ErrorAbort(state, "unknown transfer style \"%s\"\n", style);
-            goto done;
         }
     }
 
-    pthread_join(new_data_thread, NULL);
-    success = true;
+    if (params.canwrite) {
+        pthread_join(params.thread, NULL);
 
-    free(buffer);
-    printf("wrote %d blocks; expected %d\n", blocks_so_far, total_blocks);
-    printf("max alloc needed was %zu\n", buffer_alloc);
+        fprintf(stderr, "wrote %d blocks; expected %d\n", params.written, total_blocks);
+        fprintf(stderr, "max alloc needed was %zu\n", params.bufsize);
 
-done:
-    free(transfer_list);
-    FreeValue(blockdev_filename);
-    FreeValue(transfer_list_value);
-    FreeValue(new_data_fn);
-    FreeValue(patch_data_fn);
-    if (success) {
-        return StringValue(strdup("t"));
+        // Delete stash only after successfully completing the update, as it
+        // may contain blocks needed to complete the update later.
+        DeleteStash(params.stashbase);
     } else {
-        // NULL will be passed to its caller at Evaluate() and abort the OTA
-        // process.
-        return NULL;
+        fprintf(stderr, "verified partition contents; update may be resumed\n");
     }
+
+    rc = 0;
+
+pbiudone:
+    if (params.fd != -1) {
+        if (fsync(params.fd) == -1) {
+            fprintf(stderr, "fsync failed: %s\n", strerror(errno));
+        }
+        close(params.fd);
+    }
+
+    if (logcmd) {
+        free(logcmd);
+    }
+
+    if (cmdht) {
+        mzHashTableFree(cmdht);
+    }
+
+    if (params.buffer) {
+        free(params.buffer);
+    }
+
+    if (transfer_list) {
+        free(transfer_list);
+    }
+
+    if (blockdev_filename) {
+        FreeValue(blockdev_filename);
+    }
+
+    if (transfer_list_value) {
+        FreeValue(transfer_list_value);
+    }
+
+    if (new_data_fn) {
+        FreeValue(new_data_fn);
+    }
+
+    if (patch_data_fn) {
+        FreeValue(patch_data_fn);
+    }
+
+    // Only delete the stash if the update cannot be resumed, or it's
+    // a verification run and we created the stash.
+    if (params.isunresumable || (!params.canwrite && params.createdstash)) {
+        DeleteStash(params.stashbase);
+    }
+
+    if (params.stashbase) {
+        free(params.stashbase);
+    }
+
+    return StringValue(rc == 0 ? strdup("t") : strdup(""));
+}
+
+// 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
+//
+//    erase [rangeset]
+//      - mark the given blocks as empty
+//
+//    move <...>
+//    bsdiff <patchstart> <patchlen> <...>
+//    imgdiff <patchstart> <patchlen> <...>
+//      - read the source blocks, apply a patch (or not in the
+//        case of move), write result to target blocks.  bsdiff or
+//        imgdiff specifies the type of patch; move means no patch
+//        at all.
+//
+//        The format of <...> differs between versions 1 and 2;
+//        see the LoadSrcTgtVersion{1,2}() functions for a
+//        description of what's expected.
+//
+//    stash <stash_id> <src_range>
+//      - (version 2+ only) load the given source range and stash
+//        the data in the given slot of the stash table.
+//
+// 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.
+//
+// In version 2, the creator will guarantee that a given stash is
+// loaded (with a stash command) before it's used in a
+// move/bsdiff/imgdiff command.
+//
+// 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.)
+//
+// In version 3, commands that read data from the partition (i.e.
+// move/bsdiff/imgdiff/stash) have one or more additional hashes
+// before the range parameters, which are used to check if the
+// command has already been completed and verify the integrity of
+// the source data.
+
+Value* BlockImageVerifyFn(const char* name, State* state, int argc, Expr* argv[]) {
+    // Commands which are not tested are set to NULL to skip them completely
+    const Command commands[] = {
+        { "bsdiff",     PerformCommandDiff  },
+        { "erase",      NULL                },
+        { "free",       PerformCommandFree  },
+        { "imgdiff",    PerformCommandDiff  },
+        { "move",       PerformCommandMove  },
+        { "new",        NULL                },
+        { "stash",      PerformCommandStash },
+        { "zero",       NULL                }
+    };
+
+    // Perform a dry run without writing to test if an update can proceed
+    return PerformBlockImageUpdate(name, state, argc, argv, commands,
+                sizeof(commands) / sizeof(commands[0]), 1);
+}
+
+Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) {
+    const Command commands[] = {
+        { "bsdiff",     PerformCommandDiff  },
+        { "erase",      PerformCommandErase },
+        { "free",       PerformCommandFree  },
+        { "imgdiff",    PerformCommandDiff  },
+        { "move",       PerformCommandMove  },
+        { "new",        PerformCommandNew   },
+        { "stash",      PerformCommandStash },
+        { "zero",       PerformCommandZero  }
+    };
+
+    return PerformBlockImageUpdate(name, state, argc, argv, commands,
+                sizeof(commands) / sizeof(commands[0]), 0);
 }
 
 Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) {
@@ -786,7 +1905,7 @@
 
     int fd = open(blockdev_filename->data, O_RDWR);
     if (fd < 0) {
-        ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno));
+        ErrorAbort(state, "open \"%s\" failed: %s", blockdev_filename->data, strerror(errno));
         goto done;
     }
 
@@ -798,26 +1917,37 @@
 
     int i, j;
     for (i = 0; i < rs->count; ++i) {
-        check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET);
+        if (!check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET)) {
+            ErrorAbort(state, "failed to seek %s: %s", blockdev_filename->data,
+                strerror(errno));
+            goto done;
+        }
+
         for (j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) {
-            readblock(fd, buffer, BLOCKSIZE);
+            if (read_all(fd, buffer, BLOCKSIZE) == -1) {
+                ErrorAbort(state, "failed to read %s: %s", blockdev_filename->data,
+                    strerror(errno));
+                goto done;
+            }
+
             SHA_update(&ctx, buffer, BLOCKSIZE);
         }
     }
     digest = SHA_final(&ctx);
     close(fd);
 
-done:
+  done:
     FreeValue(blockdev_filename);
     FreeValue(ranges);
     if (digest == NULL) {
-        return NULL;
+        return StringValue(strdup(""));
     } else {
         return StringValue(PrintSha1(digest));
     }
 }
 
 void RegisterBlockImageFunctions() {
+    RegisterFunction("block_image_verify", BlockImageVerifyFn);
     RegisterFunction("block_image_update", BlockImageUpdateFn);
     RegisterFunction("range_sha1", RangeSha1Fn);
 }
diff --git a/updater/install.c b/updater/install.c
index 2b2ffb0..01a5dd2 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -496,7 +496,7 @@
     struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
 
     bool success = mzExtractRecursive(za, zip_path, dest_path,
-                                      MZ_EXTRACT_FILES_ONLY, &timestamp,
+                                      &timestamp,
                                       NULL, NULL, sehandle);
     free(zip_path);
     free(dest_path);
diff --git a/updater/updater.c b/updater/updater.c
index 465e123..661f695 100644
--- a/updater/updater.c
+++ b/updater/updater.c
@@ -17,6 +17,7 @@
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include "edify/expr.h"
 #include "updater.h"
diff --git a/verifier.cpp b/verifier.cpp
index 3d4f603..bf7071d 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -26,9 +26,10 @@
 #include "mincrypt/sha.h"
 #include "mincrypt/sha256.h"
 
-#include <string.h>
-#include <stdio.h>
 #include <errno.h>
+#include <malloc.h>
+#include <stdio.h>
+#include <string.h>
 
 extern RecoveryUI* ui;
 
diff --git a/verifier_test.cpp b/verifier_test.cpp
index 10a5dda..82546ed 100644
--- a/verifier_test.cpp
+++ b/verifier_test.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <stdarg.h>
+#include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <fcntl.h>
 
 #include "common.h"
 #include "verifier.h"
@@ -122,6 +124,8 @@
 // nothing but print.
 class FakeUI : public RecoveryUI {
     void Init() { }
+    void SetStage(int, int) { }
+    void SetLocale(const char*) { }
     void SetBackground(Icon icon) { }
 
     void SetProgressType(ProgressType determinate) { }
@@ -137,6 +141,7 @@
         vfprintf(stderr, fmt, ap);
         va_end(ap);
     }
+    void ShowFile(const char*) { }
 
     void StartMenu(const char* const * headers, const char* const * items,
                            int initial_selection) { }