Merge "adb: win32: fix Ctrl-C of adb server nodaemon"
diff --git a/adb/Android.bp b/adb/Android.bp
index 3685687..1469e77 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -142,6 +142,8 @@
         "client/fastdeploycallbacks.cpp",
     ],
 
+    generated_headers: ["platform_tools_version"],
+
     target: {
         linux: {
             srcs: ["client/usb_linux.cpp"],
@@ -311,6 +313,8 @@
         "daemon/include",
     ],
 
+    generated_headers: ["platform_tools_version"],
+
     static_libs: [
         "libdiagnose_usb",
         "libqemu_pipe",
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 8e028f4..791899e 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -45,6 +45,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <build/version.h>
+#include <platform_tools_version.h>
 
 #include "adb_auth.h"
 #include "adb_io.h"
@@ -65,10 +66,11 @@
     // Don't change the format of this --- it's parsed by ddmlib.
     return android::base::StringPrintf(
         "Android Debug Bridge version %d.%d.%d\n"
-        "Version %s\n"
+        "Version %s-%s\n"
         "Installed as %s\n",
         ADB_VERSION_MAJOR, ADB_VERSION_MINOR, ADB_SERVER_VERSION,
-        android::build::GetBuildNumber().c_str(), android::base::GetExecutablePath().c_str());
+        PLATFORM_TOOLS_VERSION, android::build::GetBuildNumber().c_str(),
+        android::base::GetExecutablePath().c_str());
 }
 
 void fatal(const char *fmt, ...) {
diff --git a/adb/client/fastdeploy.cpp b/adb/client/fastdeploy.cpp
index cd42b56..fda3889 100644
--- a/adb/client/fastdeploy.cpp
+++ b/adb/client/fastdeploy.cpp
@@ -51,7 +51,7 @@
 
     int statusCode = capture_shell_command("getprop ro.build.version.sdk", &sdkVersionOutputBuffer,
                                            &sdkVersionErrorBuffer);
-    if (statusCode == 0 && statusCode == 0 && sdkVersionOutputBuffer.size() > 0) {
+    if (statusCode == 0 && sdkVersionOutputBuffer.size() > 0) {
         api_level = strtol((char*)sdkVersionOutputBuffer.data(), NULL, 10);
     }
 
@@ -107,7 +107,7 @@
         const char* kChmodCommandPattern = "chmod 777 %sdeployagent.sh";
         std::string chmodCommand =
                 android::base::StringPrintf(kChmodCommandPattern, kDeviceAgentPath);
-        int ret = send_shell_command(chmodCommand.c_str());
+        int ret = send_shell_command(chmodCommand);
         return (ret == 0);
     } else {
         return false;
@@ -248,7 +248,7 @@
     std::vector<char> extractErrorBuffer;
     int statusCode;
     DeployAgentFileCallback cb(outputFp, &extractErrorBuffer, &statusCode);
-    int ret = send_shell_command(extractCommand.c_str(), false, &cb);
+    int ret = send_shell_command(extractCommand, false, &cb);
 
     if (ret == 0) {
         return cb.getBytesWritten();
diff --git a/adb/client/line_printer.cpp b/adb/client/line_printer.cpp
index 9758526..4dc2d28 100644
--- a/adb/client/line_printer.cpp
+++ b/adb/client/line_printer.cpp
@@ -67,7 +67,11 @@
 
 void LinePrinter::Print(string to_print, LineType type) {
   if (!smart_terminal_) {
-    Out(to_print + "\n");
+    if (type == LineType::INFO) {
+        info_line_ = to_print + "\n";
+    } else {
+        Out(to_print + "\n");
+    }
     return;
   }
 
@@ -123,6 +127,11 @@
 }
 
 void LinePrinter::KeepInfoLine() {
-  if (!have_blank_line_) Out("\n");
-  have_blank_line_ = true;
+  if (smart_terminal_) {
+      if (!have_blank_line_) Out("\n");
+      have_blank_line_ = true;
+  } else {
+      Out(info_line_);
+      info_line_.clear();
+  }
 }
diff --git a/adb/client/line_printer.h b/adb/client/line_printer.h
index 42345e2..4c4c7c6 100644
--- a/adb/client/line_printer.h
+++ b/adb/client/line_printer.h
@@ -42,6 +42,9 @@
   /// Whether the caret is at the beginning of a blank line.
   bool have_blank_line_;
 
+  /// The last printed info line when printing to a dumb terminal.
+  std::string info_line_;
+
 #ifdef _WIN32
   void* console_;
 #endif
diff --git a/adb/client/usb_linux.cpp b/adb/client/usb_linux.cpp
index 869e858..f1bf559 100644
--- a/adb/client/usb_linux.cpp
+++ b/adb/client/usb_linux.cpp
@@ -30,6 +30,7 @@
 #include <string.h>
 #include <sys/ioctl.h>
 #include <sys/time.h>
+#include <sys/sysmacros.h>
 #include <sys/types.h>
 #include <unistd.h>
 
diff --git a/adb/client/usb_windows.cpp b/adb/client/usb_windows.cpp
index e928377..00aeb9b 100644
--- a/adb/client/usb_windows.cpp
+++ b/adb/client/usb_windows.cpp
@@ -357,7 +357,8 @@
 
     if (handle->zero_mask && (len & handle->zero_mask) == 0) {
         // Send a zero length packet
-        if (!AdbWriteEndpointSync(handle->adb_write_pipe, (void*)data, 0, &written, time_out)) {
+        unsigned long dummy;
+        if (!AdbWriteEndpointSync(handle->adb_write_pipe, (void*)data, 0, &dummy, time_out)) {
             D("AdbWriteEndpointSync of zero length packet failed: %s",
               android::base::SystemErrorCodeToString(GetLastError()).c_str());
             err = EIO;
diff --git a/adb/daemon/include/adbd/usb.h b/adb/daemon/include/adbd/usb.h
index 7905d9d..78e4cd5 100644
--- a/adb/daemon/include/adbd/usb.h
+++ b/adb/daemon/include/adbd/usb.h
@@ -21,6 +21,7 @@
 #include <mutex>
 #include <vector>
 
+#include <android-base/unique_fd.h>
 #include <asyncio/AsyncIO.h>
 
 struct aio_block {
@@ -47,11 +48,11 @@
     void (*close)(usb_handle* h);
 
     // FunctionFS
-    int control = -1;
-    int bulk_out = -1; /* "out" from the host's perspective => source for adbd */
-    int bulk_in = -1;  /* "in" from the host's perspective => sink for adbd */
+    android::base::unique_fd control;
+    android::base::unique_fd bulk_out;  // "out" from the host's perspective => source for adbd
+    android::base::unique_fd bulk_in;   // "in" from the host's perspective => sink for adbd
 
-    // Access to these blocks is very not thread safe. Have one block for both the
+    // Access to these blocks is very not thread safe. Have one block for each of the
     // read and write threads.
     struct aio_block read_aiob;
     struct aio_block write_aiob;
diff --git a/adb/daemon/remount_service.cpp b/adb/daemon/remount_service.cpp
index f7017dd..ed5f944 100644
--- a/adb/daemon/remount_service.cpp
+++ b/adb/daemon/remount_service.cpp
@@ -38,7 +38,6 @@
 #include <android-base/properties.h>
 #include <bootloader_message/bootloader_message.h>
 #include <cutils/android_reboot.h>
-#include <ext4_utils/ext4_utils.h>
 #include <fs_mgr.h>
 #include <fs_mgr_overlayfs.h>
 
@@ -96,27 +95,6 @@
     return result;
 }
 
-static bool fs_has_shared_blocks(const std::string& mount_point, const std::string& device) {
-    std::string path = mount_point + "/lost+found";
-    struct statfs fs;
-    if (statfs(path.c_str(), &fs) == -1 || fs.f_type != EXT4_SUPER_MAGIC) {
-        return false;
-    }
-    unique_fd fd(unix_open(device.c_str(), O_RDONLY));
-    if (fd < 0) {
-        return false;
-    }
-    struct ext4_super_block sb;
-    if (lseek64(fd, 1024, SEEK_SET) < 0 || unix_read(fd, &sb, sizeof(sb)) < 0) {
-        return false;
-    }
-    struct fs_info info;
-    if (ext4_parse_sb(&sb, &info) < 0) {
-        return false;
-    }
-    return (info.feat_ro_compat & EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS) != 0;
-}
-
 static bool can_unshare_blocks(int fd, const char* dev) {
     const char* E2FSCK_BIN = "/system/bin/e2fsck";
     if (access(E2FSCK_BIN, X_OK)) {
@@ -250,7 +228,7 @@
     std::set<std::string> dedup;
     for (const auto& partition : partitions) {
         std::string dev = find_mount(partition.c_str(), partition == "/");
-        if (dev.empty() || !fs_has_shared_blocks(partition, dev)) {
+        if (dev.empty() || !fs_mgr_has_shared_blocks(partition, dev)) {
             continue;
         }
         if (can_unshare_blocks(fd.get(), dev.c_str())) {
diff --git a/adb/daemon/usb.cpp b/adb/daemon/usb.cpp
index c79d6fc..9d495b0 100644
--- a/adb/daemon/usb.cpp
+++ b/adb/daemon/usb.cpp
@@ -58,7 +58,7 @@
 #define cpu_to_le16(x) htole16(x)
 #define cpu_to_le32(x) htole32(x)
 
-static int dummy_fd = -1;
+static unique_fd& dummy_fd = *new unique_fd();
 
 struct func_desc {
     struct usb_interface_descriptor intf;
@@ -273,13 +273,13 @@
 
     if (h->control < 0) { // might have already done this before
         LOG(INFO) << "opening control endpoint " << USB_FFS_ADB_EP0;
-        h->control = adb_open(USB_FFS_ADB_EP0, O_WRONLY);
+        h->control.reset(adb_open(USB_FFS_ADB_EP0, O_WRONLY));
         if (h->control < 0) {
             PLOG(ERROR) << "cannot open control endpoint " << USB_FFS_ADB_EP0;
             goto err;
         }
 
-        ret = adb_write(h->control, &v2_descriptor, sizeof(v2_descriptor));
+        ret = adb_write(h->control.get(), &v2_descriptor, sizeof(v2_descriptor));
         if (ret < 0) {
             v1_descriptor.header.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC);
             v1_descriptor.header.length = cpu_to_le32(sizeof(v1_descriptor));
@@ -288,14 +288,14 @@
             v1_descriptor.fs_descs = fs_descriptors;
             v1_descriptor.hs_descs = hs_descriptors;
             D("[ %s: Switching to V1_descriptor format errno=%d ]", USB_FFS_ADB_EP0, errno);
-            ret = adb_write(h->control, &v1_descriptor, sizeof(v1_descriptor));
+            ret = adb_write(h->control.get(), &v1_descriptor, sizeof(v1_descriptor));
             if (ret < 0) {
                 D("[ %s: write descriptors failed: errno=%d ]", USB_FFS_ADB_EP0, errno);
                 goto err;
             }
         }
 
-        ret = adb_write(h->control, &strings, sizeof(strings));
+        ret = adb_write(h->control.get(), &strings, sizeof(strings));
         if (ret < 0) {
             D("[ %s: writing strings failed: errno=%d]", USB_FFS_ADB_EP0, errno);
             goto err;
@@ -304,13 +304,13 @@
         android::base::SetProperty("sys.usb.ffs.ready", "1");
     }
 
-    h->bulk_out = adb_open(USB_FFS_ADB_OUT, O_RDONLY);
+    h->bulk_out.reset(adb_open(USB_FFS_ADB_OUT, O_RDONLY));
     if (h->bulk_out < 0) {
         PLOG(ERROR) << "cannot open bulk-out endpoint " << USB_FFS_ADB_OUT;
         goto err;
     }
 
-    h->bulk_in = adb_open(USB_FFS_ADB_IN, O_WRONLY);
+    h->bulk_in.reset(adb_open(USB_FFS_ADB_IN, O_WRONLY));
     if (h->bulk_in < 0) {
         PLOG(ERROR) << "cannot open bulk-in endpoint " << USB_FFS_ADB_IN;
         goto err;
@@ -322,18 +322,9 @@
     return true;
 
 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;
-    }
+    h->bulk_in.reset();
+    h->bulk_out.reset();
+    h->control.reset();
     return false;
 }
 
@@ -366,7 +357,7 @@
 }
 
 static int usb_ffs_write(usb_handle* h, const void* data, int len) {
-    D("about to write (fd=%d, len=%d)", h->bulk_in, len);
+    D("about to write (fd=%d, len=%d)", h->bulk_in.get(), len);
 
     const char* buf = static_cast<const char*>(data);
     int orig_len = len;
@@ -374,19 +365,19 @@
         int write_len = std::min(USB_FFS_BULK_SIZE, len);
         int n = adb_write(h->bulk_in, buf, write_len);
         if (n < 0) {
-            D("ERROR: fd = %d, n = %d: %s", h->bulk_in, n, strerror(errno));
+            D("ERROR: fd = %d, n = %d: %s", h->bulk_in.get(), n, strerror(errno));
             return -1;
         }
         buf += n;
         len -= n;
     }
 
-    D("[ done fd=%d ]", h->bulk_in);
+    D("[ done fd=%d ]", h->bulk_in.get());
     return orig_len;
 }
 
 static int usb_ffs_read(usb_handle* h, void* data, int len) {
-    D("about to read (fd=%d, len=%d)", h->bulk_out, len);
+    D("about to read (fd=%d, len=%d)", h->bulk_out.get(), len);
 
     char* buf = static_cast<char*>(data);
     int orig_len = len;
@@ -394,14 +385,14 @@
         int read_len = std::min(USB_FFS_BULK_SIZE, len);
         int n = adb_read(h->bulk_out, buf, read_len);
         if (n < 0) {
-            D("ERROR: fd = %d, n = %d: %s", h->bulk_out, n, strerror(errno));
+            D("ERROR: fd = %d, n = %d: %s", h->bulk_out.get(), n, strerror(errno));
             return -1;
         }
         buf += n;
         len -= n;
     }
 
-    D("[ done fd=%d ]", h->bulk_out);
+    D("[ done fd=%d ]", h->bulk_out.get());
     return orig_len;
 }
 
@@ -475,14 +466,14 @@
 static void usb_ffs_kick(usb_handle* h) {
     int err;
 
-    err = ioctl(h->bulk_in, FUNCTIONFS_CLEAR_HALT);
+    err = ioctl(h->bulk_in.get(), FUNCTIONFS_CLEAR_HALT);
     if (err < 0) {
-        D("[ kick: source (fd=%d) clear halt failed (%d) ]", h->bulk_in, errno);
+        D("[ kick: source (fd=%d) clear halt failed (%d) ]", h->bulk_in.get(), errno);
     }
 
-    err = ioctl(h->bulk_out, FUNCTIONFS_CLEAR_HALT);
+    err = ioctl(h->bulk_out.get(), FUNCTIONFS_CLEAR_HALT);
     if (err < 0) {
-        D("[ kick: sink (fd=%d) clear halt failed (%d) ]", h->bulk_out, errno);
+        D("[ kick: sink (fd=%d) clear halt failed (%d) ]", h->bulk_out.get(), errno);
     }
 
     // don't close ep0 here, since we may not need to reinitialize it with
@@ -490,16 +481,16 @@
     // init_functionfs, only then would we close and open ep0 again.
     // Ditto the comment in usb_adb_kick.
     h->kicked = true;
-    TEMP_FAILURE_RETRY(dup2(dummy_fd, h->bulk_out));
-    TEMP_FAILURE_RETRY(dup2(dummy_fd, h->bulk_in));
+    TEMP_FAILURE_RETRY(dup2(dummy_fd.get(), h->bulk_out.get()));
+    TEMP_FAILURE_RETRY(dup2(dummy_fd.get(), h->bulk_in.get()));
 }
 
 static void usb_ffs_close(usb_handle* h) {
     LOG(INFO) << "closing functionfs transport";
 
     h->kicked = false;
-    adb_close(h->bulk_out);
-    adb_close(h->bulk_in);
+    h->bulk_out.reset();
+    h->bulk_in.reset();
 
     // Notify usb_adb_open_thread to open a new connection.
     h->lock.lock();
@@ -530,8 +521,8 @@
 
 void usb_init() {
     D("[ usb_init - using FunctionFS ]");
-    dummy_fd = adb_open("/dev/null", O_WRONLY);
-    CHECK_NE(dummy_fd, -1);
+    dummy_fd.reset(adb_open("/dev/null", O_WRONLY | O_CLOEXEC));
+    CHECK_NE(-1, dummy_fd.get());
 
     std::thread(usb_ffs_open_thread, create_usb_handle(USB_FFS_NUM_BUFS, USB_FFS_BULK_SIZE)).detach();
 }
diff --git a/adb/test_device.py b/adb/test_device.py
index 42aadc4..20f224a 100644
--- a/adb/test_device.py
+++ b/adb/test_device.py
@@ -1302,6 +1302,37 @@
                 self.assertEqual(length, len(stdout) - 4)
                 self.assertEqual(stdout, "\0" * length + "foo\n")
 
+    def test_zero_packet(self):
+        """Test for http://b/113070258
+
+        Make sure that we don't blow up when sending USB transfers that line up
+        exactly with the USB packet size.
+        """
+
+        local_port = int(self.device.forward("tcp:0", "tcp:12345"))
+        try:
+            for size in [512, 1024]:
+                def listener():
+                    cmd = ["echo foo | nc -l -p 12345; echo done"]
+                    rc, stdout, stderr = self.device.shell_nocheck(cmd)
+
+                thread = threading.Thread(target=listener)
+                thread.start()
+
+                # Wait a bit to let the shell command start.
+                time.sleep(0.25)
+
+                sock = socket.create_connection(("localhost", local_port))
+                with contextlib.closing(sock):
+                    bytesWritten = sock.send("a" * size)
+                    self.assertEqual(size, bytesWritten)
+                    readBytes = sock.recv(4096)
+                    self.assertEqual("foo\n", readBytes)
+
+                thread.join()
+        finally:
+            self.device.forward_remove("tcp:{}".format(local_port))
+
 
 def main():
     random.seed(0)
diff --git a/adb/transport.cpp b/adb/transport.cpp
index 332e0f8..239403a 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -220,25 +220,34 @@
         }
         D("attempting to reconnect %s", attempt.transport->serial.c_str());
 
-        if (!attempt.transport->Reconnect()) {
-            D("attempting to reconnect %s failed.", attempt.transport->serial.c_str());
-            if (attempt.attempts_left == 0) {
-                D("transport %s exceeded the number of retry attempts. giving up on it.",
-                  attempt.transport->serial.c_str());
-                remove_transport(attempt.transport);
+        switch (attempt.transport->Reconnect()) {
+            case ReconnectResult::Retry: {
+                D("attempting to reconnect %s failed.", attempt.transport->serial.c_str());
+                if (attempt.attempts_left == 0) {
+                    D("transport %s exceeded the number of retry attempts. giving up on it.",
+                      attempt.transport->serial.c_str());
+                    remove_transport(attempt.transport);
+                    continue;
+                }
+
+                std::lock_guard<std::mutex> lock(reconnect_mutex_);
+                reconnect_queue_.emplace(ReconnectAttempt{
+                        attempt.transport,
+                        std::chrono::steady_clock::now() + ReconnectHandler::kDefaultTimeout,
+                        attempt.attempts_left - 1});
                 continue;
             }
 
-            std::lock_guard<std::mutex> lock(reconnect_mutex_);
-            reconnect_queue_.emplace(ReconnectAttempt{
-                    attempt.transport,
-                    std::chrono::steady_clock::now() + ReconnectHandler::kDefaultTimeout,
-                    attempt.attempts_left - 1});
-            continue;
-        }
+            case ReconnectResult::Success:
+                D("reconnection to %s succeeded.", attempt.transport->serial.c_str());
+                register_transport(attempt.transport);
+                continue;
 
-        D("reconnection to %s succeeded.", attempt.transport->serial.c_str());
-        register_transport(attempt.transport);
+            case ReconnectResult::Abort:
+                D("cancelling reconnection attempt to %s.", attempt.transport->serial.c_str());
+                remove_transport(attempt.transport);
+                continue;
+        }
     }
 }
 
@@ -1128,7 +1137,7 @@
     connection_waitable_->SetConnectionEstablished(success);
 }
 
-bool atransport::Reconnect() {
+ReconnectResult atransport::Reconnect() {
     return reconnect_(this);
 }
 
diff --git a/adb/transport.h b/adb/transport.h
index f362f24..f854ce5 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -193,6 +193,12 @@
     DISALLOW_COPY_AND_ASSIGN(ConnectionWaitable);
 };
 
+enum class ReconnectResult {
+    Retry,
+    Success,
+    Abort,
+};
+
 class atransport {
   public:
     // TODO(danalbert): We expose waaaaaaay too much stuff because this was
@@ -200,7 +206,7 @@
     // class in one go is a very large change. Given how bad our testing is,
     // it's better to do this piece by piece.
 
-    using ReconnectCallback = std::function<bool(atransport*)>;
+    using ReconnectCallback = std::function<ReconnectResult(atransport*)>;
 
     atransport(ReconnectCallback reconnect, ConnectionState state)
         : id(NextTransportId()),
@@ -215,7 +221,7 @@
         max_payload = MAX_PAYLOAD;
     }
     atransport(ConnectionState state = kCsOffline)
-        : atransport([](atransport*) { return false; }, state) {}
+        : atransport([](atransport*) { return ReconnectResult::Abort; }, state) {}
     virtual ~atransport();
 
     int Write(apacket* p);
@@ -295,9 +301,8 @@
     // Gets a shared reference to the ConnectionWaitable.
     std::shared_ptr<ConnectionWaitable> connection_waitable() { return connection_waitable_; }
 
-    // Attempts to reconnect with the underlying Connection. Returns true if the
-    // reconnection attempt succeeded.
-    bool Reconnect();
+    // Attempts to reconnect with the underlying Connection.
+    ReconnectResult Reconnect();
 
   private:
     std::atomic<bool> kicked_;
diff --git a/adb/transport_local.cpp b/adb/transport_local.cpp
index 8b8fd51..4fd483b 100644
--- a/adb/transport_local.cpp
+++ b/adb/transport_local.cpp
@@ -117,14 +117,15 @@
         std::tie(fd, port, serial) = tcp_connect(address, &response);
         if (fd == -1) {
             D("reconnect failed: %s", response.c_str());
-            return false;
+            return ReconnectResult::Retry;
         }
 
         // This invokes the part of register_socket_transport() that needs to be
         // invoked if the atransport* has already been setup. This eventually
         // calls atransport->SetConnection() with a newly created Connection*
         // that will in turn send the CNXN packet.
-        return init_socket_transport(t, std::move(fd), port, 0) >= 0;
+        return init_socket_transport(t, std::move(fd), port, 0) >= 0 ? ReconnectResult::Success
+                                                                     : ReconnectResult::Retry;
     };
 
     int error;
@@ -166,7 +167,7 @@
         disable_tcp_nagle(fd.get());
         std::string serial = getEmulatorSerialString(console_port);
         if (register_socket_transport(std::move(fd), std::move(serial), adb_port, 1,
-                                      [](atransport*) { return false; })) {
+                                      [](atransport*) { return ReconnectResult::Abort; })) {
             return 0;
         }
     }
@@ -269,7 +270,7 @@
             disable_tcp_nagle(fd.get());
             std::string serial = android::base::StringPrintf("host-%d", fd.get());
             register_socket_transport(std::move(fd), std::move(serial), port, 1,
-                                      [](atransport*) { return false; });
+                                      [](atransport*) { return ReconnectResult::Abort; });
         }
     }
     D("transport: server_socket_thread() exiting");
@@ -366,7 +367,7 @@
                 std::string serial = android::base::StringPrintf("host-%d", fd.get());
                 WriteFdExactly(fd.get(), _start_req, strlen(_start_req));
                 register_socket_transport(std::move(fd), std::move(serial), port, 1,
-                                          [](atransport*) { return false; });
+                                          [](atransport*) { return ReconnectResult::Abort; });
             }
 
             /* Prepare for accepting of the next ADB host connection. */
diff --git a/base/include/android-base/parseint.h b/base/include/android-base/parseint.h
index 55f1ed3..9444fdd 100644
--- a/base/include/android-base/parseint.h
+++ b/base/include/android-base/parseint.h
@@ -33,18 +33,35 @@
 template <typename T>
 bool ParseUint(const char* s, T* out, T max = std::numeric_limits<T>::max(),
                bool allow_suffixes = false) {
+  while (isspace(*s)) {
+    s++;
+  }
+
+  if (s[0] == '-') {
+    errno = EINVAL;
+    return false;
+  }
+
   int base = (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10;
   errno = 0;
   char* end;
   unsigned long long int result = strtoull(s, &end, base);
-  if (errno != 0 || end == s) return false;
+  if (errno != 0) return false;
+  if (end == s) {
+    errno = EINVAL;
+    return false;
+  }
   if (*end != '\0') {
     const char* suffixes = "bkmgtpe";
     const char* suffix;
-    if (!allow_suffixes || (suffix = strchr(suffixes, tolower(*end))) == nullptr) return false;
-    if (__builtin_mul_overflow(result, 1ULL << (10 * (suffix - suffixes)), &result)) return false;
+    if ((!allow_suffixes || (suffix = strchr(suffixes, tolower(*end))) == nullptr) ||
+        __builtin_mul_overflow(result, 1ULL << (10 * (suffix - suffixes)), &result)) {
+      errno = EINVAL;
+      return false;
+    }
   }
   if (max < result) {
+    errno = ERANGE;
     return false;
   }
   if (out != nullptr) {
@@ -79,14 +96,23 @@
 bool ParseInt(const char* s, T* out,
               T min = std::numeric_limits<T>::min(),
               T max = std::numeric_limits<T>::max()) {
+  while (isspace(*s)) {
+    s++;
+  }
+
   int base = (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10;
   errno = 0;
   char* end;
   long long int result = strtoll(s, &end, base);
-  if (errno != 0 || s == end || *end != '\0') {
+  if (errno != 0) {
+    return false;
+  }
+  if (s == end || *end != '\0') {
+    errno = EINVAL;
     return false;
   }
   if (result < min || max < result) {
+    errno = ERANGE;
     return false;
   }
   if (out != nullptr) {
diff --git a/base/parseint_test.cpp b/base/parseint_test.cpp
index 8f9ed77..e449c33 100644
--- a/base/parseint_test.cpp
+++ b/base/parseint_test.cpp
@@ -16,17 +16,30 @@
 
 #include "android-base/parseint.h"
 
+#include <errno.h>
+
 #include <gtest/gtest.h>
 
 TEST(parseint, signed_smoke) {
+  errno = 0;
   int i = 0;
   ASSERT_FALSE(android::base::ParseInt("x", &i));
+  ASSERT_EQ(EINVAL, errno);
+  errno = 0;
   ASSERT_FALSE(android::base::ParseInt("123x", &i));
+  ASSERT_EQ(EINVAL, errno);
 
   ASSERT_TRUE(android::base::ParseInt("123", &i));
   ASSERT_EQ(123, i);
+  ASSERT_EQ(0, errno);
+  i = 0;
+  EXPECT_TRUE(android::base::ParseInt("  123", &i));
+  EXPECT_EQ(123, i);
   ASSERT_TRUE(android::base::ParseInt("-123", &i));
   ASSERT_EQ(-123, i);
+  i = 0;
+  EXPECT_TRUE(android::base::ParseInt("  -123", &i));
+  EXPECT_EQ(-123, i);
 
   short s = 0;
   ASSERT_TRUE(android::base::ParseInt("1234", &s));
@@ -34,22 +47,43 @@
 
   ASSERT_TRUE(android::base::ParseInt("12", &i, 0, 15));
   ASSERT_EQ(12, i);
+  errno = 0;
   ASSERT_FALSE(android::base::ParseInt("-12", &i, 0, 15));
+  ASSERT_EQ(ERANGE, errno);
+  errno = 0;
   ASSERT_FALSE(android::base::ParseInt("16", &i, 0, 15));
+  ASSERT_EQ(ERANGE, errno);
 
+  errno = 0;
   ASSERT_FALSE(android::base::ParseInt<int>("x", nullptr));
+  ASSERT_EQ(EINVAL, errno);
+  errno = 0;
   ASSERT_FALSE(android::base::ParseInt<int>("123x", nullptr));
+  ASSERT_EQ(EINVAL, errno);
   ASSERT_TRUE(android::base::ParseInt<int>("1234", nullptr));
 }
 
 TEST(parseint, unsigned_smoke) {
+  errno = 0;
   unsigned int i = 0u;
   ASSERT_FALSE(android::base::ParseUint("x", &i));
+  ASSERT_EQ(EINVAL, errno);
+  errno = 0;
   ASSERT_FALSE(android::base::ParseUint("123x", &i));
+  ASSERT_EQ(EINVAL, errno);
 
   ASSERT_TRUE(android::base::ParseUint("123", &i));
   ASSERT_EQ(123u, i);
+  ASSERT_EQ(0, errno);
+  i = 0u;
+  EXPECT_TRUE(android::base::ParseUint("  123", &i));
+  EXPECT_EQ(123u, i);
+  errno = 0;
   ASSERT_FALSE(android::base::ParseUint("-123", &i));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  EXPECT_FALSE(android::base::ParseUint("  -123", &i));
+  EXPECT_EQ(EINVAL, errno);
 
   unsigned short s = 0u;
   ASSERT_TRUE(android::base::ParseUint("1234", &s));
@@ -57,12 +91,28 @@
 
   ASSERT_TRUE(android::base::ParseUint("12", &i, 15u));
   ASSERT_EQ(12u, i);
+  errno = 0;
   ASSERT_FALSE(android::base::ParseUint("-12", &i, 15u));
+  ASSERT_EQ(EINVAL, errno);
+  errno = 0;
   ASSERT_FALSE(android::base::ParseUint("16", &i, 15u));
+  ASSERT_EQ(ERANGE, errno);
 
+  errno = 0;
   ASSERT_FALSE(android::base::ParseUint<unsigned short>("x", nullptr));
+  ASSERT_EQ(EINVAL, errno);
+  errno = 0;
   ASSERT_FALSE(android::base::ParseUint<unsigned short>("123x", nullptr));
+  ASSERT_EQ(EINVAL, errno);
   ASSERT_TRUE(android::base::ParseUint<unsigned short>("1234", nullptr));
+
+  errno = 0;
+  unsigned long long int lli;
+  EXPECT_FALSE(android::base::ParseUint("-123", &lli));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  EXPECT_FALSE(android::base::ParseUint("  -123", &lli));
+  EXPECT_EQ(EINVAL, errno);
 }
 
 TEST(parseint, no_implicit_octal) {
@@ -79,10 +129,16 @@
   int i = 0;
   ASSERT_TRUE(android::base::ParseInt("0x123", &i));
   ASSERT_EQ(0x123, i);
+  i = 0;
+  EXPECT_TRUE(android::base::ParseInt("  0x123", &i));
+  EXPECT_EQ(0x123, i);
 
   unsigned int u = 0u;
   ASSERT_TRUE(android::base::ParseUint("0x123", &u));
   ASSERT_EQ(0x123u, u);
+  u = 0u;
+  EXPECT_TRUE(android::base::ParseUint("  0x123", &u));
+  EXPECT_EQ(0x123u, u);
 }
 
 TEST(parseint, string) {
@@ -101,7 +157,7 @@
   ASSERT_EQ(123, i);
 
   unsigned int u = 123u;
-  ASSERT_FALSE(android::base::ParseInt("456x", &u));
+  ASSERT_FALSE(android::base::ParseUint("456x", &u));
   ASSERT_EQ(123u, u);
 }
 
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index dfb7a6a..e2ea480 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -29,6 +29,7 @@
 #include <regex>
 #include <thread>
 
+#include <android/fdsan.h>
 #include <android/set_abort_message.h>
 
 #include <android-base/file.h>
@@ -801,6 +802,31 @@
   AssertDeath(SIGABRT);
 }
 
+TEST_F(CrasherTest, fdsan_warning_abort_message) {
+  int intercept_result;
+  unique_fd output_fd;
+
+  StartProcess([]() {
+    android_fdsan_set_error_level(ANDROID_FDSAN_ERROR_LEVEL_WARN_ONCE);
+    unique_fd fd(open("/dev/null", O_RDONLY | O_CLOEXEC));
+    if (fd == -1) {
+      abort();
+    }
+    close(fd.get());
+    _exit(0);
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(0);
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, "Abort message: 'attempted to close");
+}
+
 TEST(crash_dump, zombie) {
   pid_t forkpid = fork();
 
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 91e6f71..15557b6 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -457,14 +457,14 @@
     info = nullptr;
   }
 
-  struct siginfo si = {};
+  struct siginfo dummy_info = {};
   if (!info) {
-    memset(&si, 0, sizeof(si));
-    si.si_signo = signal_number;
-    si.si_code = SI_USER;
-    si.si_pid = __getpid();
-    si.si_uid = getuid();
-    info = &si;
+    memset(&dummy_info, 0, sizeof(dummy_info));
+    dummy_info.si_signo = signal_number;
+    dummy_info.si_code = SI_USER;
+    dummy_info.si_pid = __getpid();
+    dummy_info.si_uid = getuid();
+    info = &dummy_info;
   } else if (info->si_code >= 0 || info->si_code == SI_TKILL) {
     // rt_tgsigqueueinfo(2)'s documentation appears to be incorrect on kernels
     // that contain commit 66dd34a (3.9+). The manpage claims to only allow
@@ -473,8 +473,18 @@
   }
 
   void* abort_message = nullptr;
-  if (signal_number != DEBUGGER_SIGNAL && g_callbacks.get_abort_message) {
-    abort_message = g_callbacks.get_abort_message();
+  if (signal_number == DEBUGGER_SIGNAL) {
+    if (info->si_code == SI_QUEUE && info->si_pid == __getpid()) {
+      // Allow for the abort message to be explicitly specified via the sigqueue value.
+      // Keep the bottom bit intact for representing whether we want a backtrace or a tombstone.
+      uintptr_t value = reinterpret_cast<uintptr_t>(info->si_ptr);
+      abort_message = reinterpret_cast<void*>(value & ~1);
+      info->si_ptr = reinterpret_cast<void*>(value & 1);
+    }
+  } else {
+    if (g_callbacks.get_abort_message) {
+      abort_message = g_callbacks.get_abort_message();
+    }
   }
 
   // If sival_int is ~0, it means that the fallback handler has been called
diff --git a/debuggerd/libdebuggerd/open_files_list.cpp b/debuggerd/libdebuggerd/open_files_list.cpp
index 03e4e8e..743a2e7 100644
--- a/debuggerd/libdebuggerd/open_files_list.cpp
+++ b/debuggerd/libdebuggerd/open_files_list.cpp
@@ -18,6 +18,7 @@
 
 #include "libdebuggerd/open_files_list.h"
 
+#include <android/fdsan.h>
 #include <dirent.h>
 #include <errno.h>
 #include <stdio.h>
@@ -122,8 +123,10 @@
     const std::optional<std::string>& path = entry.path;
     const std::optional<uint64_t>& fdsan_owner = entry.fdsan_owner;
     if (path && fdsan_owner) {
-      _LOG(log, logtype::OPEN_FILES, "%sfd %i: %s (owned by %#" PRIx64 ")\n", prefix, fd,
-           path->c_str(), *fdsan_owner);
+      const char* type = android_fdsan_get_tag_type(*fdsan_owner);
+      uint64_t value = android_fdsan_get_tag_value(*fdsan_owner);
+      _LOG(log, logtype::OPEN_FILES, "%sfd %i: %s (owned by %s %#" PRIx64 ")\n", prefix, fd,
+           path->c_str(), type, value);
     } else if (path && !fdsan_owner) {
       _LOG(log, logtype::OPEN_FILES, "%sfd %i: %s (unowned)\n", prefix, fd, path->c_str());
     } else if (!path && fdsan_owner) {
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 3414d53..3b91ddd 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// This is required because no Android.bp can include a library defined in an
+// Android.mk. Eventually should kill libfastboot (defined in Android.mk)
 cc_library_host_static {
     name: "libfastboot2",
 
@@ -19,50 +21,66 @@
 
     compile_multilib: "first",
     srcs: [
-      "bootimg_utils.cpp",
-      "fs.cpp",
-      "socket.cpp",
-      "tcp.cpp",
-      "udp.cpp",
-      "util.cpp",
-      "fastboot_driver.cpp",
+        "bootimg_utils.cpp",
+        "fs.cpp",
+        "socket.cpp",
+        "tcp.cpp",
+        "udp.cpp",
+        "util.cpp",
+        "fastboot_driver.cpp",
     ],
 
     static_libs: [
-      "libziparchive",
-      "libsparse",
-      "libutils",
-      "liblog",
-      "libz",
-      "libdiagnose_usb",
-      "libbase",
-      "libcutils",
-      "libgtest",
-      "libgtest_main",
-      "libbase",
-      "libadb_host"
+        "libziparchive",
+        "libsparse",
+        "libutils",
+        "liblog",
+        "libz",
+        "libdiagnose_usb",
+        "libbase",
+        "libcutils",
+        "libgtest",
+        "libgtest_main",
+        "libbase",
+        "libadb_host",
     ],
 
     header_libs: [
-      "bootimg_headers"
+        "bootimg_headers",
     ],
 
     export_header_lib_headers: [
-      "bootimg_headers"
+        "bootimg_headers",
     ],
 
-
     target: {
-      linux: {
-        srcs: ["usb_linux.cpp"],
-      },
+        linux: {
+            srcs: ["usb_linux.cpp"],
+        },
+
+        darwin: {
+            srcs: ["usb_osx.cpp"],
+
+            host_ldlibs: [
+                "-framework CoreFoundation",
+                "-framework IOKit",
+            ],
+        },
+
+        windows: {
+            srcs: ["usb_windows.cpp"],
+
+            host_ldlibs: [
+                "-lws2_32",
+            ],
+        },
     },
 
     cflags: [
-      "-Wall",
-      "-Wextra",
-      "-Werror",
-      "-Wunreachable-code",
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-Wunreachable-code",
     ],
 
     export_include_dirs: ["."],
@@ -122,3 +140,154 @@
 
     cpp_std: "c++17",
 }
+
+cc_defaults {
+    name: "fastboot_host_defaults",
+
+    use_version_lib: true,
+
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-Wunreachable-code",
+    ],
+
+    target: {
+        darwin: {
+            cflags: ["-Wno-unused-parameter"],
+            host_ldlibs: [
+                "-lpthread",
+                "-framework CoreFoundation",
+                "-framework IOKit",
+            ],
+        },
+        windows: {
+            enabled: true,
+
+            host_ldlibs: ["-lws2_32"],
+        },
+    },
+
+    stl: "libc++_static",
+
+    // Don't add anything here, we don't want additional shared dependencies
+    // on the host fastboot tool, and shared libraries that link against libc++
+    // will violate ODR.
+    shared_libs: [],
+
+    header_libs: ["bootimg_headers"],
+    static_libs: [
+        "libziparchive",
+        "libsparse",
+        "libutils",
+        "liblog",
+        "libz",
+        "libdiagnose_usb",
+        "libbase",
+        "libcutils",
+        "libgtest_host",
+    ],
+}
+
+//
+// Build host libfastboot.
+//
+
+cc_library_host_static {
+    name: "libfastboot",
+    defaults: ["fastboot_host_defaults"],
+
+    cpp_std: "c++17",
+    srcs: [
+        "bootimg_utils.cpp",
+        "engine.cpp",
+        "fastboot.cpp",
+        "fs.cpp",
+        "socket.cpp",
+        "tcp.cpp",
+        "udp.cpp",
+        "util.cpp",
+        "fastboot_driver.cpp",
+    ],
+
+    // Only version the final binaries
+    use_version_lib: false,
+    static_libs: ["libbuildversion"],
+
+    generated_headers: ["platform_tools_version"],
+
+    target: {
+        windows: {
+            srcs: ["usb_windows.cpp"],
+
+            include_dirs: ["development/host/windows/usb/api"],
+        },
+        darwin: {
+            srcs: ["usb_osx.cpp"],
+        },
+        linux_glibc: {
+            srcs: ["usb_linux.cpp"],
+        },
+    },
+}
+
+//
+// Build host fastboot / fastboot.exe
+//
+
+cc_binary_host {
+    name: "fastboot",
+    defaults: ["fastboot_host_defaults"],
+
+    srcs: ["main.cpp"],
+    static_libs: ["libfastboot"],
+
+    required: [
+        "mke2fs",
+        "make_f2fs",
+    ],
+
+    target: {
+        not_windows: {
+            required: [
+                "e2fsdroid",
+                "mke2fs.conf",
+                "sload_f2fs",
+            ],
+        },
+        windows: {
+            required: ["AdbWinUsbApi"],
+            shared_libs: ["AdbWinApi"],
+        },
+    },
+}
+
+//
+// Build host fastboot_test.
+//
+
+cc_test_host {
+    name: "fastboot_test",
+    defaults: ["fastboot_host_defaults"],
+
+    srcs: [
+        "fastboot_test.cpp",
+        "socket_mock.cpp",
+        "socket_test.cpp",
+        "tcp_test.cpp",
+        "udp_test.cpp",
+    ],
+
+    static_libs: ["libfastboot"],
+
+    target: {
+        windows: {
+            shared_libs: ["AdbWinApi"],
+        },
+        windows_x86_64: {
+            // Avoid trying to build for win64
+            enabled: false,
+        },
+    },
+}
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index a679143..e4c1317 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -14,89 +14,6 @@
 
 LOCAL_PATH:= $(call my-dir)
 
-include $(LOCAL_PATH)/../platform_tools_tool_version.mk
-
-fastboot_cflags := -Wall -Wextra -Werror -Wunreachable-code
-fastboot_cflags += -DFASTBOOT_VERSION="\"$(tool_version)\""
-fastboot_cflags_darwin := -Wno-unused-parameter
-fastboot_ldlibs_darwin := -lpthread -framework CoreFoundation -framework IOKit -framework Carbon
-fastboot_ldlibs_windows := -lws2_32
-# Don't add anything here, we don't want additional shared dependencies
-# on the host fastboot tool, and shared libraries that link against libc++
-# will violate ODR.
-fastboot_shared_libs :=
-fastboot_static_libs := \
-    libziparchive \
-    libsparse \
-    libutils \
-    liblog \
-    libz \
-    libdiagnose_usb \
-    libbase \
-    libcutils \
-    libgtest_host \
-
-fastboot_stl := libc++_static
-
-#
-# Build host libfastboot.
-#
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := libfastboot
-LOCAL_MODULE_HOST_OS := darwin linux windows
-
-LOCAL_SRC_FILES := \
-    bootimg_utils.cpp \
-    engine.cpp \
-    fastboot.cpp \
-    fs.cpp \
-    socket.cpp \
-    tcp.cpp \
-    udp.cpp \
-    util.cpp \
-    fastboot_driver.cpp \
-
-LOCAL_SRC_FILES_darwin := usb_osx.cpp
-LOCAL_SRC_FILES_linux := usb_linux.cpp
-LOCAL_SRC_FILES_windows := usb_windows.cpp
-
-LOCAL_C_INCLUDES_windows := development/host/windows/usb/api
-LOCAL_CFLAGS := $(fastboot_cflags)
-LOCAL_CFLAGS_darwin := $(fastboot_cflags_darwin)
-LOCAL_CXX_STL := $(fastboot_stl)
-LOCAL_HEADER_LIBRARIES := bootimg_headers
-LOCAL_LDLIBS_darwin := $(fastboot_ldlibs_darwin)
-LOCAL_LDLIBS_windows := $(fastboot_ldlibs_windows)
-LOCAL_SHARED_LIBRARIES := $(fastboot_shared_libs)
-LOCAL_STATIC_LIBRARIES := $(fastboot_static_libs)
-include $(BUILD_HOST_STATIC_LIBRARY)
-
-#
-# Build host fastboot / fastboot.exe
-#
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := fastboot
-LOCAL_MODULE_HOST_OS := darwin linux windows
-
-LOCAL_CFLAGS := $(fastboot_cflags)
-LOCAL_CFLAGS_darwin := $(fastboot_cflags_darwin)
-LOCAL_CPP_STD := c++17
-LOCAL_CXX_STL := $(fastboot_stl)
-LOCAL_HEADER_LIBRARIES := bootimg_headers
-LOCAL_LDLIBS_darwin := $(fastboot_ldlibs_darwin)
-LOCAL_LDLIBS_windows := $(fastboot_ldlibs_windows)
-LOCAL_REQUIRED_MODULES := mke2fs make_f2fs
-LOCAL_REQUIRED_MODULES_darwin := e2fsdroid mke2fs.conf sload_f2fs
-LOCAL_REQUIRED_MODULES_linux := e2fsdroid mke2fs.conf sload_f2fs
-LOCAL_REQUIRED_MODULES_windows := AdbWinUsbApi
-LOCAL_SRC_FILES := main.cpp
-LOCAL_SHARED_LIBRARIES := $(fastboot_shared_libs)
-LOCAL_SHARED_LIBRARIES_windows := AdbWinApi
-LOCAL_STATIC_LIBRARIES := libfastboot $(fastboot_static_libs)
-include $(BUILD_HOST_EXECUTABLE)
-
 #
 # Package fastboot-related executables.
 #
@@ -111,30 +28,3 @@
 $(call dist-for-goals,dist_files sdk win_sdk,$(ALL_MODULES.host_cross_fastboot.BUILT))
 endif
 my_dist_files :=
-
-#
-# Build host fastboot_test.
-#
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := fastboot_test
-LOCAL_MODULE_HOST_OS := darwin linux windows
-LOCAL_MODULE_HOST_CROSS_ARCH := x86 # Avoid trying to build for win64.
-
-LOCAL_SRC_FILES := \
-    fastboot_test.cpp \
-    socket_mock.cpp \
-    socket_test.cpp \
-    tcp_test.cpp \
-    udp_test.cpp \
-
-LOCAL_CFLAGS := $(fastboot_cflags)
-LOCAL_CFLAGS_darwin := $(fastboot_cflags_darwin)
-LOCAL_CXX_STL := $(fastboot_stl)
-LOCAL_HEADER_LIBRARIES := bootimg_headers
-LOCAL_LDLIBS_darwin := $(fastboot_ldlibs_darwin)
-LOCAL_LDLIBS_windows := $(fastboot_ldlibs_windows)
-LOCAL_SHARED_LIBRARIES := $(fastboot_shared_libs)
-LOCAL_SHARED_LIBRARIES_windows := AdbWinApi
-LOCAL_STATIC_LIBRARIES := libfastboot $(fastboot_static_libs)
-include $(BUILD_HOST_NATIVE_TEST)
diff --git a/fastboot/README.md b/fastboot/README.md
index ec7dcb4..c224448 100644
--- a/fastboot/README.md
+++ b/fastboot/README.md
@@ -136,10 +136,6 @@
                        should not support "upload" unless it supports an
                        oem command that requires "upload" capabilities.
 
-    verify:%08x        Send a digital signature to verify the downloaded
-                       data.  Required if the bootloader is "secure"
-                       otherwise "flash" and "boot" will be ignored.
-
     flash:%s           Write the previously downloaded image to the
                        named partition (if possible).
 
@@ -159,8 +155,6 @@
                        the bootloader and then upgrading other partitions
                        using the new bootloader.
 
-    powerdown          Power off the device.
-
 
 
 ## Client Variables
@@ -186,10 +180,45 @@
                         bootloader requiring a signature before
                         it will install or boot images.
 
+    is-userspace        If the value is "yes", the device is running
+                        fastbootd. Otherwise, it is running fastboot
+                        in the bootloader.
+
 Names starting with a lowercase character are reserved by this
 specification.  OEM-specific names should not start with lowercase
 characters.
 
+## Logical Partitions
+
+There are a number of commands to interact with logical partitions:
+
+    update-super:%s:%s  Write the previously downloaded image to a super
+                        partition. Unlike the "flash" command, this has
+                        special rules. The image must have been created by
+                        the lpmake command, and must not be a sparse image.
+                        If the last argument is "wipe", then all existing
+                        logical partitions are deleted. If no final argument
+                        is specified, the partition tables are merged. Any
+                        partition in the new image that does not exist in the
+                        old image is created with a zero size.
+
+                        In all cases, this will cause the temporary "scratch"
+                        partition to be deleted if it exists.
+
+    create-logical-partition:%s:%d
+                        Create a logical partition with the given name and
+                        size, in the super partition.
+
+    delete-logical-partition:%s
+                        Delete a logical partition with the given name.
+
+    resize-logical-partition:%s:%d
+                        Change the size of the named logical partition.
+
+In addition, there is a variable to test whether a partition is logical:
+
+    is-logical:%s       If the value is "yes", the partition is logical.
+                        Otherwise the partition is physical.
 
 ## TCP Protocol v1
 
diff --git a/fastboot/constants.h b/fastboot/constants.h
index 43daab0..063cd40 100644
--- a/fastboot/constants.h
+++ b/fastboot/constants.h
@@ -18,7 +18,6 @@
 #define FB_CMD_GETVAR "getvar"
 #define FB_CMD_DOWNLOAD "download"
 #define FB_CMD_UPLOAD "upload"
-#define FB_CMD_VERIFY "verify"
 #define FB_CMD_FLASH "flash"
 #define FB_CMD_ERASE "erase"
 #define FB_CMD_BOOT "boot"
@@ -29,10 +28,10 @@
 #define FB_CMD_REBOOT_BOOTLOADER "reboot-bootloader"
 #define FB_CMD_REBOOT_RECOVERY "reboot-recovery"
 #define FB_CMD_REBOOT_FASTBOOT "reboot-fastboot"
-#define FB_CMD_POWERDOWN "powerdown"
 #define FB_CMD_CREATE_PARTITION "create-logical-partition"
 #define FB_CMD_DELETE_PARTITION "delete-logical-partition"
 #define FB_CMD_RESIZE_PARTITION "resize-logical-partition"
+#define FB_CMD_UPDATE_SUPER "update-super"
 
 #define RESPONSE_OKAY "OKAY"
 #define RESPONSE_FAIL "FAIL"
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index 690bfa8..b1c2958 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -42,34 +42,79 @@
 using ::android::hardware::boot::V1_0::Slot;
 using namespace android::fs_mgr;
 
+struct VariableHandlers {
+    // Callback to retrieve the value of a single variable.
+    std::function<bool(FastbootDevice*, const std::vector<std::string>&, std::string*)> get;
+    // Callback to retrieve all possible argument combinations, for getvar all.
+    std::function<std::vector<std::vector<std::string>>(FastbootDevice*)> get_all_args;
+};
+
+static void GetAllVars(FastbootDevice* device, const std::string& name,
+                       const VariableHandlers& handlers) {
+    if (!handlers.get_all_args) {
+        std::string message;
+        if (!handlers.get(device, std::vector<std::string>(), &message)) {
+            return;
+        }
+        device->WriteInfo(android::base::StringPrintf("%s:%s", name.c_str(), message.c_str()));
+        return;
+    }
+
+    auto all_args = handlers.get_all_args(device);
+    for (const auto& args : all_args) {
+        std::string message;
+        if (!handlers.get(device, args, &message)) {
+            continue;
+        }
+        std::string arg_string = android::base::Join(args, ":");
+        device->WriteInfo(android::base::StringPrintf("%s:%s:%s", name.c_str(), arg_string.c_str(),
+                                                      message.c_str()));
+    }
+}
+
 bool GetVarHandler(FastbootDevice* device, const std::vector<std::string>& args) {
-    using VariableHandler = std::function<bool(FastbootDevice*, const std::vector<std::string>&)>;
-    const std::unordered_map<std::string, VariableHandler> kVariableMap = {
-            {FB_VAR_VERSION, GetVersion},
-            {FB_VAR_VERSION_BOOTLOADER, GetBootloaderVersion},
-            {FB_VAR_VERSION_BASEBAND, GetBasebandVersion},
-            {FB_VAR_PRODUCT, GetProduct},
-            {FB_VAR_SERIALNO, GetSerial},
-            {FB_VAR_SECURE, GetSecure},
-            {FB_VAR_UNLOCKED, GetUnlocked},
-            {FB_VAR_MAX_DOWNLOAD_SIZE, GetMaxDownloadSize},
-            {FB_VAR_CURRENT_SLOT, ::GetCurrentSlot},
-            {FB_VAR_SLOT_COUNT, GetSlotCount},
-            {FB_VAR_HAS_SLOT, GetHasSlot},
-            {FB_VAR_SLOT_SUCCESSFUL, GetSlotSuccessful},
-            {FB_VAR_SLOT_UNBOOTABLE, GetSlotUnbootable},
-            {FB_VAR_PARTITION_SIZE, GetPartitionSize},
-            {FB_VAR_IS_LOGICAL, GetPartitionIsLogical},
-            {FB_VAR_IS_USERSPACE, GetIsUserspace}};
+    const std::unordered_map<std::string, VariableHandlers> kVariableMap = {
+            {FB_VAR_VERSION, {GetVersion, nullptr}},
+            {FB_VAR_VERSION_BOOTLOADER, {GetBootloaderVersion, nullptr}},
+            {FB_VAR_VERSION_BASEBAND, {GetBasebandVersion, nullptr}},
+            {FB_VAR_PRODUCT, {GetProduct, nullptr}},
+            {FB_VAR_SERIALNO, {GetSerial, nullptr}},
+            {FB_VAR_SECURE, {GetSecure, nullptr}},
+            {FB_VAR_UNLOCKED, {GetUnlocked, nullptr}},
+            {FB_VAR_MAX_DOWNLOAD_SIZE, {GetMaxDownloadSize, nullptr}},
+            {FB_VAR_CURRENT_SLOT, {::GetCurrentSlot, nullptr}},
+            {FB_VAR_SLOT_COUNT, {GetSlotCount, nullptr}},
+            {FB_VAR_HAS_SLOT, {GetHasSlot, GetAllPartitionArgsNoSlot}},
+            {FB_VAR_SLOT_SUCCESSFUL, {GetSlotSuccessful, nullptr}},
+            {FB_VAR_SLOT_UNBOOTABLE, {GetSlotUnbootable, nullptr}},
+            {FB_VAR_PARTITION_SIZE, {GetPartitionSize, GetAllPartitionArgsWithSlot}},
+            {FB_VAR_IS_LOGICAL, {GetPartitionIsLogical, GetAllPartitionArgsWithSlot}},
+            {FB_VAR_IS_USERSPACE, {GetIsUserspace, nullptr}}};
+
+    if (args.size() < 2) {
+        return device->WriteFail("Missing argument");
+    }
+
+    // Special case: return all variables that we can.
+    if (args[1] == "all") {
+        for (const auto& [name, handlers] : kVariableMap) {
+            GetAllVars(device, name, handlers);
+        }
+        return device->WriteOkay("");
+    }
 
     // args[0] is command name, args[1] is variable.
     auto found_variable = kVariableMap.find(args[1]);
     if (found_variable == kVariableMap.end()) {
-        return device->WriteStatus(FastbootResult::FAIL, "Unknown variable");
+        return device->WriteFail("Unknown variable");
     }
 
+    std::string message;
     std::vector<std::string> getvar_args(args.begin() + 2, args.end());
-    return found_variable->second(device, getvar_args);
+    if (!found_variable->second.get(device, getvar_args, &message)) {
+        return device->WriteFail(message);
+    }
+    return device->WriteOkay(message);
 }
 
 bool EraseHandler(FastbootDevice* device, const std::vector<std::string>& args) {
@@ -336,3 +381,11 @@
     }
     return device->WriteOkay("Partition resized");
 }
+
+bool UpdateSuperHandler(FastbootDevice* device, const std::vector<std::string>& args) {
+    if (args.size() < 2) {
+        return device->WriteFail("Invalid arguments");
+    }
+    bool wipe = (args.size() >= 3 && args[2] == "wipe");
+    return UpdateSuper(device, args[1], wipe);
+}
diff --git a/fastboot/device/commands.h b/fastboot/device/commands.h
index f67df91..4778d23 100644
--- a/fastboot/device/commands.h
+++ b/fastboot/device/commands.h
@@ -44,3 +44,4 @@
 bool CreatePartitionHandler(FastbootDevice* device, const std::vector<std::string>& args);
 bool DeletePartitionHandler(FastbootDevice* device, const std::vector<std::string>& args);
 bool ResizePartitionHandler(FastbootDevice* device, const std::vector<std::string>& args);
+bool UpdateSuperHandler(FastbootDevice* device, const std::vector<std::string>& args);
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index c306e67..55aca9c 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -46,6 +46,7 @@
               {FB_CMD_CREATE_PARTITION, CreatePartitionHandler},
               {FB_CMD_DELETE_PARTITION, DeletePartitionHandler},
               {FB_CMD_RESIZE_PARTITION, ResizePartitionHandler},
+              {FB_CMD_UPDATE_SUPER, UpdateSuperHandler},
       }),
       transport_(std::make_unique<ClientUsbTransport>()),
       boot_control_hal_(IBootControl::getService()) {}
@@ -136,3 +137,7 @@
 bool FastbootDevice::WriteFail(const std::string& message) {
     return WriteStatus(FastbootResult::FAIL, message);
 }
+
+bool FastbootDevice::WriteInfo(const std::string& message) {
+    return WriteStatus(FastbootResult::INFO, message);
+}
diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h
index addc2ef..171e7ae 100644
--- a/fastboot/device/fastboot_device.h
+++ b/fastboot/device/fastboot_device.h
@@ -39,9 +39,10 @@
     bool HandleData(bool read, std::vector<char>* data);
     std::string GetCurrentSlot();
 
-    // Shortcuts for writing OKAY and FAIL status results.
+    // Shortcuts for writing status results.
     bool WriteOkay(const std::string& message);
     bool WriteFail(const std::string& message);
+    bool WriteInfo(const std::string& message);
 
     std::vector<char>& download_data() { return download_data_; }
     Transport* get_transport() { return transport_.get(); }
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index b5ed170..e3efbcb 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -25,6 +25,8 @@
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <ext4_utils/ext4_utils.h>
+#include <liblp/builder.h>
+#include <liblp/liblp.h>
 #include <sparse/sparse.h>
 
 #include "fastboot_device.h"
@@ -36,6 +38,8 @@
 
 }  // namespace
 
+using namespace android::fs_mgr;
+
 int FlashRawDataChunk(int fd, const char* data, size_t len) {
     size_t ret = 0;
     while (ret < len) {
@@ -99,3 +103,74 @@
     }
     return FlashBlockDevice(handle.fd(), data);
 }
+
+bool UpdateSuper(FastbootDevice* device, const std::string& partition_name, bool wipe) {
+    std::optional<std::string> super = FindPhysicalPartition(partition_name);
+    if (!super) {
+        return device->WriteFail("Could not find partition: " + partition_name);
+    }
+
+    std::vector<char> data = std::move(device->download_data());
+    if (data.empty()) {
+        return device->WriteFail("No data available");
+    }
+
+    std::unique_ptr<LpMetadata> new_metadata = ReadFromImageBlob(data.data(), data.size());
+    if (!new_metadata) {
+        return device->WriteFail("Data is not a valid logical partition metadata image");
+    }
+
+    // If we are unable to read the existing metadata, then the super partition
+    // is corrupt. In this case we reflash the whole thing using the provided
+    // image.
+    std::string slot_suffix = device->GetCurrentSlot();
+    uint32_t slot_number = SlotNumberForSlotSuffix(slot_suffix);
+    std::unique_ptr<LpMetadata> metadata = ReadMetadata(super->c_str(), slot_number);
+    if (!metadata || wipe) {
+        if (!FlashPartitionTable(super.value(), *new_metadata.get())) {
+            return device->WriteFail("Unable to flash new partition table");
+        }
+        return device->WriteOkay("Successfully flashed partition table");
+    }
+
+    // There's a working super partition, and we don't want to wipe it - it may
+    // may contain partitions created for the user. Instead, we create a zero-
+    // sized partition for each entry in the new partition table. It is then
+    // the host's responsibility to size it correctly via resize-logical-partition.
+    std::unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(*metadata.get());
+    if (!builder) {
+        return device->WriteFail("Unable to create a metadata builder");
+    }
+    for (const auto& partition : new_metadata->partitions) {
+        std::string name = GetPartitionName(partition);
+        if (builder->FindPartition(name)) {
+            continue;
+        }
+        std::string guid = GetPartitionGuid(partition);
+        if (!builder->AddPartition(name, guid, partition.attributes)) {
+            return device->WriteFail("Unable to add partition: " + name);
+        }
+    }
+
+    // The scratch partition may exist as temporary storage, created for
+    // use by adb remount for overlayfs. If we're performing a flashall
+    // operation then we want to start over with a clean slate, so we
+    // remove the scratch partition until it is requested again.
+    builder->RemovePartition("scratch");
+
+    new_metadata = builder->Export();
+    if (!new_metadata) {
+        return device->WriteFail("Unable to export new partition table");
+    }
+
+    // Write the new table to every metadata slot.
+    bool ok = true;
+    for (size_t i = 0; i < new_metadata->geometry.metadata_slot_count; i++) {
+        ok &= UpdatePartitionTable(super.value(), *new_metadata.get(), i);
+    }
+
+    if (!ok) {
+        return device->WriteFail("Unable to write new partition table");
+    }
+    return device->WriteOkay("Successfully updated partition table");
+}
diff --git a/fastboot/device/flashing.h b/fastboot/device/flashing.h
index 206a407..89e20fc 100644
--- a/fastboot/device/flashing.h
+++ b/fastboot/device/flashing.h
@@ -22,3 +22,4 @@
 class FastbootDevice;
 
 int Flash(FastbootDevice* device, const std::string& partition_name);
+bool UpdateSuper(FastbootDevice* device, const std::string& partition_name, bool wipe);
diff --git a/fastboot/device/usb_client.cpp b/fastboot/device/usb_client.cpp
index 215f99a..fb51a90 100644
--- a/fastboot/device/usb_client.cpp
+++ b/fastboot/device/usb_client.cpp
@@ -187,18 +187,9 @@
 
 // Reimplementing since usb_ffs_close() does not close the control FD.
 static void CloseFunctionFs(usb_handle* h) {
-    if (h->bulk_in > 0) {
-        close(h->bulk_in);
-        h->bulk_in = -1;
-    }
-    if (h->bulk_out > 0) {
-        close(h->bulk_out);
-        h->bulk_out = -1;
-    }
-    if (h->control > 0) {
-        close(h->control);
-        h->control = -1;
-    }
+    h->bulk_in.reset();
+    h->bulk_out.reset();
+    h->control.reset();
 }
 
 static bool InitFunctionFs(usb_handle* h) {
@@ -206,19 +197,19 @@
 
     if (h->control < 0) {  // might have already done this before
         LOG(INFO) << "opening control endpoint " << kUsbFfsFastbootEp0;
-        h->control = open(kUsbFfsFastbootEp0, O_RDWR);
+        h->control.reset(open(kUsbFfsFastbootEp0, O_RDWR));
         if (h->control < 0) {
             PLOG(ERROR) << "cannot open control endpoint " << kUsbFfsFastbootEp0;
             goto err;
         }
 
-        auto ret = write(h->control, &v2_descriptor, sizeof(v2_descriptor));
+        auto ret = write(h->control.get(), &v2_descriptor, sizeof(v2_descriptor));
         if (ret < 0) {
             PLOG(ERROR) << "cannot write descriptors " << kUsbFfsFastbootEp0;
             goto err;
         }
 
-        ret = write(h->control, &strings, sizeof(strings));
+        ret = write(h->control.get(), &strings, sizeof(strings));
         if (ret < 0) {
             PLOG(ERROR) << "cannot write strings " << kUsbFfsFastbootEp0;
             goto err;
@@ -227,20 +218,20 @@
         android::base::SetProperty("sys.usb.ffs.ready", "1");
     }
 
-    h->bulk_out = open(kUsbFfsFastbootOut, O_RDONLY);
+    h->bulk_out.reset(open(kUsbFfsFastbootOut, O_RDONLY));
     if (h->bulk_out < 0) {
         PLOG(ERROR) << "cannot open bulk-out endpoint " << kUsbFfsFastbootOut;
         goto err;
     }
 
-    h->bulk_in = open(kUsbFfsFastbootIn, O_WRONLY);
+    h->bulk_in.reset(open(kUsbFfsFastbootIn, O_WRONLY));
     if (h->bulk_in < 0) {
         PLOG(ERROR) << "cannot open bulk-in endpoint " << kUsbFfsFastbootIn;
         goto err;
     }
 
-    h->read_aiob.fd = h->bulk_out;
-    h->write_aiob.fd = h->bulk_in;
+    h->read_aiob.fd = h->bulk_out.get();
+    h->write_aiob.fd = h->bulk_in.get();
     h->reads_zero_packets = false;
     return true;
 
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index ec84576..0157e7f 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -16,6 +16,11 @@
 
 #include "utility.h"
 
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
 #include <android-base/logging.h>
 #include <fs_mgr_dm_linear.h>
 #include <liblp/liblp.h>
@@ -123,3 +128,33 @@
     *number = slot[0] - 'a';
     return true;
 }
+
+std::vector<std::string> ListPartitions(FastbootDevice* device) {
+    std::vector<std::string> partitions;
+
+    // First get physical partitions.
+    struct dirent* de;
+    std::unique_ptr<DIR, decltype(&closedir)> by_name(opendir("/dev/block/by-name"), closedir);
+    while ((de = readdir(by_name.get())) != nullptr) {
+        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
+            continue;
+        }
+        struct stat s;
+        std::string path = "/dev/block/by-name/" + std::string(de->d_name);
+        if (!stat(path.c_str(), &s) && S_ISBLK(s.st_mode)) {
+            partitions.emplace_back(de->d_name);
+        }
+    }
+
+    // Next get logical partitions.
+    if (auto path = FindPhysicalPartition(LP_METADATA_PARTITION_NAME)) {
+        uint32_t slot_number = SlotNumberForSlotSuffix(device->GetCurrentSlot());
+        if (auto metadata = ReadMetadata(path->c_str(), slot_number)) {
+            for (const auto& partition : metadata->partitions) {
+                std::string partition_name = GetPartitionName(partition);
+                partitions.emplace_back(partition_name);
+            }
+        }
+    }
+    return partitions;
+}
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index 0931fc3..4f0d079 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -56,5 +56,5 @@
 bool LogicalPartitionExists(const std::string& name, const std::string& slot_suffix,
                             bool* is_zero_length = nullptr);
 bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle);
-
 bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number);
+std::vector<std::string> ListPartitions(FastbootDevice* device);
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index 65cfea3..91e844a 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -35,141 +35,224 @@
 constexpr int kMaxDownloadSizeDefault = 0x20000000;
 constexpr char kFastbootProtocolVersion[] = "0.4";
 
-bool GetVersion(FastbootDevice* device, const std::vector<std::string>& /* args */) {
-    return device->WriteOkay(kFastbootProtocolVersion);
+bool GetVersion(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                std::string* message) {
+    *message = kFastbootProtocolVersion;
+    return true;
 }
 
-bool GetBootloaderVersion(FastbootDevice* device, const std::vector<std::string>& /* args */) {
-    return device->WriteOkay(android::base::GetProperty("ro.bootloader", ""));
+bool GetBootloaderVersion(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                          std::string* message) {
+    *message = android::base::GetProperty("ro.bootloader", "");
+    return true;
 }
 
-bool GetBasebandVersion(FastbootDevice* device, const std::vector<std::string>& /* args */) {
-    return device->WriteOkay(android::base::GetProperty("ro.build.expect.baseband", ""));
+bool GetBasebandVersion(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                        std::string* message) {
+    *message = android::base::GetProperty("ro.build.expect.baseband", "");
+    return true;
 }
 
-bool GetProduct(FastbootDevice* device, const std::vector<std::string>& /* args */) {
-    return device->WriteOkay(android::base::GetProperty("ro.product.device", ""));
+bool GetProduct(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                std::string* message) {
+    *message = android::base::GetProperty("ro.product.device", "");
+    return true;
 }
 
-bool GetSerial(FastbootDevice* device, const std::vector<std::string>& /* args */) {
-    return device->WriteOkay(android::base::GetProperty("ro.serialno", ""));
+bool GetSerial(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+               std::string* message) {
+    *message = android::base::GetProperty("ro.serialno", "");
+    return true;
 }
 
-bool GetSecure(FastbootDevice* device, const std::vector<std::string>& /* args */) {
-    return device->WriteOkay(android::base::GetBoolProperty("ro.secure", "") ? "yes" : "no");
+bool GetSecure(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+               std::string* message) {
+    *message = android::base::GetBoolProperty("ro.secure", "") ? "yes" : "no";
+    return true;
 }
 
-bool GetCurrentSlot(FastbootDevice* device, const std::vector<std::string>& /* args */) {
+bool GetCurrentSlot(FastbootDevice* device, const std::vector<std::string>& /* args */,
+                    std::string* message) {
     std::string suffix = device->GetCurrentSlot();
-    std::string slot = suffix.size() == 2 ? suffix.substr(1) : suffix;
-    return device->WriteOkay(slot);
+    *message = suffix.size() == 2 ? suffix.substr(1) : suffix;
+    return true;
 }
 
-bool GetSlotCount(FastbootDevice* device, const std::vector<std::string>& /* args */) {
+bool GetSlotCount(FastbootDevice* device, const std::vector<std::string>& /* args */,
+                  std::string* message) {
     auto boot_control_hal = device->boot_control_hal();
     if (!boot_control_hal) {
-        return "0";
+        *message = "0";
+    } else {
+        *message = std::to_string(boot_control_hal->getNumberSlots());
     }
-    return device->WriteOkay(std::to_string(boot_control_hal->getNumberSlots()));
+    return true;
 }
 
-bool GetSlotSuccessful(FastbootDevice* device, const std::vector<std::string>& args) {
+bool GetSlotSuccessful(FastbootDevice* device, const std::vector<std::string>& args,
+                       std::string* message) {
     if (args.empty()) {
-        return device->WriteFail("Missing argument");
+        *message = "Missing argument";
+        return false;
     }
     Slot slot;
     if (!GetSlotNumber(args[0], &slot)) {
-        return device->WriteFail("Invalid slot");
+        *message = "Invalid slot";
+        return false;
     }
     auto boot_control_hal = device->boot_control_hal();
     if (!boot_control_hal) {
-        return device->WriteFail("Device has no slots");
+        *message = "Device has no slots";
+        return false;
     }
     if (boot_control_hal->isSlotMarkedSuccessful(slot) != BoolResult::TRUE) {
-        return device->WriteOkay("no");
+        *message = "no";
+    } else {
+        *message = "yes";
     }
-    return device->WriteOkay("yes");
+    return true;
 }
 
-bool GetSlotUnbootable(FastbootDevice* device, const std::vector<std::string>& args) {
+bool GetSlotUnbootable(FastbootDevice* device, const std::vector<std::string>& args,
+                       std::string* message) {
     if (args.empty()) {
-        return device->WriteFail("Missing argument");
+        *message = "Missing argument";
+        return false;
     }
     Slot slot;
     if (!GetSlotNumber(args[0], &slot)) {
-        return device->WriteFail("Invalid slot");
+        *message = "Invalid slot";
+        return false;
     }
     auto boot_control_hal = device->boot_control_hal();
     if (!boot_control_hal) {
-        return device->WriteFail("Device has no slots");
+        *message = "Device has no slots";
+        return false;
     }
     if (boot_control_hal->isSlotBootable(slot) != BoolResult::TRUE) {
-        return device->WriteOkay("yes");
+        *message = "yes";
+    } else {
+        *message = "no";
     }
-    return device->WriteOkay("no");
+    return true;
 }
 
-bool GetMaxDownloadSize(FastbootDevice* device, const std::vector<std::string>& /* args */) {
-    return device->WriteOkay(std::to_string(kMaxDownloadSizeDefault));
+bool GetMaxDownloadSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                        std::string* message) {
+    *message = std::to_string(kMaxDownloadSizeDefault);
+    return true;
 }
 
-bool GetUnlocked(FastbootDevice* device, const std::vector<std::string>& /* args */) {
-    return device->WriteOkay("yes");
+bool GetUnlocked(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                 std::string* message) {
+    *message = "yes";
+    return true;
 }
 
-bool GetHasSlot(FastbootDevice* device, const std::vector<std::string>& args) {
+bool GetHasSlot(FastbootDevice* device, const std::vector<std::string>& args,
+                std::string* message) {
     if (args.empty()) {
-        return device->WriteFail("Missing argument");
+        *message = "Missing argument";
+        return false;
     }
     std::string slot_suffix = device->GetCurrentSlot();
     if (slot_suffix.empty()) {
-        return device->WriteOkay("no");
+        *message = "no";
+        return true;
     }
     std::string partition_name = args[0] + slot_suffix;
     if (FindPhysicalPartition(partition_name) ||
         LogicalPartitionExists(partition_name, slot_suffix)) {
-        return device->WriteOkay("yes");
+        *message = "yes";
+    } else {
+        *message = "no";
     }
-    return device->WriteOkay("no");
+    return true;
 }
 
-bool GetPartitionSize(FastbootDevice* device, const std::vector<std::string>& args) {
+bool GetPartitionSize(FastbootDevice* device, const std::vector<std::string>& args,
+                      std::string* message) {
     if (args.size() < 1) {
-        return device->WriteFail("Missing argument");
+        *message = "Missing argument";
+        return false;
     }
     // Zero-length partitions cannot be created through device-mapper, so we
     // special case them here.
     bool is_zero_length;
     if (LogicalPartitionExists(args[0], device->GetCurrentSlot(), &is_zero_length) &&
         is_zero_length) {
-        return device->WriteOkay("0");
+        *message = "0";
+        return true;
     }
     // Otherwise, open the partition as normal.
     PartitionHandle handle;
     if (!OpenPartition(device, args[0], &handle)) {
-        return device->WriteFail("Could not open partition");
+        *message = "Could not open partition";
+        return false;
     }
     uint64_t size = get_block_device_size(handle.fd());
-    return device->WriteOkay(android::base::StringPrintf("%" PRIX64, size));
+    *message = android::base::StringPrintf("0x%" PRIX64, size);
+    return true;
 }
 
-bool GetPartitionIsLogical(FastbootDevice* device, const std::vector<std::string>& args) {
+bool GetPartitionIsLogical(FastbootDevice* device, const std::vector<std::string>& args,
+                           std::string* message) {
     if (args.size() < 1) {
-        return device->WriteFail("Missing argument");
+        *message = "Missing argument";
+        return false;
     }
     // Note: if a partition name is in both the GPT and the super partition, we
     // return "true", to be consistent with prefering to flash logical partitions
     // over physical ones.
     std::string partition_name = args[0];
     if (LogicalPartitionExists(partition_name, device->GetCurrentSlot())) {
-        return device->WriteOkay("yes");
+        *message = "yes";
+        return true;
     }
     if (FindPhysicalPartition(partition_name)) {
-        return device->WriteOkay("no");
+        *message = "no";
+        return true;
     }
-    return device->WriteFail("Partition not found");
+    *message = "Partition not found";
+    return false;
 }
 
-bool GetIsUserspace(FastbootDevice* device, const std::vector<std::string>& /* args */) {
-    return device->WriteOkay("yes");
+bool GetIsUserspace(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                    std::string* message) {
+    *message = "yes";
+    return true;
+}
+
+std::vector<std::vector<std::string>> GetAllPartitionArgsWithSlot(FastbootDevice* device) {
+    std::vector<std::vector<std::string>> args;
+    auto partitions = ListPartitions(device);
+    for (const auto& partition : partitions) {
+        args.emplace_back(std::initializer_list<std::string>{partition});
+    }
+    return args;
+}
+
+std::vector<std::vector<std::string>> GetAllPartitionArgsNoSlot(FastbootDevice* device) {
+    auto partitions = ListPartitions(device);
+
+    std::string slot_suffix = device->GetCurrentSlot();
+    if (!slot_suffix.empty()) {
+        auto names = std::move(partitions);
+        for (const auto& name : names) {
+            std::string slotless_name = name;
+            if (android::base::EndsWith(name, "_a") || android::base::EndsWith(name, "_b")) {
+                slotless_name = name.substr(0, name.rfind("_"));
+            }
+            if (std::find(partitions.begin(), partitions.end(), slotless_name) ==
+                partitions.end()) {
+                partitions.emplace_back(slotless_name);
+            }
+        }
+    }
+
+    std::vector<std::vector<std::string>> args;
+    for (const auto& partition : partitions) {
+        args.emplace_back(std::initializer_list<std::string>{partition});
+    }
+    return args;
 }
diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h
index 554a080..c3a64cf 100644
--- a/fastboot/device/variables.h
+++ b/fastboot/device/variables.h
@@ -21,19 +21,34 @@
 
 class FastbootDevice;
 
-bool GetVersion(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetBootloaderVersion(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetBasebandVersion(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetProduct(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetSerial(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetSecure(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetCurrentSlot(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetSlotCount(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetSlotSuccessful(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetSlotUnbootable(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetMaxDownloadSize(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetUnlocked(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetHasSlot(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetPartitionSize(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetPartitionIsLogical(FastbootDevice* device, const std::vector<std::string>& args);
-bool GetIsUserspace(FastbootDevice* device, const std::vector<std::string>& args);
+bool GetVersion(FastbootDevice* device, const std::vector<std::string>& args, std::string* message);
+bool GetBootloaderVersion(FastbootDevice* device, const std::vector<std::string>& args,
+                          std::string* message);
+bool GetBasebandVersion(FastbootDevice* device, const std::vector<std::string>& args,
+                        std::string* message);
+bool GetProduct(FastbootDevice* device, const std::vector<std::string>& args, std::string* message);
+bool GetSerial(FastbootDevice* device, const std::vector<std::string>& args, std::string* message);
+bool GetSecure(FastbootDevice* device, const std::vector<std::string>& args, std::string* message);
+bool GetCurrentSlot(FastbootDevice* device, const std::vector<std::string>& args,
+                    std::string* message);
+bool GetSlotCount(FastbootDevice* device, const std::vector<std::string>& args,
+                  std::string* message);
+bool GetSlotSuccessful(FastbootDevice* device, const std::vector<std::string>& args,
+                       std::string* message);
+bool GetSlotUnbootable(FastbootDevice* device, const std::vector<std::string>& args,
+                       std::string* message);
+bool GetMaxDownloadSize(FastbootDevice* device, const std::vector<std::string>& args,
+                        std::string* message);
+bool GetUnlocked(FastbootDevice* device, const std::vector<std::string>& args,
+                 std::string* message);
+bool GetHasSlot(FastbootDevice* device, const std::vector<std::string>& args, std::string* message);
+bool GetPartitionSize(FastbootDevice* device, const std::vector<std::string>& args,
+                      std::string* message);
+bool GetPartitionIsLogical(FastbootDevice* device, const std::vector<std::string>& args,
+                           std::string* message);
+bool GetIsUserspace(FastbootDevice* device, const std::vector<std::string>& args,
+                    std::string* message);
+
+// Helpers for getvar all.
+std::vector<std::vector<std::string>> GetAllPartitionArgsWithSlot(FastbootDevice* device);
+std::vector<std::vector<std::string>> GetAllPartitionArgsNoSlot(FastbootDevice* device);
diff --git a/fastboot/engine.cpp b/fastboot/engine.cpp
index 6890643..d80e986 100644
--- a/fastboot/engine.cpp
+++ b/fastboot/engine.cpp
@@ -79,12 +79,20 @@
 static std::vector<std::unique_ptr<Action>> action_list;
 static fastboot::FastBootDriver* fb = nullptr;
 
+static constexpr char kStatusFormat[] = "%-50s ";
+
 void fb_init(fastboot::FastBootDriver& fbi) {
     fb = &fbi;
     auto cb = [](std::string& info) { fprintf(stderr, "(bootloader) %s\n", info.c_str()); };
     fb->SetInfoCallback(cb);
 }
 
+void fb_reinit(Transport* transport) {
+    if (Transport* old_transport = fb->set_transport(transport)) {
+        delete old_transport;
+    }
+}
+
 const std::string fb_get_error() {
     return fb->Error();
 }
@@ -332,7 +340,7 @@
     for (auto& a : action_list) {
         a->start = now();
         if (!a->msg.empty()) {
-            fprintf(stderr, "%-50s ", a->msg.c_str());
+            fprintf(stderr, kStatusFormat, a->msg.c_str());
             verbose("\n");
         }
         if (a->op == OP_DOWNLOAD) {
@@ -372,3 +380,20 @@
     action_list.clear();
     return status;
 }
+
+bool fb_reboot_to_userspace() {
+    // First ensure that the queue is flushed.
+    fb_execute_queue();
+
+    fprintf(stderr, kStatusFormat, "Rebooting to userspace fastboot");
+    verbose("\n");
+
+    if (fb->RebootTo("fastboot") != fastboot::RetCode::SUCCESS) {
+        fprintf(stderr, "FAILED (%s)\n", fb->Error().c_str());
+        return false;
+    }
+    fprintf(stderr, "OKAY\n");
+
+    fb_reinit(nullptr);
+    return true;
+}
diff --git a/fastboot/engine.h b/fastboot/engine.h
index 8aebdd7..f098ca7 100644
--- a/fastboot/engine.h
+++ b/fastboot/engine.h
@@ -50,6 +50,7 @@
 /* engine.c - high level command queue engine */
 
 void fb_init(fastboot::FastBootDriver& fbi);
+void fb_reinit(Transport* transport);
 
 bool fb_getvar(const std::string& key, std::string* value);
 void fb_queue_flash(const std::string& partition, void* data, uint32_t sz);
@@ -74,6 +75,7 @@
 void fb_queue_resize_partition(const std::string& partition, const std::string& size);
 int64_t fb_execute_queue();
 void fb_set_active(const std::string& slot);
+bool fb_reboot_to_userspace();
 
 /* Current product */
 extern char cur_product[FB_RESPONSE_SZ + 1];
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index db6d5d6..1aef567 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -55,6 +55,8 @@
 #include <android-base/strings.h>
 #include <android-base/test_utils.h>
 #include <android-base/unique_fd.h>
+#include <build/version.h>
+#include <platform_tools_version.h>
 #include <sparse/sparse.h>
 #include <ziparchive/zip_archive.h>
 
@@ -104,45 +106,59 @@
     void* data;
     int64_t sz;
     int fd;
+    int64_t image_size;
 };
 
-static struct {
+enum class ImageType {
+    // Must be flashed for device to boot into the kernel.
+    BootCritical,
+    // Normal partition to be flashed during "flashall".
+    Normal,
+    // Partition that is never flashed during "flashall".
+    Extra
+};
+
+struct Image {
     const char* nickname;
     const char* img_name;
     const char* sig_name;
     const char* part_name;
     bool optional_if_no_image;
-    bool optional_if_no_partition;
+    ImageType type;
     bool IsSecondary() const { return nickname == nullptr; }
-} images[] = {
+};
+
+static Image images[] = {
         // clang-format off
-    { "boot",     "boot.img",         "boot.sig",     "boot",     false, false },
-    { nullptr,    "boot_other.img",   "boot.sig",     "boot",     true,  false },
-    { "dtbo",     "dtbo.img",         "dtbo.sig",     "dtbo",     true,  false },
-    { "dts",      "dt.img",           "dt.sig",       "dts",      true,  false },
-    { "odm",      "odm.img",          "odm.sig",      "odm",      true,  false },
-    { "product",  "product.img",      "product.sig",  "product",  true,  false },
+    { "boot",     "boot.img",         "boot.sig",     "boot",     false, ImageType::BootCritical },
+    { nullptr,    "boot_other.img",   "boot.sig",     "boot",     true,  ImageType::Normal },
+    { "cache",    "cache.img",        "cache.sig",    "cache",    true,  ImageType::Extra },
+    { "dtbo",     "dtbo.img",         "dtbo.sig",     "dtbo",     true,  ImageType::BootCritical },
+    { "dts",      "dt.img",           "dt.sig",       "dts",      true,  ImageType::BootCritical },
+    { "odm",      "odm.img",          "odm.sig",      "odm",      true,  ImageType::Normal },
+    { "product",  "product.img",      "product.sig",  "product",  true,  ImageType::Normal },
     { "product_services",
                   "product_services.img",
                                       "product_services.sig",
                                                       "product_services",
-                                                                  true,  true  },
-    { "recovery", "recovery.img",     "recovery.sig", "recovery", true,  false },
-    { "super",    "super.img",        "super.sig",    "super",    true,  true  },
-    { "system",   "system.img",       "system.sig",   "system",   false, true  },
-    { nullptr,    "system_other.img", "system.sig",   "system",   true,  false },
-    { "vbmeta",   "vbmeta.img",       "vbmeta.sig",   "vbmeta",   true,  false },
-    { "vendor",   "vendor.img",       "vendor.sig",   "vendor",   true,  true  },
-    { nullptr,    "vendor_other.img", "vendor.sig",   "vendor",   true,  false },
+                                                                  true,  ImageType::Normal },
+    { "recovery", "recovery.img",     "recovery.sig", "recovery", true,  ImageType::BootCritical },
+    { "super",    "super.img",        "super.sig",    "super",    true,  ImageType::Extra },
+    { "system",   "system.img",       "system.sig",   "system",   false, ImageType::Normal },
+    { nullptr,    "system_other.img", "system.sig",   "system",   true,  ImageType::Normal },
+    { "userdata", "userdata.img",     "userdata.sig", "userdata", true,  ImageType::Extra },
+    { "vbmeta",   "vbmeta.img",       "vbmeta.sig",   "vbmeta",   true,  ImageType::BootCritical },
+    { "vendor",   "vendor.img",       "vendor.sig",   "vendor",   true,  ImageType::Normal },
+    { nullptr,    "vendor_other.img", "vendor.sig",   "vendor",   true,  ImageType::Normal },
         // clang-format on
 };
 
-static std::string find_item_given_name(const char* img_name) {
+static std::string find_item_given_name(const std::string& img_name) {
     char* dir = getenv("ANDROID_PRODUCT_OUT");
     if (dir == nullptr || dir[0] == '\0') {
         die("ANDROID_PRODUCT_OUT not set");
     }
-    return android::base::StringPrintf("%s/%s", dir, img_name);
+    return std::string(dir) + "/" + img_name;
 }
 
 static std::string find_item(const std::string& item) {
@@ -152,9 +168,6 @@
         }
     }
 
-    if (item == "userdata") return find_item_given_name("userdata.img");
-    if (item == "cache") return find_item_given_name("cache.img");
-
     fprintf(stderr, "unknown partition '%s'\n", item.c_str());
     return "";
 }
@@ -242,13 +255,8 @@
 // The returned Transport is a singleton, so multiple calls to this function will return the same
 // object, and the caller should not attempt to delete the returned Transport.
 static Transport* open_device() {
-    static Transport* transport = nullptr;
     bool announce = true;
 
-    if (transport != nullptr) {
-        return transport;
-    }
-
     Socket::Protocol protocol = Socket::Protocol::kTcp;
     std::string host;
     int port = 0;
@@ -273,6 +281,7 @@
         }
     }
 
+    Transport* transport = nullptr;
     while (true) {
         if (!host.empty()) {
             std::string error;
@@ -567,6 +576,7 @@
     ZipEntry zip_entry;
     if (FindEntry(zip, zip_entry_name, &zip_entry) != 0) {
         fprintf(stderr, "archive does not contain '%s'\n", entry_name);
+        errno = ENOENT;
         return -1;
     }
 
@@ -638,8 +648,7 @@
     // "require partition-exists=x" is a special case, added because of the trouble we had when
     // Pixel 2 shipped with new partitions and users used old versions of fastboot to flash them,
     // missing out new partitions. A device with new partitions can use "partition-exists" to
-    // override the fields `optional_if_no_image` and 'optional_if_no_partition' in the `images`
-    // array.
+    // override the fields `optional_if_no_image` in the `images` array.
     if (!strcmp(name, "partition-exists")) {
         const char* partition_name = val[0];
         std::string has_slot;
@@ -651,7 +660,6 @@
         for (size_t i = 0; i < arraysize(images); ++i) {
             if (images[i].nickname && !strcmp(images[i].nickname, partition_name)) {
                 images[i].optional_if_no_image = false;
-                images[i].optional_if_no_partition = false;
                 known_partition = true;
             }
         }
@@ -773,6 +781,13 @@
         return false;
     }
 
+    if (sparse_file* s = sparse_file_import_auto(fd, false, false)) {
+        buf->image_size = sparse_file_len(s, false, false);
+        sparse_file_destroy(s);
+    } else {
+        buf->image_size = sz;
+    }
+
     lseek64(fd, 0, SEEK_SET);
     int64_t limit = get_sparse_limit(sz);
     if (limit) {
@@ -1021,14 +1036,6 @@
     flash_buf(pname, &buf);
 }
 
-static void do_update_signature(ZipArchiveHandle zip, const char* filename) {
-    int64_t sz;
-    void* data = unzip_to_memory(zip, filename, &sz);
-    if (data == nullptr) return;
-    fb_queue_download("signature", data, sz);
-    fb_queue_command("signature", "installing signature");
-}
-
 // Sets slot_override as the active slot. If slot_override is blank,
 // set current slot as active instead. This clears slot-unbootable.
 static void set_active(const std::string& slot_override) {
@@ -1044,6 +1051,11 @@
     }
 }
 
+static bool is_userspace_fastboot() {
+    std::string value;
+    return fb_getvar("is-userspace", &value) && value == "yes";
+}
+
 static bool if_partition_exists(const std::string& partition, const std::string& slot) {
     std::string has_slot;
     std::string partition_name = partition;
@@ -1063,6 +1075,209 @@
     return fb_getvar("partition-size:" + partition_name, &partition_size);
 }
 
+static bool is_logical(const std::string& partition) {
+    std::string value;
+    return fb_getvar("is-logical:" + partition, &value) && value == "yes";
+}
+
+static void reboot_to_userspace_fastboot() {
+    if (!fb_reboot_to_userspace()) {
+        die("Must reboot to userspace fastboot to flash logical partitions");
+    }
+
+    // Give the current connection time to close.
+    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+
+    fb_reinit(open_device());
+}
+
+class ImageSource {
+  public:
+    virtual void* ReadFile(const std::string& name, int64_t* size) const = 0;
+    virtual int OpenFile(const std::string& name) const = 0;
+};
+
+class FlashAllTool {
+  public:
+    FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary, bool wipe);
+
+    void Flash();
+
+  private:
+    void CheckRequirements();
+    void DetermineSecondarySlot();
+    void CollectImages();
+    void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
+    void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
+    void UpdateSuperPartition();
+
+    const ImageSource& source_;
+    std::string slot_override_;
+    bool skip_secondary_;
+    bool wipe_;
+    std::string secondary_slot_;
+    std::vector<std::pair<const Image*, std::string>> boot_images_;
+    std::vector<std::pair<const Image*, std::string>> os_images_;
+};
+
+FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary, bool wipe)
+   : source_(source),
+     slot_override_(slot_override),
+     skip_secondary_(skip_secondary),
+     wipe_(wipe)
+{
+}
+
+void FlashAllTool::Flash() {
+    CheckRequirements();
+    DetermineSecondarySlot();
+    CollectImages();
+
+    // First flash boot partitions. We allow this to happen either in userspace
+    // or in bootloader fastboot.
+    FlashImages(boot_images_);
+
+    // Sync the super partition. This will reboot to userspace fastboot if needed.
+    UpdateSuperPartition();
+
+    // Resize any logical partition to 0, so each partition is reset to 0
+    // extents, and will achieve more optimal allocation.
+    for (const auto& [image, slot] : os_images_) {
+        auto resize_partition = [](const std::string& partition) -> void {
+            if (is_logical(partition)) {
+                fb_queue_resize_partition(partition, "0");
+            }
+        };
+        do_for_partitions(image->part_name, slot, resize_partition, false);
+    }
+
+    // Flash OS images, resizing logical partitions as needed.
+    FlashImages(os_images_);
+
+    if (slot_override_ == "all") {
+        set_active("a");
+    } else {
+        set_active(slot_override_);
+    }
+}
+
+void FlashAllTool::CheckRequirements() {
+    int64_t sz;
+    void* data = source_.ReadFile("android-info.txt", &sz);
+    if (data == nullptr) {
+        die("could not read android-info.txt");
+    }
+    check_requirements(reinterpret_cast<char*>(data), sz);
+}
+
+void FlashAllTool::DetermineSecondarySlot() {
+    if (skip_secondary_) {
+        return;
+    }
+    if (slot_override_ != "") {
+        secondary_slot_ = get_other_slot(slot_override_);
+    } else {
+        secondary_slot_ = get_other_slot();
+    }
+    if (secondary_slot_ == "") {
+        if (supports_AB()) {
+            fprintf(stderr, "Warning: Could not determine slot for secondary images. Ignoring.\n");
+        }
+        skip_secondary_ = true;
+    }
+}
+
+void FlashAllTool::CollectImages() {
+    for (size_t i = 0; i < arraysize(images); ++i) {
+        std::string slot = slot_override_;
+        if (images[i].IsSecondary()) {
+            if (skip_secondary_) {
+                continue;
+            }
+            slot = secondary_slot_;
+        }
+        if (images[i].type == ImageType::BootCritical) {
+            boot_images_.emplace_back(&images[i], slot);
+        } else if (images[i].type == ImageType::Normal) {
+            os_images_.emplace_back(&images[i], slot);
+        }
+    }
+}
+
+void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) {
+    for (const auto& [image, slot] : images) {
+        fastboot_buffer buf;
+        int fd = source_.OpenFile(image->img_name);
+        if (fd < 0 || !load_buf_fd(fd, &buf)) {
+            if (image->optional_if_no_image) {
+                continue;
+            }
+            die("could not load '%s': %s", image->img_name, strerror(errno));
+        }
+        FlashImage(*image, slot, &buf);
+    }
+}
+
+void FlashAllTool::FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf) {
+    auto flash = [&, this](const std::string& partition_name) {
+        int64_t sz;
+        void* data = source_.ReadFile(image.sig_name, &sz);
+        if (data) {
+            fb_queue_download("signature", data, sz);
+            fb_queue_command("signature", "installing signature");
+        }
+
+        if (is_logical(partition_name)) {
+            fb_queue_resize_partition(partition_name, std::to_string(buf->image_size));
+        }
+        flash_buf(partition_name.c_str(), buf);
+    };
+    do_for_partitions(image.part_name, slot, flash, false);
+}
+
+void FlashAllTool::UpdateSuperPartition() {
+    if (!if_partition_exists("super", "")) {
+        return;
+    }
+
+    int fd = source_.OpenFile("super_empty.img");
+    if (fd < 0) {
+        return;
+    }
+    if (!is_userspace_fastboot()) {
+        reboot_to_userspace_fastboot();
+    }
+    fb_queue_download_fd("super", fd, get_file_size(fd));
+
+    std::string command = "update-super:super";
+    if (wipe_) {
+        command += ":wipe";
+    }
+    fb_queue_command(command, "Updating super partition");
+
+    // We need these commands to have finished before proceeding, since
+    // otherwise "getvar is-logical" may not return a correct answer below.
+    fb_execute_queue();
+}
+
+class ZipImageSource final : public ImageSource {
+  public:
+    explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
+    void* ReadFile(const std::string& name, int64_t* size) const override;
+    int OpenFile(const std::string& name) const override;
+
+  private:
+    ZipArchiveHandle zip_;
+};
+
+void* ZipImageSource::ReadFile(const std::string& name, int64_t* size) const {
+    return unzip_to_memory(zip_, name.c_str(), size);
+}
+
+int ZipImageSource::OpenFile(const std::string& name) const {
+    return unzip_to_file(zip_, name.c_str());
+}
+
 static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary) {
     queue_info_dump();
 
@@ -1074,150 +1289,39 @@
         die("failed to open zip file '%s': %s", filename, ErrorCodeString(error));
     }
 
-    int64_t sz;
-    void* data = unzip_to_memory(zip, "android-info.txt", &sz);
-    if (data == nullptr) {
-        die("update package '%s' has no android-info.txt", filename);
-    }
-
-    check_requirements(reinterpret_cast<char*>(data), sz);
-
-    std::string secondary;
-    if (!skip_secondary) {
-        if (slot_override != "") {
-            secondary = get_other_slot(slot_override);
-        } else {
-            secondary = get_other_slot();
-        }
-        if (secondary == "") {
-            if (supports_AB()) {
-                fprintf(stderr, "Warning: Could not determine slot for secondary images. Ignoring.\n");
-            }
-            skip_secondary = true;
-        }
-    }
-    for (size_t i = 0; i < arraysize(images); ++i) {
-        const char* slot = slot_override.c_str();
-        if (images[i].IsSecondary()) {
-            if (!skip_secondary) {
-                slot = secondary.c_str();
-            } else {
-                continue;
-            }
-        }
-
-        int fd = unzip_to_file(zip, images[i].img_name);
-        if (fd == -1) {
-            if (images[i].optional_if_no_image) {
-                continue; // An optional file is missing, so ignore it.
-            }
-            die("non-optional file %s missing", images[i].img_name);
-        }
-
-        if (images[i].optional_if_no_partition &&
-            !if_partition_exists(images[i].part_name, slot)) {
-            continue;
-        }
-
-        fastboot_buffer buf;
-        if (!load_buf_fd(fd, &buf)) {
-            die("cannot load %s from flash: %s", images[i].img_name, strerror(errno));
-        }
-
-        auto update = [&](const std::string& partition) {
-            do_update_signature(zip, images[i].sig_name);
-            flash_buf(partition.c_str(), &buf);
-            /* not closing the fd here since the sparse code keeps the fd around
-             * but hasn't mmaped data yet. The temporary file will get cleaned up when the
-             * program exits.
-             */
-        };
-        do_for_partitions(images[i].part_name, slot, update, false);
-    }
-
-    if (slot_override == "all") {
-        set_active("a");
-    } else {
-        set_active(slot_override);
-    }
+    FlashAllTool tool(ZipImageSource(zip), slot_override, skip_secondary, false);
+    tool.Flash();
 
     CloseArchive(zip);
 }
 
-static void do_send_signature(const std::string& fn) {
-    std::size_t extension_loc = fn.find(".img");
-    if (extension_loc == std::string::npos) return;
+class LocalImageSource final : public ImageSource {
+  public:
+    void* ReadFile(const std::string& name, int64_t* size) const override;
+    int OpenFile(const std::string& name) const override;
+};
 
-    std::string fs_sig = fn.substr(0, extension_loc) + ".sig";
-
-    int64_t sz;
-    void* data = load_file(fs_sig.c_str(), &sz);
-    if (data == nullptr) return;
-
-    fb_queue_download("signature", data, sz);
-    fb_queue_command("signature", "installing signature");
+void* LocalImageSource::ReadFile(const std::string& name, int64_t* size) const {
+    auto path = find_item_given_name(name);
+    if (path.empty()) {
+        return nullptr;
+    }
+    return load_file(path.c_str(), size);
 }
 
-static void do_flashall(const std::string& slot_override, bool skip_secondary) {
+int LocalImageSource::OpenFile(const std::string& name) const {
+    auto path = find_item_given_name(name);
+    return open(path.c_str(), O_RDONLY);
+}
+
+static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe) {
     std::string fname;
     queue_info_dump();
 
     fb_queue_query_save("product", cur_product, sizeof(cur_product));
 
-    fname = find_item_given_name("android-info.txt");
-    if (fname.empty()) die("cannot find android-info.txt");
-
-    int64_t sz;
-    void* data = load_file(fname.c_str(), &sz);
-    if (data == nullptr) die("could not load android-info.txt: %s", strerror(errno));
-
-    check_requirements(reinterpret_cast<char*>(data), sz);
-
-    std::string secondary;
-    if (!skip_secondary) {
-        if (slot_override != "") {
-            secondary = get_other_slot(slot_override);
-        } else {
-            secondary = get_other_slot();
-        }
-        if (secondary == "") {
-            if (supports_AB()) {
-                fprintf(stderr, "Warning: Could not determine slot for secondary images. Ignoring.\n");
-            }
-            skip_secondary = true;
-        }
-    }
-
-    for (size_t i = 0; i < arraysize(images); i++) {
-        const char* slot = NULL;
-        if (images[i].IsSecondary()) {
-            if (!skip_secondary) slot = secondary.c_str();
-        } else {
-            slot = slot_override.c_str();
-        }
-        if (!slot) continue;
-        fname = find_item_given_name(images[i].img_name);
-        fastboot_buffer buf;
-        if (!load_buf(fname.c_str(), &buf)) {
-            if (images[i].optional_if_no_image) continue;
-            die("could not load '%s': %s", images[i].img_name, strerror(errno));
-        }
-        if (images[i].optional_if_no_partition &&
-            !if_partition_exists(images[i].part_name, slot)) {
-            continue;
-        }
-        auto flashall = [&](const std::string &partition) {
-            do_send_signature(fname.c_str());
-            flash_buf(partition.c_str(), &buf);
-        };
-        do_for_partitions(images[i].part_name, slot, flashall, false);
-    }
-
-    if (slot_override == "all") {
-        set_active("a");
-    } else {
-        set_active(slot_override);
-    }
+    FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe);
+    tool.Flash();
 }
 
 static std::string next_arg(std::vector<std::string>* args) {
@@ -1446,7 +1550,7 @@
                 setvbuf(stdout, nullptr, _IONBF, 0);
                 setvbuf(stderr, nullptr, _IONBF, 0);
             } else if (name == "version") {
-                fprintf(stdout, "fastboot version %s\n", FASTBOOT_VERSION);
+                fprintf(stdout, "fastboot version %s-%s\n", PLATFORM_TOOLS_VERSION, android::build::GetBuildNumber().c_str());
                 fprintf(stdout, "Installed as %s\n", android::base::GetExecutablePath().c_str());
                 return 0;
 #if !defined(_WIN32)
@@ -1648,9 +1752,9 @@
         } else if (command == "flashall") {
             if (slot_override == "all") {
                 fprintf(stderr, "Warning: slot set to 'all'. Secondary slots will not be flashed.\n");
-                do_flashall(slot_override, true);
+                do_flashall(slot_override, true, wants_wipe);
             } else {
-                do_flashall(slot_override, skip_secondary);
+                do_flashall(slot_override, skip_secondary, wants_wipe);
             }
             wants_reboot = true;
         } else if (command == "update") {
@@ -1743,6 +1847,10 @@
 
     int status = fb_execute_queue() ? EXIT_FAILURE : EXIT_SUCCESS;
     fprintf(stderr, "Finished. Total time: %.3fs\n", (now() - start));
+
+    if (Transport* old_transport = fb.set_transport(nullptr)) {
+        delete old_transport;
+    }
     return status;
 }
 
diff --git a/fastboot/fastboot_driver.cpp b/fastboot/fastboot_driver.cpp
index 55ca65d..f9e640a 100644
--- a/fastboot/fastboot_driver.cpp
+++ b/fastboot/fastboot_driver.cpp
@@ -52,11 +52,14 @@
 /*************************** PUBLIC *******************************/
 FastBootDriver::FastBootDriver(Transport* transport, std::function<void(std::string&)> info,
                                bool no_checks)
-    : transport(transport) {
+    : transport_(transport) {
     info_cb_ = info;
     disable_checks_ = no_checks;
 }
 
+FastBootDriver::~FastBootDriver() {
+}
+
 RetCode FastBootDriver::Boot(std::string* response, std::vector<std::string>* info) {
     return RawCommand(Commands::BOOT, response, info);
 }
@@ -85,24 +88,20 @@
     return GetVar("all", &tmp, response);
 }
 
-RetCode FastBootDriver::Powerdown(std::string* response, std::vector<std::string>* info) {
-    return RawCommand(Commands::POWERDOWN, response, info);
-}
-
 RetCode FastBootDriver::Reboot(std::string* response, std::vector<std::string>* info) {
     return RawCommand(Commands::REBOOT, response, info);
 }
 
+RetCode FastBootDriver::RebootTo(std::string target, std::string* response,
+                                 std::vector<std::string>* info) {
+    return RawCommand("reboot-" + target, response, info);
+}
+
 RetCode FastBootDriver::SetActive(const std::string& part, std::string* response,
                                   std::vector<std::string>* info) {
     return RawCommand(Commands::SET_ACTIVE + part, response, info);
 }
 
-RetCode FastBootDriver::Verify(uint32_t num, std::string* response, std::vector<std::string>* info) {
-    std::string cmd = android::base::StringPrintf("%s%08" PRIx32, Commands::VERIFY.c_str(), num);
-    return RawCommand(cmd, response, info);
-}
-
 RetCode FastBootDriver::FlashPartition(const std::string& part, const std::vector<char>& data) {
     RetCode ret;
     if ((ret = Download(data))) {
@@ -127,7 +126,7 @@
     return RawCommand(Commands::FLASH + part);
 }
 
-RetCode FastBootDriver::Partitions(std::vector<std::tuple<std::string, uint32_t>>* parts) {
+RetCode FastBootDriver::Partitions(std::vector<std::tuple<std::string, uint64_t>>* parts) {
     std::vector<std::string> all;
     RetCode ret;
     if ((ret = GetVarAll(&all))) {
@@ -141,7 +140,7 @@
         if (std::regex_match(s, sm, reg)) {
             std::string m1(sm[1]);
             std::string m2(sm[2]);
-            uint32_t tmp = strtol(m2.c_str(), 0, 16);
+            uint64_t tmp = strtoll(m2.c_str(), 0, 16);
             parts->push_back(std::make_tuple(m1, tmp));
         }
     }
@@ -220,10 +219,10 @@
     return HandleResponse(response, info);
 }
 
-RetCode FastBootDriver::Download(sparse_file* s, std::string* response,
+RetCode FastBootDriver::Download(sparse_file* s, bool use_crc, std::string* response,
                                  std::vector<std::string>* info) {
     error_ = "";
-    int64_t size = sparse_file_len(s, true, false);
+    int64_t size = sparse_file_len(s, true, use_crc);
     if (size <= 0 || size > MAX_DOWNLOAD_SIZE) {
         error_ = "Sparse file is too large or invalid";
         return BAD_ARG;
@@ -247,7 +246,7 @@
         return data->self->SparseWriteCallback(data->tpbuf, cbuf, len);
     };
 
-    if (sparse_file_callback(s, true, false, cb, &cb_priv) < 0) {
+    if (sparse_file_callback(s, true, use_crc, cb, &cb_priv) < 0) {
         error_ = "Error reading sparse file";
         return IO_ERROR;
     }
@@ -332,7 +331,7 @@
 }
 
 RetCode FastBootDriver::WaitForDisconnect() {
-    return transport->WaitForDisconnect() ? IO_ERROR : SUCCESS;
+    return transport_->WaitForDisconnect() ? IO_ERROR : SUCCESS;
 }
 
 /****************************** PROTECTED *************************************/
@@ -344,7 +343,7 @@
         return BAD_ARG;
     }
 
-    if (transport->Write(cmd.c_str(), cmd.size()) != static_cast<int>(cmd.size())) {
+    if (transport_->Write(cmd.c_str(), cmd.size()) != static_cast<int>(cmd.size())) {
         error_ = ErrnoStr("Write to device failed");
         return IO_ERROR;
     }
@@ -378,7 +377,7 @@
     // erase response
     set_response("");
     while ((std::chrono::system_clock::now() - start) < std::chrono::seconds(RESP_TIMEOUT)) {
-        int r = transport->Read(status, FB_RESPONSE_SZ);
+        int r = transport_->Read(status, FB_RESPONSE_SZ);
         if (r < 0) {
             error_ = ErrnoStr("Status read failed");
             return IO_ERROR;
@@ -427,11 +426,9 @@
 const std::string FastBootDriver::Commands::ERASE = "erase:";
 const std::string FastBootDriver::Commands::FLASH = "flash:";
 const std::string FastBootDriver::Commands::GET_VAR = "getvar:";
-const std::string FastBootDriver::Commands::POWERDOWN = "powerdown";
 const std::string FastBootDriver::Commands::REBOOT = "reboot";
 const std::string FastBootDriver::Commands::SET_ACTIVE = "set_active:";
 const std::string FastBootDriver::Commands::UPLOAD = "upload";
-const std::string FastBootDriver::Commands::VERIFY = "verify:";
 
 /******************************* PRIVATE **************************************/
 RetCode FastBootDriver::SendBuffer(int fd, size_t size) {
@@ -472,7 +469,7 @@
         return BAD_ARG;
     }
     // Write the buffer
-    ssize_t tmp = transport->Write(buf, size);
+    ssize_t tmp = transport_->Write(buf, size);
 
     if (tmp < 0) {
         error_ = ErrnoStr("Write to device failed in SendBuffer()");
@@ -493,7 +490,7 @@
 
 RetCode FastBootDriver::ReadBuffer(void* buf, size_t size) {
     // Read the buffer
-    ssize_t tmp = transport->Read(buf, size);
+    ssize_t tmp = transport_->Read(buf, size);
 
     if (tmp < 0) {
         error_ = ErrnoStr("Read from device failed in ReadBuffer()");
@@ -539,4 +536,9 @@
     return 0;
 }
 
+Transport* FastBootDriver::set_transport(Transport* transport) {
+    std::swap(transport_, transport);
+    return transport;
+}
+
 }  // End namespace fastboot
diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h
index dd199c0..ca9003a 100644
--- a/fastboot/fastboot_driver.h
+++ b/fastboot/fastboot_driver.h
@@ -56,6 +56,8 @@
 };
 
 class FastBootDriver {
+    friend class FastBootTest;
+
   public:
     static constexpr int RESP_TIMEOUT = 30;  // 30 seconds
     static constexpr uint32_t MAX_DOWNLOAD_SIZE = std::numeric_limits<uint32_t>::max();
@@ -64,6 +66,7 @@
     FastBootDriver(Transport* transport,
                    std::function<void(std::string&)> info = [](std::string&) {},
                    bool no_checks = false);
+    ~FastBootDriver();
 
     RetCode Boot(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
     RetCode Continue(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
@@ -74,7 +77,7 @@
     // This will be removed after fastboot is modified to use a vector
     RetCode Download(const char* buf, uint32_t size, std::string* response = nullptr,
                      std::vector<std::string>* info = nullptr);
-    RetCode Download(sparse_file* s, std::string* response = nullptr,
+    RetCode Download(sparse_file* s, bool use_crc = false, std::string* response = nullptr,
                      std::vector<std::string>* info = nullptr);
     RetCode Erase(const std::string& part, std::string* response = nullptr,
                   std::vector<std::string>* info = nullptr);
@@ -83,21 +86,20 @@
     RetCode GetVar(const std::string& key, std::string* val,
                    std::vector<std::string>* info = nullptr);
     RetCode GetVarAll(std::vector<std::string>* response);
-    RetCode Powerdown(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
     RetCode Reboot(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
+    RetCode RebootTo(std::string target, std::string* response = nullptr,
+                     std::vector<std::string>* info = nullptr);
     RetCode SetActive(const std::string& part, std::string* response = nullptr,
                       std::vector<std::string>* info = nullptr);
     RetCode Upload(const std::string& outfile, std::string* response = nullptr,
                    std::vector<std::string>* info = nullptr);
-    RetCode Verify(uint32_t num, std::string* response = nullptr,
-                   std::vector<std::string>* info = nullptr);
 
     /* HIGHER LEVEL COMMANDS -- Composed of the commands above */
     RetCode FlashPartition(const std::string& part, const std::vector<char>& data);
     RetCode FlashPartition(const std::string& part, int fd, uint32_t sz);
     RetCode FlashPartition(const std::string& part, sparse_file* s);
 
-    RetCode Partitions(std::vector<std::tuple<std::string, uint32_t>>* parts);
+    RetCode Partitions(std::vector<std::tuple<std::string, uint64_t>>* parts);
     RetCode Require(const std::string& var, const std::vector<std::string>& allowed, bool* reqmet,
                     bool invert = false);
 
@@ -107,6 +109,10 @@
     std::string Error();
     RetCode WaitForDisconnect();
 
+    // Note: set_transport will return the previous transport.
+    Transport* set_transport(Transport* transport);
+    Transport* transport() const { return transport_; }
+
     // This is temporarily public for engine.cpp
     RetCode RawCommand(const std::string& cmd, std::string* response = nullptr,
                        std::vector<std::string>* info = nullptr, int* dsize = nullptr);
@@ -127,14 +133,12 @@
         static const std::string ERASE;
         static const std::string FLASH;
         static const std::string GET_VAR;
-        static const std::string POWERDOWN;
         static const std::string REBOOT;
         static const std::string SET_ACTIVE;
         static const std::string UPLOAD;
-        static const std::string VERIFY;
     };
 
-    Transport* const transport;
+    Transport* transport_;
 
   private:
     RetCode SendBuffer(int fd, size_t size);
diff --git a/fastboot/fs.cpp b/fastboot/fs.cpp
index b0ac2ef..fc6a16b 100644
--- a/fastboot/fs.cpp
+++ b/fastboot/fs.cpp
@@ -8,7 +8,7 @@
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#ifndef WIN32
+#ifndef _WIN32
 #include <sys/wait.h>
 #else
 #include <tchar.h>
@@ -27,7 +27,7 @@
 using android::base::StringPrintf;
 using android::base::unique_fd;
 
-#ifdef WIN32
+#ifdef _WIN32
 static int exec_cmd(const char* path, const char** argv, const char** envp) {
     std::string cmd;
     int i = 0;
diff --git a/fastboot/fuzzy_fastboot/Android.bp b/fastboot/fuzzy_fastboot/Android.bp
new file mode 100644
index 0000000..301534b
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/Android.bp
@@ -0,0 +1,41 @@
+cc_test_host {
+  name: "fuzzy_fastboot",
+  compile_multilib: "first",
+
+  srcs: [
+    "main.cpp",
+    "extensions.cpp",
+    "usb_transport_sniffer.cpp",
+    "fixtures.cpp",
+    "test_utils.cpp",
+  ],
+
+  static_libs: [
+    "libfastboot2",
+    "libziparchive",
+    "libsparse",
+    "libutils",
+    "liblog",
+    "libz",
+    "libdiagnose_usb",
+    "libbase",
+    "libcutils",
+    "libgtest",
+    "libgtest_main",
+    "libbase",
+    "libadb_host",
+    "libtinyxml2",
+    "libsparse",
+  ],
+
+  // Static libs (libfastboot2) shared library dependencies are not transitively included
+  // This is needed to avoid link time errors when building for mac
+  target: {
+    darwin: {
+      host_ldlibs: [
+          "-framework CoreFoundation",
+          "-framework IOKit",
+      ],
+    },
+  }
+}
diff --git a/fastboot/fuzzy_fastboot/README.md b/fastboot/fuzzy_fastboot/README.md
new file mode 100644
index 0000000..72967c5
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/README.md
@@ -0,0 +1,394 @@
+# Fuzzy Fastboot
+
+Fuzzy Fastboot (FF) is a standalone automated conformance and penetration tester for
+validating device-side fastboot protocol implementations.
+The tool is completely generic, and uses a simple extensible XML
+configuration file to auto-generate device-specific tests for any device.
+Any Android device that uses the fastboot protocol should have fuzzy fastboot run on it prior to
+release to find implementation bugs, make sure it conforms to the fastboot spec,
+and that it safely handles malicious inputs.
+
+
+## Background
+The [fastboot protocol](../README.md) provides an easy way to manage low level
+aspects of the device directly from bootloader. However, with great power comes
+great responsibility. An improper or insecure fastboot implementation can
+open the possibility for critical security exploits on the bootloader via fastboot
+commands. Furthermore, an untrustworthy or insecure bootloader means nothing that is
+either directly or indirectly bootstrapped by the bootloader can be trusted (including Android).
+By checking a bootloader's conformance to the fastboot spec, as well as make sure
+nefarious/malformed input is properly and gracefully handled, easy exploits of a
+device's bootloaders can be mitigated.
+
+Additionally, since the fastboot tool itself must support a myriad of fastboot
+implementations, it is important to make sure an implementation is conforming to
+avoid potential incompatibilities with the fastboot command line tool itself.
+Thus, Fuzzy Fastboot also checks for proper conformance to the spec.
+
+## Overview
+Fuzzy Fastboot is written in C++ and uses [Google Test](https://github.com/google/googletest)
+for the underlying test framework. This means that Fuzzy Fastboot supports all of
+gtest's command line flags and options.
+
+Additionally, by using gtest it makes it extremely easy to add additional C++ based
+tests to Fuzzy Fastboot. However, in most cases the optional device specific
+XML configuration file that is provided to Fuzzy Fastboot supports the necessary
+features and hooks for testing device specific commands/features
+without touching the underlying C++.
+
+### Generic Tests
+Without a provided device XML configuration, Fuzzy Fastboot can only perform
+some basic tests that are generic to any fastboot device. These generic tests are
+divided into several test suite categories:
+
+1. **USBFunctionality** - Test USB communication
+2. **Conformance** - Test the device properly handles well-formed fastboot commands
+3. **UnlockPermissions** - Test commands only allowed in the unlocked state work
+4. **LockPermissions** - Test commands only not allowed in the locked state are rejected
+5. **Fuzz** - Test malicious and/or ill-formed commands are properly and gracefully handled
+
+
+### XML Generated Tests
+With a provided XML device configuration, Fuzzy Fastboot will be able to generate
+many more additional tests cases.
+
+The device config XML has five element pairs all inside a root level `<config>`:
+
+#### `<getvar>` Element
+Inside the `<getvar></getvar>` element pairs, one should list all the device's getvar
+variables, with an associated ECMAScript regex you wish the returned variable to match on.
+Each tested variable should appear in a `<var key="key" assert="regex"/>` format.
+For example:
+```xml
+<getvar>
+  <var key="product" assert="superphone2000"/>
+  <var key="secure" assert="no|yes"/>
+  <var key="battery-voltage" assert="[34][[:digit:]]{3}"/>
+  <!-- etc...  -->
+</getvar>
+```
+
+#### `<partitions>` Element
+Inside the `<partitions></partitions>` element pairs, one should list all the device's
+partitions. Each device partition has should be put inside a `<part/>` element.
+The `<part/>` element supports the following attributes:
+
+
+| Attribute | Value          | Purpose                                                                                     | Default  |
+|-----------|----------------|---------------------------------------------------------------------------------------------|----------|
+| value     | Partition name | The name of the partition                                                                   | Required |
+| slots     | "yes" or "no"  | Is this partition is slotted                                                                | "no"     |
+| test      | "yes" or "no"  | Is Fuzzy Fastboot is allowed to generate tests that overwrite this partition                | Required |
+| hashable  | "yes" or "no"  | Is this partition hashable with the hash command specified in `<checksum>`                  | "yes"    |
+| parsed    | "yes" or "no"  | Does the bootloader parse this partition, such as look for a header, look for magic, etc... | "no"     |
+
+For example:
+```xml
+<!-- All the device partitions should be listed here -->
+<partitions>
+  <part value="boot" slots="yes" test="yes" hashable="yes" parsed="yes"/>
+  <part value="modem" slots="yes" test="yes" hashable="yes"/>
+  <part value="userdata" slots="no" test="yes" hashable="no"/>
+  <!-- etc...  -->
+</partitions>
+```
+
+#### `<packed>` Element
+Most devices have pseudo partitions, such as a `bootloader` partition,
+that in reality is composed of several real partitions.
+When one of these pseudo partitions is flashed, the bootloader
+will internally expand the image into the individual images for each underlying
+partition. These pseudo partitions should be listed inside a `<part></part>`
+element pair. Each element `<part>` has a mandatory attribute `value`,
+which lists the name of this pseudo partition, and a `slots` attribute,
+which can be yes or no if this pseudo partition is slotted.
+Additionally, inside the `<part></part>` element pair, one should list
+all the real partition that make up this pseudo partition inside of
+`<child>PART_NAME</child>` element pairs.
+An example is should below:
+
+```xml
+<!-- All the device packed partitions should be listed here -->
+<packed>
+	<part value="bootloader" slots="yes">
+		<!-- We list the real partitions it is composed of -->
+		<child>foo1</child>
+		<child>foo2</child>
+		<child>bar3</child>
+		<!-- We list tests, expect defaults to 'okay' -->
+		<test packed="bootloader.img" unpacked="unpacked"/>
+		<test packed="bootloader_garbage.img" expect="fail"/>
+	</part>
+</packed>
+```
+
+You might notice there are additional `<test/>` elements as well contained inside of
+a `<part></part>` pair. This is because Fuzzy Fastboot allows (and recommends) one to specify
+valid and invalid test packed images for flashing this particular pseudo partition.
+Additionally, one should specify a folder with all the partitions' images
+that the packed image unpacks to. If your device supports hashing partitions, this
+will allow Fuzzy Fastboot to validate the images are unpacking correctly, and
+the correct slots are being flashed.
+
+Each `<test/>` element has the following supported attributes:
+
+| Attribute | Value | Purpose | Default |
+|-----------|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------|
+| packed | The name of the packed test image | The image uploaded to the device. It is searched for in dir if --search_path=dir | Required |
+| unpacked | The name of the directory containing the unpacked version of packed | Searched for in dir if --search_path=dir. This folder should have the all the images that packed unpacks to. The name of each of the images should be the name of the real partition it is flashed to. | Required if expect != "fail" |
+| expect | "okay" or "fail" | If uploading a invalid or garbage image the bootloader should reject use "fail" otherwise "okay" | "okay" |
+
+
+#### `<oem>` Element
+Vendors can extend the fastboot protocol with oem commands. This allows vendors
+to support device/vendor specific features/commands over the fastboot protocol.
+Fuzzy Fastboot allows testing these oem commands as well.
+
+Oem commands are specefied in `<command></command>` element pairs. Each command
+element supports the following attributes:
+
+
+| Attribute   | Value                | Purpose                                                       | Default  |
+|-------------|----------------------|---------------------------------------------------------------|----------|
+| value       | The oem command name | Ex: if value="foo", the oem command will start with "oem foo" | Required |
+| permissions | "none" or "unlocked" | Whether the bootloader must be "unlocked" to perform command  | "none"   |
+
+An example is should below:
+```xml
+<oem>
+  <command value="self_destruct" permissions="unlocked">
+    <!-- This will test that "oem self_destruct now" returns 'okay' -->
+    <test value="now" expect="okay"/>
+    <!-- This will test that "oem self_destruct yesterday" returns 'fail' -->
+    <test value="yesterday" expect="fail" />
+  </command>
+
+  <command value="foobar" permissions="unlocked">
+    <!-- FF will first stage test_image.img before running 'oem foobar use_staged' -->
+    <test value="use_staged" expect="okay" input="test_image.img" />
+    <!-- FF will run 'oem foobar send_response', upload data from device, then run the validator script -->
+    <test value="send_response" expect="fail" validate="python validator.py"/>
+  </command>
+<oem/>
+```
+
+Again you will notice that one can, and should, specify tests to run with `<test/>` elements.
+The test elements support the following attributes:
+
+
+| Attribute | Value                                            | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                                            | Default                    |
+|-----------|--------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|
+| value     | The oem command argument                         | Ex: if value="bar", and the oem command name was "foo", the full command will be "oem foo bar"                                                                                                                                                                                                                                                                                                                                                     | Empty String (no argument) |
+| expect    | "okay" or "fail"                                 | Whether the bootloader should accept or reject this command                                                                                                                                                                                                                                                                                                                                                                                        | "okay"                     |
+| input     | A image filename                                 | Some oem commands require staging files before the command is executed                                                                                                                                                                                                                                                                                                                                                                             | Empty String (no argument) |
+| validate  | A program/script to run to validate the response | Some oem commands will stage data that can be downloaded afterwards and should be validated to be correct. Fuzzy Fastboot will launch the validation program with the first arg the oem command executed, the second arg  the path to the downloaded image. Ex: "python validate.py'. If the program has a non-zero  return code, the validation is marked as failed and anything from the launched programs stderr is logged in the test failure. | Empty String (no argument) |
+| assert    | A Regular expression                             | In the "okay" or "fail" response, Fuzzy Fastboot will assert the  response matches this regular expression.                                                                                                                                                                                                                                                                                                                                        | Empty String (no argument) |
+| output    | The name of the saved file                       | This is the name of the saved output file passed to the validation script. It is saved in whatever location is specified by the --output_path argument                                                                                                                                                                                                                                                                                             | out.img                    |
+
+
+#### `<checksum/>` Element
+If the bootloader supports hashing partitions (implementing this is strongly recommended), Fuzzy Fastboot can
+use it to do a bunch more testing. Make sure this hash is a cryptographically secure hash, as a non-secure one
+might reveal secrets about the partitions' contents.
+
+The checksum element has no children and only two required attributes:
+
+- **value** - The command that hashes a partition. Note that the name of the partition will be appended to the end of the command. For example, if `value="oem hash"`, hashing the partition `bar` would be issued with `oem hash bar`.
+- **parser** - How the hash is returned is up to the vendor's implementation. It could be part of the `OKAY` response, or be encoded in `INFO` responses. Thus, the parser attribute is used to specify a program/script that will extract the hash. The first argument to the program will be the be the response from `OKAY`, the second argument will be all the `INFO` responses joined by newlines. The extracted hash should be sent back to Fuzzy Fastboot as a string written to stderr, and a return code of 0 to signal the parsing was successful. In the case of failure, return a non-zero return code, an optionally an associated error message written to stderr.
+
+
+
+## Full Example XML Configuration
+Here is a basic example configuration. This can also be found in the 'example' folder
+as well as the associated python scripts 'checksum_parser.py' (used to extract partition hash),
+and 'validator.py' (used to validate an oem command that returns data).
+```xml
+<?xml version="1.0"?>
+<config>
+<!-- All the device getvar variables should be listed here -->
+<getvar>
+	<var key="product" assert="superphone2000"/>
+	<var key="secure" assert="no|yes"/>
+</getvar>
+
+<!-- All the device partitions should be listed here -->
+<partitions>
+	<part value="boot" slots="yes" test="yes" hashable="yes" parsed="yes"/>
+	<part value="modem" slots="yes" test="yes" hashable="yes"/>
+	<part value="userdata" slots="no" test="yes" hashable="no"/>
+
+	<!-- Bootloader partitions -->
+	<part value="foo1" slots="yes" test="no" hashable="yes"/>
+	<part value="foo2" slots="yes" test="no" hashable="yes"/>
+	<part value="bar3" slots="yes" test="no" hashable="yes"/>
+</partitions>
+
+<!-- All the device packed partitions should be listed here -->
+<packed>
+	<part value="bootloader" slots="yes">
+		<!-- We list the real partitions it is composed of -->
+		<child>foo1</child>
+		<child>foo2</child>
+		<child>bar3</child>
+		<!-- We list tests, expect defaults to 'okay' -->
+		<test packed="bootloader.img" unpacked="unpacked"/>
+		<test packed="bootloader_garbage.img" expect="fail"/>
+	</part>
+</packed>
+
+<!-- All the oem commands should be listed here -->
+<oem>
+	<!-- The 'oem self_destruct' command requires an unlocked bootloader -->
+	<command value="self_destruct" permissions="unlocked">
+		<!-- This will test that "oem self_destruct now" returns 'okay' -->
+		<test value="now" expect="okay"/>
+		<test value="yesterday" expect="fail" />
+	</command>
+
+	<!-- Test a fictional 'oem get' command -->
+	<command value="get" permissions="none">
+		<test value="batch_id" expect="okay" assert="[[:digit:]]+"/>
+		<test value="device_color" expect="okay" assert="green|blue"/>
+		<test value="build_num" expect="okay" assert="[\w\-.]+"/>
+		<test value="garbage" expect="fail" assert="Invalid var '[\w ]+'"/>
+	</command>
+
+	<!-- Some oem commands might require staging or downloading data, or both -->
+	<command value="foobar" permissions="unlocked">
+		<!-- FF will first stage test_image.img before running 'oem foobar use_staged' -->
+		<test value="use_staged" expect="okay" input="test_image.img" />
+		<!-- FF will run 'oem foobar send_response', upload data from device, then run the validator script -->
+		<test value="send_response" expect="fail" validate="python validator.py"/>
+	</command>
+</oem>
+
+<!-- If there is a custom oem checksum command to hash partitions, add it here -->
+<checksum value="oem sha1sum"/>
+</config>
+
+```
+
+## Running Fuzzy Fastboot
+Fuzzy Fastboot is built with the fastboot tool itself. It will appear in `out/host/linux-x86/testcases/fuzzy_fastboot/x86_64`.
+
+### Command Line Arguments
+- **--config=**: Specify the name of the configuration XML file. If omitted, only the generic tests will be available.
+- **--search_path=**: Specify the path where Fuzzy Fastboot will look for files referenced in the XML. This includes all the test images and the referenced programs/scripts. This is also where the --config is searched for. If this argument is omitted it defaults to the current directory.
+- **--output_path**: Some oem tests can download an image to the host for validation. This is the location where that image is stored. This deafults to '/tmp'.
+- **--serial_port**: Many devices have a UART or serial log, that reports logging information. Fuzzy Fastboot can include this logging information in the backtraces it generates. This can make debugging far easier. If your device has this, it can be specified with the path to the tty device. Ex: "/dev/ttyUSB0".
+- **--gtest_***: Any valid gtest argument (they all start with 'gtest_')
+- **-h**: Print gtest's help message
+
+
+## Using Fuzzy Fastboot on my Device
+All Fuzzy Fastboot tests should pass on your device. No test should be able to
+crash the bootloader. Invalid input MUST be handled gracefully. Using "asserts"
+or panicking on invalid or malformed input is not an acceptable way to handle
+these tests, as ungraceful forced termination of the bootloader can expose
+vulnerabilities and leave the device in a bad state.
+
+The following is the recommended workflow for using Fuzzy Fastboot on a new device:
+
+### Step 1: Pass the generic Conformance tests
+Begin with just the generic tests (i.e. no XML file). In particular, make sure all
+the conformance tests are passing before you move on. All other tests require that
+the basic generic conformance tests all pass for them to be valid. The conformance
+tests can be run with `./fuzzy_fastboot --gtests_filter=Conformance.*`.
+
+#### Understanding and Fixing Failed Tests
+Whenever a test fails, it will print out to the console the reason for failure
+and the lines and file where the error happened. At the end of each failure
+block, there will usually be a message that Fuzzy Fastboot reports to gtest
+explaining what went wrong. An example is shown below:
+
+```
+Expected equality of these values:
+  resp
+    Which is: "no"
+  unlock ? "yes" : "no"
+    Which is: "yes"
+getvar:unlocked response was not 'no' or 'yes': no
+system/core/fastboot/fuzzy_fastboot/fixtures.cpp:227: Failure
+Expected: SetLockState(UNLOCKED) doesn't generate new fatal failures in the current thread.
+  Actual: it does.
+[THERE WILL BE A MESSAGE HERE EXPLAINING WHY IT FAILED]
+```
+
+In most cases this message at the bottom is all that is needed to figure out why it failed.
+If this is not enough information, below this, gtest will also print out a human readable
+backtrace of the underlying fastboot commands leading up the failure in this test.
+Here is an example:
+```
+<<<<<<<< TRACE BEGIN >>>>>>>>>
+[WRITE 0ms](15 bytes): "getvar:unlocked"
+[READ 20ms](6 bytes): "OKAYno"
+<<<<<<<< TRACE END >>>>>>>>>
+```
+One can easily see the reason for the failure was the test expected the device to
+be unlocked.
+
+If it is still unclear why the failure is happening, the last thing to do is look
+at what line number and file is generating the error. Gtest will always print this out.
+You can then manually look through Fuzzy Fastboot's test source code, and figure out
+what went wrong.
+
+
+### Step 2: Pass all the other generic tests
+Run all the other generic tests (still no XML file). A list of all of them can be
+printed out with: "./fuzzy_fastboot --gtest_list_tests". As before, "--gtest_filter"
+can be used to select certain tests to run, once you figure out which ones failed.
+
+One particular set of tests to watch out for are the ones that involve USB resets.
+USB resets effectively unplug and replug the device in software. Fuzzy Fastboot,
+expects USB resets to cancel whatever transaction is currently going on.
+This is also how Fuzzy Fastboot attempts to recover from errors when the device is
+unresponsive.
+
+### Step 3: Create a device XML configuration
+Without a device specific configuration file, Fuzzy Fastboot will have poor test
+coverage of your device. The vast majority of tests are auto-generated via the XML
+configuration file. Use the guide above to generate a configuration for your device.
+Make sure to be as thorough as possible, and list everything in the configuration
+that can be tested. Finally, make sure that the packed pseudo partitions and
+oem commands all have provided test cases. Be sure to test both the positive case
+(i.e. with valid input), as well as the opposite. Make sure the failure tests
+have good coverage by thinking about all the ways invalid and malicious inputs
+could be formed. These means creating images with malformed headers, illegal chars,
+and other evil inputs.
+
+Now run fuzzy_fastboot with the supplied configuration file. If you do "--gtest_list_tests",
+you should see a ton more tests that were autogenerated by Fuzzy Fastboot.
+As before, run these tests till everything passes. Again, use "--gtest_filter"
+to select specific tests to run once you know what fail,
+as running the whole things with a large configuration can take over 30 minutes.
+See the gtest documentation, for nifty tricks and command line options.
+
+### Step 4: Figure out what Fuzzy Fastboot can't/isn't testing
+While Fuzzy Fastboot with a XML configuration file, should provide good test coverage.
+Think about what device specific things are not being tested, and test them manually.
+In particular, things that if not handled carefully could create security exploits.
+Don't be lazy here, as you already put in the time to get this far.
+
+### Step 5: Celebrate
+You're done :). Now you can be more confident that your implementation is sound, and
+have piece of mind knowing you are protecting the users' security and data by
+running these tests. Don't get too complacent. If the bootloader's source code
+is modified in a way that could introduce bugs or security issues. Make sure to
+test again. You might have to add to your existing configuration file.
+
+## Limitations and Warnings
+- Currently this only works on Linux (even if it builds on Mac)
+- Only fastboot over USB is currently supported
+- Passing these tests does not mean there are not bugs/security issues. For example, a buffer overrun might not always trigger a crash or have any noticeable side effects.
+- **Be extremely careful of the Fuzzy Fastboot tests you are running. Know exactly what the tests do you are about to run before you run them. It is very possible to brick a device with many of these tests.**
+
+## Fuzzy Fastboot Missing Features TODO's
+The following are missing features that should eventually be added
+- *Sparse Image Tests*: Currently there are no tests that tests sparse images. Both well-formed and malicious images need to be tested.
+- *Unlocking/Locking Critical*: Currently there are no tests that tests that locking/unlocking critical functionality.
+- *Saved Test Log*: Fuzzy Fastboot should be able to create a failure log for every failing test and save it to a file. This file should include the test information, the reason it failed, and the fastboot command trace (with the serial console logs). Currently it just prints it to the console at the end of every test.
+- *Host Side Hashing*: One should be able to provide the hashing algorithm to the Fuzzy Fastboot, so it can be checked to agree with what the device is reporting.
+
+
+## Author
+Aaron Wisner - awisner@google.com
diff --git a/fastboot/fuzzy_fastboot/example/checksum_parser.py b/fastboot/fuzzy_fastboot/example/checksum_parser.py
new file mode 100644
index 0000000..1a890e6
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/example/checksum_parser.py
@@ -0,0 +1,37 @@
+'''
+Some bootloader's support hashing partitions. This is a great feature for testing
+correctness. However, the format for the way the hash is returned depends on the
+implementation. The hash could be send through an INFO response, or be as part
+of the OKAY response itself. This script is called with the first argument
+as the string mesage from the okay response. The second argument is each
+info response joined by newlines into one argument.
+'''
+
+import sys
+
+
+def main():
+  '''
+  Data is sent back to the parent fuzzy_fastboot process through the stderr pipe.
+  There are two interpretations of this data by FF.
+
+  0 return code:
+    Anything written to STDERR will be interpreted as part of the hash.
+
+  non-zero return code:
+    Anything written to STDERR is part of the error message that will logged by FF
+    to explain why hash extraction failed.
+
+  Feel free to print to to STDOUT with print() as usual to print info to console
+  '''
+  script, response, info = sys.argv
+  # the info responses are concated by newlines
+  infos = [s.strip() for s in info.splitlines()]
+  sys.stderr.write(infos[-1])
+  print("Extracted checksum: '%s'" % infos[-1])
+  # non-zero return code signals error
+  return 0
+
+
+if __name__ == "__main__":
+  sys.exit(main())
diff --git a/fastboot/fuzzy_fastboot/example/config.xml b/fastboot/fuzzy_fastboot/example/config.xml
new file mode 100644
index 0000000..af2a3b9
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/example/config.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<config>
+<!-- All the device getvar variables should be listed here -->
+<getvar>
+	<var key="product" assert="superphone2000"/>
+	<var key="secure" assert="no|yes"/>
+</getvar>
+
+<!-- All the device partitions should be listed here -->
+<partitions>
+	<part value="boot" slots="yes" test="yes" hashable="yes" parsed="yes"/>
+	<part value="modem" slots="yes" test="yes" hashable="yes"/>
+	<part value="userdata" slots="no" test="yes" hashable="no"/>
+
+	<!-- Bootloader partitions -->
+	<part value="foo1" slots="yes" test="no" hashable="yes"/>
+	<part value="foo2" slots="yes" test="no" hashable="yes"/>
+	<part value="bar3" slots="yes" test="no" hashable="yes"/>
+</partitions>
+
+<!-- All the device packed partitions should be listed here -->
+<packed>
+	<part value="bootloader" slots="yes">
+		<!-- We list the real partitions it is composed of -->
+		<child>foo1</child>
+		<child>foo2</child>
+		<child>bar3</child>
+		<!-- We list tests, expect defaults to 'okay' -->
+		<test packed="bootloader.img" unpacked="unpacked"/>
+		<test packed="bootloader_garbage.img" expect="fail"/>
+	</part>
+</packed>
+
+<!-- All the oem commands should be listed here -->
+<oem>
+	<!-- The 'oem self_destruct' command requires an unlocked bootloader -->
+	<command value="self_destruct" permissions="unlocked">
+		<!-- This will test that "oem self_destruct now" returns 'okay' -->
+		<test value="now" expect="okay"/>
+		<test value="yesterday" expect="fail" />
+	</command>
+
+	<!-- Test a fictional 'oem get' command -->
+	<command value="get" permissions="none">
+		<test value="batch_id" expect="okay" assert="[[:digit:]]+"/>
+		<test value="device_color" expect="okay" assert="green|blue"/>
+		<test value="build_num" expect="okay" assert="[\w\-.]+"/>
+		<test value="garbage" expect="fail" assert="Invalid var '[\w ]+'"/>
+	</command>
+
+	<!-- Some oem commands might require staging or downloading data, or both -->
+	<command value="foobar" permissions="unlocked">
+		<!-- FF will first stage test_image.img before running 'oem foobar use_staged' -->
+		<test value="use_staged" expect="okay" input="test_image.img" />
+		<!-- FF will run 'oem foobar send_response', upload data from device, then run the validator script -->
+		<test value="send_response" expect="fail" validate="python validator.py"/>
+	</command>
+</oem>
+
+<!-- If there is a custom oem checksum command to hash partitions, add it here -->
+<checksum value="oem sha1sum"/>
+</config>
diff --git a/fastboot/fuzzy_fastboot/example/validator.py b/fastboot/fuzzy_fastboot/example/validator.py
new file mode 100644
index 0000000..9c5081f
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/example/validator.py
@@ -0,0 +1,37 @@
+'''
+This is an example validator to be used with oem commands that allow you to
+upload data afterwards that you wish to validate locally.
+'''
+import sys
+
+def eprint(msg):
+  '''
+  A helper function for logging error messages to fuzzy_fastboot
+  Use this function as you would "print()"
+  '''
+  sys.stderr.write(msg + '\n')
+
+
+def main():
+  '''
+  Data is sent back to the parent fuzzy_fastboot process through the stderr pipe.
+
+  If this script has a non-zero return code, anything written to STDERR is part of
+  the error message that will logged by FF to explain why this validation failed.
+
+  Feel free to print to to STDOUT with print() as usual to print info to console
+  '''
+  script, command, fname = sys.argv
+  eprint("Messages here will go to the parent testers logs")
+  eprint("Hello world")
+  print("This goes to stdout as expected")
+  with open(fname, "rb") as fd:
+    # Do some validation on the buffer
+    pass
+
+  # non-zero return code signals error
+  return -1
+
+
+if __name__ == "__main__":
+  sys.exit(main())
diff --git a/fastboot/fuzzy_fastboot/extensions.cpp b/fastboot/fuzzy_fastboot/extensions.cpp
new file mode 100644
index 0000000..62ef5ba
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/extensions.cpp
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <csignal>
+#include <cstdlib>
+#include <fstream>
+
+#include "extensions.h"
+#include "test_utils.h"
+#include "tinyxml2.h"
+
+namespace fastboot {
+namespace extension {
+
+namespace {  // private to this file
+
+// Since exceptions are disabled, a bad regex will trigger an abort in the constructor
+// We at least need to print something out
+std::regex MakeRegex(const std::string& regex_str, int line_num,
+                     std::regex_constants::syntax_option_type type = std::regex::ECMAScript) {
+    // The signal handler can only access static vars
+    static std::string err_str;
+    err_str = android::base::StringPrintf("'%s' is not a valid regex string (line %d)\n",
+                                          regex_str.c_str(), line_num);
+    const auto sighandler = [](int) {
+        int nbytes = write(fileno(stderr), err_str.c_str(), err_str.length());
+        static_cast<void>(nbytes);  // need to supress the unused nbytes/ or unused result
+    };
+    std::signal(SIGABRT, sighandler);
+    // Now attempt to create the regex
+    std::regex ret(regex_str, type);
+    // unregister
+    std::signal(SIGABRT, SIG_DFL);
+
+    return ret;
+}
+
+bool XMLAssert(bool cond, const tinyxml2::XMLElement* elem, const char* msg) {
+    if (!cond) {
+        printf("%s (line %d)\n", msg, elem->GetLineNum());
+    }
+    return !cond;
+}
+
+const std::string XMLAttribute(const tinyxml2::XMLElement* elem, const std::string key,
+                               const std::string key_default = "") {
+    if (!elem->Attribute(key.c_str())) {
+        return key_default;
+    }
+
+    return elem->Attribute(key.c_str());
+}
+
+bool XMLYesNo(const tinyxml2::XMLElement* elem, const std::string key, bool* res,
+              bool def = false) {
+    if (!elem->Attribute(key.c_str())) {
+        *res = def;
+        return true;
+    }
+    const std::string val = elem->Attribute(key.c_str());
+    if (val != "yes" && val != "no") {
+        return false;
+    }
+    *res = (val == "yes");
+    return true;
+}
+
+bool ExtractPartitions(tinyxml2::XMLConstHandle handle, Configuration* config) {
+    // Extract partitions
+    const tinyxml2::XMLElement* part = handle.FirstChildElement("part").ToElement();
+    while (part) {
+        Configuration::PartitionInfo part_info;
+        const std::string name = XMLAttribute(part, "value");
+        const std::string test = XMLAttribute(part, "test");
+        if (XMLAssert(!name.empty(), part, "The name of a partition can not be empty") ||
+            XMLAssert(XMLYesNo(part, "slots", &part_info.slots), part,
+                      "Slots attribute must be 'yes' or 'no'") ||
+            XMLAssert(XMLYesNo(part, "hashable", &part_info.hashable, true), part,
+                      "Hashable attribute must be 'yes' or 'no'") ||
+            XMLAssert(XMLYesNo(part, "parsed", &part_info.parsed), part,
+                      "Parsed attribute must be 'yes' or 'no'"))
+            return false;
+
+        bool allowed = test == "yes" || test == "no-writes" || test == "no";
+        if (XMLAssert(allowed, part, "The test attribute must be 'yes' 'no-writes' or 'no'"))
+            return false;
+        if (XMLAssert(config->partitions.find(name) == config->partitions.end(), part,
+                      "The same partition name is listed twice"))
+            return false;
+        part_info.test = (test == "yes")
+                                 ? Configuration::PartitionInfo::YES
+                                 : (test == "no-writes") ? Configuration::PartitionInfo::NO_WRITES
+                                                         : Configuration::PartitionInfo::NO;
+        config->partitions[name] = part_info;
+        part = part->NextSiblingElement("part");
+    }
+    return true;
+}
+
+bool ExtractPacked(tinyxml2::XMLConstHandle handle, Configuration* config) {
+    // Extract partitions
+    const tinyxml2::XMLElement* part = handle.FirstChildElement("part").ToElement();
+    while (part) {
+        Configuration::PackedInfo packed_info;
+        const std::string name = XMLAttribute(part, "value");
+
+        if (XMLAssert(!name.empty(), part, "The name of a packed partition can not be empty") ||
+            XMLAssert(XMLYesNo(part, "slots", &packed_info.slots), part,
+                      "Slots attribute must be 'yes' or 'no'") ||
+            XMLAssert(config->partitions.find(name) == config->partitions.end(), part,
+                      "A packed partition can not have same name as a real one") ||
+            XMLAssert(config->partitions.find(name) == config->partitions.end(), part,
+                      "The same partition name is listed twice"))
+            return false;
+
+        // Extract children partitions
+        const tinyxml2::XMLElement* child = part->FirstChildElement("child")
+                                                    ? part->FirstChildElement("child")->ToElement()
+                                                    : nullptr;
+        while (child) {
+            const std::string text(child->GetText());
+            // Make sure child exists
+            if (XMLAssert(config->partitions.find(text) != config->partitions.end(), child,
+                          "The child partition was not listed in <partitions>"))
+                return false;
+            packed_info.children.insert(text);
+            child = child->NextSiblingElement("child");
+        }
+
+        // Extract tests
+        const tinyxml2::XMLElement* test = part->FirstChildElement("test")
+                                                   ? part->FirstChildElement("test")->ToElement()
+                                                   : nullptr;
+        while (test) {
+            Configuration::PackedInfoTest packed_test;
+            packed_test.packed_img = XMLAttribute(test, "packed");
+            packed_test.unpacked_dir = XMLAttribute(test, "unpacked");
+            const std::string expect = XMLAttribute(test, "expect", "okay");
+
+            if (XMLAssert(!packed_test.packed_img.empty(), test,
+                          "The packed image location must be specified") ||
+                XMLAssert(CMD_EXPECTS.find(expect) != CMD_EXPECTS.end(), test,
+                          "Expect attribute must be 'okay' or 'fail'"))
+                return false;
+
+            packed_test.expect = CMD_EXPECTS.at(expect);
+            // The expect is only unpacked directory is only needed if success
+            if (packed_test.expect == OKAY &&
+                XMLAssert(!packed_test.unpacked_dir.empty(), test,
+                          "The unpacked image folder location must be specified"))
+                return false;
+
+            packed_info.tests.push_back(packed_test);
+            test = test->NextSiblingElement("test");
+        }
+
+        config->packed[name] = packed_info;
+        part = part->NextSiblingElement("part");
+    }
+    return true;
+}
+
+bool ExtractGetVars(tinyxml2::XMLConstHandle handle, Configuration* config) {
+    // Extract getvars
+    const tinyxml2::XMLElement* var = handle.FirstChildElement("var").ToElement();
+    while (var) {
+        const std::string key = XMLAttribute(var, "key");
+        const std::string reg = XMLAttribute(var, "assert");
+        if (XMLAssert(key.size(), var, "The var key name is empty")) return false;
+        if (XMLAssert(config->getvars.find(key) == config->getvars.end(), var,
+                      "The same getvar variable name is listed twice"))
+            return false;
+        Configuration::GetVar getvar{reg, MakeRegex(reg, var->GetLineNum()), var->GetLineNum()};
+        config->getvars[key] = std::move(getvar);
+        var = var->NextSiblingElement("var");
+    }
+    return true;
+}
+
+bool ExtractOem(tinyxml2::XMLConstHandle handle, Configuration* config) {
+    // Extract getvars
+    // Extract oem commands
+    const tinyxml2::XMLElement* command = handle.FirstChildElement("command").ToElement();
+    while (command) {
+        const std::string cmd = XMLAttribute(command, "value");
+        const std::string permissions = XMLAttribute(command, "permissions");
+        if (XMLAssert(cmd.size(), command, "Empty command value")) return false;
+        if (XMLAssert(permissions == "none" || permissions == "unlocked", command,
+                      "Permissions attribute must be 'none' or 'unlocked'"))
+            return false;
+
+        // Each command has tests
+        std::vector<Configuration::CommandTest> tests;
+        const tinyxml2::XMLElement* test = command->FirstChildElement("test");
+        while (test) {  // iterate through tests
+            Configuration::CommandTest ctest;
+
+            ctest.line_num = test->GetLineNum();
+            const std::string default_name = "XMLTest-line-" + std::to_string(test->GetLineNum());
+            ctest.name = XMLAttribute(test, "name", default_name);
+            ctest.arg = XMLAttribute(test, "value");
+            ctest.input = XMLAttribute(test, "input");
+            ctest.output = XMLAttribute(test, "output");
+            ctest.validator = XMLAttribute(test, "validate");
+            ctest.regex_str = XMLAttribute(test, "assert");
+
+            const std::string expect = XMLAttribute(test, "expect");
+
+            if (XMLAssert(CMD_EXPECTS.find(expect) != CMD_EXPECTS.end(), test,
+                          "Expect attribute must be 'okay' or 'fail'"))
+                return false;
+            ctest.expect = CMD_EXPECTS.at(expect);
+            std::regex regex;
+            if (expect == "okay" && ctest.regex_str.size()) {
+                ctest.regex = MakeRegex(ctest.regex_str, test->GetLineNum());
+            }
+            tests.push_back(std::move(ctest));
+            test = test->NextSiblingElement("test");
+        }
+
+        // Build the command struct
+        const Configuration::OemCommand oem_cmd{permissions == "unlocked", std::move(tests)};
+        config->oem[cmd] = std::move(oem_cmd);
+
+        command = command->NextSiblingElement("command");
+    }
+    return true;
+}
+
+bool ExtractChecksum(tinyxml2::XMLConstHandle handle, Configuration* config) {
+    const tinyxml2::XMLElement* checksum = handle.ToElement();
+    if (checksum && checksum->Attribute("value")) {
+        config->checksum = XMLAttribute(checksum, "value");
+        config->checksum_parser = XMLAttribute(checksum, "parser");
+        if (XMLAssert(config->checksum_parser != "", checksum,
+                      "A checksum parser attribute is mandatory"))
+            return false;
+    }
+    return true;
+}
+
+}  // anonymous namespace
+
+bool ParseXml(const std::string& file, Configuration* config) {
+    tinyxml2::XMLDocument doc;
+    if (doc.LoadFile(file.c_str())) {
+        printf("Failed to open/parse XML file '%s'\nXMLError: %s\n", file.c_str(), doc.ErrorStr());
+        return false;
+    }
+
+    tinyxml2::XMLConstHandle handle(&doc);
+    tinyxml2::XMLConstHandle root(handle.FirstChildElement("config"));
+
+    // Extract the getvars
+    if (!ExtractGetVars(root.FirstChildElement("getvar"), config)) {
+        return false;
+    }
+    // Extract the partition info
+    if (!ExtractPartitions(root.FirstChildElement("partitions"), config)) {
+        return false;
+    }
+
+    // Extract packed
+    if (!ExtractPacked(root.FirstChildElement("packed"), config)) {
+        return false;
+    }
+
+    // Extract oem commands
+    if (!ExtractOem(root.FirstChildElement("oem"), config)) {
+        return false;
+    }
+
+    // Extract checksum
+    if (!ExtractChecksum(root.FirstChildElement("checksum"), config)) {
+        return false;
+    }
+
+    return true;
+}
+
+}  // namespace extension
+}  // namespace fastboot
diff --git a/fastboot/fuzzy_fastboot/extensions.h b/fastboot/fuzzy_fastboot/extensions.h
new file mode 100644
index 0000000..58312e5
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/extensions.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#pragma once
+
+#include <regex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace fastboot {
+namespace extension {
+
+enum Expect { OKAY = 0, FAIL, DATA };
+
+static const std::unordered_map<std::string, Expect> CMD_EXPECTS = {
+        {"okay", OKAY},
+        {"fail", FAIL},
+        {"data", DATA},
+};
+
+static const std::unordered_map<Expect, std::string> EXPECTS_STR = {
+        {OKAY, "okay"},
+        {FAIL, "fail"},
+        {DATA, "data"},
+};
+
+struct Configuration {
+    struct GetVar {
+        std::string regex_str;
+        std::regex regex;
+        int line_num;
+
+        // So gtest can print me
+        friend ::std::ostream& operator<<(::std::ostream& os, const GetVar& self) {
+            return os << "<regex='" << self.regex_str << "' line_num=" << self.line_num << ">";
+        }
+    };
+    struct PartitionInfo {
+        enum TestConfig { NO = 0, NO_WRITES, YES };
+        bool hashable;
+        bool slots;   // Does it have slots
+        bool parsed;  // Does the bootloader do parsing on the img?
+        TestConfig test;
+
+        // So gtest can print me
+        friend ::std::ostream& operator<<(::std::ostream& os, const PartitionInfo& pinfo) {
+            return os << "<hashable=" << pinfo.hashable << " slots=" << pinfo.slots
+                      << " parsed=" << pinfo.parsed << ">";
+        }
+    };
+
+    struct PackedInfoTest {
+        Expect expect;  // Does it have slots
+        std::string packed_img;
+        std::string unpacked_dir;
+
+        // So gtest can print me
+        friend ::std::ostream& operator<<(::std::ostream& os, const PackedInfoTest& pinfo) {
+            return os << "<"
+                      << "expect=" << EXPECTS_STR.at(pinfo.expect)
+                      << " packed_img=" << pinfo.packed_img
+                      << " unpacked_dir=" << pinfo.unpacked_dir << ">";
+        }
+    };
+
+    struct PackedInfo {
+        bool slots;  // Does it have slots
+        std::unordered_set<std::string> children;
+        std::vector<PackedInfoTest> tests;
+    };
+
+    struct CommandTest {
+        std::string name;
+        int line_num;
+        std::string arg;
+        Expect expect;
+        std::string regex_str;
+        std::regex regex;
+        std::string input;
+        std::string output;
+        std::string validator;
+
+        // So gtest can print me
+        friend ::std::ostream& operator<<(::std::ostream& os, const CommandTest& self) {
+            return os << "test: " << self.name << " (line: " << self.line_num << ")";
+        }
+    };
+
+    struct OemCommand {
+        bool restricted;  // Does device need to be unlocked?
+        std::vector<CommandTest> tests;
+    };
+
+    std::unordered_map<std::string, GetVar> getvars;
+    std::unordered_map<std::string, PartitionInfo> partitions;
+    std::unordered_map<std::string, PackedInfo> packed;
+    std::unordered_map<std::string, OemCommand> oem;
+
+    std::string checksum;
+    std::string checksum_parser;
+};
+
+bool ParseXml(const std::string& file, Configuration* config);
+
+}  // namespace extension
+}  // namespace fastboot
diff --git a/fastboot/fuzzy_fastboot/fixtures.cpp b/fastboot/fuzzy_fastboot/fixtures.cpp
new file mode 100644
index 0000000..4da71ca
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/fixtures.cpp
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <unistd.h>
+#include <chrono>
+#include <cstdlib>
+#include <fstream>
+#include <map>
+#include <random>
+#include <regex>
+#include <set>
+#include <thread>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "fastboot_driver.h"
+#include "usb.h"
+
+#include "extensions.h"
+#include "fixtures.h"
+#include "test_utils.h"
+#include "usb_transport_sniffer.h"
+
+namespace fastboot {
+
+int FastBootTest::MatchFastboot(usb_ifc_info* info, const char* local_serial) {
+    if (info->ifc_class != 0xff || info->ifc_subclass != 0x42 || info->ifc_protocol != 0x03) {
+        return -1;
+    }
+
+    cb_scratch = info->device_path;
+
+    // require matching serial number or device path if requested
+    // at the command line with the -s option.
+    if (local_serial && (strcmp(local_serial, info->serial_number) != 0 &&
+                         strcmp(local_serial, info->device_path) != 0))
+        return -1;
+    return 0;
+}
+
+bool FastBootTest::UsbStillAvailible() {
+    // For some reason someone decided to prefix the path with "usb:"
+    std::string prefix("usb:");
+    if (std::equal(prefix.begin(), prefix.end(), device_path.begin())) {
+        std::string fname(device_path.begin() + prefix.size(), device_path.end());
+        std::string real_path =
+                android::base::StringPrintf("/sys/bus/usb/devices/%s/serial", fname.c_str());
+        std::ifstream f(real_path.c_str());
+        return f.good();
+    }
+    exit(-1);  // This should never happen
+    return true;
+}
+
+RetCode FastBootTest::DownloadCommand(uint32_t size, std::string* response,
+                                      std::vector<std::string>* info) {
+    return fb->DownloadCommand(size, response, info);
+}
+
+RetCode FastBootTest::SendBuffer(const std::vector<char>& buf) {
+    return fb->SendBuffer(buf);
+}
+
+RetCode FastBootTest::HandleResponse(std::string* response, std::vector<std::string>* info,
+                                     int* dsize) {
+    return fb->HandleResponse(response, info, dsize);
+}
+
+void FastBootTest::SetUp() {
+    if (device_path != "") {               // make sure the device is still connected
+        ASSERT_TRUE(UsbStillAvailible());  // The device disconnected
+    }
+
+    const auto matcher = [](usb_ifc_info* info) -> int { return MatchFastboot(info, nullptr); };
+    for (int i = 0; i < MAX_USB_TRIES && !transport; i++) {
+        std::unique_ptr<UsbTransport> usb(usb_open(matcher, USB_TIMEOUT));
+        if (usb)
+            transport = std::unique_ptr<UsbTransportSniffer>(
+                    new UsbTransportSniffer(std::move(usb), serial_port));
+        std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    }
+
+    ASSERT_TRUE(transport);  // no nullptr
+
+    if (device_path == "") {  // We set it the first time, then make sure it never changes
+        device_path = cb_scratch;
+    } else {
+        ASSERT_EQ(device_path, cb_scratch);  // The path can not change
+    }
+    fb = std::unique_ptr<FastBootDriver>(
+            new FastBootDriver(transport.get(), [](std::string&) {}, true));
+}
+
+void FastBootTest::TearDown() {
+    EXPECT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
+
+    TearDownSerial();
+
+    fb.reset();
+
+    if (transport) {
+        transport.reset();
+    }
+
+    ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
+}
+
+// TODO, this should eventually be piped to a file instead of stdout
+void FastBootTest::TearDownSerial() {
+    if (!transport) return;
+    // One last read from serial
+    transport->ProcessSerial();
+    if (HasFailure()) {
+        // TODO, print commands leading up
+        printf("<<<<<<<< TRACE BEGIN >>>>>>>>>\n");
+        printf("%s", transport->CreateTrace().c_str());
+        printf("<<<<<<<< TRACE END >>>>>>>>>\n");
+        // std::vector<std::pair<const TransferType, const std::vector<char>>>  prev =
+        // transport->Transfers();
+    }
+}
+
+void FastBootTest::SetLockState(bool unlock, bool assert_change) {
+    if (!fb) {
+        return;
+    }
+
+    std::string resp;
+    std::vector<std::string> info;
+    // To avoid risk of bricking device, make sure unlock ability is set to 1
+    ASSERT_EQ(fb->RawCommand("flashing get_unlock_ability", &resp, &info), SUCCESS)
+            << "'flashing get_unlock_ability' failed";
+
+    // There are two ways this can be reported, through info or the actual response
+    if (!resp.empty()) {  // must be in the info response
+        ASSERT_EQ(resp.back(), '1')
+                << "Unlock ability must be set to 1 to avoid bricking device, see "
+                   "'https://source.android.com/devices/bootloader/unlock-trusty'";
+    } else {
+        ASSERT_FALSE(info.empty()) << "'flashing get_unlock_ability' returned empty response";
+        ASSERT_FALSE(info.back().empty()) << "Expected non-empty info response";
+        ASSERT_EQ(info.back().back(), '1')
+                << "Unlock ability must be set to 1 to avoid bricking device, see "
+                   "'https://source.android.com/devices/bootloader/unlock-trusty'";
+    }
+
+    EXPECT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed";
+    ASSERT_TRUE(resp == "no" || resp == "yes")
+            << "getvar:unlocked response was not 'no' or 'yes': " + resp;
+
+    if ((unlock && resp == "no") || (!unlock && resp == "yes")) {
+        std::string cmd = unlock ? "unlock" : "lock";
+        ASSERT_EQ(fb->RawCommand("flashing " + cmd, &resp), SUCCESS)
+                << "Attempting to change locked state, but 'flashing" + cmd + "' command failed";
+        fb.reset();
+        transport.reset();
+        printf("PLEASE RESPOND TO PROMPT FOR '%sing' BOOTLOADER ON DEVICE\n", cmd.c_str());
+        while (UsbStillAvailible())
+            ;  // Wait for disconnect
+        printf("WAITING FOR DEVICE");
+        // Need to wait for device
+        const auto matcher = [](usb_ifc_info* info) -> int { return MatchFastboot(info, nullptr); };
+        while (!transport) {
+            std::unique_ptr<UsbTransport> usb(usb_open(matcher, USB_TIMEOUT));
+            if (usb) {
+                transport = std::unique_ptr<UsbTransportSniffer>(
+                        new UsbTransportSniffer(std::move(usb), serial_port));
+            }
+            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+            putchar('.');
+        }
+        device_path = cb_scratch;
+        fb = std::unique_ptr<FastBootDriver>(
+                new FastBootDriver(transport.get(), [](std::string&) {}, true));
+        if (assert_change) {
+            ASSERT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed";
+            ASSERT_EQ(resp, unlock ? "yes" : "no")
+                    << "getvar:unlocked response was not 'no' or 'yes': " + resp;
+        }
+        printf("SUCCESS\n");
+    }
+}
+
+std::string FastBootTest::device_path = "";
+std::string FastBootTest::cb_scratch = "";
+int FastBootTest::serial_port = 0;
+
+template <bool UNLOCKED>
+void ModeTest<UNLOCKED>::SetUp() {
+    ASSERT_NO_FATAL_FAILURE(FastBootTest::SetUp());
+    ASSERT_NO_FATAL_FAILURE(SetLockState(UNLOCKED));
+}
+// Need to instatiate it, so linker can find it later
+template class ModeTest<true>;
+template class ModeTest<false>;
+
+void Fuzz::TearDown() {
+    ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
+
+    TearDownSerial();
+
+    std::string tmp;
+    if (fb->GetVar("product", &tmp) != SUCCESS) {
+        printf("DEVICE UNRESPONSE, attempting to recover...");
+        transport->Reset();
+        printf("issued USB reset...");
+
+        if (fb->GetVar("product", &tmp) != SUCCESS) {
+            printf("FAIL\n");
+            exit(-1);
+        }
+        printf("SUCCESS!\n");
+    }
+
+    if (transport) {
+        transport.reset();
+    }
+
+    ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
+}
+
+template <bool UNLOCKED>
+void ExtensionsPartition<UNLOCKED>::SetUp() {
+    ASSERT_NO_FATAL_FAILURE(FastBootTest::SetUp());
+    ASSERT_NO_FATAL_FAILURE(SetLockState(UNLOCKED));
+
+    if (!fb) {
+        return;
+    }
+    const std::string name = GetParam().first;
+
+    std::string var;
+    ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "Getting slot count failed";
+    int32_t num_slots = strtol(var.c_str(), nullptr, 10);
+    real_parts = GeneratePartitionNames(name, GetParam().second.slots ? num_slots : 0);
+
+    ASSERT_EQ(fb->GetVar("partition-size:" + real_parts.front(), &var), SUCCESS)
+            << "Getting partition size failed";
+    part_size = strtoll(var.c_str(), nullptr, 16);
+    ASSERT_GT(part_size, 0) << "Partition size reported was invalid";
+
+    ASSERT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "Getting max download size failed";
+    max_dl = strtoll(var.c_str(), nullptr, 16);
+    ASSERT_GT(max_dl, 0) << "Max download size reported was invalid";
+
+    max_flash = std::min(part_size, max_dl);
+}
+template class ExtensionsPartition<true>;
+template class ExtensionsPartition<false>;
+
+}  // end namespace fastboot
diff --git a/fastboot/fuzzy_fastboot/fixtures.h b/fastboot/fuzzy_fastboot/fixtures.h
new file mode 100644
index 0000000..e47d0fd
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/fixtures.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#pragma once
+#include <gtest/gtest.h>
+
+#include "fastboot_driver.h"
+
+#include "extensions.h"
+#include "usb_transport_sniffer.h"
+
+namespace fastboot {
+
+const int USB_TIMEOUT = 30000;
+
+constexpr char USB_PORT_GONE[] =
+        "The USB port has disappeared, this is usually due to the bootloader crashing";
+
+class FastBootTest : public testing::Test {
+  public:
+    static int serial_port;
+    static constexpr int MAX_USB_TRIES = 10;
+
+    static int MatchFastboot(usb_ifc_info* info, const char* local_serial = nullptr);
+    bool UsbStillAvailible();
+
+  protected:
+    RetCode DownloadCommand(uint32_t size, std::string* response = nullptr,
+                            std::vector<std::string>* info = nullptr);
+
+    RetCode SendBuffer(const std::vector<char>& buf);
+    RetCode HandleResponse(std::string* response = nullptr,
+                           std::vector<std::string>* info = nullptr, int* dsize = nullptr);
+
+    void SetUp() override;
+    void TearDown() override;
+    void TearDownSerial();
+    void SetLockState(bool unlock, bool assert_change = true);
+
+    std::unique_ptr<UsbTransportSniffer> transport;
+    std::unique_ptr<FastBootDriver> fb;
+
+  private:
+    // This is an annoying hack
+    static std::string cb_scratch;
+    static std::string device_path;
+};
+
+template <bool UNLOCKED>
+class ModeTest : public FastBootTest {
+  protected:
+    void SetUp() override;
+};
+
+class Fuzz : public ModeTest<true> {
+  protected:
+    void TearDown() override;
+};
+
+// These derived classes without overrides serve no purpose other than to allow gtest to name them
+// differently
+class BasicFunctionality : public ModeTest<true> {};
+class Conformance : public ModeTest<true> {};
+class UnlockPermissions : public ModeTest<true> {};
+class LockPermissions : public ModeTest<false> {};
+
+// Magic C++ double inheritance
+class ExtensionsGetVarConformance
+    : public ModeTest<true>,
+      public ::testing::WithParamInterface<
+              std::pair<std::string, extension::Configuration::GetVar>> {};
+
+class ExtensionsOemConformance
+    : public ModeTest<true>,
+      public ::testing::WithParamInterface<
+              std::tuple<std::string, bool, extension::Configuration::CommandTest>> {};
+
+class ExtensionsPackedValid
+    : public ModeTest<true>,
+      public ::testing::WithParamInterface<
+              std::pair<std::string, extension::Configuration::PackedInfoTest>> {};
+
+class ExtensionsPackedInvalid
+    : public ModeTest<true>,
+      public ::testing::WithParamInterface<
+              std::pair<std::string, extension::Configuration::PackedInfoTest>> {};
+
+template <bool UNLOCKED>
+class ExtensionsPartition
+    : public FastBootTest,
+      public ::testing::WithParamInterface<
+              std::pair<std::string, extension::Configuration::PartitionInfo>> {
+  protected:
+    void SetUp() override;
+    int64_t part_size;
+    int64_t max_flash;
+    int64_t max_dl;
+    std::vector<std::string> real_parts;  // includes the slots
+};
+
+class AnyPartition : public ExtensionsPartition<true> {};
+class WriteablePartition : public ExtensionsPartition<true> {};
+class WriteHashablePartition : public ExtensionsPartition<true> {};
+class WriteHashNonParsedPartition : public ExtensionsPartition<true> {};
+
+class FuzzWriteablePartition : public ExtensionsPartition<true> {};
+class FuzzWriteableParsedPartition : public ExtensionsPartition<true> {};
+class FuzzAnyPartitionLocked : public ExtensionsPartition<false> {};
+
+class UserdataPartition : public ExtensionsPartition<true> {};
+
+class SparseTestPartition : public ExtensionsPartition<true> {};
+
+}  // end namespace fastboot
diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp
new file mode 100644
index 0000000..dd64082
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/main.cpp
@@ -0,0 +1,1691 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <chrono>
+#include <cstdlib>
+#include <fstream>
+#include <map>
+#include <random>
+#include <regex>
+#include <set>
+#include <thread>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <gtest/gtest.h>
+#include <sparse/sparse.h>
+
+#include "fastboot_driver.h"
+#include "usb.h"
+
+#include "extensions.h"
+#include "fixtures.h"
+#include "test_utils.h"
+#include "usb_transport_sniffer.h"
+
+namespace fastboot {
+
+extension::Configuration config;  // The parsed XML config
+
+std::string SEARCH_PATH;
+std::string OUTPUT_PATH;
+
+// gtest's INSTANTIATE_TEST_CASE_P() must be at global scope,
+// so our autogenerated tests must be as well
+std::vector<std::pair<std::string, extension::Configuration::GetVar>> GETVAR_XML_TESTS;
+std::vector<std::tuple<std::string, bool, extension::Configuration::CommandTest>> OEM_XML_TESTS;
+std::vector<std::pair<std::string, extension::Configuration::PartitionInfo>> PARTITION_XML_TESTS;
+std::vector<std::pair<std::string, extension::Configuration::PartitionInfo>>
+        PARTITION_XML_WRITEABLE;
+std::vector<std::pair<std::string, extension::Configuration::PartitionInfo>>
+        PARTITION_XML_WRITE_HASHABLE;
+std::vector<std::pair<std::string, extension::Configuration::PartitionInfo>>
+        PARTITION_XML_WRITE_PARSED;
+std::vector<std::pair<std::string, extension::Configuration::PartitionInfo>>
+        PARTITION_XML_WRITE_HASH_NONPARSED;
+std::vector<std::pair<std::string, extension::Configuration::PartitionInfo>>
+        PARTITION_XML_USERDATA_CHECKSUM_WRITEABLE;
+std::vector<std::pair<std::string, extension::Configuration::PackedInfoTest>>
+        PACKED_XML_SUCCESS_TESTS;
+std::vector<std::pair<std::string, extension::Configuration::PackedInfoTest>> PACKED_XML_FAIL_TESTS;
+// This only has 1 or zero elements so it will disappear from gtest when empty
+std::vector<std::pair<std::string, extension::Configuration::PartitionInfo>>
+        SINGLE_PARTITION_XML_WRITE_HASHABLE;
+
+const std::string DEFAULT_OUPUT_NAME = "out.img";
+// const char scratch_partition[] = "userdata";
+const std::vector<std::string> CMDS{"boot",    "continue", "download:",   "erase:", "flash:",
+                                    "getvar:", "reboot",   "set_active:", "upload"};
+
+// For pretty printing we need all these overloads
+::std::ostream& operator<<(::std::ostream& os, const RetCode& ret) {
+    return os << FastBootDriver::RCString(ret);
+}
+
+bool PartitionHash(FastBootDriver* fb, const std::string& part, std::string* hash, int* retcode,
+                   std::string* err_msg) {
+    if (config.checksum.empty()) {
+        return -1;
+    }
+
+    std::string resp;
+    std::vector<std::string> info;
+    const std::string cmd = config.checksum + ' ' + part;
+    RetCode ret;
+    if ((ret = fb->RawCommand(cmd, &resp, &info)) != SUCCESS) {
+        *err_msg =
+                android::base::StringPrintf("Hashing partition with command '%s' failed with: %s",
+                                            cmd.c_str(), fb->RCString(ret).c_str());
+        return false;
+    }
+    std::stringstream imploded;
+    std::copy(info.begin(), info.end(), std::ostream_iterator<std::string>(imploded, "\n"));
+
+    // If payload, we validate that as well
+    const std::vector<std::string> args = SplitBySpace(config.checksum_parser);
+    std::vector<std::string> prog_args(args.begin() + 1, args.end());
+    prog_args.push_back(resp);                          // Pass in the full command
+    prog_args.push_back(SEARCH_PATH + imploded.str());  // Pass in the save location
+
+    int pipe;
+    pid_t pid = StartProgram(args[0], prog_args, &pipe);
+    if (pid <= 0) {
+        *err_msg = android::base::StringPrintf("Launching hash parser '%s' failed with: %s",
+                                               config.checksum_parser.c_str(), strerror(errno));
+        return false;
+    }
+    *retcode = WaitProgram(pid, pipe, hash);
+    if (*retcode) {
+        // In this case the stderr pipe is a log message
+        *err_msg = android::base::StringPrintf("Hash parser '%s' failed with: %s",
+                                               config.checksum_parser.c_str(), hash->c_str());
+        return false;
+    }
+
+    return true;
+}
+
+bool SparseToBuf(sparse_file* sf, std::vector<char>* out, bool with_crc = false) {
+    int64_t len = sparse_file_len(sf, true, with_crc);
+    if (len <= 0) {
+        return false;
+    }
+    out->clear();
+    auto cb = [](void* priv, const void* data, size_t len) {
+        auto vec = static_cast<std::vector<char>*>(priv);
+        const char* cbuf = static_cast<const char*>(data);
+        vec->insert(vec->end(), cbuf, cbuf + len);
+        return 0;
+    };
+
+    return !sparse_file_callback(sf, true, with_crc, cb, out);
+}
+
+// Only allow alphanumeric, _, -, and .
+const auto not_allowed = [](char c) -> int {
+    return !(isalnum(c) || c == '_' || c == '-' || c == '.');
+};
+
+// Test that USB even works
+TEST(USBFunctionality, USBConnect) {
+    const auto matcher = [](usb_ifc_info* info) -> int {
+        return FastBootTest::MatchFastboot(info, nullptr);
+    };
+    Transport* transport = nullptr;
+    for (int i = 0; i < FastBootTest::MAX_USB_TRIES && !transport; i++) {
+        transport = usb_open(matcher);
+        std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    }
+    ASSERT_NE(transport, nullptr) << "Could not find the fastboot device after: "
+                                  << 10 * FastBootTest::MAX_USB_TRIES << "ms";
+    if (transport) {
+        transport->Close();
+        delete transport;
+    }
+}
+
+// Conformance tests
+TEST_F(Conformance, GetVar) {
+    std::string product;
+    EXPECT_EQ(fb->GetVar("product", &product), SUCCESS) << "getvar:product failed";
+    EXPECT_NE(product, "") << "getvar:product response was empty string";
+    EXPECT_EQ(std::count_if(product.begin(), product.end(), not_allowed), 0)
+            << "getvar:product response contained illegal chars";
+    EXPECT_LE(product.size(), FB_RESPONSE_SZ - 4) << "getvar:product response was too large";
+}
+
+TEST_F(Conformance, GetVarVersionBootloader) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("version-bootloader", &var), SUCCESS)
+            << "getvar:version-bootloader failed";
+    EXPECT_NE(var, "") << "getvar:version-bootloader response was empty string";
+    EXPECT_EQ(std::count_if(var.begin(), var.end(), not_allowed), 0)
+            << "getvar:version-bootloader response contained illegal chars";
+    EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:version-bootloader response was too large";
+}
+
+TEST_F(Conformance, GetVarVersionBaseband) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("version-baseband", &var), SUCCESS) << "getvar:version-baseband failed";
+    EXPECT_NE(var, "") << "getvar:version-baseband response was empty string";
+    EXPECT_EQ(std::count_if(var.begin(), var.end(), not_allowed), 0)
+            << "getvar:version-baseband response contained illegal chars";
+    EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:version-baseband response was too large";
+}
+
+TEST_F(Conformance, GetVarSerialNo) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("serialno", &var), SUCCESS) << "getvar:serialno failed";
+    EXPECT_NE(var, "") << "getvar:serialno can not be empty string";
+    EXPECT_EQ(std::count_if(var.begin(), var.end(), isalnum), var.size())
+            << "getvar:serialno must be alpha-numeric";
+    EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:serialno response is too long";
+}
+
+TEST_F(Conformance, GetVarSecure) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("secure", &var), SUCCESS);
+    EXPECT_TRUE(var == "yes" || var == "no");
+}
+
+TEST_F(Conformance, GetVarOffModeCharge) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("off-mode-charge", &var), SUCCESS) << "getvar:off-mode-charge failed";
+    EXPECT_TRUE(var == "0" || var == "1") << "getvar:off-mode-charge response must be '0' or '1'";
+}
+
+TEST_F(Conformance, GetVarVariant) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("variant", &var), SUCCESS) << "getvar:variant failed";
+    EXPECT_NE(var, "") << "getvar:variant response can not be empty";
+    EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:variant response is too large";
+}
+
+TEST_F(Conformance, GetVarRevision) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("hw-revision", &var), SUCCESS) << "getvar:hw-revision failed";
+    EXPECT_NE(var, "") << "getvar:battery-voltage response was empty";
+    EXPECT_EQ(std::count_if(var.begin(), var.end(), not_allowed), 0)
+            << "getvar:hw-revision contained illegal ASCII chars";
+    EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:hw-revision response was too large";
+}
+
+TEST_F(Conformance, GetVarBattVoltage) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("battery-voltage", &var), SUCCESS) << "getvar:battery-voltage failed";
+    EXPECT_NE(var, "") << "getvar:battery-voltage response was empty";
+    EXPECT_EQ(std::count_if(var.begin(), var.end(), not_allowed), 0)
+            << "getvar:battery-voltage response contains illegal ASCII chars";
+    EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4)
+            << "getvar:battery-voltage response is too large: " + var;
+}
+
+TEST_F(Conformance, GetVarBattVoltageOk) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("battery-soc-ok", &var), SUCCESS) << "getvar:battery-soc-ok failed";
+    EXPECT_TRUE(var == "yes" || var == "no") << "getvar:battery-soc-ok must be 'yes' or 'no'";
+}
+
+TEST_F(Conformance, GetVarDownloadSize) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
+    EXPECT_NE(var, "") << "getvar:max-download-size responded with empty string";
+    // This must start with 0x
+    EXPECT_FALSE(isspace(var.front()))
+            << "getvar:max-download-size responded with a string with leading whitespace";
+    EXPECT_FALSE(var.compare(0, 2, "0x"))
+            << "getvar:max-download-size responded with a string that does not start with 0x...";
+    int64_t size = strtoll(var.c_str(), nullptr, 16);
+    EXPECT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:max-download-size";
+    // At most 32-bits
+    EXPECT_LE(size, std::numeric_limits<uint32_t>::max())
+            << "getvar:max-download-size must fit in a uint32_t";
+    EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4)
+            << "getvar:max-download-size responded with too large of string: " + var;
+}
+
+TEST_F(Conformance, GetVarAll) {
+    std::vector<std::string> vars;
+    EXPECT_EQ(fb->GetVarAll(&vars), SUCCESS) << "getvar:all failed";
+    EXPECT_GT(vars.size(), 0) << "getvar:all did not respond with any INFO responses";
+    for (const auto s : vars) {
+        EXPECT_LE(s.size(), FB_RESPONSE_SZ - 4)
+                << "getvar:all included an INFO response: 'INFO" + s << "' which is too long";
+    }
+}
+
+TEST_F(Conformance, UnlockAbility) {
+    std::string resp;
+    std::vector<std::string> info;
+    EXPECT_EQ(fb->RawCommand("flashing get_unlock_ability", &resp, &info), SUCCESS)
+            << "'flashing get_unlock_ability' failed";
+    // There are two ways this can be reported, through info or the actual response
+    char last;
+    if (!resp.empty()) {  // must be in the response
+        last = resp.back();
+    } else {  // else must be in info
+        ASSERT_FALSE(info.empty()) << "'flashing get_unlock_ability' returned empty response";
+        ASSERT_FALSE(info.back().empty()) << "Expected non-empty info response";
+        last = info.back().back();
+    }
+    ASSERT_TRUE(last == '1' || last == '0') << "Unlock ability must report '0' or '1' in response";
+}
+
+TEST_F(Conformance, PartitionInfo) {
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+    EXPECT_GT(parts.size(), 0)
+            << "getvar:all did not report any partition-size: through INFO responses";
+    std::set<std::string> allowed{"ext4", "f2fs", "raw"};
+    for (const auto p : parts) {
+        EXPECT_GE(std::get<1>(p), 0);
+        std::string part(std::get<0>(p));
+        std::set<std::string> allowed{"ext4", "f2fs", "raw"};
+        std::string resp;
+        EXPECT_EQ(fb->GetVar("partition-type:" + part, &resp), SUCCESS);
+        EXPECT_NE(allowed.find(resp), allowed.end()) << "getvar:partition-type:" + part << " was '"
+                                                     << resp << "' this is not a valid type";
+        const std::string cmd = "partition-size:" + part;
+        EXPECT_EQ(fb->GetVar(cmd, &resp), SUCCESS);
+
+        // This must start with 0x
+        EXPECT_FALSE(isspace(resp.front()))
+                << cmd + " responded with a string with leading whitespace";
+        EXPECT_FALSE(resp.compare(0, 2, "0x"))
+                << cmd + "responded with a string that does not start with 0x...";
+        int64_t size = strtoll(resp.c_str(), nullptr, 16);
+        EXPECT_GT(size, 0) << "'" + resp + "' is not a valid response from " + cmd;
+    }
+}
+
+TEST_F(Conformance, Slots) {
+    std::string var;
+    ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "getvar:slot-count failed";
+    ASSERT_EQ(std::count_if(var.begin(), var.end(), isdigit), var.size())
+            << "'" << var << "' is not all digits which it should be for getvar:slot-count";
+    int32_t num_slots = strtol(var.c_str(), nullptr, 10);
+
+    // Can't run out of alphabet letters...
+    ASSERT_LE(num_slots, 26) << "What?! You can't have more than 26 slots";
+
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+
+    std::map<std::string, std::set<char>> part_slots;
+    if (num_slots > 0) {
+        EXPECT_EQ(fb->GetVar("current-slot", &var), SUCCESS) << "getvar:current-slot failed";
+
+        for (const auto p : parts) {
+            std::string part(std::get<0>(p));
+            std::regex reg("([[:graph:]]*)_([[:lower:]])");
+            std::smatch sm;
+
+            if (std::regex_match(part, sm, reg)) {  // This partition has slots
+                std::string part_base(sm[1]);
+                std::string slot(sm[2]);
+                EXPECT_EQ(fb->GetVar("has-slot:" + part_base, &var), SUCCESS)
+                        << "'getvar:has-slot:" << part_base << "' failed";
+                EXPECT_EQ(var, "yes") << "'getvar:has-slot:" << part_base << "' was not 'yes'";
+                EXPECT_TRUE(islower(slot.front()))
+                        << "'" << slot.front() << "' is an invalid slot-suffix for " << part_base;
+                std::set<char> tmp{slot.front()};
+                part_slots.emplace(part_base, tmp);
+                part_slots.at(part_base).insert(slot.front());
+            } else {
+                EXPECT_EQ(fb->GetVar("has-slot:" + part, &var), SUCCESS)
+                        << "'getvar:has-slot:" << part << "' failed";
+                EXPECT_EQ(var, "no") << "'getvar:has-slot:" << part << "' should be no";
+            }
+        }
+        // Ensure each partition has the correct slot suffix
+        for (const auto iter : part_slots) {
+            const std::set<char>& char_set = iter.second;
+            std::string chars;
+            for (char c : char_set) {
+                chars += c;
+                chars += ',';
+            }
+            EXPECT_EQ(char_set.size(), num_slots)
+                    << "There should only be slot suffixes from a to " << 'a' + num_slots - 1
+                    << " instead encountered: " << chars;
+            for (const char c : char_set) {
+                EXPECT_GE(c, 'a') << "Encountered invalid slot suffix of '" << c << "'";
+                EXPECT_LT(c, 'a' + num_slots) << "Encountered invalid slot suffix of '" << c << "'";
+            }
+        }
+    }
+}
+
+TEST_F(Conformance, SetActive) {
+    std::string var;
+    ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "getvar:slot-count failed";
+    ASSERT_EQ(std::count_if(var.begin(), var.end(), isdigit), var.size())
+            << "'" << var << "' is not all digits which it should be for getvar:slot-count";
+    int32_t num_slots = strtol(var.c_str(), nullptr, 10);
+
+    // Can't run out of alphabet letters...
+    ASSERT_LE(num_slots, 26) << "You can't have more than 26 slots";
+
+    for (char c = 'a'; c < 'a' + num_slots; c++) {
+        const std::string slot(&c, &c + 1);
+        ASSERT_EQ(fb->SetActive(slot), SUCCESS) << "Set active for slot '" << c << "' failed";
+        ASSERT_EQ(fb->GetVar("current-slot", &var), SUCCESS) << "getvar:current-slot failed";
+        EXPECT_EQ(var, slot) << "getvar:current-slot repots incorrect slot after setting it";
+    }
+}
+
+TEST_F(Conformance, LockAndUnlockPrompt) {
+    std::string resp;
+    ASSERT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed";
+    ASSERT_TRUE(resp == "yes" || resp == "no")
+            << "Device did not respond with 'yes' or 'no' for getvar:unlocked";
+    bool curr = resp == "yes";
+
+    for (int i = 0; i < 2; i++) {
+        std::string action = !curr ? "unlock" : "lock";
+        printf("Device should prompt to '%s' bootloader, select 'no'\n", action.c_str());
+        SetLockState(!curr, false);
+        ASSERT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed";
+        ASSERT_EQ(resp, curr ? "yes" : "no") << "The locked/unlocked state of the bootloader "
+                                                "incorrectly changed after selecting no";
+        printf("Device should prompt to '%s' bootloader, select 'yes'\n", action.c_str());
+        SetLockState(!curr, true);
+        ASSERT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed";
+        ASSERT_EQ(resp, !curr ? "yes" : "no") << "The locked/unlocked state of the bootloader "
+                                                 "failed to change after selecting yes";
+        curr = !curr;
+    }
+}
+
+TEST_F(Conformance, SparseBlockSupport0) {
+    // The sparse block size can be any multiple of 4
+    std::string var;
+    EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
+    int64_t size = strtoll(var.c_str(), nullptr, 16);
+
+    // It is reasonable to expect it to handle a single dont care block equal to its DL size
+    for (int64_t bs = 4; bs < size; bs <<= 1) {
+        SparseWrapper sparse(bs, bs);
+        ASSERT_TRUE(*sparse) << "Sparse file creation failed on: " << bs;
+        EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep();
+        EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+        EXPECT_EQ(fb->Download(*sparse, true), SUCCESS)
+                << "Download sparse with crc failed: " << sparse.Rep();
+        EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+    }
+}
+
+TEST_F(Conformance, SparseBlockSupport1) {
+    // The sparse block size can be any multiple of 4
+    std::string var;
+    EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
+    int64_t size = strtoll(var.c_str(), nullptr, 16);
+
+    // handle a packed block to half its max download size block
+    for (int64_t bs = 4; bs < size / 2; bs <<= 1) {
+        SparseWrapper sparse(bs, bs);
+        ASSERT_TRUE(*sparse) << "Sparse file creation failed on: " << bs;
+        std::vector<char> buf = RandomBuf(bs);
+        ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0)
+                << "Adding data failed to sparse file: " << sparse.Rep();
+        EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep();
+        EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+        EXPECT_EQ(fb->Download(*sparse, true), SUCCESS)
+                << "Download sparse with crc failed: " << sparse.Rep();
+        EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+    }
+}
+
+// A single don't care download
+TEST_F(Conformance, SparseDownload0) {
+    SparseWrapper sparse(4096, 4096);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Download(*sparse, true), SUCCESS)
+            << "Download sparse with crc failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+}
+
+TEST_F(Conformance, SparseDownload1) {
+    SparseWrapper sparse(4096, 10 * 4096);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    std::vector<char> buf = RandomBuf(4096);
+    ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 9), 0)
+            << "Adding data failed to sparse file: " << sparse.Rep();
+    EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Download(*sparse, true), SUCCESS)
+            << "Download sparse with crc failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+}
+
+TEST_F(Conformance, SparseDownload2) {
+    SparseWrapper sparse(4096, 4097);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    std::vector<char> buf = RandomBuf(4096);
+    ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0)
+            << "Adding data failed to sparse file: " << sparse.Rep();
+    std::vector<char> buf2 = RandomBuf(1);
+    ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 1), 0)
+            << "Adding data failed to sparse file: " << sparse.Rep();
+    EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Download(*sparse, true), SUCCESS)
+            << "Download sparse with crc failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+}
+
+TEST_F(Conformance, SparseDownload3) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
+    int size = strtoll(var.c_str(), nullptr, 16);
+
+    SparseWrapper sparse(4096, size);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    // Don't want this to take forever
+    unsigned num_chunks = std::min(1000, size / (2 * 4096));
+    for (int i = 0; i < num_chunks; i++) {
+        std::vector<char> buf;
+        int r = random_int(0, 2);
+        // Three cases
+        switch (r) {
+            case 0:
+                break;  // Dont Care chunnk
+            case 1:     // Buffer
+                buf = RandomBuf(4096);
+                ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), i), 0)
+                        << "Adding data failed to sparse file: " << sparse.Rep();
+                break;
+            case 2:  // fill
+                ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, 4096, i), 0)
+                        << "Adding fill to sparse file failed: " << sparse.Rep();
+                break;
+        }
+    }
+    EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Download(*sparse, true), SUCCESS)
+            << "Download sparse with crc failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+}
+
+TEST_F(Conformance, SparseVersionCheck) {
+    SparseWrapper sparse(4096, 4096);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    std::vector<char> buf;
+    ASSERT_TRUE(SparseToBuf(*sparse, &buf)) << "Sparse buffer creation failed";
+    // Invalid, right after magic
+    buf[4] = 0xff;
+    ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command";
+    ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed";
+
+    // It can either reject this download or reject it during flash
+    if (HandleResponse() != DEVICE_FAIL) {
+        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
+                << "Flashing an invalid sparse version should fail " << sparse.Rep();
+    }
+}
+
+TEST_F(Conformance, SparseCRCCheck) {
+    SparseWrapper sparse(4096, 4096);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    std::vector<char> buf = RandomBuf(4096);
+    ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0)
+            << "Adding data failed to sparse file: " << sparse.Rep();
+    ASSERT_TRUE(SparseToBuf(*sparse, &buf, true)) << "Sparse buffer creation failed";
+    // Flip a bit in the crc
+    buf.back() = buf.back() ^ 0x01;
+    ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command";
+    ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed";
+    printf("%02x\n", (unsigned char)buf.back());
+    // It can either reject this download or reject it during flash
+    if (HandleResponse() != DEVICE_FAIL) {
+        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
+                << "Flashing an invalid sparse version should fail " << sparse.Rep();
+    }
+}
+
+TEST_F(UnlockPermissions, Download) {
+    std::vector<char> buf{'a', 'o', 's', 'p'};
+    EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download 4-byte payload failed";
+}
+
+TEST_F(UnlockPermissions, DownloadFlash) {
+    std::vector<char> buf{'a', 'o', 's', 'p'};
+    EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download failed in unlocked mode";
+    ;
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed in unlocked mode";
+}
+
+TEST_F(LockPermissions, DownloadFlash) {
+    std::vector<char> buf{'a', 'o', 's', 'p'};
+    EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download failed in locked mode";
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed in locked mode";
+    std::string resp;
+    for (const auto tup : parts) {
+        EXPECT_EQ(fb->Flash(std::get<0>(tup), &resp), DEVICE_FAIL)
+                << "Device did not respond with FAIL when trying to flash '" << std::get<0>(tup)
+                << "' in locked mode";
+        EXPECT_GT(resp.size(), 0)
+                << "Device sent empty error message after FAIL";  // meaningful error message
+    }
+}
+
+TEST_F(LockPermissions, Erase) {
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+    std::string resp;
+    for (const auto tup : parts) {
+        EXPECT_EQ(fb->Erase(std::get<0>(tup), &resp), DEVICE_FAIL)
+                << "Device did not respond with FAIL when trying to erase '" << std::get<0>(tup)
+                << "' in locked mode";
+        EXPECT_GT(resp.size(), 0) << "Device sent empty error message after FAIL";
+    }
+}
+
+TEST_F(LockPermissions, SetActive) {
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+
+    std::string resp;
+    EXPECT_EQ(fb->GetVar("slot-count", &resp), SUCCESS) << "getvar:slot-count failed";
+    int32_t num_slots = strtol(resp.c_str(), nullptr, 10);
+
+    for (const auto tup : parts) {
+        std::string part(std::get<0>(tup));
+        std::regex reg("([[:graph:]]*)_([[:lower:]])");
+        std::smatch sm;
+
+        if (std::regex_match(part, sm, reg)) {  // This partition has slots
+            std::string part_base(sm[1]);
+            for (char c = 'a'; c < 'a' + num_slots; c++) {
+                // We should not be able to SetActive any of these
+                EXPECT_EQ(fb->SetActive(part_base + '_' + c, &resp), DEVICE_FAIL)
+                        << "set:active:" << part_base + '_' + c << " did not fail in locked mode";
+            }
+        }
+    }
+}
+
+TEST_F(LockPermissions, Boot) {
+    std::vector<char> buf;
+    buf.resize(1000);
+    EXPECT_EQ(fb->Download(buf), SUCCESS) << "A 1000 byte download failed";
+    std::string resp;
+    ASSERT_EQ(fb->Boot(&resp), DEVICE_FAIL)
+            << "The device did not respond with failure for 'boot' when locked";
+    EXPECT_GT(resp.size(), 0) << "No error message was returned by device after FAIL";
+}
+
+TEST_F(Fuzz, DownloadSize) {
+    std::string var;
+    EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
+    int64_t size = strtoll(var.c_str(), nullptr, 0);
+    EXPECT_GT(size, 0) << '\'' << var << "' is not a valid response for getvar:max-download-size";
+
+    EXPECT_EQ(DownloadCommand(size + 1), DEVICE_FAIL)
+            << "Device reported max-download-size as '" << size
+            << "' but did not reject a download of " << size + 1;
+
+    std::vector<char> buf(size);
+    EXPECT_EQ(fb->Download(buf), SUCCESS) << "Device reported max-download-size as '" << size
+                                          << "' but downloading a payload of this size failed";
+    ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
+}
+
+TEST_F(Fuzz, DownloadPartialBuf) {
+    std::vector<char> buf{'a', 'o', 's', 'p'};
+    ASSERT_EQ(DownloadCommand(buf.size() + 1), SUCCESS)
+            << "Download command for " << buf.size() + 1 << " bytes failed";
+
+    std::string resp;
+    RetCode ret = SendBuffer(buf);
+    EXPECT_EQ(ret, SUCCESS) << "Device did not accept partial payload download";
+    // Send the partial buffer, then cancel it with a reset
+    EXPECT_EQ(transport->Reset(), 0) << "USB reset failed";
+
+    ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
+    // The device better still work after all that if we unplug and replug
+    EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS) << "getvar:product failed";
+}
+
+TEST_F(Fuzz, DownloadOverRun) {
+    std::vector<char> buf(1000, 'F');
+    ASSERT_EQ(DownloadCommand(10), SUCCESS) << "Device rejected download request for 10 bytes";
+    // There are two ways to handle this
+    // Accept download, but send error response
+    // Reject the download outright
+    std::string resp;
+    RetCode ret = SendBuffer(buf);
+    if (ret == SUCCESS) {
+        // If it accepts the buffer, it better send back an error response
+        EXPECT_EQ(HandleResponse(&resp), DEVICE_FAIL)
+                << "After sending too large of a payload for a download command, device accepted "
+                   "payload and did not respond with FAIL";
+    } else {
+        EXPECT_EQ(ret, IO_ERROR) << "After sending too large of a payload for a download command, "
+                                    "device did not return error";
+    }
+
+    ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
+    // The device better still work after all that if we unplug and replug
+    EXPECT_EQ(transport->Reset(), 0) << "USB reset failed";
+    EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS)
+            << "Device did not respond with SUCCESS to getvar:product.";
+}
+
+TEST_F(Fuzz, DownloadInvalid1) {
+    EXPECT_EQ(DownloadCommand(0), DEVICE_FAIL)
+            << "Device did not respond with FAIL for malformed download command 'download:0'";
+}
+
+TEST_F(Fuzz, DownloadInvalid2) {
+    std::string cmd("download:1");
+    EXPECT_EQ(fb->RawCommand("download:1"), DEVICE_FAIL)
+            << "Device did not respond with FAIL for malformed download command '" << cmd << "'";
+}
+
+TEST_F(Fuzz, DownloadInvalid3) {
+    std::string cmd("download:-1");
+    EXPECT_EQ(fb->RawCommand("download:-1"), DEVICE_FAIL)
+            << "Device did not respond with FAIL for malformed download command '" << cmd << "'";
+}
+
+TEST_F(Fuzz, DownloadInvalid4) {
+    std::string cmd("download:-01000000");
+    EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL)
+            << "Device did not respond with FAIL for malformed download command '" << cmd << "'";
+}
+
+TEST_F(Fuzz, DownloadInvalid5) {
+    std::string cmd("download:-0100000");
+    EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL)
+            << "Device did not respond with FAIL for malformed download command '" << cmd << "'";
+}
+
+TEST_F(Fuzz, DownloadInvalid6) {
+    std::string cmd("download:");
+    EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL)
+            << "Device did not respond with FAIL for malformed download command '" << cmd << "'";
+}
+
+TEST_F(Fuzz, DownloadInvalid7) {
+    std::string cmd("download:01000000\0999", sizeof("download:01000000\0999"));
+    EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL)
+            << "Device did not respond with FAIL for malformed download command '" << cmd << "'";
+}
+
+TEST_F(Fuzz, DownloadInvalid8) {
+    std::string cmd("download:01000000\0dkjfvijafdaiuybgidabgybr",
+                    sizeof("download:01000000\0dkjfvijafdaiuybgidabgybr"));
+    EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL)
+            << "Device did not respond with FAIL for malformed download command '" << cmd << "'";
+}
+
+TEST_F(Fuzz, GetVarAllSpam) {
+    auto start = std::chrono::high_resolution_clock::now();
+    std::chrono::duration<double> elapsed;
+    unsigned i = 1;
+    do {
+        std::vector<std::string> vars;
+        ASSERT_EQ(fb->GetVarAll(&vars), SUCCESS) << "Device did not respond with success after "
+                                                 << i << "getvar:all commands in a row";
+        ASSERT_GT(vars.size(), 0)
+                << "Device did not send any INFO responses after getvar:all command";
+        elapsed = std::chrono::high_resolution_clock::now() - start;
+    } while (i++, elapsed.count() < 5);
+}
+
+TEST_F(Fuzz, BadCommandTooLarge) {
+    std::string s = RandomString(fastboot::FB_COMMAND_SZ + 1, rand_legal);
+    EXPECT_EQ(fb->RawCommand(s), DEVICE_FAIL)
+            << "Device did not respond with failure after sending length " << s.size()
+            << " string of random ASCII chars";
+    std::string s1 = RandomString(1000, rand_legal);
+    EXPECT_EQ(fb->RawCommand(s1), DEVICE_FAIL)
+            << "Device did not respond with failure after sending length " << s1.size()
+            << " string of random ASCII chars";
+    std::string s2 = RandomString(1000, rand_illegal);
+    EXPECT_EQ(fb->RawCommand(s2), DEVICE_FAIL)
+            << "Device did not respond with failure after sending length " << s1.size()
+            << " string of random non-ASCII chars";
+    std::string s3 = RandomString(1000, rand_char);
+    EXPECT_EQ(fb->RawCommand(s3), DEVICE_FAIL)
+            << "Device did not respond with failure after sending length " << s1.size()
+            << " string of random chars";
+}
+
+TEST_F(Fuzz, CommandTooLarge) {
+    for (const std::string& s : CMDS) {
+        std::string rs = RandomString(1000, rand_char);
+        EXPECT_EQ(fb->RawCommand(s + rs), DEVICE_FAIL)
+                << "Device did not respond with failure after '" << s + rs << "'";
+        ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
+        std::string resp;
+        EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS)
+                << "Device is unresponsive to getvar command";
+    }
+}
+
+TEST_F(Fuzz, CommandMissingArgs) {
+    for (const std::string& s : CMDS) {
+        if (s.back() == ':') {
+            EXPECT_EQ(fb->RawCommand(s), DEVICE_FAIL)
+                    << "Device did not respond with failure after '" << s << "'";
+            std::string sub(s.begin(), s.end() - 1);
+            EXPECT_EQ(fb->RawCommand(sub), DEVICE_FAIL)
+                    << "Device did not respond with failure after '" << sub << "'";
+        } else {
+            std::string rs = RandomString(10, rand_illegal);
+            EXPECT_EQ(fb->RawCommand(rs + s), DEVICE_FAIL)
+                    << "Device did not respond with failure after '" << rs + s << "'";
+        }
+        std::string resp;
+        EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS)
+                << "Device is unresponsive to getvar command";
+    }
+}
+
+TEST_F(Fuzz, SparseZeroLength) {
+    SparseWrapper sparse(4096, 0);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    RetCode ret = fb->Download(*sparse);
+    // Two ways to handle it
+    if (ret != DEVICE_FAIL) {  // if lazily parsed it better fail on a flash
+        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
+                << "Flashing zero length sparse image did not fail: " << sparse.Rep();
+    }
+    ret = fb->Download(*sparse, true);
+    if (ret != DEVICE_FAIL) {  // if lazily parsed it better fail on a flash
+        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
+                << "Flashing zero length sparse image did not fail " << sparse.Rep();
+    }
+}
+
+TEST_F(Fuzz, SparseTooManyChunks) {
+    SparseWrapper sparse(4096, 4096);  // 1 block, but we send two chunks that will use 2 blocks
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    std::vector<char> buf = RandomBuf(4096);
+    ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0)
+            << "Adding data failed to sparse file: " << sparse.Rep();
+    // We take advantage of the fact the sparse library does not check this
+    ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, 4096, 1), 0)
+            << "Adding fill to sparse file failed: " << sparse.Rep();
+
+    RetCode ret = fb->Download(*sparse);
+    // Two ways to handle it
+    if (ret != DEVICE_FAIL) {  // if lazily parsed it better fail on a flash
+        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
+                << "Flashing sparse image with 'total_blks' in header 1 too small did not fail "
+                << sparse.Rep();
+    }
+    ret = fb->Download(*sparse, true);
+    if (ret != DEVICE_FAIL) {  // if lazily parsed it better fail on a flash
+        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
+                << "Flashing sparse image with 'total_blks' in header 1 too small did not fail "
+                << sparse.Rep();
+    }
+}
+
+TEST_F(Fuzz, USBResetSpam) {
+    auto start = std::chrono::high_resolution_clock::now();
+    std::chrono::duration<double> elapsed;
+    int i = 0;
+    do {
+        ASSERT_EQ(transport->Reset(), 0) << "USB Reset failed after " << i << " resets in a row";
+        elapsed = std::chrono::high_resolution_clock::now() - start;
+    } while (i++, elapsed.count() < 5);
+    std::string resp;
+    EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS)
+            << "getvar failed after " << i << " USB reset(s) in a row";
+}
+
+TEST_F(Fuzz, USBResetCommandSpam) {
+    auto start = std::chrono::high_resolution_clock::now();
+    std::chrono::duration<double> elapsed;
+    do {
+        std::string resp;
+        std::vector<std::string> all;
+        ASSERT_EQ(transport->Reset(), 0) << "USB Reset failed";
+        EXPECT_EQ(fb->GetVarAll(&all), SUCCESS) << "getvar:all failed after USB reset";
+        EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS) << "getvar:product failed";
+        elapsed = std::chrono::high_resolution_clock::now() - start;
+    } while (elapsed.count() < 10);
+}
+
+TEST_F(Fuzz, USBResetAfterDownload) {
+    std::vector<char> buf;
+    buf.resize(1000000);
+    EXPECT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Download command failed";
+    EXPECT_EQ(transport->Reset(), 0) << "USB Reset failed";
+    std::vector<std::string> all;
+    EXPECT_EQ(fb->GetVarAll(&all), SUCCESS) << "getvar:all failed after USB reset.";
+}
+
+// Getvar XML tests
+TEST_P(ExtensionsGetVarConformance, VarExists) {
+    std::string resp;
+    EXPECT_EQ(fb->GetVar(GetParam().first, &resp), SUCCESS);
+}
+
+TEST_P(ExtensionsGetVarConformance, VarMatchesRegex) {
+    std::string resp;
+    ASSERT_EQ(fb->GetVar(GetParam().first, &resp), SUCCESS);
+    std::smatch sm;
+    std::regex_match(resp, sm, GetParam().second.regex);
+    EXPECT_FALSE(sm.empty()) << "The regex did not match";
+}
+
+INSTANTIATE_TEST_CASE_P(XMLGetVar, ExtensionsGetVarConformance,
+                        ::testing::ValuesIn(GETVAR_XML_TESTS));
+
+TEST_P(AnyPartition, ReportedGetVarAll) {
+    // As long as the partition is reported in INFO, it would be tested by generic Conformance
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    ASSERT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+    const std::string name = GetParam().first;
+    if (GetParam().second.slots) {
+        auto matcher = [&](const std::tuple<std::string, uint32_t>& tup) {
+            return std::get<0>(tup) == name + "_a";
+        };
+        EXPECT_NE(std::find_if(parts.begin(), parts.end(), matcher), parts.end())
+                << "partition '" + name + "_a' not reported in getvar:all";
+    } else {
+        auto matcher = [&](const std::tuple<std::string, uint32_t>& tup) {
+            return std::get<0>(tup) == name;
+        };
+        EXPECT_NE(std::find_if(parts.begin(), parts.end(), matcher), parts.end())
+                << "partition '" + name + "' not reported in getvar:all";
+    }
+}
+
+TEST_P(AnyPartition, Hashable) {
+    const std::string name = GetParam().first;
+    if (!config.checksum.empty()) {  // We can use hash to validate
+        for (const auto& part_name : real_parts) {
+            // Get hash
+            std::string hash;
+            int retcode;
+            std::string err_msg;
+            if (GetParam().second.hashable) {
+                ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg))
+                        << err_msg;
+                EXPECT_EQ(retcode, 0) << err_msg;
+            } else {  // Make sure it fails
+                const std::string cmd = config.checksum + ' ' + part_name;
+                EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL)
+                        << part_name + " is marked as non-hashable, but hashing did not fail";
+            }
+        }
+    }
+}
+
+TEST_P(WriteablePartition, FlashCheck) {
+    const std::string name = GetParam().first;
+    auto part_info = GetParam().second;
+
+    for (const auto& part_name : real_parts) {
+        std::vector<char> buf = RandomBuf(max_flash, rand_char);
+        EXPECT_EQ(fb->FlashPartition(part_name, buf), part_info.parsed ? DEVICE_FAIL : SUCCESS)
+                << "A partition with an image parsed by the bootloader should reject random "
+                   "garbage "
+                   "otherwise it should succeed";
+    }
+}
+
+TEST_P(WriteablePartition, EraseCheck) {
+    const std::string name = GetParam().first;
+
+    for (const auto& part_name : real_parts) {
+        ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed";
+    }
+}
+
+TEST_P(WriteHashNonParsedPartition, EraseZerosData) {
+    const std::string name = GetParam().first;
+
+    for (const auto& part_name : real_parts) {
+        std::string err_msg;
+        int retcode;
+        const std::vector<char> buf = RandomBuf(max_flash, rand_char);
+        // Partition is too big to write to entire thing
+        // This can eventually be supported by using sparse images if too large
+        if (max_flash < part_size) {
+            std::string hash_before, hash_after;
+            ASSERT_EQ(fb->FlashPartition(part_name, buf), SUCCESS);
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_NE(hash_before, hash_after)
+                    << "The partition hash for " + part_name +
+                               " did not change after erasing a known value";
+        } else {
+            std::string hash_zeros, hash_ones, hash_middle, hash_after;
+            const std::vector<char> buf_zeros(max_flash, 0);
+            const std::vector<char> buf_ones(max_flash, -1);  // All bits are set to 1
+            ASSERT_EQ(fb->FlashPartition(part_name, buf_zeros), SUCCESS);
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_zeros, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            ASSERT_EQ(fb->FlashPartition(part_name, buf_ones), SUCCESS);
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_ones, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            ASSERT_NE(hash_zeros, hash_ones)
+                    << "Hashes of partion should not be the same when all bytes are 0xFF or 0x00";
+            ASSERT_EQ(fb->FlashPartition(part_name, buf), SUCCESS);
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_middle, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            ASSERT_NE(hash_zeros, hash_middle)
+                    << "Hashes of partion are the same when all bytes are 0x00 or test payload";
+            ASSERT_NE(hash_ones, hash_middle)
+                    << "Hashes of partion are the same when all bytes are 0xFF or test payload";
+            ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_TRUE(hash_zeros == hash_after || hash_ones == hash_after)
+                    << "Erasing " + part_name + " should set all the bytes to 0xFF or 0x00";
+        }
+    }
+}
+
+// Only partitions that we can write and hash (name, fixture), TEST_P is (Fixture, test_name)
+INSTANTIATE_TEST_CASE_P(XMLPartitionsWriteHashNonParsed, WriteHashNonParsedPartition,
+                        ::testing::ValuesIn(PARTITION_XML_WRITE_HASH_NONPARSED));
+
+INSTANTIATE_TEST_CASE_P(XMLPartitionsWriteHashable, WriteHashablePartition,
+                        ::testing::ValuesIn(PARTITION_XML_WRITE_HASHABLE));
+
+// only partitions writeable
+INSTANTIATE_TEST_CASE_P(XMLPartitionsWriteable, WriteablePartition,
+                        ::testing::ValuesIn(PARTITION_XML_WRITEABLE));
+
+// Every partition
+INSTANTIATE_TEST_CASE_P(XMLPartitionsAll, AnyPartition, ::testing::ValuesIn(PARTITION_XML_TESTS));
+
+// Partition Fuzz tests
+TEST_P(FuzzWriteablePartition, BoundsCheck) {
+    const std::string name = GetParam().first;
+    auto part_info = GetParam().second;
+
+    for (const auto& part_name : real_parts) {
+        // try and flash +1 too large, first erase and get a hash, make sure it does not change
+        std::vector<char> buf = RandomBuf(max_flash + 1);  // One too large
+        if (part_info.hashable) {
+            std::string hash_before, hash_after, err_msg;
+            int retcode;
+            ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                    << "Flashing an image 1 byte too large to " + part_name + " did not fail";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_EQ(hash_before, hash_after)
+                    << "Flashing too large of an image resulted in a changed partition hash for " +
+                               part_name;
+        } else {
+            EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                    << "Flashing an image 1 byte too large to " + part_name + " did not fail";
+        }
+    }
+}
+
+INSTANTIATE_TEST_CASE_P(XMLFuzzPartitionsWriteable, FuzzWriteablePartition,
+                        ::testing::ValuesIn(PARTITION_XML_WRITEABLE));
+
+// A parsed partition should have magic and such that is checked by the bootloader
+// Attempting to flash a random single byte should definately fail
+TEST_P(FuzzWriteableParsedPartition, FlashGarbageImageSmall) {
+    const std::string name = GetParam().first;
+    auto part_info = GetParam().second;
+
+    for (const auto& part_name : real_parts) {
+        std::vector<char> buf = RandomBuf(1);
+        if (part_info.hashable) {
+            std::string hash_before, hash_after, err_msg;
+            int retcode;
+            ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                    << "A parsed partition should fail on a single byte";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_EQ(hash_before, hash_after)
+                    << "Flashing a single byte to parsed partition  " + part_name +
+                               " should fail and not change the partition hash";
+        } else {
+            EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                    << "Flashing a 1 byte image to a parsed partition should fail";
+        }
+    }
+}
+
+TEST_P(FuzzWriteableParsedPartition, FlashGarbageImageLarge) {
+    const std::string name = GetParam().first;
+    auto part_info = GetParam().second;
+
+    for (const auto& part_name : real_parts) {
+        std::vector<char> buf = RandomBuf(max_flash);
+        if (part_info.hashable) {
+            std::string hash_before, hash_after, err_msg;
+            int retcode;
+            ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                    << "A parsed partition should not accept randomly generated images";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_EQ(hash_before, hash_after)
+                    << "The hash of the partition has changed after attempting to flash garbage to "
+                       "a parsed partition";
+        } else {
+            EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                    << "A parsed partition should not accept randomly generated images";
+        }
+    }
+}
+
+TEST_P(FuzzWriteableParsedPartition, FlashGarbageImageLarge2) {
+    const std::string name = GetParam().first;
+    auto part_info = GetParam().second;
+
+    for (const auto& part_name : real_parts) {
+        std::vector<char> buf(max_flash, -1);  // All 1's
+        if (part_info.hashable) {
+            std::string hash_before, hash_after, err_msg;
+            int retcode;
+            ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                    << "A parsed partition should not accept a image of all 0xFF";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_EQ(hash_before, hash_after)
+                    << "The hash of the partition has changed after attempting to flash garbage to "
+                       "a parsed partition";
+        } else {
+            EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                    << "A parsed partition should not accept a image of all 0xFF";
+        }
+    }
+}
+
+TEST_P(FuzzWriteableParsedPartition, FlashGarbageImageLarge3) {
+    const std::string name = GetParam().first;
+    auto part_info = GetParam().second;
+
+    for (const auto& part_name : real_parts) {
+        std::vector<char> buf(max_flash, 0);  // All 0's
+        if (part_info.hashable) {
+            std::string hash_before, hash_after, err_msg;
+            int retcode;
+            ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                    << "A parsed partition should not accept a image of all 0x00";
+            ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg))
+                    << err_msg;
+            ASSERT_EQ(retcode, 0) << err_msg;
+            EXPECT_EQ(hash_before, hash_after)
+                    << "The hash of the partition has changed after attempting to flash garbage to "
+                       "a parsed partition";
+        } else {
+            EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                    << "A parsed partition should not accept a image of all 0x00";
+        }
+    }
+}
+
+INSTANTIATE_TEST_CASE_P(XMLFuzzPartitionsWriteableParsed, FuzzWriteableParsedPartition,
+                        ::testing::ValuesIn(PARTITION_XML_WRITE_PARSED));
+
+// Make sure all attempts to flash things are rejected
+TEST_P(FuzzAnyPartitionLocked, RejectFlash) {
+    std::vector<char> buf = RandomBuf(5);
+    for (const auto& part_name : real_parts) {
+        ASSERT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL)
+                << "Flashing a partition should always fail in locked mode";
+    }
+}
+
+INSTANTIATE_TEST_CASE_P(XMLFuzzAnyPartitionLocked, FuzzAnyPartitionLocked,
+                        ::testing::ValuesIn(PARTITION_XML_TESTS));
+
+// Test flashing unlock erases userdata
+TEST_P(UserdataPartition, UnlockErases) {
+    // Get hash after an erase
+    int retcode;
+    std::string err_msg, hash_before, hash_buf, hash_after;
+    ASSERT_EQ(fb->Erase("userdata"), SUCCESS) << "Erasing uesrdata failed";
+    ASSERT_TRUE(PartitionHash(fb.get(), "userdata", &hash_before, &retcode, &err_msg)) << err_msg;
+    ASSERT_EQ(retcode, 0) << err_msg;
+
+    // Write garbage
+    std::vector<char> buf = RandomBuf(max_flash / 2);
+    ASSERT_EQ(fb->FlashPartition("userdata", buf), SUCCESS);
+    ASSERT_TRUE(PartitionHash(fb.get(), "userdata", &hash_buf, &retcode, &err_msg)) << err_msg;
+    ASSERT_EQ(retcode, 0) << err_msg;
+
+    // Sanity check of hash
+    EXPECT_NE(hash_before, hash_buf)
+            << "Writing a random buffer to 'userdata' had the same hash as after erasing it";
+    SetLockState(true);  // Lock the device
+
+    SetLockState(false);  // Unlock the device (should cause erase)
+    ASSERT_TRUE(PartitionHash(fb.get(), "userdata", &hash_after, &retcode, &err_msg)) << err_msg;
+    ASSERT_EQ(retcode, 0) << err_msg;
+
+    EXPECT_NE(hash_after, hash_buf) << "Unlocking the device did not cause the hash of userdata to "
+                                       "change (i.e. it was not erased as required)";
+    EXPECT_EQ(hash_after, hash_before) << "Unlocking the device did not produce the same hash of "
+                                          "userdata as after doing an erase to userdata";
+}
+
+// This is a hack to make this test disapeer if there is not a checsum, userdata is not hashable,
+// or userdata is not marked to be writeable in testing
+INSTANTIATE_TEST_CASE_P(XMLUserdataLocked, UserdataPartition,
+                        ::testing::ValuesIn(PARTITION_XML_USERDATA_CHECKSUM_WRITEABLE));
+
+// Packed images test
+TEST_P(ExtensionsPackedValid, TestDeviceUnpack) {
+    const std::string& packed_name = GetParam().first;
+    const std::string& packed_image = GetParam().second.packed_img;
+    const std::string& unpacked = GetParam().second.unpacked_dir;
+
+    // First we need to check for existence of images
+    const extension::Configuration::PackedInfo& info = config.packed[packed_name];
+
+    const auto flash_part = [&](const std::string fname, const std::string part_name) {
+        FILE* to_flash = fopen((SEARCH_PATH + fname).c_str(), "rb");
+        ASSERT_NE(to_flash, nullptr) << "'" << fname << "'"
+                                     << " failed to open for flashing";
+        int fd = fileno(to_flash);
+        size_t fsize = lseek(fd, 0, SEEK_END);
+        ASSERT_GT(fsize, 0) << fname + " appears to be an empty image";
+        ASSERT_EQ(fb->FlashPartition(part_name, fd, fsize), SUCCESS);
+        fclose(to_flash);
+    };
+
+    // We first need to set the slot count
+    std::string var;
+    int num_slots = 1;
+    if (info.slots) {
+        ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "Getting slot count failed";
+        num_slots = strtol(var.c_str(), nullptr, 10);
+    } else {
+        for (const auto& part : info.children) {
+            EXPECT_FALSE(config.partitions[part].slots)
+                    << "A partition can not have slots if the packed image does not";
+        }
+    }
+
+    for (int i = 0; i < num_slots; i++) {
+        std::unordered_map<std::string, std::string> initial_hashes;
+        const std::string packed_suffix =
+                info.slots ? android::base::StringPrintf("_%c", 'a' + i) : "";
+
+        // Flash the paritions manually and get hash
+        for (const auto& part : info.children) {
+            const extension::Configuration::PartitionInfo& part_info = config.partitions[part];
+            const std::string suffix = part_info.slots ? packed_suffix : "";
+            const std::string part_name = part + suffix;
+
+            ASSERT_EQ(fb->Erase(part_name), SUCCESS);
+            const std::string fpath = unpacked + '/' + part + ".img";
+            ASSERT_NO_FATAL_FAILURE(flash_part(fpath, part_name))
+                    << "Failed to flash '" + fpath + "'";
+            // If the partition is hashable we store it
+            if (part_info.hashable) {
+                std::string hash, err_msg;
+                int retcode;
+                ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg))
+                        << err_msg;
+                ASSERT_EQ(retcode, 0) << err_msg;
+                initial_hashes[part] = hash;
+            }
+        }
+
+        // erase once at the end, to avoid false positives if flashing does nothing
+        for (const auto& part : info.children) {
+            const std::string suffix = config.partitions[part].slots ? packed_suffix : "";
+            ASSERT_EQ(fb->Erase(part + suffix), SUCCESS);
+        }
+
+        // Now we flash the packed image and compare our hashes
+        ASSERT_NO_FATAL_FAILURE(flash_part(packed_image, packed_name + packed_suffix));
+
+        for (const auto& part : info.children) {
+            const extension::Configuration::PartitionInfo& part_info = config.partitions[part];
+            // If the partition is hashable we check it
+            if (part_info.hashable) {
+                const std::string suffix = part_info.slots ? packed_suffix : "";
+                const std::string part_name = part + suffix;
+                std::string hash, err_msg;
+                int retcode;
+                ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg))
+                        << err_msg;
+                ASSERT_EQ(retcode, 0) << err_msg;
+                std::string msg =
+                        "The hashes between flashing the packed image and directly flashing '" +
+                        part_name + "' does not match";
+                EXPECT_EQ(hash, initial_hashes[part]) << msg;
+            }
+        }
+    }
+}
+
+INSTANTIATE_TEST_CASE_P(XMLTestPacked, ExtensionsPackedValid,
+                        ::testing::ValuesIn(PACKED_XML_SUCCESS_TESTS));
+
+// Packed images test
+TEST_P(ExtensionsPackedInvalid, TestDeviceUnpack) {
+    const std::string& packed_name = GetParam().first;
+    const std::string& packed_image = GetParam().second.packed_img;
+
+    // First we need to check for existence of images
+    const extension::Configuration::PackedInfo& info = config.packed[packed_name];
+
+    // We first need to set the slot count
+    std::string var;
+    int num_slots = 1;
+    if (info.slots) {
+        ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "Getting slot count failed";
+        num_slots = strtol(var.c_str(), nullptr, 10);
+    } else {
+        for (const auto& part : info.children) {
+            EXPECT_FALSE(config.partitions[part].slots)
+                    << "A partition can not have slots if the packed image does not";
+        }
+    }
+
+    for (int i = 0; i < num_slots; i++) {
+        std::unordered_map<std::string, std::string> initial_hashes;
+        const std::string packed_suffix =
+                info.slots ? android::base::StringPrintf("_%c", 'a' + i) : "";
+
+        // manually and get hash
+        for (const auto& part : info.children) {
+            const extension::Configuration::PartitionInfo& part_info = config.partitions[part];
+            const std::string suffix = part_info.slots ? packed_suffix : "";
+            const std::string part_name = part + suffix;
+
+            // If the partition is hashable we store it
+            if (part_info.hashable) {
+                std::string hash, err_msg;
+                int retcode;
+                ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg))
+                        << err_msg;
+                ASSERT_EQ(retcode, 0) << err_msg;
+                initial_hashes[part] = hash;
+            }
+        }
+
+        // Attempt to flash the invalid file
+        FILE* to_flash = fopen((SEARCH_PATH + packed_image).c_str(), "rb");
+        ASSERT_NE(to_flash, nullptr) << "'" << packed_image << "'"
+                                     << " failed to open for flashing";
+        int fd = fileno(to_flash);
+        size_t fsize = lseek(fd, 0, SEEK_END);
+        ASSERT_GT(fsize, 0) << packed_image + " appears to be an empty image";
+        ASSERT_EQ(fb->FlashPartition(packed_name + packed_suffix, fd, fsize), DEVICE_FAIL)
+                << "Expected flashing to fail for " + packed_image;
+        fclose(to_flash);
+
+        for (const auto& part : info.children) {
+            const extension::Configuration::PartitionInfo& part_info = config.partitions[part];
+            // If the partition is hashable we check it
+            if (part_info.hashable) {
+                const std::string suffix = part_info.slots ? packed_suffix : "";
+                const std::string part_name = part + suffix;
+                std::string hash, err_msg;
+                int retcode;
+                ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg))
+                        << err_msg;
+                ASSERT_EQ(retcode, 0) << err_msg;
+                std::string msg = "Flashing an invalid image changed the hash of '" + part_name;
+                EXPECT_EQ(hash, initial_hashes[part]) << msg;
+            }
+        }
+    }
+}
+
+INSTANTIATE_TEST_CASE_P(XMLTestPacked, ExtensionsPackedInvalid,
+                        ::testing::ValuesIn(PACKED_XML_FAIL_TESTS));
+
+// OEM xml tests
+TEST_P(ExtensionsOemConformance, RunOEMTest) {
+    const std::string& cmd = std::get<0>(GetParam());
+    // bool restricted = std::get<1>(GetParam());
+    const extension::Configuration::CommandTest& test = std::get<2>(GetParam());
+
+    const RetCode expect = (test.expect == extension::FAIL) ? DEVICE_FAIL : SUCCESS;
+
+    // Does the test require staging something?
+    if (!test.input.empty()) {  // Non-empty string
+        FILE* to_stage = fopen((SEARCH_PATH + test.input).c_str(), "rb");
+        ASSERT_NE(to_stage, nullptr) << "'" << test.input << "'"
+                                     << " failed to open for staging";
+        int fd = fileno(to_stage);
+        size_t fsize = lseek(fd, 0, SEEK_END);
+        std::string var;
+        EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS);
+        int64_t size = strtoll(var.c_str(), nullptr, 16);
+        EXPECT_LT(fsize, size) << "'" << test.input << "'"
+                               << " is too large for staging";
+        ASSERT_EQ(fb->Download(fd, fsize), SUCCESS) << "'" << test.input << "'"
+                                                    << " failed to download for staging";
+        fclose(to_stage);
+    }
+    // Run the command
+    int dsize = -1;
+    std::string resp;
+    const std::string full_cmd = "oem " + cmd + " " + test.arg;
+    ASSERT_EQ(fb->RawCommand(full_cmd, &resp, nullptr, &dsize), expect);
+
+    // This is how we test if indeed data response
+    if (test.expect == extension::DATA) {
+        EXPECT_GT(dsize, 0);
+    }
+
+    // Validate response if neccesary
+    if (!test.regex_str.empty()) {
+        std::smatch sm;
+        std::regex_match(resp, sm, test.regex);
+        EXPECT_FALSE(sm.empty()) << "The oem regex did not match";
+    }
+
+    // If payload, we validate that as well
+    const std::vector<std::string> args = SplitBySpace(test.validator);
+    if (args.size()) {
+        // Save output
+        const std::string save_loc =
+                OUTPUT_PATH + (test.output.empty() ? DEFAULT_OUPUT_NAME : test.output);
+        std::string resp;
+        ASSERT_EQ(fb->Upload(save_loc, &resp), SUCCESS)
+                << "Saving output file failed with (" << fb->Error() << ") " << resp;
+        // Build the arguments to the validator
+        std::vector<std::string> prog_args(args.begin() + 1, args.end());
+        prog_args.push_back(full_cmd);  // Pass in the full command
+        prog_args.push_back(save_loc);  // Pass in the save location
+        // Run the validation program
+        int pipe;
+        const pid_t pid = StartProgram(args[0], prog_args, &pipe);
+        ASSERT_GT(pid, 0) << "Failed to launch validation program: " << args[0];
+        std::string error_msg;
+        int ret = WaitProgram(pid, pipe, &error_msg);
+        EXPECT_EQ(ret, 0) << error_msg;  // Program exited correctly
+    }
+}
+
+INSTANTIATE_TEST_CASE_P(XMLOEM, ExtensionsOemConformance, ::testing::ValuesIn(OEM_XML_TESTS));
+
+// Sparse Tests
+TEST_P(SparseTestPartition, SparseSingleBlock) {
+    const std::string name = GetParam().first;
+    auto part_info = GetParam().second;
+    const std::string part_name = name + (part_info.slots ? "_a" : "");
+    SparseWrapper sparse(4096, 4096);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    std::vector<char> buf = RandomBuf(4096);
+    ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0)
+            << "Adding data failed to sparse file: " << sparse.Rep();
+
+    EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+    std::string hash, hash_new, err_msg;
+    int retcode;
+    ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg;
+    ASSERT_EQ(retcode, 0) << err_msg;
+    // Now flash it the non-sparse way
+    EXPECT_EQ(fb->FlashPartition(part_name, buf), SUCCESS) << "Flashing image failed: ";
+    ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_new, &retcode, &err_msg)) << err_msg;
+    ASSERT_EQ(retcode, 0) << err_msg;
+
+    EXPECT_EQ(hash, hash_new) << "Flashing a random buffer of 4096 using sparse and non-sparse "
+                                 "methods did not result in the same hash";
+}
+
+TEST_P(SparseTestPartition, SparseFill) {
+    const std::string name = GetParam().first;
+    auto part_info = GetParam().second;
+    const std::string part_name = name + (part_info.slots ? "_a" : "");
+    int64_t size = (max_dl / 4096) * 4096;
+    SparseWrapper sparse(4096, size);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, size, 0), 0)
+            << "Adding data failed to sparse file: " << sparse.Rep();
+
+    EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+    std::string hash, hash_new, err_msg;
+    int retcode;
+    ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg;
+    ASSERT_EQ(retcode, 0) << err_msg;
+    // Now flash it the non-sparse way
+    std::vector<char> buf(size);
+    for (auto iter = buf.begin(); iter < buf.end(); iter += 4) {
+        iter[0] = 0xef;
+        iter[1] = 0xbe;
+        iter[2] = 0xad;
+        iter[3] = 0xde;
+    }
+    EXPECT_EQ(fb->FlashPartition(part_name, buf), SUCCESS) << "Flashing image failed: ";
+    ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_new, &retcode, &err_msg)) << err_msg;
+    ASSERT_EQ(retcode, 0) << err_msg;
+
+    EXPECT_EQ(hash, hash_new) << "Flashing a random buffer of 4096 using sparse and non-sparse "
+                                 "methods did not result in the same hash";
+}
+
+// This tests to make sure it does not overwrite previous flashes
+TEST_P(SparseTestPartition, SparseMultiple) {
+    const std::string name = GetParam().first;
+    auto part_info = GetParam().second;
+    const std::string part_name = name + (part_info.slots ? "_a" : "");
+    int64_t size = (max_dl / 4096) * 4096;
+    SparseWrapper sparse(4096, size / 2);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, size / 2, 0), 0)
+            << "Adding data failed to sparse file: " << sparse.Rep();
+    EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep();
+    EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse.Rep();
+
+    SparseWrapper sparse2(4096, size / 2);
+    ASSERT_TRUE(*sparse) << "Sparse image creation failed";
+    std::vector<char> buf = RandomBuf(size / 2);
+    ASSERT_EQ(sparse_file_add_data(*sparse2, buf.data(), buf.size(), (size / 2) / 4096), 0)
+            << "Adding data failed to sparse file: " << sparse2.Rep();
+    EXPECT_EQ(fb->Download(*sparse2), SUCCESS) << "Download sparse failed: " << sparse2.Rep();
+    EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse2.Rep();
+
+    std::string hash, hash_new, err_msg;
+    int retcode;
+    ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg;
+    ASSERT_EQ(retcode, 0) << err_msg;
+    // Now flash it the non-sparse way
+    std::vector<char> fbuf(size);
+    for (auto iter = fbuf.begin(); iter < fbuf.begin() + size / 2; iter += 4) {
+        iter[0] = 0xef;
+        iter[1] = 0xbe;
+        iter[2] = 0xad;
+        iter[3] = 0xde;
+    }
+    fbuf.assign(buf.begin(), buf.end());
+    EXPECT_EQ(fb->FlashPartition(part_name, fbuf), SUCCESS) << "Flashing image failed: ";
+    ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_new, &retcode, &err_msg)) << err_msg;
+    ASSERT_EQ(retcode, 0) << err_msg;
+
+    EXPECT_EQ(hash, hash_new) << "Flashing a random buffer of 4096 using sparse and non-sparse "
+                                 "methods did not result in the same hash";
+}
+
+INSTANTIATE_TEST_CASE_P(XMLSparseTest, SparseTestPartition,
+                        ::testing::ValuesIn(SINGLE_PARTITION_XML_WRITE_HASHABLE));
+
+void GenerateXmlTests(const extension::Configuration& config) {
+    // Build the getvar tests
+    for (const auto it : config.getvars) {
+        GETVAR_XML_TESTS.push_back(std::make_pair(it.first, it.second));
+    }
+
+    // Build the partition tests, to interface with gtest we need to do it this way
+    for (const auto it : config.partitions) {
+        const auto tup = std::make_tuple(it.first, it.second);
+        PARTITION_XML_TESTS.push_back(tup);  // All partitions
+
+        if (it.second.test == it.second.YES) {
+            PARTITION_XML_WRITEABLE.push_back(tup);  // All writeable partitions
+
+            if (it.second.hashable) {
+                PARTITION_XML_WRITE_HASHABLE.push_back(tup);  // All write and hashable
+                if (!it.second.parsed) {
+                    PARTITION_XML_WRITE_HASH_NONPARSED.push_back(
+                            tup);  // All write hashed and non-parsed
+                }
+            }
+            if (it.second.parsed) {
+                PARTITION_XML_WRITE_PARSED.push_back(tup);  // All write and parsed
+            }
+        }
+    }
+
+    // Build the packed tests, only useful if we have a hash
+    if (!config.checksum.empty()) {
+        for (const auto it : config.packed) {
+            for (const auto& test : it.second.tests) {
+                const auto tup = std::make_tuple(it.first, test);
+                if (test.expect == extension::OKAY) {  // only testing the success case
+                    PACKED_XML_SUCCESS_TESTS.push_back(tup);
+                } else {
+                    PACKED_XML_FAIL_TESTS.push_back(tup);
+                }
+            }
+        }
+    }
+
+    // This is a hack to make this test disapeer if there is not a checksum, userdata is not
+    // hashable, or userdata is not marked to be writeable in testing
+    const auto part_info = config.partitions.find("userdata");
+    if (!config.checksum.empty() && part_info != config.partitions.end() &&
+        part_info->second.hashable &&
+        part_info->second.test == extension::Configuration::PartitionInfo::YES) {
+        PARTITION_XML_USERDATA_CHECKSUM_WRITEABLE.push_back(
+                std::make_tuple(part_info->first, part_info->second));
+    }
+
+    if (!PARTITION_XML_WRITE_HASHABLE.empty()) {
+        SINGLE_PARTITION_XML_WRITE_HASHABLE.push_back(PARTITION_XML_WRITE_HASHABLE.front());
+    }
+
+    // Build oem tests
+    for (const auto it : config.oem) {
+        auto oem_cmd = it.second;
+        for (const auto& t : oem_cmd.tests) {
+            OEM_XML_TESTS.push_back(std::make_tuple(it.first, oem_cmd.restricted, t));
+        }
+    }
+}
+
+}  // namespace fastboot
+
+int main(int argc, char** argv) {
+    std::string err;
+    // Parse the args
+    const std::unordered_map<std::string, std::string> args = fastboot::ParseArgs(argc, argv, &err);
+    if (!err.empty()) {
+        printf("%s\n", err.c_str());
+        return -1;
+    }
+
+    if (args.find("config") != args.end()) {
+        auto found = args.find("search_path");
+        fastboot::SEARCH_PATH = (found != args.end()) ? found->second + "/" : "";
+        found = args.find("output_path");
+        fastboot::OUTPUT_PATH = (found != args.end()) ? found->second + "/" : "/tmp/";
+        if (!fastboot::extension::ParseXml(fastboot::SEARCH_PATH + args.at("config"),
+                                           &fastboot::config)) {
+            printf("XML config parsing failed\n");
+            return -1;
+        }
+        // To interface with gtest, must set global scope test variables
+        fastboot::GenerateXmlTests(fastboot::config);
+    }
+
+    setbuf(stdout, NULL);  // no buffering
+    printf("<Waiting for Device>\n");
+    const auto matcher = [](usb_ifc_info* info) -> int {
+        return fastboot::FastBootTest::MatchFastboot(info, nullptr);
+    };
+    Transport* transport = nullptr;
+    while (!transport) {
+        transport = usb_open(matcher);
+        std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    }
+    transport->Close();
+
+    if (args.find("serial_port") != args.end()) {
+        fastboot::FastBootTest::serial_port = fastboot::ConfigureSerial(args.at("serial_port"));
+    }
+
+    ::testing::InitGoogleTest(&argc, argv);
+    auto ret = RUN_ALL_TESTS();
+    if (fastboot::FastBootTest::serial_port > 0) {
+        close(fastboot::FastBootTest::serial_port);
+    }
+    return ret;
+}
diff --git a/fastboot/fuzzy_fastboot/test_listeners.h b/fastboot/fuzzy_fastboot/test_listeners.h
new file mode 100644
index 0000000..ae5b0cb
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/test_listeners.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#pragma once
+
+// TODO, print out logs for each failed test with custom listner
+
+class MinimalistPrinter : public ::testing::EmptyTestEventListener {
+    // Called before a test starts.
+    virtual void OnTestStart(const ::testing::TestInfo& test_info) {
+        printf("*** Test %s.%s starting.\n", test_info.test_case_name(), test_info.name());
+    }
+
+    // Called after a failed assertion or a SUCCESS().
+    virtual void OnTestPartResult(const ::testing::TestPartResult& test_part_result) {
+        printf("%s in %s:%d\n%s\n", test_part_result.failed() ? "*** Failure" : "Success",
+               test_part_result.file_name(), test_part_result.line_number(),
+               test_part_result.summary());
+    }
+
+    // Called after a test ends.
+    virtual void OnTestEnd(const ::testing::TestInfo& test_info) {
+        printf("*** Test %s.%s ending.\n", test_info.test_case_name(), test_info.name());
+    }
+};
diff --git a/fastboot/fuzzy_fastboot/test_utils.cpp b/fastboot/fuzzy_fastboot/test_utils.cpp
new file mode 100644
index 0000000..9ad98be
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/test_utils.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "test_utils.h"
+#include <fcntl.h>
+#include <termios.h>
+#include <sstream>
+
+namespace fastboot {
+
+namespace {
+constexpr int rand_seed = 0;
+std::default_random_engine rnd(rand_seed);
+}  // namespace
+
+char rand_legal() {
+    return rnd() % 128;
+}
+
+char rand_illegal() {
+    return rand_legal() + 128;
+}
+
+char rand_char() {
+    return rnd() % 256;
+}
+
+int random_int(int start, int end) {
+    std::uniform_int_distribution<int> uni(start, end);
+    return uni(rnd);
+}
+
+std::string RandomString(size_t length, std::function<char(void)> provider) {
+    std::string str(length, 0);
+    std::generate_n(str.begin(), length, provider);
+    return str;
+}
+
+std::vector<char> RandomBuf(size_t length, std::function<char(void)> provider) {
+    std::vector<char> ret;
+    ret.resize(length);
+    std::generate_n(ret.begin(), length, provider);
+    return ret;
+}
+
+std::vector<std::string> SplitBySpace(const std::string& s) {
+    std::istringstream iss(s);
+    return std::vector<std::string>{std::istream_iterator<std::string>{iss},
+                                    std::istream_iterator<std::string>{}};
+}
+
+std::vector<std::string> GeneratePartitionNames(const std::string& base, int num_slots) {
+    if (!num_slots) {
+        return std::vector<std::string>{base};
+    }
+    std::vector<std::string> ret;
+    for (char c = 'a'; c < 'a' + num_slots; c++) {
+        ret.push_back(base + '_' + c);
+    }
+
+    return ret;
+}
+
+std::unordered_map<std::string, std::string> ParseArgs(int argc, char** argv,
+                                                       std::string* err_msg) {
+    // We ignore any gtest stuff
+    std::unordered_map<std::string, std::string> ret;
+
+    for (int i = 1; i < argc - 1; i++) {
+        std::string arg(argv[i]);
+
+        const std::string gtest_start("--gtest");
+        // We found a non gtest argument
+        if (!arg.find("-h") ||
+            (!arg.find("--") && arg.find("--gtest") && arg.find("=") != arg.npos)) {
+            const std::string start(arg.begin() + 2, arg.begin() + arg.find("="));
+            const std::string end(arg.begin() + arg.find("=") + 1, arg.end());
+            ret[start] = end;
+        } else if (arg.find("--gtest") != 0) {
+            *err_msg = android::base::StringPrintf("Illegal argument '%s'\n", arg.c_str());
+            return ret;
+        }
+    }
+
+    return ret;
+}
+
+int ConfigureSerial(const std::string& port) {
+    int fd = open(port.c_str(), O_RDONLY | O_NOCTTY | O_NONBLOCK);
+
+    if (fd <= 0) {
+        return fd;
+    }
+
+    struct termios tty;
+    tcgetattr(fd, &tty);
+
+    cfsetospeed(&tty, (speed_t)B115200);
+    cfsetispeed(&tty, (speed_t)B115200);
+
+    tty.c_cflag &= ~PARENB;
+    tty.c_cflag &= ~CSTOPB;
+    tty.c_cflag &= ~CSIZE;
+    tty.c_cflag |= CS8;
+
+    tty.c_cflag &= ~CRTSCTS;
+    tty.c_cc[VMIN] = 0;
+    tty.c_cc[VTIME] = 2;
+    tty.c_cflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+
+    cfmakeraw(&tty);
+
+    tcflush(fd, TCIFLUSH);
+    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
+        return -1;
+    }
+
+    return fd;
+}
+
+int StartProgram(const std::string program, const std::vector<std::string> args, int* rpipe) {
+    int link[2];
+    if (pipe(link) < 0) {
+        return -1;
+    }
+
+    pid_t pid = fork();
+    if (pid < 0) {  // error
+        return -1;
+    }
+
+    if (pid) {  // parent
+        close(link[1]);
+        *rpipe = link[0];
+        fcntl(*rpipe, F_SETFL, O_NONBLOCK);  // Non-blocking
+    } else {                                 // child
+        std::vector<const char*> argv(args.size() + 2, nullptr);
+        argv[0] = program.c_str();
+
+        for (int i = 0; i < args.size(); i++) {
+            argv[i + 1] = args[i].c_str();
+        }
+
+        // We pipe any stderr writes to the parent test process
+        dup2(link[1], STDERR_FILENO);  // close stdout and have it now be link[1]
+        // Close duplicates
+        close(link[0]);
+        close(link[1]);
+
+        execvp(program.c_str(), const_cast<char* const*>(argv.data()));
+        fprintf(stderr, "Launching validator process '%s' failed with: %s\n", program.c_str(),
+                strerror(errno));
+        exit(-1);
+    }
+
+    return pid;
+}
+
+int WaitProgram(const int pid, const int pipe, std::string* error_msg) {
+    int status;
+    if (waitpid(pid, &status, 0) != pid) {
+        close(pipe);
+        return -1;
+    }
+    // Read from pipe
+    char buf[1024];
+    int n;
+    while ((n = read(pipe, buf, sizeof(buf))) > 0) {
+        buf[n] = 0; /* terminate the string */
+        error_msg->append(buf, n);
+    }
+    close(pipe);
+
+    if (WIFEXITED(status)) {
+        // This WEXITSTATUS macro masks off lower bytes, with no sign extension
+        // casting it as a signed char fixes the sign extension issue
+        int retmask = WEXITSTATUS(status);
+        return reinterpret_cast<int8_t*>(&retmask)[0];
+    }
+
+    return -1;
+}
+
+}  // namespace fastboot
diff --git a/fastboot/fuzzy_fastboot/test_utils.h b/fastboot/fuzzy_fastboot/test_utils.h
new file mode 100644
index 0000000..0b032e4
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/test_utils.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#pragma once
+
+#include <sparse/sparse.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <cstdlib>
+#include <fstream>
+#include <random>
+#include <string>
+#include <unordered_map>
+#include "fastboot_driver.h"
+
+namespace fastboot {
+
+char rand_legal();
+char rand_illegal();
+char rand_char();
+// start and end are inclusive
+int random_int(int start, int end);
+
+// I don't want to have to manage memory for this guy
+struct SparseWrapper {
+    SparseWrapper(unsigned int block_size, int64_t len) {
+        sparse = sparse_file_new(block_size, len);
+    }
+
+    SparseWrapper(struct sparse_file* sf) { sparse = sf; }
+
+    ~SparseWrapper() {
+        if (sparse) {
+            sparse_file_destroy(sparse);
+        }
+    }
+
+    const std::string Rep() {
+        unsigned bs = sparse_file_block_size(sparse);
+        unsigned len = sparse_file_len(sparse, true, false);
+        return android::base::StringPrintf("[block_size=%u, len=%u]", bs, len);
+    }
+
+    struct sparse_file* operator*() {
+        return sparse;
+    }
+
+    struct sparse_file* sparse;
+};
+
+std::string RandomString(size_t length, std::function<char(void)> provider);
+// RVO will avoid copy
+std::vector<char> RandomBuf(size_t length, std::function<char(void)> provider = rand_char);
+
+std::vector<std::string> SplitBySpace(const std::string& s);
+
+std::unordered_map<std::string, std::string> ParseArgs(int argc, char** argv, std::string* err_msg);
+
+std::vector<std::string> GeneratePartitionNames(const std::string& base, int num_slots = 0);
+
+int ConfigureSerial(const std::string& port);
+
+int StartProgram(const std::string program, const std::vector<std::string> args, int* pipe);
+int WaitProgram(const pid_t pid, const int pipe, std::string* error_msg);
+
+}  // namespace fastboot
diff --git a/fastboot/fuzzy_fastboot/usb_transport_sniffer.cpp b/fastboot/fuzzy_fastboot/usb_transport_sniffer.cpp
new file mode 100644
index 0000000..7c595f4
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/usb_transport_sniffer.cpp
@@ -0,0 +1,193 @@
+#include "usb_transport_sniffer.h"
+#include <android-base/stringprintf.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <iomanip>
+#include <sstream>
+
+namespace fastboot {
+
+UsbTransportSniffer::UsbTransportSniffer(std::unique_ptr<UsbTransport> transport,
+                                         const int serial_fd)
+    : transport_(std::move(transport)), serial_fd_(serial_fd) {}
+
+UsbTransportSniffer::~UsbTransportSniffer() {
+    Close();
+}
+
+ssize_t UsbTransportSniffer::Read(void* data, size_t len) {
+    ProcessSerial();
+
+    ssize_t ret = transport_->Read(data, len);
+    if (ret < 0) {
+        const char* err = strerror(errno);
+        std::vector<char> buf(err, err + strlen(err));
+        Event e(READ_ERROR, std::move(buf));
+        transfers_.push_back(e);
+        return ret;
+    }
+
+    char* cdata = static_cast<char*>(data);
+    std::vector<char> buf(cdata, cdata + ret);
+    Event e(READ, std::move(buf));
+    transfers_.push_back(e);
+
+    ProcessSerial();
+    return ret;
+}
+
+ssize_t UsbTransportSniffer::Write(const void* data, size_t len) {
+    ProcessSerial();
+
+    size_t ret = transport_->Write(data, len);
+    if (ret != len) {
+        const char* err = strerror(errno);
+        std::vector<char> buf(err, err + strlen(err));
+        Event e(WRITE_ERROR, std::move(buf));
+        transfers_.push_back(e);
+        return ret;
+    }
+
+    const char* cdata = static_cast<const char*>(data);
+    std::vector<char> buf(cdata, cdata + len);
+    Event e(WRITE, std::move(buf));
+    transfers_.push_back(e);
+
+    ProcessSerial();
+    return ret;
+}
+
+int UsbTransportSniffer::Close() {
+    return transport_->Close();
+}
+
+int UsbTransportSniffer::Reset() {
+    ProcessSerial();
+    int ret = transport_->Reset();
+    std::vector<char> buf;
+    Event e(RESET, std::move(buf));
+    transfers_.push_back(e);
+    ProcessSerial();
+    return ret;
+}
+
+const std::vector<UsbTransportSniffer::Event> UsbTransportSniffer::Transfers() {
+    return transfers_;
+}
+
+/*
+ * When a test fails, we want a human readable log of everything going on up until
+ * the failure. This method will look through its log of captured events, and
+ * create a clean printable string of everything that happened.
+ */
+std::string UsbTransportSniffer::CreateTrace() {
+    std::string ret;
+
+    const auto no_print = [](char c) -> bool { return !isprint(c); };
+    // This lambda creates a humand readable representation of a byte buffer
+    // It first attempts to figure out whether it should be interpreted as an ASCII buffer,
+    // and be printed as a string, or just a raw byte-buffer
+    const auto msg = [&ret, no_print](const std::vector<char>& buf) {
+        ret += android::base::StringPrintf("(%lu bytes): ", buf.size());
+        std::vector<const char>::iterator iter = buf.end();
+        const unsigned max_chars = 50;
+        if (buf.size() > max_chars) {
+            iter = buf.begin() + max_chars;
+        }
+        ret += '"';
+        if (std::count_if(buf.begin(), iter, no_print) == 0) {  // print as ascii
+            ret.insert(ret.end(), buf.begin(), iter);
+        } else {  // print as hex
+            std::stringstream ss;
+            for (auto c = buf.begin(); c < iter; c++) {
+                ss << std::hex << std::setw(2) << std::setfill('0')
+                   << static_cast<uint16_t>(static_cast<uint8_t>(*c));
+                ss << ',';
+            }
+            ret += ss.str();
+        }
+        if (buf.size() > max_chars) {
+            ret += android::base::StringPrintf("...\"(+%lu bytes)\n", buf.size() - max_chars);
+        } else {
+            ret += "\"\n";
+        }
+    };
+
+    // Now we just scan through the log of everything that happened and create a
+    // printable string for each one
+    for (const auto& event : transfers_) {
+        const std::vector<char>& cbuf = event.buf;
+        const std::string tmp(cbuf.begin(), cbuf.end());
+        auto start = transfers_.front().start;
+        auto durr = event.start - start;
+        auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(durr).count();
+
+        switch (event.type) {
+            case READ:
+                ret += android::base::StringPrintf("[READ %lldms]", millis);
+                msg(cbuf);
+                break;
+
+            case WRITE:
+                ret += android::base::StringPrintf("[WRITE %lldms]", millis);
+                msg(cbuf);
+                break;
+
+            case RESET:
+                ret += android::base::StringPrintf("[RESET %lldms]\n", millis);
+                break;
+
+            case READ_ERROR:
+                ret += android::base::StringPrintf("[READ_ERROR %lldms] %s\n", millis, tmp.c_str());
+                break;
+
+            case WRITE_ERROR:
+                ret += android::base::StringPrintf("[WRITE_ERROR %lldms] %s\n", millis,
+                                                   tmp.c_str());
+                break;
+
+            case SERIAL:
+                ret += android::base::StringPrintf("[SERIAL %lldms] %s", millis, tmp.c_str());
+                if (ret.back() != '\n') ret += '\n';
+                break;
+        }
+    }
+    return ret;
+}
+
+// This is a quick call to flush any UART logs the device might have sent
+// to our internal event log. It will wait up to 10ms for data to appear
+void UsbTransportSniffer::ProcessSerial() {
+    if (serial_fd_ <= 0) return;
+
+    fd_set set;
+    struct timeval timeout;
+
+    FD_ZERO(&set);
+    FD_SET(serial_fd_, &set);
+    timeout.tv_sec = 0;
+    timeout.tv_usec = 10000;  // 10ms
+
+    int count = 0;
+    int n = 0;
+    std::vector<char> buf;
+    buf.resize(1000);
+    while (select(serial_fd_ + 1, &set, NULL, NULL, &timeout) > 0) {
+        n = read(serial_fd_, buf.data() + count, buf.size() - count);
+        if (n > 0) {
+            count += n;
+        } else {
+            break;
+        }
+    }
+
+    buf.resize(count);
+
+    if (count > 0) {
+        Event e(SERIAL, std::move(buf));
+        transfers_.push_back(e);
+    }
+}
+
+}  // namespace fastboot
diff --git a/fastboot/fuzzy_fastboot/usb_transport_sniffer.h b/fastboot/fuzzy_fastboot/usb_transport_sniffer.h
new file mode 100644
index 0000000..89cc009
--- /dev/null
+++ b/fastboot/fuzzy_fastboot/usb_transport_sniffer.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#pragma once
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <chrono>
+#include <cstdlib>
+#include <fstream>
+#include <string>
+#include <vector>
+
+#include "usb.h"
+
+namespace fastboot {
+
+/* A special class for sniffing reads and writes
+ *
+ * A useful debugging tool is to see the raw fastboot transactions going between
+ * the host and device. This class wraps the UsbTransport class, and snoops and saves
+ * all the transactions going on. Additionally, if there is a console serial port
+ * from the device, this class can monitor it as well and capture the interleaving of
+ * transport transactions and UART log messages.
+ */
+class UsbTransportSniffer : public UsbTransport {
+  public:
+    enum EventType {
+        READ,
+        WRITE,
+        RESET,
+        SERIAL,  // Serial log message from device
+        READ_ERROR,
+        WRITE_ERROR,
+    };
+
+    struct Event {
+        Event(EventType t, const std::vector<char> cbuf) : type(t), buf(cbuf) {
+            start = std::chrono::high_resolution_clock::now();
+        };
+        EventType type;
+        std::chrono::high_resolution_clock::time_point start;
+        const std::vector<char> buf;
+    };
+
+    UsbTransportSniffer(std::unique_ptr<UsbTransport> transport, const int serial_fd = 0);
+    ~UsbTransportSniffer() override;
+
+    virtual ssize_t Read(void* data, size_t len) override;
+    virtual ssize_t Write(const void* data, size_t len) override;
+    virtual int Close() override;
+    virtual int Reset() override;
+
+    const std::vector<Event> Transfers();
+    std::string CreateTrace();
+    void ProcessSerial();
+
+  private:
+    std::vector<Event> transfers_;
+    std::unique_ptr<UsbTransport> transport_;
+    const int serial_fd_;
+};
+
+}  // End namespace fastboot
diff --git a/fastboot/usb_linux.cpp b/fastboot/usb_linux.cpp
index 9b779dd..6363aa5 100644
--- a/fastboot/usb_linux.cpp
+++ b/fastboot/usb_linux.cpp
@@ -95,7 +95,7 @@
   public:
     explicit LinuxUsbTransport(std::unique_ptr<usb_handle> handle, uint32_t ms_timeout = 0)
         : handle_(std::move(handle)), ms_timeout_(ms_timeout) {}
-    ~LinuxUsbTransport() override = default;
+    ~LinuxUsbTransport() override;
 
     ssize_t Read(void* data, size_t len) override;
     ssize_t Write(const void* data, size_t len) override;
@@ -387,6 +387,10 @@
     return usb;
 }
 
+LinuxUsbTransport::~LinuxUsbTransport() {
+    Close();
+}
+
 ssize_t LinuxUsbTransport::Write(const void* _data, size_t len)
 {
     unsigned char *data = (unsigned char*) _data;
diff --git a/fastboot/usb_osx.cpp b/fastboot/usb_osx.cpp
index 4d48f6e..ed02c4a 100644
--- a/fastboot/usb_osx.cpp
+++ b/fastboot/usb_osx.cpp
@@ -70,7 +70,7 @@
     // A timeout of 0 is blocking
     OsxUsbTransport(std::unique_ptr<usb_handle> handle, uint32_t ms_timeout = 0)
         : handle_(std::move(handle)), ms_timeout_(ms_timeout) {}
-    ~OsxUsbTransport() override = default;
+    ~OsxUsbTransport() override;
 
     ssize_t Read(void* data, size_t len) override;
     ssize_t Write(const void* data, size_t len) override;
@@ -471,6 +471,10 @@
     return new OsxUsbTransport(std::move(handle), timeout_ms);
 }
 
+OsxUsbTransport::~OsxUsbTransport() {
+    Close();
+}
+
 int OsxUsbTransport::Close() {
     /* TODO: Something better here? */
     return 0;
diff --git a/fastboot/usb_windows.cpp b/fastboot/usb_windows.cpp
index 8c60a71..b00edb3 100644
--- a/fastboot/usb_windows.cpp
+++ b/fastboot/usb_windows.cpp
@@ -69,7 +69,7 @@
 class WindowsUsbTransport : public UsbTransport {
   public:
     WindowsUsbTransport(std::unique_ptr<usb_handle> handle) : handle_(std::move(handle)) {}
-    ~WindowsUsbTransport() override = default;
+    ~WindowsUsbTransport() override;
 
     ssize_t Read(void* data, size_t len) override;
     ssize_t Write(const void* data, size_t len) override;
@@ -250,6 +250,10 @@
     }
 }
 
+WindowsUsbTransport::~WindowsUsbTransport() {
+    Close();
+}
+
 int WindowsUsbTransport::Close() {
     DBG("usb_close\n");
 
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 99f2df8..e3c2c2b 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -79,7 +79,8 @@
 
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
 
-using DeviceMapper = android::dm::DeviceMapper;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
 
 // record fs stat
 enum FsStatFlags {
@@ -790,7 +791,8 @@
         argv.emplace_back(arg.c_str());
     }
     LOG(INFO) << "Calling: " << android::base::Join(argv, ' ');
-    int ret = android_fork_execvp(4, const_cast<char**>(argv.data()), nullptr, false, true);
+    int ret =
+            android_fork_execvp(argv.size(), const_cast<char**>(argv.data()), nullptr, false, true);
     if (ret != 0) {
         LOG(ERROR) << "vdc returned error code: " << ret;
         return false;
@@ -852,7 +854,8 @@
         }
 
         /* Skip mounting the root partition, as it will already have been mounted */
-        if (!strcmp(fstab->recs[i].mount_point, "/")) {
+        if (!strcmp(fstab->recs[i].mount_point, "/") ||
+            !strcmp(fstab->recs[i].mount_point, "/system")) {
             if ((fstab->recs[i].fs_mgr_flags & MS_RDONLY) != 0) {
                 fs_mgr_set_blk_ro(fstab->recs[i].blk_device);
             }
@@ -1400,8 +1403,12 @@
             mount_point = basename(fstab->recs[i].mount_point);
         }
 
-        const char* status = nullptr;
+        if (dm.GetState(mount_point) == DmDeviceState::INVALID) {
+            PERROR << "Could not find verity device for mount point: " << mount_point;
+            continue;
+        }
 
+        const char* status = nullptr;
         std::vector<DeviceMapper::TargetInfo> table;
         if (!dm.GetTableStatus(mount_point, &table) || table.empty() || table[0].data.empty()) {
             if (fstab->recs[i].fs_mgr_flags & MF_VERIFYATBOOT) {
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 78151d5..720dcfd 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -16,6 +16,7 @@
 
 #include <dirent.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <linux/fs.h>
 #include <selinux/selinux.h>
 #include <stdio.h>
@@ -23,7 +24,9 @@
 #include <sys/mount.h>
 #include <sys/param.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 #include <sys/types.h>
+#include <sys/vfs.h>
 #include <unistd.h>
 
 #include <map>
@@ -35,6 +38,8 @@
 #include <android-base/macros.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
 #include <fs_mgr_overlayfs.h>
 #include <fstab/fstab.h>
 
@@ -96,9 +101,26 @@
     return "";
 }
 
+// At less than 1% free space return value of false,
+// means we will try to wrap with overlayfs.
+bool fs_mgr_filesystem_has_space(const char* mount_point) {
+    // If we have access issues to find out space remaining, return true
+    // to prevent us trying to override with overlayfs.
+    struct statvfs vst;
+    if (statvfs(mount_point, &vst)) return true;
+
+    static constexpr int percent = 1;  // 1%
+
+    return (vst.f_bfree >= (vst.f_blocks * percent / 100));
+}
+
 bool fs_mgr_overlayfs_enabled(const struct fstab_rec* fsrec) {
     // readonly filesystem, can not be mount -o remount,rw
-    return "squashfs"s == fsrec->fs_type;
+    // if squashfs or if free space is (near) zero making such a remount
+    // virtually useless, or if there are shared blocks that prevent remount,rw
+    return ("squashfs"s == fsrec->fs_type) ||
+           fs_mgr_has_shared_blocks(fsrec->mount_point, fsrec->blk_device) ||
+           !fs_mgr_filesystem_has_space(fsrec->mount_point);
 }
 
 constexpr char upper_name[] = "upper";
@@ -119,14 +141,13 @@
 constexpr char upperdir_option[] = "upperdir=";
 
 // default options for mount_point, returns empty string for none available.
-std::string fs_mgr_get_overlayfs_options(const char* mount_point) {
-    auto fsrec_mount_point = std::string(mount_point);
-    auto candidate = fs_mgr_get_overlayfs_candidate(fsrec_mount_point);
+std::string fs_mgr_get_overlayfs_options(const std::string& mount_point) {
+    auto candidate = fs_mgr_get_overlayfs_candidate(mount_point);
     if (candidate.empty()) return "";
 
-    auto context = fs_mgr_get_context(fsrec_mount_point);
+    auto context = fs_mgr_get_context(mount_point);
     if (!context.empty()) context = ",rootcontext="s + context;
-    return "override_creds=off,"s + lowerdir_option + fsrec_mount_point + "," + upperdir_option +
+    return "override_creds=off,"s + lowerdir_option + mount_point + "," + upperdir_option +
            candidate + upper_name + ",workdir=" + candidate + work_name + context;
 }
 
@@ -145,10 +166,11 @@
     return true;
 }
 
-std::string fs_mgr_get_overlayfs_options(const fstab* fstab, const char* mount_point) {
-    if (fs_mgr_system_root_image(fstab) && ("/"s == mount_point)) mount_point = "/system";
-
-    return fs_mgr_get_overlayfs_options(mount_point);
+const char* fs_mgr_mount_point(const fstab* fstab, const char* mount_point) {
+    if (!mount_point) return mount_point;
+    if ("/"s != mount_point) return mount_point;
+    if (!fs_mgr_system_root_image(fstab)) return mount_point;
+    return "/system";
 }
 
 // return true if system supports overlayfs
@@ -174,7 +196,8 @@
     if (!fsrec) return false;
 
     auto fsrec_mount_point = fsrec->mount_point;
-    if (!fsrec_mount_point) return false;
+    if (!fsrec_mount_point || !fsrec_mount_point[0]) return false;
+    if (!fsrec->blk_device) return false;
 
     if (!fsrec->fs_type) return false;
 
@@ -242,10 +265,17 @@
     return ret;
 }
 
+constexpr char overlayfs_file_context[] = "u:object_r:overlayfs_file:s0";
+
 bool fs_mgr_overlayfs_setup_one(const std::string& overlay, const std::string& mount_point,
                                 bool* change) {
     auto ret = true;
     auto fsrec_mount_point = overlay + android::base::Basename(mount_point) + "/";
+
+    if (setfscreatecon(overlayfs_file_context)) {
+        ret = false;
+        PERROR << "overlayfs setfscreatecon " << overlayfs_file_context;
+    }
     auto save_errno = errno;
     if (!mkdir(fsrec_mount_point.c_str(), 0755)) {
         if (change) *change = true;
@@ -265,6 +295,7 @@
     } else {
         errno = save_errno;
     }
+    setfscreatecon(nullptr);
 
     auto new_context = fs_mgr_get_context(mount_point);
     if (!new_context.empty() && setfscreatecon(new_context.c_str())) {
@@ -286,15 +317,12 @@
     return ret;
 }
 
-bool fs_mgr_overlayfs_mount(const fstab* fstab, const fstab_rec* fsrec) {
-    if (!fs_mgr_wants_overlayfs(fsrec)) return false;
-    auto fsrec_mount_point = fsrec->mount_point;
-    if (!fsrec_mount_point || !fsrec_mount_point[0]) return false;
-    auto options = fs_mgr_get_overlayfs_options(fstab, fsrec_mount_point);
+bool fs_mgr_overlayfs_mount(const std::string& mount_point) {
+    auto options = fs_mgr_get_overlayfs_options(mount_point);
     if (options.empty()) return false;
 
     // hijack __mount() report format to help triage
-    auto report = "__mount(source=overlay,target="s + fsrec_mount_point + ",type=overlay";
+    auto report = "__mount(source=overlay,target="s + mount_point + ",type=overlay";
     const auto opt_list = android::base::Split(options, ",");
     for (const auto opt : opt_list) {
         if (android::base::StartsWith(opt, upperdir_option)) {
@@ -304,7 +332,7 @@
     }
     report = report + ")=";
 
-    auto ret = mount("overlay", fsrec_mount_point, "overlay", MS_RDONLY | MS_RELATIME,
+    auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_RELATIME,
                      options.c_str());
     if (ret) {
         PERROR << report << ret;
@@ -315,8 +343,7 @@
     }
 }
 
-bool fs_mgr_overlayfs_already_mounted(const char* mount_point) {
-    if (!mount_point) return false;
+bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point) {
     std::unique_ptr<struct fstab, decltype(&fs_mgr_free_fstab)> fstab(
             fs_mgr_read_fstab("/proc/mounts"), fs_mgr_free_fstab);
     if (!fstab) return false;
@@ -328,7 +355,7 @@
         if (("overlay"s != fs_type) && ("overlayfs"s != fs_type)) continue;
         auto fsrec_mount_point = fsrec->mount_point;
         if (!fsrec_mount_point) continue;
-        if (strcmp(fsrec_mount_point, mount_point)) continue;
+        if (mount_point != fsrec_mount_point) continue;
         const auto fs_options = fsrec->fs_options;
         if (!fs_options) continue;
         const auto options = android::base::Split(fs_options, ",");
@@ -341,6 +368,34 @@
     return false;
 }
 
+std::vector<std::string> fs_mgr_candidate_list(const fstab* fstab,
+                                               const char* mount_point = nullptr) {
+    std::vector<std::string> mounts;
+    if (!fstab) return mounts;
+
+    for (auto i = 0; i < fstab->num_entries; i++) {
+        const auto fsrec = &fstab->recs[i];
+        if (!fs_mgr_wants_overlayfs(fsrec)) continue;
+        std::string new_mount_point(fs_mgr_mount_point(fstab, fsrec->mount_point));
+        if (mount_point && (new_mount_point != mount_point)) continue;
+        auto duplicate_or_more_specific = false;
+        for (auto it = mounts.begin(); it != mounts.end();) {
+            if ((*it == new_mount_point) ||
+                (android::base::StartsWith(new_mount_point, *it + "/"))) {
+                duplicate_or_more_specific = true;
+                break;
+            }
+            if (android::base::StartsWith(*it, new_mount_point + "/")) {
+                it = mounts.erase(it);
+            } else {
+                ++it;
+            }
+        }
+        if (!duplicate_or_more_specific) mounts.emplace_back(new_mount_point);
+    }
+    return mounts;
+}
+
 }  // namespace
 
 bool fs_mgr_overlayfs_mount_all() {
@@ -352,13 +407,9 @@
                                                                       fs_mgr_free_fstab);
     if (!fstab) return ret;
 
-    for (auto i = 0; i < fstab->num_entries; i++) {
-        const auto fsrec = &fstab->recs[i];
-        auto fsrec_mount_point = fsrec->mount_point;
-        if (!fsrec_mount_point) continue;
-        if (fs_mgr_overlayfs_already_mounted(fsrec_mount_point)) continue;
-
-        if (fs_mgr_overlayfs_mount(fstab.get(), fsrec)) ret = true;
+    for (const auto& mount_point : fs_mgr_candidate_list(fstab.get())) {
+        if (fs_mgr_overlayfs_already_mounted(mount_point)) continue;
+        if (fs_mgr_overlayfs_mount(mount_point)) ret = true;
     }
     return ret;
 }
@@ -381,22 +432,12 @@
 
     std::unique_ptr<struct fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
                                                                       fs_mgr_free_fstab);
-    std::vector<std::string> mounts;
-    if (fstab) {
-        if (!fs_mgr_get_entry_for_mount_point(fstab.get(), kOverlayMountPoint)) return ret;
-        for (auto i = 0; i < fstab->num_entries; i++) {
-            const auto fsrec = &fstab->recs[i];
-            auto fsrec_mount_point = fsrec->mount_point;
-            if (!fsrec_mount_point) continue;
-            if (mount_point && strcmp(fsrec_mount_point, mount_point)) continue;
-            if (!fs_mgr_wants_overlayfs(fsrec)) continue;
-            mounts.emplace_back(fsrec_mount_point);
-        }
-        if (mounts.empty()) return ret;
-    }
+    if (fstab && !fs_mgr_get_entry_for_mount_point(fstab.get(), kOverlayMountPoint)) return ret;
+    auto mounts = fs_mgr_candidate_list(fstab.get(), fs_mgr_mount_point(fstab.get(), mount_point));
+    if (fstab && mounts.empty()) return ret;
 
-    if (mount_point && ("/"s == mount_point) && fs_mgr_system_root_image(fstab.get())) {
-        mount_point = "/system";
+    if (setfscreatecon(overlayfs_file_context)) {
+        PERROR << "overlayfs setfscreatecon " << overlayfs_file_context;
     }
     auto overlay = kOverlayMountPoint + "/overlay/";
     auto save_errno = errno;
@@ -407,6 +448,7 @@
     } else {
         errno = save_errno;
     }
+    setfscreatecon(nullptr);
     if (!fstab && mount_point && fs_mgr_overlayfs_setup_one(overlay, mount_point, change)) {
         ret = true;
     }
@@ -420,11 +462,10 @@
 // If something is altered, set *change.
 bool fs_mgr_overlayfs_teardown(const char* mount_point, bool* change) {
     if (change) *change = false;
-    if (mount_point && ("/"s == mount_point)) {
-        std::unique_ptr<struct fstab, decltype(&fs_mgr_free_fstab)> fstab(
-                fs_mgr_read_fstab_default(), fs_mgr_free_fstab);
-        if (fs_mgr_system_root_image(fstab.get())) mount_point = "/system";
-    }
+    mount_point = fs_mgr_mount_point(std::unique_ptr<struct fstab, decltype(&fs_mgr_free_fstab)>(
+                                             fs_mgr_read_fstab_default(), fs_mgr_free_fstab)
+                                             .get(),
+                                     mount_point);
     auto ret = true;
     const auto overlay = kOverlayMountPoint + "/overlay";
     const auto oldpath = overlay + (mount_point ?: "");
@@ -477,3 +518,25 @@
 }
 
 #endif  // ALLOW_ADBD_DISABLE_VERITY != 0
+
+bool fs_mgr_has_shared_blocks(const std::string& mount_point, const std::string& dev) {
+    struct statfs fs;
+    if ((statfs((mount_point + "/lost+found").c_str(), &fs) == -1) ||
+        (fs.f_type != EXT4_SUPER_MAGIC)) {
+        return false;
+    }
+
+    android::base::unique_fd fd(open(dev.c_str(), O_RDONLY | O_CLOEXEC));
+    if (fd < 0) return false;
+
+    struct ext4_super_block sb;
+    if ((TEMP_FAILURE_RETRY(lseek64(fd, 1024, SEEK_SET)) < 0) ||
+        (TEMP_FAILURE_RETRY(read(fd, &sb, sizeof(sb))) < 0)) {
+        return false;
+    }
+
+    struct fs_info info;
+    if (ext4_parse_sb(&sb, &info) < 0) return false;
+
+    return (info.feat_ro_compat & EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS) != 0;
+}
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index 1d2ff03..ceb45de 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -18,7 +18,10 @@
 
 #include <fstab/fstab.h>
 
+#include <string>
+
 bool fs_mgr_overlayfs_mount_all();
 bool fs_mgr_overlayfs_setup(const char* backing = nullptr, const char* mount_point = nullptr,
                             bool* change = nullptr);
 bool fs_mgr_overlayfs_teardown(const char* mount_point = nullptr, bool* change = nullptr);
+bool fs_mgr_has_shared_blocks(const std::string& mount_point, const std::string& dev);
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index ad3b6f4..2b526f6 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -20,11 +20,19 @@
 #include <sys/sysmacros.h>
 #include <sys/types.h>
 
+#include <android-base/logging.h>
 #include <android-base/macros.h>
 
 namespace android {
 namespace dm {
 
+DeviceMapper::DeviceMapper() : fd_(-1) {
+    fd_ = TEMP_FAILURE_RETRY(open("/dev/device-mapper", O_RDWR | O_CLOEXEC));
+    if (fd_ < 0) {
+        PLOG(ERROR) << "Failed to open device-mapper";
+    }
+}
+
 DeviceMapper& DeviceMapper::Instance() {
     static DeviceMapper instance;
     return instance;
@@ -90,9 +98,16 @@
     return nullptr;
 }
 
-DmDeviceState DeviceMapper::state(const std::string& /* name */) const {
-    // TODO(b/110035986): Return the state, as read from the kernel instead
-    return DmDeviceState::INVALID;
+DmDeviceState DeviceMapper::GetState(const std::string& name) const {
+    struct dm_ioctl io;
+    InitIo(&io, name);
+    if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) {
+        return DmDeviceState::INVALID;
+    }
+    if ((io.flags & DM_ACTIVE_PRESENT_FLAG) && !(io.flags & DM_SUSPEND_FLAG)) {
+        return DmDeviceState::ACTIVE;
+    }
+    return DmDeviceState::SUSPENDED;
 }
 
 bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table) {
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index e2bc729..91f8bb4 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -29,8 +29,6 @@
 #include <utility>
 #include <vector>
 
-#include <android-base/logging.h>
-
 #include "dm_table.h"
 
 // The minimum expected device mapper major.minor version
@@ -81,7 +79,7 @@
     // Returns the current state of the underlying device mapper device
     // with given name.
     // One of INVALID, SUSPENDED or ACTIVE.
-    DmDeviceState state(const std::string& name) const;
+    DmDeviceState GetState(const std::string& name) const;
 
     // Creates a device, loads the given table, and activates it. If the device
     // is not able to be activated, it is destroyed, and false is returned.
@@ -144,12 +142,7 @@
 
     void InitIo(struct dm_ioctl* io, const std::string& name = std::string()) const;
 
-    DeviceMapper() : fd_(-1) {
-        fd_ = TEMP_FAILURE_RETRY(open("/dev/device-mapper", O_RDWR | O_CLOEXEC));
-        if (fd_ < 0) {
-            PLOG(ERROR) << "Failed to open device-mapper";
-        }
-    }
+    DeviceMapper();
 
     // Creates a device mapper device with given name.
     // Return 'true' on success and 'false' on failure to
diff --git a/fs_mgr/libdm/include/libdm/dm_target.h b/fs_mgr/libdm/include/libdm/dm_target.h
index 31863c8..aab89e5 100644
--- a/fs_mgr/libdm/include/libdm/dm_target.h
+++ b/fs_mgr/libdm/include/libdm/dm_target.h
@@ -23,8 +23,6 @@
 #include <string>
 #include <vector>
 
-#include <android-base/logging.h>
-
 namespace android {
 namespace dm {
 
diff --git a/fs_mgr/liblp/images.cpp b/fs_mgr/liblp/images.cpp
index a361a5d..742b1d0 100644
--- a/fs_mgr/liblp/images.cpp
+++ b/fs_mgr/liblp/images.cpp
@@ -38,12 +38,24 @@
         PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << LP_METADATA_GEOMETRY_SIZE;
         return nullptr;
     }
-    std::unique_ptr<LpMetadata> metadata = ParseMetadata(fd);
-    if (!metadata) {
+    return ParseMetadata(geometry, fd);
+}
+
+std::unique_ptr<LpMetadata> ReadFromImageBlob(const void* data, size_t bytes) {
+    if (bytes < LP_METADATA_GEOMETRY_SIZE) {
+        LERROR << __PRETTY_FUNCTION__ << ": " << bytes << " is smaller than geometry header";
         return nullptr;
     }
-    metadata->geometry = geometry;
-    return metadata;
+
+    LpMetadataGeometry geometry;
+    if (!ParseGeometry(data, &geometry)) {
+        return nullptr;
+    }
+
+    const uint8_t* metadata_buffer =
+            reinterpret_cast<const uint8_t*>(data) + LP_METADATA_GEOMETRY_SIZE;
+    size_t metadata_buffer_size = bytes - LP_METADATA_GEOMETRY_SIZE;
+    return ParseMetadata(geometry, metadata_buffer, metadata_buffer_size);
 }
 
 std::unique_ptr<LpMetadata> ReadFromImageFile(const char* file) {
diff --git a/fs_mgr/liblp/include/liblp/liblp.h b/fs_mgr/liblp/include/liblp/liblp.h
index 627aa8c..6da24f6 100644
--- a/fs_mgr/liblp/include/liblp/liblp.h
+++ b/fs_mgr/liblp/include/liblp/liblp.h
@@ -42,8 +42,7 @@
 // existing geometry, and should not be used for normal partition table
 // updates. False can be returned if the geometry is incompatible with the
 // block device or an I/O error occurs.
-bool FlashPartitionTable(const std::string& block_device, const LpMetadata& metadata,
-                         uint32_t slot_number);
+bool FlashPartitionTable(const std::string& block_device, const LpMetadata& metadata);
 
 // Update the partition table for a given metadata slot number. False is
 // returned if an error occurs, which can include:
@@ -64,6 +63,7 @@
                        const std::map<std::string, std::string>& images);
 bool WriteToImageFile(const char* file, const LpMetadata& metadata);
 std::unique_ptr<LpMetadata> ReadFromImageFile(const char* file);
+std::unique_ptr<LpMetadata> ReadFromImageBlob(const void* data, size_t bytes);
 
 // Helper to extract safe C++ strings from partition info.
 std::string GetPartitionName(const LpMetadataPartition& partition);
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index 638f4b3..329a901 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -103,7 +103,7 @@
     if (!exported) {
         return {};
     }
-    if (!FlashPartitionTable(fd, *exported.get(), 0)) {
+    if (!FlashPartitionTable(fd, *exported.get())) {
         return {};
     }
     return fd;
@@ -132,7 +132,7 @@
     unique_fd fd = CreateFakeDisk();
     ASSERT_GE(fd, 0);
 
-    EXPECT_FALSE(FlashPartitionTable(fd, *exported.get(), 0));
+    EXPECT_FALSE(FlashPartitionTable(fd, *exported.get()));
 }
 
 // Test the basics of flashing a partition and reading it back.
@@ -147,7 +147,7 @@
     // Export and flash.
     unique_ptr<LpMetadata> exported = builder->Export();
     ASSERT_NE(exported, nullptr);
-    ASSERT_TRUE(FlashPartitionTable(fd, *exported.get(), 0));
+    ASSERT_TRUE(FlashPartitionTable(fd, *exported.get()));
 
     // Read back. Note that some fields are only filled in during
     // serialization, so exported and imported will not be identical. For
@@ -354,8 +354,7 @@
     ASSERT_GE(fd, 0);
 
     // Check that we are able to write our table.
-    ASSERT_TRUE(FlashPartitionTable(fd, *exported.get(), 0));
-    ASSERT_TRUE(UpdatePartitionTable(fd, *exported.get(), 1));
+    ASSERT_TRUE(FlashPartitionTable(fd, *exported.get()));
 
     // Check that adding one more partition overflows the metadata allotment.
     partition = builder->AddPartition("final", TEST_GUID, LP_PARTITION_ATTR_NONE);
@@ -395,6 +394,27 @@
     ASSERT_NE(imported, nullptr);
 }
 
+// Test that we can read images from buffers.
+TEST(liblp, ImageFilesInMemory) {
+    unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
+    ASSERT_NE(builder, nullptr);
+    ASSERT_TRUE(AddDefaultPartitions(builder.get()));
+    unique_ptr<LpMetadata> exported = builder->Export();
+
+    unique_fd fd(syscall(__NR_memfd_create, "image_file", 0));
+    ASSERT_GE(fd, 0);
+    ASSERT_TRUE(WriteToImageFile(fd, *exported.get()));
+
+    int64_t offset = SeekFile64(fd, 0, SEEK_CUR);
+    ASSERT_GE(offset, 0);
+    ASSERT_EQ(SeekFile64(fd, 0, SEEK_SET), 0);
+
+    size_t bytes = static_cast<size_t>(offset);
+    std::unique_ptr<char[]> buffer = std::make_unique<char[]>(bytes);
+    ASSERT_TRUE(android::base::ReadFully(fd, buffer.get(), bytes));
+    ASSERT_NE(ReadFromImageBlob(buffer.get(), bytes), nullptr);
+}
+
 class BadWriter {
   public:
     // When requested, write garbage instead of the requested bytes, then
diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp
index 117da59..117f5d5 100644
--- a/fs_mgr/liblp/reader.cpp
+++ b/fs_mgr/liblp/reader.cpp
@@ -30,9 +30,45 @@
 namespace android {
 namespace fs_mgr {
 
-// Parse an LpMetadataGeometry from a buffer. The buffer must be at least
-// LP_METADATA_GEOMETRY_SIZE bytes in size.
-static bool ParseGeometry(const void* buffer, LpMetadataGeometry* geometry) {
+// Helper class for reading descriptors and memory buffers in the same manner.
+class Reader {
+  public:
+    virtual ~Reader(){};
+    virtual bool ReadFully(void* buffer, size_t length) = 0;
+};
+
+class FileReader final : public Reader {
+  public:
+    explicit FileReader(int fd) : fd_(fd) {}
+    bool ReadFully(void* buffer, size_t length) override {
+        return android::base::ReadFully(fd_, buffer, length);
+    }
+
+  private:
+    int fd_;
+};
+
+class MemoryReader final : public Reader {
+  public:
+    MemoryReader(const void* buffer, size_t size)
+        : buffer_(reinterpret_cast<const uint8_t*>(buffer)), size_(size), pos_(0) {}
+    bool ReadFully(void* out, size_t length) override {
+        if (size_ - pos_ < length) {
+            errno = EINVAL;
+            return false;
+        }
+        memcpy(out, buffer_ + pos_, length);
+        pos_ += length;
+        return true;
+    }
+
+  private:
+    const uint8_t* buffer_;
+    size_t size_;
+    size_t pos_;
+};
+
+bool ParseGeometry(const void* buffer, LpMetadataGeometry* geometry) {
     static_assert(sizeof(*geometry) <= LP_METADATA_GEOMETRY_SIZE);
     memcpy(geometry, buffer, sizeof(*geometry));
 
@@ -171,16 +207,18 @@
 
 // Parse and validate all metadata at the current position in the given file
 // descriptor.
-std::unique_ptr<LpMetadata> ParseMetadata(int fd) {
+static std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry,
+                                                 Reader* reader) {
     // First read and validate the header.
     std::unique_ptr<LpMetadata> metadata = std::make_unique<LpMetadata>();
-    if (!android::base::ReadFully(fd, &metadata->header, sizeof(metadata->header))) {
+    if (!reader->ReadFully(&metadata->header, sizeof(metadata->header))) {
         PERROR << __PRETTY_FUNCTION__ << "read " << sizeof(metadata->header) << "bytes failed";
         return nullptr;
     }
     if (!ValidateMetadataHeader(metadata->header)) {
         return nullptr;
     }
+    metadata->geometry = geometry;
 
     LpMetadataHeader& header = metadata->header;
 
@@ -191,7 +229,7 @@
         LERROR << "Out of memory reading logical partition tables.";
         return nullptr;
     }
-    if (!android::base::ReadFully(fd, buffer.get(), header.tables_size)) {
+    if (!reader->ReadFully(buffer.get(), header.tables_size)) {
         PERROR << __PRETTY_FUNCTION__ << "read " << header.tables_size << "bytes failed";
         return nullptr;
     }
@@ -231,10 +269,20 @@
 
         metadata->extents.push_back(extent);
     }
-
     return metadata;
 }
 
+std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry, const void* buffer,
+                                          size_t size) {
+    MemoryReader reader(buffer, size);
+    return ParseMetadata(geometry, &reader);
+}
+
+std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry, int fd) {
+    FileReader reader(fd);
+    return ParseMetadata(geometry, &reader);
+}
+
 std::unique_ptr<LpMetadata> ReadPrimaryMetadata(int fd, const LpMetadataGeometry& geometry,
                                                 uint32_t slot_number) {
     int64_t offset = GetPrimaryMetadataOffset(geometry, slot_number);
@@ -242,7 +290,7 @@
         PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << offset;
         return nullptr;
     }
-    return ParseMetadata(fd);
+    return ParseMetadata(geometry, fd);
 }
 
 std::unique_ptr<LpMetadata> ReadBackupMetadata(int fd, const LpMetadataGeometry& geometry,
@@ -252,7 +300,7 @@
         PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << offset;
         return nullptr;
     }
-    return ParseMetadata(fd);
+    return ParseMetadata(geometry, fd);
 }
 
 std::unique_ptr<LpMetadata> ReadMetadata(int fd, uint32_t slot_number) {
@@ -268,13 +316,10 @@
 
     // Read the priamry copy, and if that fails, try the backup.
     std::unique_ptr<LpMetadata> metadata = ReadPrimaryMetadata(fd, geometry, slot_number);
-    if (!metadata) {
-        metadata = ReadBackupMetadata(fd, geometry, slot_number);
-    }
     if (metadata) {
-        metadata->geometry = geometry;
+        return metadata;
     }
-    return metadata;
+    return ReadBackupMetadata(fd, geometry, slot_number);
 }
 
 std::unique_ptr<LpMetadata> ReadMetadata(const char* block_device, uint32_t slot_number) {
diff --git a/fs_mgr/liblp/reader.h b/fs_mgr/liblp/reader.h
index 843b2f2..9f6ca6e 100644
--- a/fs_mgr/liblp/reader.h
+++ b/fs_mgr/liblp/reader.h
@@ -26,11 +26,16 @@
 namespace android {
 namespace fs_mgr {
 
-std::unique_ptr<LpMetadata> ReadMetadata(int fd, uint32_t slot_number);
+// Parse an LpMetadataGeometry from a buffer. The buffer must be at least
+// LP_METADATA_GEOMETRY_SIZE bytes in size.
+bool ParseGeometry(const void* buffer, LpMetadataGeometry* geometry);
 
 // Helper functions for manually reading geometry and metadata.
+std::unique_ptr<LpMetadata> ReadMetadata(int fd, uint32_t slot_number);
+std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry, int fd);
+std::unique_ptr<LpMetadata> ParseMetadata(const LpMetadataGeometry& geometry, const void* buffer,
+                                          size_t size);
 bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry);
-std::unique_ptr<LpMetadata> ParseMetadata(int fd);
 
 // These functions assume a valid geometry and slot number.
 std::unique_ptr<LpMetadata> ReadPrimaryMetadata(int fd, const LpMetadataGeometry& geometry,
diff --git a/fs_mgr/liblp/writer.cpp b/fs_mgr/liblp/writer.cpp
index fc9d83f..ad84b22 100644
--- a/fs_mgr/liblp/writer.cpp
+++ b/fs_mgr/liblp/writer.cpp
@@ -196,7 +196,7 @@
     return android::base::WriteFully(fd, blob.data(), blob.size());
 }
 
-bool FlashPartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number) {
+bool FlashPartitionTable(int fd, const LpMetadata& metadata) {
     // Before writing geometry and/or logical partition tables, perform some
     // basic checks that the geometry and tables are coherent, and will fit
     // on the given block device.
@@ -224,8 +224,11 @@
         return false;
     }
 
-    // Write metadata to the correct slot, now that geometry is in place.
-    return WriteMetadata(fd, metadata.geometry, slot_number, metadata_blob, DefaultWriter);
+    bool ok = true;
+    for (size_t i = 0; i < metadata.geometry.metadata_slot_count; i++) {
+        ok &= WriteMetadata(fd, metadata.geometry, i, metadata_blob, DefaultWriter);
+    }
+    return ok;
 }
 
 static bool CompareMetadata(const LpMetadata& a, const LpMetadata& b) {
@@ -297,14 +300,13 @@
     return WriteMetadata(fd, geometry, slot_number, blob, writer);
 }
 
-bool FlashPartitionTable(const std::string& block_device, const LpMetadata& metadata,
-                         uint32_t slot_number) {
+bool FlashPartitionTable(const std::string& block_device, const LpMetadata& metadata) {
     android::base::unique_fd fd(open(block_device.c_str(), O_RDWR | O_SYNC));
     if (fd < 0) {
         PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device;
         return false;
     }
-    if (!FlashPartitionTable(fd, metadata, slot_number)) {
+    if (!FlashPartitionTable(fd, metadata)) {
         return false;
     }
     LWARN << "Flashed new logical partition geometry to " << block_device;
diff --git a/fs_mgr/liblp/writer.h b/fs_mgr/liblp/writer.h
index adbbebf..ab18d45 100644
--- a/fs_mgr/liblp/writer.h
+++ b/fs_mgr/liblp/writer.h
@@ -30,7 +30,7 @@
 
 // These variants are for testing only. The path-based functions should be used
 // for actual operation, so that open() is called with the correct flags.
-bool FlashPartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number);
+bool FlashPartitionTable(int fd, const LpMetadata& metadata);
 bool UpdatePartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number);
 
 bool UpdatePartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number,
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index 45a81af..879ba21 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -22,6 +22,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/unique_fd.h>
 #include <libdm/dm.h>
diff --git a/init/Android.bp b/init/Android.bp
index 84a78e2..a2c49d0 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -59,33 +59,24 @@
         },
     },
     static_libs: [
-        "libbootloader_message",
-        "libfs_mgr",
-        "libfec",
-        "libfec_rs",
-        "libhidl-gen-utils",
-        "libsquashfs_utils",
-        "liblogwrap",
-        "libext4_utils",
         "libseccomp_policy",
-        "libcrypto_utils",
-        "libsparse",
         "libprocessgroup",
         "libavb",
-        "libkeyutils",
         "libprotobuf-cpp-lite",
         "libpropertyinfoserializer",
         "libpropertyinfoparser",
     ],
     shared_libs: [
-        "libcutils",
         "libbase",
-        "libc",
-        "liblog",
-        "libcrypto",
-        "libc++",
+        "libbootloader_message",
+        "libcutils",
         "libdl",
-        "libz",
+        "libext4_utils",
+        "libfs_mgr",
+        "libhidl-gen-utils",
+        "libkeyutils",
+        "liblog",
+        "liblogwrap",
         "libselinux",
     ],
 }
@@ -160,6 +151,7 @@
 cc_test {
     name: "init_tests",
     defaults: ["init_defaults"],
+    compile_multilib: "first",
     srcs: [
         "devices_test.cpp",
         "init_test.cpp",
diff --git a/init/Android.mk b/init/Android.mk
index ada87b8..dc400ad 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -47,6 +47,7 @@
     init_first_stage.cpp \
     reboot_utils.cpp \
     selinux.cpp \
+    switch_root.cpp \
     uevent_listener.cpp \
     util.cpp \
 
@@ -54,8 +55,15 @@
 
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
-LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
-LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED)
+LOCAL_MODULE_PATH := $(TARGET_RAMDISK_OUT)
+LOCAL_UNSTRIPPED_PATH := $(TARGET_RAMDISK_OUT_UNSTRIPPED)
+
+# Set up the same mount points on the ramdisk that system-as-root contains.
+LOCAL_POST_INSTALL_CMD := \
+    mkdir -p $(TARGET_RAMDISK_OUT)/dev \
+    mkdir -p $(TARGET_RAMDISK_OUT)/mnt \
+    mkdir -p $(TARGET_RAMDISK_OUT)/proc \
+    mkdir -p $(TARGET_RAMDISK_OUT)/sys \
 
 LOCAL_STATIC_LIBRARIES := \
     libfs_mgr \
diff --git a/init/README.md b/init/README.md
index b0a73b9..b45da21 100644
--- a/init/README.md
+++ b/init/README.md
@@ -506,6 +506,7 @@
   _resource_ is best specified using its text representation ('cpu', 'rtio', etc
   or 'RLIM_CPU', 'RLIM_RTIO', etc). It also may be specified as the int value
   that the resource enum corresponds to.
+  _cur_ and _max_ can be 'unlimited' or '-1' to indicate an infinite rlimit.
 
 `start <service>`
 > Start a service running if it is not already running.
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 0c5cf76..fa3392e 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -29,19 +29,22 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/strings.h>
+#include <fs_mgr.h>
+#include <fs_mgr_avb.h>
+#include <fs_mgr_dm_linear.h>
+#include <fs_mgr_overlayfs.h>
 #include <liblp/metadata_format.h>
 
 #include "devices.h"
-#include "fs_mgr.h"
-#include "fs_mgr_avb.h"
-#include "fs_mgr_dm_linear.h"
-#include "fs_mgr_overlayfs.h"
+#include "switch_root.h"
 #include "uevent.h"
 #include "uevent_listener.h"
 #include "util.h"
 
 using android::base::Timer;
 
+using namespace std::literals;
+
 namespace android {
 namespace init {
 
@@ -63,6 +66,7 @@
     bool InitRequiredDevices();
     bool InitMappedDevice(const std::string& verity_device);
     bool CreateLogicalPartitions();
+    bool MountPartition(fstab_rec* fstab_rec);
     bool MountPartitions();
     bool GetBackingDmLinearDevices();
 
@@ -333,26 +337,51 @@
     return true;
 }
 
-bool FirstStageMount::MountPartitions() {
-    for (auto fstab_rec : mount_fstab_recs_) {
-        if (fs_mgr_is_logical(fstab_rec)) {
-            if (!fs_mgr_update_logical_partition(fstab_rec)) {
-                return false;
-            }
-            if (!InitMappedDevice(fstab_rec->blk_device)) {
-                return false;
-            }
-        }
-        if (!SetUpDmVerity(fstab_rec)) {
-            PLOG(ERROR) << "Failed to setup verity for '" << fstab_rec->mount_point << "'";
+bool FirstStageMount::MountPartition(fstab_rec* fstab_rec) {
+    if (fs_mgr_is_logical(fstab_rec)) {
+        if (!fs_mgr_update_logical_partition(fstab_rec)) {
             return false;
         }
-        if (fs_mgr_do_mount_one(fstab_rec)) {
-            PLOG(ERROR) << "Failed to mount '" << fstab_rec->mount_point << "'";
+        if (!InitMappedDevice(fstab_rec->blk_device)) {
             return false;
         }
     }
+    if (!SetUpDmVerity(fstab_rec)) {
+        PLOG(ERROR) << "Failed to setup verity for '" << fstab_rec->mount_point << "'";
+        return false;
+    }
+    if (fs_mgr_do_mount_one(fstab_rec)) {
+        PLOG(ERROR) << "Failed to mount '" << fstab_rec->mount_point << "'";
+        return false;
+    }
+    return true;
+}
+
+bool FirstStageMount::MountPartitions() {
+    // If system is in the fstab then we're not a system-as-root device, and in
+    // this case, we mount system first then pivot to it.  From that point on,
+    // we are effectively identical to a system-as-root device.
+    auto system_partition =
+            std::find_if(mount_fstab_recs_.begin(), mount_fstab_recs_.end(),
+                         [](const auto& rec) { return rec->mount_point == "/system"s; });
+    if (system_partition != mount_fstab_recs_.end()) {
+        if (!MountPartition(*system_partition)) {
+            return false;
+        }
+
+        SwitchRoot("/system");
+
+        mount_fstab_recs_.erase(system_partition);
+    }
+
+    for (auto fstab_rec : mount_fstab_recs_) {
+        if (!MountPartition(fstab_rec) && !fs_mgr_is_nofail(fstab_rec)) {
+            return false;
+        }
+    }
+
     fs_mgr_overlayfs_mount_all();
+
     return true;
 }
 
diff --git a/init/keychords.cpp b/init/keychords.cpp
index 1af06dd..b8c1cfd 100644
--- a/init/keychords.cpp
+++ b/init/keychords.cpp
@@ -192,7 +192,7 @@
 
 void Keychords::GeteventOpenDevice(const std::string& device) {
     if (registration_.count(device)) return;
-    auto fd = TEMP_FAILURE_RETRY(::open(device.c_str(), O_RDWR | O_CLOEXEC));
+    auto fd = TEMP_FAILURE_RETRY(::open(device.c_str(), O_RDONLY | O_CLOEXEC));
     if (fd == -1) {
         PLOG(ERROR) << "Can not open " << device;
         return;
diff --git a/init/property_type.cpp b/init/property_type.cpp
index 249b12b..7d80555 100644
--- a/init/property_type.cpp
+++ b/init/property_type.cpp
@@ -29,6 +29,11 @@
 namespace init {
 
 bool CheckType(const std::string& type_string, const std::string& value) {
+    // Always allow clearing a property such that the default value when it is not set takes over.
+    if (value.empty()) {
+        return true;
+    }
+
     auto type_strings = Split(type_string, " ");
     if (type_strings.empty()) {
         return false;
diff --git a/init/property_type_test.cpp b/init/property_type_test.cpp
index 068bccc..6d7f927 100644
--- a/init/property_type_test.cpp
+++ b/init/property_type_test.cpp
@@ -32,7 +32,7 @@
 }
 
 TEST(property_type, CheckType_int) {
-    EXPECT_FALSE(CheckType("int", ""));
+    EXPECT_TRUE(CheckType("int", ""));
     EXPECT_FALSE(CheckType("int", "abc"));
     EXPECT_FALSE(CheckType("int", "-abc"));
     EXPECT_TRUE(CheckType("int", "0"));
@@ -43,7 +43,7 @@
 }
 
 TEST(property_type, CheckType_uint) {
-    EXPECT_FALSE(CheckType("uint", ""));
+    EXPECT_TRUE(CheckType("uint", ""));
     EXPECT_FALSE(CheckType("uint", "abc"));
     EXPECT_FALSE(CheckType("uint", "-abc"));
     EXPECT_TRUE(CheckType("uint", "0"));
@@ -53,7 +53,7 @@
 }
 
 TEST(property_type, CheckType_double) {
-    EXPECT_FALSE(CheckType("double", ""));
+    EXPECT_TRUE(CheckType("double", ""));
     EXPECT_FALSE(CheckType("double", "abc"));
     EXPECT_FALSE(CheckType("double", "-abc"));
     EXPECT_TRUE(CheckType("double", "0.0"));
@@ -64,7 +64,7 @@
 }
 
 TEST(property_type, CheckType_size) {
-    EXPECT_FALSE(CheckType("size", ""));
+    EXPECT_TRUE(CheckType("size", ""));
     EXPECT_FALSE(CheckType("size", "ab"));
     EXPECT_FALSE(CheckType("size", "abcd"));
     EXPECT_FALSE(CheckType("size", "0"));
@@ -80,7 +80,7 @@
 }
 
 TEST(property_type, CheckType_enum) {
-    EXPECT_FALSE(CheckType("enum abc", ""));
+    EXPECT_TRUE(CheckType("enum abc", ""));
     EXPECT_FALSE(CheckType("enum abc", "ab"));
     EXPECT_FALSE(CheckType("enum abc", "abcd"));
     EXPECT_FALSE(CheckType("enum 123 456 789", "0"));
diff --git a/init/rlimit_parser.cpp b/init/rlimit_parser.cpp
index fe1d6a7..1e0754a 100644
--- a/init/rlimit_parser.cpp
+++ b/init/rlimit_parser.cpp
@@ -65,12 +65,18 @@
     }
 
     rlimit limit;
-    if (!ParseUint(args[2], &limit.rlim_cur)) {
+    if (args[2] == "-1" || args[2] == "unlimited") {
+        limit.rlim_cur = RLIM_INFINITY;
+    } else if (!ParseUint(args[2], &limit.rlim_cur)) {
         return Error() << "Could not parse soft limit '" << args[2] << "'";
     }
-    if (!ParseUint(args[3], &limit.rlim_max)) {
+
+    if (args[3] == "-1" || args[3] == "unlimited") {
+        limit.rlim_max = RLIM_INFINITY;
+    } else if (!ParseUint(args[3], &limit.rlim_max)) {
         return Error() << "Could not parse hard limit '" << args[3] << "'";
     }
+
     return {resource, limit};
 }
 
diff --git a/init/rlimit_parser_test.cpp b/init/rlimit_parser_test.cpp
index f3f9eb4..659ba8a 100644
--- a/init/rlimit_parser_test.cpp
+++ b/init/rlimit_parser_test.cpp
@@ -49,58 +49,58 @@
 
 TEST(rlimit, RlimitSuccess) {
     const std::vector<std::pair<std::vector<std::string>, std::pair<int, rlimit>>>
-        inputs_and_results = {
-            {{"cpu", "10", "10"}, {0, {10, 10}}},
-            {{"fsize", "10", "10"}, {1, {10, 10}}},
-            {{"data", "10", "10"}, {2, {10, 10}}},
-            {{"stack", "10", "10"}, {3, {10, 10}}},
-            {{"core", "10", "10"}, {4, {10, 10}}},
-            {{"rss", "10", "10"}, {5, {10, 10}}},
-            {{"nproc", "10", "10"}, {6, {10, 10}}},
-            {{"nofile", "10", "10"}, {7, {10, 10}}},
-            {{"memlock", "10", "10"}, {8, {10, 10}}},
-            {{"as", "10", "10"}, {9, {10, 10}}},
-            {{"locks", "10", "10"}, {10, {10, 10}}},
-            {{"sigpending", "10", "10"}, {11, {10, 10}}},
-            {{"msgqueue", "10", "10"}, {12, {10, 10}}},
-            {{"nice", "10", "10"}, {13, {10, 10}}},
-            {{"rtprio", "10", "10"}, {14, {10, 10}}},
-            {{"rttime", "10", "10"}, {15, {10, 10}}},
+            inputs_and_results = {
+                    {{"cpu", "10", "10"}, {0, {10, 10}}},
+                    {{"fsize", "10", "10"}, {1, {10, 10}}},
+                    {{"data", "10", "10"}, {2, {10, 10}}},
+                    {{"stack", "10", "10"}, {3, {10, 10}}},
+                    {{"core", "10", "10"}, {4, {10, 10}}},
+                    {{"rss", "10", "10"}, {5, {10, 10}}},
+                    {{"nproc", "10", "10"}, {6, {10, 10}}},
+                    {{"nofile", "10", "10"}, {7, {10, 10}}},
+                    {{"memlock", "10", "10"}, {8, {10, 10}}},
+                    {{"as", "10", "10"}, {9, {10, 10}}},
+                    {{"locks", "10", "10"}, {10, {10, 10}}},
+                    {{"sigpending", "10", "10"}, {11, {10, 10}}},
+                    {{"msgqueue", "10", "10"}, {12, {10, 10}}},
+                    {{"nice", "10", "10"}, {13, {10, 10}}},
+                    {{"rtprio", "10", "10"}, {14, {10, 10}}},
+                    {{"rttime", "10", "10"}, {15, {10, 10}}},
 
-            {{"RLIM_CPU", "10", "10"}, {0, {10, 10}}},
-            {{"RLIM_FSIZE", "10", "10"}, {1, {10, 10}}},
-            {{"RLIM_DATA", "10", "10"}, {2, {10, 10}}},
-            {{"RLIM_STACK", "10", "10"}, {3, {10, 10}}},
-            {{"RLIM_CORE", "10", "10"}, {4, {10, 10}}},
-            {{"RLIM_RSS", "10", "10"}, {5, {10, 10}}},
-            {{"RLIM_NPROC", "10", "10"}, {6, {10, 10}}},
-            {{"RLIM_NOFILE", "10", "10"}, {7, {10, 10}}},
-            {{"RLIM_MEMLOCK", "10", "10"}, {8, {10, 10}}},
-            {{"RLIM_AS", "10", "10"}, {9, {10, 10}}},
-            {{"RLIM_LOCKS", "10", "10"}, {10, {10, 10}}},
-            {{"RLIM_SIGPENDING", "10", "10"}, {11, {10, 10}}},
-            {{"RLIM_MSGQUEUE", "10", "10"}, {12, {10, 10}}},
-            {{"RLIM_NICE", "10", "10"}, {13, {10, 10}}},
-            {{"RLIM_RTPRIO", "10", "10"}, {14, {10, 10}}},
-            {{"RLIM_RTTIME", "10", "10"}, {15, {10, 10}}},
+                    {{"RLIM_CPU", "10", "10"}, {0, {10, 10}}},
+                    {{"RLIM_FSIZE", "10", "10"}, {1, {10, 10}}},
+                    {{"RLIM_DATA", "10", "10"}, {2, {10, 10}}},
+                    {{"RLIM_STACK", "10", "10"}, {3, {10, 10}}},
+                    {{"RLIM_CORE", "10", "10"}, {4, {10, 10}}},
+                    {{"RLIM_RSS", "10", "10"}, {5, {10, 10}}},
+                    {{"RLIM_NPROC", "10", "10"}, {6, {10, 10}}},
+                    {{"RLIM_NOFILE", "10", "10"}, {7, {10, 10}}},
+                    {{"RLIM_MEMLOCK", "10", "10"}, {8, {10, 10}}},
+                    {{"RLIM_AS", "10", "10"}, {9, {10, 10}}},
+                    {{"RLIM_LOCKS", "10", "10"}, {10, {10, 10}}},
+                    {{"RLIM_SIGPENDING", "10", "10"}, {11, {10, 10}}},
+                    {{"RLIM_MSGQUEUE", "10", "10"}, {12, {10, 10}}},
+                    {{"RLIM_NICE", "10", "10"}, {13, {10, 10}}},
+                    {{"RLIM_RTPRIO", "10", "10"}, {14, {10, 10}}},
+                    {{"RLIM_RTTIME", "10", "10"}, {15, {10, 10}}},
 
-            {{"0", "10", "10"}, {0, {10, 10}}},
-            {{"1", "10", "10"}, {1, {10, 10}}},
-            {{"2", "10", "10"}, {2, {10, 10}}},
-            {{"3", "10", "10"}, {3, {10, 10}}},
-            {{"4", "10", "10"}, {4, {10, 10}}},
-            {{"5", "10", "10"}, {5, {10, 10}}},
-            {{"6", "10", "10"}, {6, {10, 10}}},
-            {{"7", "10", "10"}, {7, {10, 10}}},
-            {{"8", "10", "10"}, {8, {10, 10}}},
-            {{"9", "10", "10"}, {9, {10, 10}}},
-            {{"10", "10", "10"}, {10, {10, 10}}},
-            {{"11", "10", "10"}, {11, {10, 10}}},
-            {{"12", "10", "10"}, {12, {10, 10}}},
-            {{"13", "10", "10"}, {13, {10, 10}}},
-            {{"14", "10", "10"}, {14, {10, 10}}},
-            {{"15", "10", "10"}, {15, {10, 10}}},
-        };
+                    {{"0", "10", "10"}, {0, {10, 10}}},
+                    {{"1", "10", "10"}, {1, {10, 10}}},
+                    {{"2", "10", "10"}, {2, {10, 10}}},
+                    {{"3", "10", "10"}, {3, {10, 10}}},
+                    {{"4", "10", "10"}, {4, {10, 10}}},
+                    {{"5", "10", "10"}, {5, {10, 10}}},
+                    {{"6", "10", "10"}, {6, {10, 10}}},
+                    {{"7", "10", "10"}, {7, {10, 10}}},
+                    {{"8", "10", "10"}, {8, {10, 10}}},
+                    {{"9", "10", "10"}, {9, {10, 10}}},
+                    {{"10", "10", "10"}, {10, {10, 10}}},
+                    {{"11", "10", "10"}, {11, {10, 10}}},
+                    {{"12", "unlimited", "10"}, {12, {RLIM_INFINITY, 10}}},
+                    {{"13", "-1", "10"}, {13, {RLIM_INFINITY, 10}}},
+                    {{"14", "10", "unlimited"}, {14, {10, RLIM_INFINITY}}},
+                    {{"15", "10", "-1"}, {15, {10, RLIM_INFINITY}}},
+            };
 
     for (const auto& [input, expected_result] : inputs_and_results) {
         TestRlimitSuccess(input, expected_result);
@@ -109,12 +109,16 @@
 
 TEST(rlimit, RlimitFailure) {
     const std::vector<std::pair<std::vector<std::string>, std::string>> inputs_and_results = {
-        {{"-4", "10", "10"}, "Resource '-4' below the minimum resource value '0'"},
-        {{"100", "10", "10"}, "Resource '100' over the maximum resource value '16'"},
-        {{"bad_string", "10", "10"}, "Could not parse resource 'bad_string'"},
-        {{"RLIM_", "10", "10"}, "Could not parse resource 'RLIM_'"},
-        {{"cpu", "abc", "10"}, "Could not parse soft limit 'abc'"},
-        {{"cpu", "10", "abc"}, "Could not parse hard limit 'abc'"},
+            {{"-4", "10", "10"}, "Resource '-4' below the minimum resource value '0'"},
+            {{"100", "10", "10"}, "Resource '100' over the maximum resource value '16'"},
+            {{"bad_string", "10", "10"}, "Could not parse resource 'bad_string'"},
+            {{"RLIM_", "10", "10"}, "Could not parse resource 'RLIM_'"},
+            {{"cpu", "abc", "10"}, "Could not parse soft limit 'abc'"},
+            {{"cpu", "10", "abc"}, "Could not parse hard limit 'abc'"},
+            {{"cpu", "unlimit", "10"}, "Could not parse soft limit 'unlimit'"},
+            {{"cpu", "10", "unlimit"}, "Could not parse hard limit 'unlimit'"},
+            {{"cpu", "-2", "10"}, "Could not parse soft limit '-2'"},
+            {{"cpu", "10", "-2"}, "Could not parse hard limit '-2'"},
     };
 
     for (const auto& [input, expected_result] : inputs_and_results) {
diff --git a/init/switch_root.cpp b/init/switch_root.cpp
new file mode 100644
index 0000000..0e59b57
--- /dev/null
+++ b/init/switch_root.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 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 "switch_root.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <mntent.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+using android::base::StartsWith;
+
+using namespace std::literals;
+
+namespace android {
+namespace init {
+
+namespace {
+
+void FreeRamdisk(DIR* dir, dev_t dev) {
+    int dfd = dirfd(dir);
+
+    dirent* de;
+    while ((de = readdir(dir)) != nullptr) {
+        if (de->d_name == "."s || de->d_name == ".."s) {
+            continue;
+        }
+
+        bool is_dir = false;
+
+        if (de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) {
+            struct stat info;
+            if (fstatat(dfd, de->d_name, &info, AT_SYMLINK_NOFOLLOW) != 0) {
+                continue;
+            }
+
+            if (info.st_dev != dev) {
+                continue;
+            }
+
+            if (S_ISDIR(info.st_mode)) {
+                is_dir = true;
+                auto fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
+                if (fd >= 0) {
+                    auto subdir =
+                            std::unique_ptr<DIR, decltype(&closedir)>{fdopendir(fd), closedir};
+                    if (subdir) {
+                        FreeRamdisk(subdir.get(), dev);
+                    } else {
+                        close(fd);
+                    }
+                }
+            }
+        }
+        unlinkat(dfd, de->d_name, is_dir ? AT_REMOVEDIR : 0);
+    }
+}
+
+std::vector<std::string> GetMounts(const std::string& new_root) {
+    auto fp = std::unique_ptr<std::FILE, decltype(&endmntent)>{setmntent("/proc/mounts", "re"),
+                                                               endmntent};
+    if (fp == nullptr) {
+        PLOG(FATAL) << "Failed to open /proc/mounts";
+    }
+
+    std::vector<std::string> result;
+    mntent* mentry;
+    while ((mentry = getmntent(fp.get())) != nullptr) {
+        // We won't try to move rootfs.
+        if (mentry->mnt_dir == "/"s) {
+            continue;
+        }
+
+        // The new root mount is handled separately.
+        if (mentry->mnt_dir == new_root) {
+            continue;
+        }
+
+        // Move operates on subtrees, so do not try to move children of other mounts.
+        if (std::find_if(result.begin(), result.end(), [&mentry](const auto& older_mount) {
+                return StartsWith(mentry->mnt_dir, older_mount);
+            }) != result.end()) {
+            continue;
+        }
+
+        result.emplace_back(mentry->mnt_dir);
+    }
+
+    return result;
+}
+
+}  // namespace
+
+void SwitchRoot(const std::string& new_root) {
+    auto mounts = GetMounts(new_root);
+
+    for (const auto& mount_path : mounts) {
+        auto new_mount_path = new_root + mount_path;
+        if (mount(mount_path.c_str(), new_mount_path.c_str(), nullptr, MS_MOVE, nullptr) != 0) {
+            PLOG(FATAL) << "Unable to move mount at '" << mount_path << "'";
+        }
+    }
+
+    auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir};
+    if (!old_root_dir) {
+        PLOG(ERROR) << "Could not opendir(\"/\"), not freeing ramdisk";
+    }
+
+    struct stat old_root_info;
+    if (stat("/", &old_root_info) != 0) {
+        PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
+        old_root_dir.reset();
+    }
+
+    if (chdir(new_root.c_str()) != 0) {
+        PLOG(FATAL) << "Could not chdir to new_root, '" << new_root << "'";
+    }
+
+    if (mount(new_root.c_str(), "/", nullptr, MS_MOVE, nullptr) != 0) {
+        PLOG(FATAL) << "Unable to move root mount to new_root, '" << new_root << "'";
+    }
+
+    if (chroot(".") != 0) {
+        PLOG(FATAL) << "Unable to chroot to new root";
+    }
+
+    if (old_root_dir) {
+        FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
+    }
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/libmemunreachable/anon_vma_naming.h b/init/switch_root.h
similarity index 66%
rename from libmemunreachable/anon_vma_naming.h
rename to init/switch_root.h
index fb31e41..d515e5d 100644
--- a/libmemunreachable/anon_vma_naming.h
+++ b/init/switch_root.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-#ifndef LIBMEMUNREACHABLE_ANON_VMA_NAMING_H_
-#define LIBMEMUNREACHABLE_ANON_VMA_NAMING_H_
+#pragma once
 
-#include <sys/prctl.h>
+#include <string>
 
-#define PR_SET_VMA 0x53564d41
-#define PR_SET_VMA_ANON_NAME 0
+namespace android {
+namespace init {
 
-#endif  // LIBMEMUNREACHABLE_ANON_VMA_NAMING_H_
+void SwitchRoot(const std::string& new_root);
+
+}  // namespace init
+}  // namespace android
diff --git a/libbacktrace/Android.bp b/libbacktrace/Android.bp
index a10e636..c42ae49 100644
--- a/libbacktrace/Android.bp
+++ b/libbacktrace/Android.bp
@@ -89,29 +89,20 @@
                 "libbase",
                 "liblog",
                 "libunwindstack",
-                "libdexfile",
             ],
 
             static_libs: [
                 "libprocinfo",
             ],
-
-            // libdexfile will eventually properly export headers, for now
-            // include these directly.
-            include_dirs: [
-                "art/runtime",
-            ],
         },
         android: {
             static_libs: ["libasync_safe"],
         },
         vendor: {
             cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
-            exclude_shared_libs: ["libdexfile"],
         },
         recovery: {
             cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
-            exclude_shared_libs: ["libdexfile"],
         },
     },
     whole_static_libs: ["libdemangle"],
@@ -154,7 +145,6 @@
     shared_libs: [
         "libbacktrace_test",
         "libbacktrace",
-        "libdexfile",
         "libbase",
         "liblog",
         "libunwindstack",
@@ -174,12 +164,6 @@
         },
     },
 
-    // libdexfile will eventually properly export headers, for now
-    // include these directly.
-    include_dirs: [
-        "art/runtime",
-    ],
-
     test_suites: ["device-tests"],
     data: [
         "testdata/arm/*",
diff --git a/libbacktrace/backtrace_benchmarks.cpp b/libbacktrace/backtrace_benchmarks.cpp
index 099ac60..a93a25e 100644
--- a/libbacktrace/backtrace_benchmarks.cpp
+++ b/libbacktrace/backtrace_benchmarks.cpp
@@ -35,10 +35,6 @@
 #include <backtrace/BacktraceMap.h>
 #include <unwindstack/Memory.h>
 
-// Definitions of prctl arguments to set a vma name in Android kernels.
-#define ANDROID_PR_SET_VMA 0x53564d41
-#define ANDROID_PR_SET_VMA_ANON_NAME 0
-
 constexpr size_t kNumMaps = 2000;
 
 static bool CountMaps(pid_t pid, size_t* num_maps) {
@@ -93,10 +89,11 @@
         exit(1);
       }
       memset(memory, 0x1, PAGE_SIZE);
-      if (prctl(ANDROID_PR_SET_VMA, ANDROID_PR_SET_VMA_ANON_NAME, memory, PAGE_SIZE, "test_map") ==
-          -1) {
+#if defined(PR_SET_VMA)
+      if (prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, memory, PAGE_SIZE, "test_map") == -1) {
         fprintf(stderr, "Failed: %s\n", strerror(errno));
       }
+#endif
       maps.push_back(memory);
     }
 
diff --git a/libcutils/tests/sched_policy_test.cpp b/libcutils/tests/sched_policy_test.cpp
index 5942ee5..1f657e2 100644
--- a/libcutils/tests/sched_policy_test.cpp
+++ b/libcutils/tests/sched_policy_test.cpp
@@ -67,6 +67,21 @@
 }
 
 TEST(SchedPolicy, set_sched_policy) {
+    if (!schedboost_enabled()) {
+        // schedboost_enabled() (i.e. CONFIG_CGROUP_SCHEDTUNE) is optional;
+        // it's only needed on devices using energy-aware scheduler.
+        GTEST_LOG_(INFO) << "skipping test that requires CONFIG_CGROUP_SCHEDTUNE";
+        return;
+    }
+
+    ASSERT_EQ(0, set_sched_policy(0, SP_BACKGROUND));
+    AssertPolicy(SP_BACKGROUND);
+
+    ASSERT_EQ(0, set_sched_policy(0, SP_FOREGROUND));
+    AssertPolicy(SP_FOREGROUND);
+}
+
+TEST(SchedPolicy, set_sched_policy_timerslack) {
     if (!hasCapSysNice()) {
         GTEST_LOG_(INFO) << "skipping test that requires CAP_SYS_NICE";
         return;
@@ -82,11 +97,9 @@
     const unsigned int BG_FG_SLACK_FACTOR = 100;
 
     ASSERT_EQ(0, set_sched_policy(0, SP_BACKGROUND));
-    AssertPolicy(SP_BACKGROUND);
     auto bgSleepTime = medianSleepTime();
 
     ASSERT_EQ(0, set_sched_policy(0, SP_FOREGROUND));
-    AssertPolicy(SP_FOREGROUND);
     auto fgSleepTime = medianSleepTime();
     ASSERT_GT(bgSleepTime, fgSleepTime * BG_FG_SLACK_FACTOR);
 }
diff --git a/libmemunreachable/Allocator.cpp b/libmemunreachable/Allocator.cpp
index 213be17..1eb7e98 100644
--- a/libmemunreachable/Allocator.cpp
+++ b/libmemunreachable/Allocator.cpp
@@ -24,6 +24,7 @@
 
 #include <sys/cdefs.h>
 #include <sys/mman.h>
+#include <sys/prctl.h>
 
 #include <cmath>
 #include <cstddef>
@@ -35,7 +36,6 @@
 
 #include "Allocator.h"
 #include "LinkedList.h"
-#include "anon_vma_naming.h"
 
 namespace android {
 
@@ -153,10 +153,10 @@
     munmap(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(ptr) + size), map_size - size);
   }
 
-#define PR_SET_VMA 0x53564d41
-#define PR_SET_VMA_ANON_NAME 0
+#if defined(PR_SET_VMA)
   prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, reinterpret_cast<uintptr_t>(ptr), size,
         "leak_detector_malloc");
+#endif
 
   return ptr;
 }
diff --git a/libmemunreachable/MemUnreachable.cpp b/libmemunreachable/MemUnreachable.cpp
index 529a043..b160de9 100644
--- a/libmemunreachable/MemUnreachable.cpp
+++ b/libmemunreachable/MemUnreachable.cpp
@@ -244,7 +244,7 @@
     } else if (mapping_name == "[anon:libc_malloc]") {
       // named malloc mapping
       heap_mappings.emplace_back(*it);
-    } else if (has_prefix(mapping_name, "/dev/ashmem/dalvik")) {
+    } else if (has_prefix(mapping_name, "[anon:dalvik-")) {
       // named dalvik heap mapping
       globals_mappings.emplace_back(*it);
     } else if (has_prefix(mapping_name, "[stack")) {
diff --git a/libmemunreachable/PtracerThread.cpp b/libmemunreachable/PtracerThread.cpp
index 61a1d24..d2fca49 100644
--- a/libmemunreachable/PtracerThread.cpp
+++ b/libmemunreachable/PtracerThread.cpp
@@ -24,6 +24,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mman.h>
+#include <sys/prctl.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -32,7 +33,6 @@
 #include "android-base/macros.h"
 
 #include "PtracerThread.h"
-#include "anon_vma_naming.h"
 #include "log.h"
 
 namespace android {
diff --git a/libpixelflinger/Android.mk b/libpixelflinger/Android.mk
index c7306cd..14883f4 100644
--- a/libpixelflinger/Android.mk
+++ b/libpixelflinger/Android.mk
@@ -72,8 +72,7 @@
 LOCAL_SRC_FILES_mips64 := $(PIXELFLINGER_SRC_FILES_mips64)
 LOCAL_CFLAGS := $(PIXELFLINGER_CFLAGS)
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_C_INCLUDES += $(LOCAL_EXPORT_C_INCLUDE_DIRS) \
-		    external/safe-iop/include
+LOCAL_C_INCLUDES += $(LOCAL_EXPORT_C_INCLUDE_DIRS)
 LOCAL_SHARED_LIBRARIES := libcutils liblog libutils
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/libpixelflinger/arch-arm64/t32cb16blend.S b/libpixelflinger/arch-arm64/t32cb16blend.S
index b1a950d..a9733c0 100644
--- a/libpixelflinger/arch-arm64/t32cb16blend.S
+++ b/libpixelflinger/arch-arm64/t32cb16blend.S
@@ -49,7 +49,7 @@
  *      upper 16-bit pixels in DREG into FB
  *
  *
- * clobbered: w6, w7, w16, w17, w18
+ * clobbered: w6, w7, w15, w16, w17
  *
  */
 
@@ -73,8 +73,8 @@
     add     w16, w6, w16, lsr #8
     cmp     w16, #0x1F
     orr     w17, \FB, #(0x1F<<(16 + 11))
-    orr     w18, \FB, w16, lsl #(16 + 11)
-    csel    \FB, w17, w18, hi
+    orr     w15, \FB, w16, lsl #(16 + 11)
+    csel    \FB, w17, w15, hi
         // green
         and     w6, \DREG, #(0x3F<<(16 + 5))
         lsr     w17,w6,#(16+5)
@@ -84,8 +84,8 @@
         add     w6, w16, w6, lsr #8
         cmp     w6, #0x3F
         orr     w17, \FB, #(0x3F<<(16 + 5))
-        orr     w18, \FB, w6, lsl #(16 + 5)
-        csel    \FB, w17, w18, hi
+        orr     w15, \FB, w6, lsl #(16 + 5)
+        csel    \FB, w17, w15, hi
             // blue
             and     w16, \DREG, #(0x1F << 16)
             lsr     w17,w16,#16
@@ -95,8 +95,8 @@
             add     w16, w6, w16, lsr #8
             cmp     w16, #0x1F
             orr     w17, \FB, #(0x1F << 16)
-            orr     w18, \FB, w16, lsl #16
-            csel    \FB, w17, w18, hi
+            orr     w15, \FB, w16, lsl #16
+            csel    \FB, w17, w15, hi
 
 .else //Blending even pixel present in bottom 16 bits of DREG register
 
@@ -109,8 +109,8 @@
     add     w16, w6, w16, lsr #8
     cmp     w16, #0x1F
     mov     w17, #(0x1F<<11)
-    lsl     w18, w16, #11
-    csel    \FB, w17, w18, hi
+    lsl     w15, w16, #11
+    csel    \FB, w17, w15, hi
 
 
         // green
@@ -121,8 +121,8 @@
         add     w6, w16, w6, lsr #(5+8)
         cmp     w6, #0x3F
         orr     w17, \FB, #(0x3F<<5)
-        orr     w18, \FB, w6, lsl #5
-        csel    \FB, w17, w18, hi
+        orr     w15, \FB, w6, lsl #5
+        csel    \FB, w17, w15, hi
 
             // blue
             and     w16, \DREG, #0x1F
@@ -132,8 +132,8 @@
             add     w16, w6, w16, lsr #8
             cmp     w16, #0x1F
             orr     w17, \FB, #0x1F
-            orr     w18, \FB, w16
-            csel    \FB, w17, w18, hi
+            orr     w15, \FB, w16
+            csel    \FB, w17, w15, hi
 
 .endif // End of blending even pixel
 
diff --git a/libprocinfo/process_map_test.cpp b/libprocinfo/process_map_test.cpp
index 900fd85..4b93c5b 100644
--- a/libprocinfo/process_map_test.cpp
+++ b/libprocinfo/process_map_test.cpp
@@ -44,7 +44,7 @@
   ASSERT_EQ(maps[0].end, 0x2ac00000ULL);
   ASSERT_EQ(maps[0].flags, PROT_READ | PROT_WRITE);
   ASSERT_EQ(maps[0].pgoff, 0ULL);
-  ASSERT_EQ(maps[0].name, "/dev/ashmem/dalvik-main space (region space) (deleted)");
+  ASSERT_EQ(maps[0].name, "[anon:dalvik-main space (region space)]");
   ASSERT_EQ(maps[876].start, 0x70e6c4f000ULL);
   ASSERT_EQ(maps[876].end, 0x70e6c6b000ULL);
   ASSERT_EQ(maps[876].flags, PROT_READ | PROT_EXEC);
@@ -55,6 +55,6 @@
   ASSERT_EQ(maps[1260].flags, PROT_READ);
   ASSERT_EQ(maps[1260].pgoff, 0ULL);
   ASSERT_EQ(maps[1260].name,
-            "/dev/ashmem/dalvik-classes.dex extracted in memory from "
-            "/data/app/com.google.sample.tunnel-HGGRU03Gu1Mwkf_-RnFmvw==/base.apk (deleted)");
+            "[anon:dalvik-classes.dex extracted in memory from "
+            "/data/app/com.google.sample.tunnel-HGGRU03Gu1Mwkf_-RnFmvw==/base.apk]");
 }
diff --git a/libprocinfo/testdata/maps b/libprocinfo/testdata/maps
index 3b312e3..098cf25 100644
--- a/libprocinfo/testdata/maps
+++ b/libprocinfo/testdata/maps
@@ -1,4 +1,4 @@
-12c00000-2ac00000 rw-p 00000000 00:05 10267643                           /dev/ashmem/dalvik-main space (region space) (deleted)
+12c00000-2ac00000 rw-p 00000000 00:05 10267643                           [anon:dalvik-main space (region space)]
 6fb5d000-6fd6e000 rw-p 00000000 103:1d 639511                            /data/dalvik-cache/arm64/system@framework@boot.art
 6fd6e000-6fd82000 r--p 00211000 103:1d 639511                            /data/dalvik-cache/arm64/system@framework@boot.art
 6fd82000-6fe47000 rw-p 00000000 103:1d 639514                            /data/dalvik-cache/arm64/system@framework@boot-core-libart.art
@@ -121,12 +121,12 @@
 73b88000-73b89000 r--s 00000000 fc:00 884                                /system/framework/boot-com.google.vr.platform.vdex
 73b89000-73b8a000 r--p 00004000 fc:00 899                                /system/framework/arm64/boot-com.google.vr.platform.oat
 73b8a000-73b8b000 rw-p 00005000 fc:00 899                                /system/framework/arm64/boot-com.google.vr.platform.oat
-73b8b000-73b93000 rw-p 00000000 00:05 10267640                           /dev/ashmem/dalvik-non moving space (deleted)
-73b93000-77b8b000 ---p 00008000 00:05 10267640                           /dev/ashmem/dalvik-non moving space (deleted)
-77b8b000-97b8b000 rw-p 00000000 00:05 10267645                           /dev/ashmem/dalvik-free list large object space (deleted)
-97b8b000-99b8b000 rw-p 00000000 00:05 10270989                           /dev/ashmem/dalvik-data-code-cache (deleted)
-99b8b000-9bb8b000 r-xp 00000000 00:05 10270990                           /dev/ashmem/dalvik-jit-code-cache (deleted)
-ebad6000-ebad7000 ---p 00000000 00:05 10269717                           /dev/ashmem/dalvik-Sentinel fault page (deleted)
+73b8b000-73b93000 rw-p 00000000 00:05 10267640                           [anon:dalvik-non moving space]
+73b93000-77b8b000 ---p 00008000 00:05 10267640                           [anon:dalvik-non moving space]
+77b8b000-97b8b000 rw-p 00000000 00:05 10267645                           [anon:dalvik-free list large object space]
+97b8b000-99b8b000 rw-p 00000000 00:05 10270989                           [anon:dalvik-data-code-cache]
+99b8b000-9bb8b000 r-xp 00000000 00:05 10270990                           [anon:dalvik-jit-code-cache]
+ebad6000-ebad7000 ---p 00000000 00:05 10269717                           [anon:dalvik-Sentinel fault page]
 7ffb6e000-7ffb76000 rw-s 000e5000 00:10 20630                            /dev/kgsl-3d0
 7ffb76000-7ffb78000 rw-s 000e0000 00:10 20630                            /dev/kgsl-3d0
 7ffbc3000-7ffbc4000 rw-s 000e8000 00:10 20630                            /dev/kgsl-3d0
@@ -453,7 +453,7 @@
 7058043000-7058044000 r--p 0002f000 fc:00 2354                           /system/lib64/libcompiler_rt.so
 7058044000-7058045000 rw-p 00030000 fc:00 2354                           /system/lib64/libcompiler_rt.so
 7058045000-70580b2000 rw-p 00000000 00:00 0                              [anon:.bss]
-70580bd000-70580dd000 rw-p 00000000 00:05 10265386                       /dev/ashmem/dalvik-LinearAlloc (deleted)
+70580bd000-70580dd000 rw-p 00000000 00:05 10265386                       [anon:dalvik-LinearAlloc]
 70580dd000-70580df000 r-xp 00000000 fc:00 2597                           /system/lib64/vndk-sp-28/libhardware.so
 70580df000-70580fc000 ---p 00000000 00:00 0 
 70580fc000-70580fd000 r--p 0000f000 fc:00 2597                           /system/lib64/vndk-sp-28/libhardware.so
@@ -568,9 +568,9 @@
 705fe4c000-705fe4d000 ---p 00000000 00:00 0                              [anon:thread stack guard]
 705fe4d000-705fe4e000 ---p 00000000 00:00 0 
 705fe4e000-705ff4a000 rw-p 00000000 00:00 0 
-705ff4a000-705ff4b000 ---p 00000000 00:05 10270991                       /dev/ashmem/dalvik-Jit thread pool worker thread 0 (deleted)
-705ff4b000-705ff4c000 ---p 00001000 00:05 10270991                       /dev/ashmem/dalvik-Jit thread pool worker thread 0 (deleted)
-705ff4c000-706004b000 rw-p 00002000 00:05 10270991                       /dev/ashmem/dalvik-Jit thread pool worker thread 0 (deleted)
+705ff4a000-705ff4b000 ---p 00000000 00:05 10270991                       [anon:dalvik-Jit thread pool worker thread 0]
+705ff4b000-705ff4c000 ---p 00001000 00:05 10270991                       [anon:dalvik-Jit thread pool worker thread 0]
+705ff4c000-706004b000 rw-p 00002000 00:05 10270991                       [anon:dalvik-Jit thread pool worker thread 0]
 706004b000-706010f000 r-xp 00000000 fc:00 2390                           /system/lib64/libvixl-arm64.so
 706010f000-7060120000 ---p 00000000 00:00 0 
 7060120000-7060125000 r--p 000cb000 fc:00 2390                           /system/lib64/libvixl-arm64.so
@@ -635,12 +635,12 @@
 7062003000-706201f000 ---p 00000000 00:00 0 
 706201f000-7062020000 r--p 0000f000 fc:00 2447                           /system/lib64/libtextclassifier_hash.so
 7062020000-7062021000 rw-p 00010000 fc:00 2447                           /system/lib64/libtextclassifier_hash.so
-7062022000-7062042000 rw-p 00000000 00:05 10269731                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+7062022000-7062042000 rw-p 00000000 00:05 10269731                       [anon:dalvik-CompilerMetadata]
 7062042000-7062077000 r-xp 00000000 fc:00 2372                           /system/lib64/android.hardware.neuralnetworks@1.0.so
 7062077000-7062095000 ---p 00000000 00:00 0 
 7062095000-706209b000 r--p 0003a000 fc:00 2372                           /system/lib64/android.hardware.neuralnetworks@1.0.so
 706209b000-706209c000 rw-p 00040000 fc:00 2372                           /system/lib64/android.hardware.neuralnetworks@1.0.so
-70620a9000-70620c9000 rw-p 00000000 00:05 10269730                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70620a9000-70620c9000 rw-p 00000000 00:05 10269730                       [anon:dalvik-CompilerMetadata]
 70620c9000-70620e3000 r-xp 00000000 fc:00 2956                           /system/lib64/android.hardware.neuralnetworks@1.1.so
 70620e3000-70620f4000 ---p 00000000 00:00 0 
 70620f4000-70620f7000 r--p 0001d000 fc:00 2956                           /system/lib64/android.hardware.neuralnetworks@1.1.so
@@ -659,7 +659,7 @@
 70623e0000-70623f7000 ---p 00000000 00:00 0 
 70623f7000-70623f8000 r--p 0001f000 fc:00 2662                           /system/lib64/libGLESv3.so
 70623f8000-70623f9000 rw-p 00020000 fc:00 2662                           /system/lib64/libGLESv3.so
-70623fc000-706241c000 rw-p 00000000 00:05 10269729                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70623fc000-706241c000 rw-p 00000000 00:05 10269729                       [anon:dalvik-CompilerMetadata]
 706241c000-7062444000 r-xp 00000000 fc:00 2603                           /system/lib64/libexif.so
 7062444000-706245f000 ---p 00000000 00:00 0 
 706245f000-7062472000 r--p 0002d000 fc:00 2603                           /system/lib64/libexif.so
@@ -674,7 +674,7 @@
 7062508000-7062522000 ---p 00000000 00:00 0 
 7062522000-7062525000 r--p 0003d000 fc:00 2401                           /system/lib64/libmtp.so
 7062525000-706252c000 rw-p 00040000 fc:00 2401                           /system/lib64/libmtp.so
-7062530000-7062550000 rw-p 00000000 00:05 10269728                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+7062530000-7062550000 rw-p 00000000 00:05 10269728                       [anon:dalvik-CompilerMetadata]
 7062550000-7062572000 r--s 00000000 fc:00 234                            /system/fonts/NotoSerifMyanmar-Regular.otf
 7062572000-706259e000 r-xp 00000000 fc:00 2620                           /system/lib64/libmediandk.so
 706259e000-70625b9000 ---p 00000000 00:00 0 
@@ -688,7 +688,7 @@
 7062621000-706263d000 ---p 00000000 00:00 0 
 706263d000-706263f000 r--p 0002e000 fc:00 2366                           /system/lib64/libmediadrmmetrics_lite.so
 706263f000-7062640000 rw-p 00030000 fc:00 2366                           /system/lib64/libmediadrmmetrics_lite.so
-706264b000-706266b000 rw-p 00000000 00:05 10269727                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+706264b000-706266b000 rw-p 00000000 00:05 10269727                       [anon:dalvik-CompilerMetadata]
 706266b000-70626d4000 r-xp 00000000 fc:00 2727                           /system/lib64/libmedia_jni.so
 70626d4000-70626eb000 ---p 00000000 00:00 0 
 70626eb000-70626f2000 r--p 00069000 fc:00 2727                           /system/lib64/libmedia_jni.so
@@ -697,7 +697,7 @@
 7062732000-7062748000 ---p 00000000 00:00 0 
 7062748000-706274b000 r--p 0003d000 fc:00 2399                           /system/lib64/libcamera2ndk.so
 706274b000-7062750000 rw-p 00040000 fc:00 2399                           /system/lib64/libcamera2ndk.so
-7062768000-7062788000 rw-p 00000000 00:05 10269726                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+7062768000-7062788000 rw-p 00000000 00:05 10269726                       [anon:dalvik-CompilerMetadata]
 7062788000-70627ee000 r-xp 00000000 fc:00 2974                           /system/lib64/android.hardware.drm@1.0.so
 70627ee000-7062805000 ---p 00000000 00:00 0 
 7062805000-706280d000 r--p 00068000 fc:00 2974                           /system/lib64/android.hardware.drm@1.0.so
@@ -737,17 +737,17 @@
 70629f9000-70629fc000 rw-p 00000000 00:00 0                              [anon:bionic TLS]
 70629fc000-70629fd000 ---p 00000000 00:00 0                              [anon:bionic TLS guard]
 70629fd000-7062a3e000 r--s 00000000 fc:00 216                            /system/fonts/NotoSerif-BoldItalic.ttf
-7062a3e000-7062b06000 rw-p 00000000 00:05 10270984                       /dev/ashmem/dalvik-indirect ref table (deleted)
-7062b06000-7062bce000 rw-p 00000000 00:05 10270983                       /dev/ashmem/dalvik-indirect ref table (deleted)
-7062bce000-7062dce000 rw-p 00000000 00:05 10270726                       /dev/ashmem/dalvik-rb copying gc mark stack (deleted)
-7062dce000-70635ce000 rw-p 00000000 00:05 10270725                       /dev/ashmem/dalvik-concurrent copying gc mark stack (deleted)
-70635ce000-7063dcf000 rw-p 00000000 00:05 10270724                       /dev/ashmem/dalvik-live stack (deleted)
-7063dcf000-70645d0000 rw-p 00000000 00:05 10270723                       /dev/ashmem/dalvik-allocation stack (deleted)
-70645d0000-70649d1000 rw-p 00000000 00:05 10270721                       /dev/ashmem/dalvik-card table (deleted)
-70649d1000-7064ad1000 rw-p 00000000 00:05 10267648                       /dev/ashmem/dalvik-large object free list space allocation info map (deleted)
-7064ad1000-7065ad1000 rw-p 00000000 00:05 10267644                       /dev/ashmem/dalvik-region space live bitmap (deleted)
-7065ad1000-7065bd1000 rw-p 00000000 00:05 10267642                       /dev/ashmem/dalvik-allocspace zygote / non moving space mark-bitmap 0 (deleted)
-7065bd1000-7065cd1000 rw-p 00000000 00:05 10267641                       /dev/ashmem/dalvik-allocspace zygote / non moving space live-bitmap 0 (deleted)
+7062a3e000-7062b06000 rw-p 00000000 00:05 10270984                       [anon:dalvik-indirect ref table]
+7062b06000-7062bce000 rw-p 00000000 00:05 10270983                       [anon:dalvik-indirect ref table]
+7062bce000-7062dce000 rw-p 00000000 00:05 10270726                       [anon:dalvik-rb copying gc mark stack]
+7062dce000-70635ce000 rw-p 00000000 00:05 10270725                       [anon:dalvik-concurrent copying gc mark stack]
+70635ce000-7063dcf000 rw-p 00000000 00:05 10270724                       [anon:dalvik-live stack]
+7063dcf000-70645d0000 rw-p 00000000 00:05 10270723                       [anon:dalvik-allocation stack]
+70645d0000-70649d1000 rw-p 00000000 00:05 10270721                       [anon:dalvik-card table]
+70649d1000-7064ad1000 rw-p 00000000 00:05 10267648                       [anon:dalvik-large object free list space allocation info map]
+7064ad1000-7065ad1000 rw-p 00000000 00:05 10267644                       [anon:dalvik-region space live bitmap]
+7065ad1000-7065bd1000 rw-p 00000000 00:05 10267642                       [anon:dalvik-allocspace zygote / non moving space mark-bitmap 0]
+7065bd1000-7065cd1000 rw-p 00000000 00:05 10267641                       [anon:dalvik-allocspace zygote / non moving space live-bitmap 0]
 7065cd1000-7065cd2000 r-xp 00000000 fc:00 2946                           /system/lib64/libsigchain.so
 7065cd2000-7065cf0000 ---p 00000000 00:00 0 
 7065cf0000-7065cf1000 r--p 0000f000 fc:00 2946                           /system/lib64/libsigchain.so
@@ -769,7 +769,7 @@
 706638d000-706639e000 r--p 005ef000 fc:00 2671                           /system/lib64/libart.so
 706639e000-70663a1000 rw-p 00600000 fc:00 2671                           /system/lib64/libart.so
 70663a1000-70663a4000 rw-p 00000000 00:00 0                              [anon:.bss]
-70663a6000-70663c6000 rw-p 00000000 00:05 10269725                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70663a6000-70663c6000 rw-p 00000000 00:05 10269725                       [anon:dalvik-CompilerMetadata]
 70663c6000-70663c8000 r-xp 00000000 fc:00 2673                           /system/lib64/libmetricslogger.so
 70663c8000-70663e5000 ---p 00000000 00:00 0 
 70663e5000-70663e6000 r--p 0000f000 fc:00 2673                           /system/lib64/libmetricslogger.so
@@ -801,8 +801,8 @@
 70e688e000-70e68ab000 ---p 00000000 00:00 0 
 70e68ab000-70e68ac000 r--p 0000f000 fc:00 2943                           /system/lib64/libion.so
 70e68ac000-70e68ad000 rw-p 00010000 fc:00 2943                           /system/lib64/libion.so
-70e68ad000-70e68af000 rw-p 00000000 00:05 10282496                       /dev/ashmem/dalvik-indirect ref table (deleted)
-70e68af000-70e68b1000 rw-p 00000000 00:05 10282493                       /dev/ashmem/dalvik-indirect ref table (deleted)
+70e68ad000-70e68af000 rw-p 00000000 00:05 10282496                       [anon:dalvik-indirect ref table]
+70e68af000-70e68b1000 rw-p 00000000 00:05 10282493                       [anon:dalvik-indirect ref table]
 70e68b1000-70e68ee000 r--s 00000000 fc:00 256                            /system/fonts/NotoSerif-Italic.ttf
 70e68ee000-70e6910000 r-xp 00000000 fc:00 2502                           /system/lib64/libhidlbase.so
 70e6910000-70e692c000 ---p 00000000 00:00 0 
@@ -821,7 +821,7 @@
 70e69e1000-70e69fa000 ---p 00000000 00:00 0 
 70e69fa000-70e69fb000 r--p 0001f000 fc:00 2537                           /system/lib64/liblog.so
 70e69fb000-70e69fc000 rw-p 00020000 fc:00 2537                           /system/lib64/liblog.so
-70e69fc000-70e69fe000 rw-p 00000000 00:05 10266158                       /dev/ashmem/dalvik-indirect ref table (deleted)
+70e69fc000-70e69fe000 rw-p 00000000 00:05 10266158                       [anon:dalvik-indirect ref table]
 70e69fe000-70e69ff000 ---p 00000000 00:00 0                              [anon:bionic TLS guard]
 70e69ff000-70e6a02000 rw-p 00000000 00:00 0                              [anon:bionic TLS]
 70e6a02000-70e6a03000 ---p 00000000 00:00 0                              [anon:bionic TLS guard]
@@ -854,7 +854,7 @@
 70e6b61000-70e6b73000 ---p 00000000 00:00 0 
 70e6b73000-70e6b75000 r--p 0003e000 fc:00 2695                           /system/lib64/libdexfile.so
 70e6b75000-70e6b76000 rw-p 00040000 fc:00 2695                           /system/lib64/libdexfile.so
-70e6b76000-70e6b78000 rw-p 00000000 00:05 10253452                       /dev/ashmem/dalvik-indirect ref table (deleted)
+70e6b76000-70e6b78000 rw-p 00000000 00:05 10253452                       [anon:dalvik-indirect ref table]
 70e6b78000-70e6b85000 r--s 00000000 fc:00 1080                           /system/usr/hyphen-data/hyph-cu.hyb
 70e6b85000-70e6b96000 r-xp 00000000 fc:00 2957                           /system/lib64/libaudioutils.so
 70e6b96000-70e6bb4000 ---p 00000000 00:00 0 
@@ -1258,7 +1258,7 @@
 70e96dd000-70e96f8000 ---p 00000000 00:00 0 
 70e96f8000-70e96f9000 r--p 0000f000 fc:00 2386                           /system/lib64/libusbhost.so
 70e96f9000-70e96fa000 rw-p 00010000 fc:00 2386                           /system/lib64/libusbhost.so
-70e96fa000-70e96fb000 r--p 00000000 00:05 10266154                       /dev/ashmem/dalvik-classes.dex extracted in memory from /data/app/com.google.sample.tunnel-HGGRU03Gu1Mwkf_-RnFmvw==/base.apk (deleted)
+70e96fa000-70e96fb000 r--p 00000000 00:05 10266154                       [anon:dalvik-classes.dex extracted in memory from /data/app/com.google.sample.tunnel-HGGRU03Gu1Mwkf_-RnFmvw==/base.apk]
 70e96fb000-70e9701000 r--s 00000000 fc:00 280                            /system/fonts/NotoSansCoptic-Regular.ttf
 70e9701000-70e9720000 r-xp 00000000 fc:00 2490                           /system/lib64/libstagefright_bufferqueue_helper.so
 70e9720000-70e973b000 ---p 00000000 00:00 0 
@@ -1291,7 +1291,7 @@
 70e98e7000-70e98e8000 rw-p 00010000 fc:00 2952                           /system/lib64/libETC1.so
 70e98e8000-70e98e9000 r--s 00000000 fc:00 1121                           /system/usr/hyphen-data/hyph-und-ethi.hyb
 70e98e9000-70e98ef000 r--s 00000000 fc:00 64                             /system/fonts/NotoSansBrahmi-Regular.ttf
-70e98ef000-70e990f000 rw-p 00000000 00:05 10271012                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70e98ef000-70e990f000 rw-p 00000000 00:05 10271012                       [anon:dalvik-CompilerMetadata]
 70e990f000-70e9926000 r-xp 00000000 fc:00 2526                           /system/lib64/libbacktrace.so
 70e9926000-70e993e000 ---p 00000000 00:00 0 
 70e993e000-70e993f000 r--p 0001f000 fc:00 2526                           /system/lib64/libbacktrace.so
@@ -1304,7 +1304,7 @@
 70e99d0000-70e99d1000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70e99d1000-70e99d3000 r--s 00000000 fc:00 200                            /system/fonts/NotoSansOldItalic-Regular.ttf
 70e99d3000-70e99e1000 r--s 00000000 fc:00 153                            /system/fonts/NotoSansMalayalam-Regular.ttf
-70e99e1000-70e9a01000 rw-p 00000000 00:05 10271011                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70e99e1000-70e9a01000 rw-p 00000000 00:05 10271011                       [anon:dalvik-CompilerMetadata]
 70e9a01000-70e9a1e000 r-xp 00000000 fc:00 2542                           /system/lib64/libimg_utils.so
 70e9a1e000-70e9a39000 ---p 00000000 00:00 0 
 70e9a39000-70e9a3c000 r--p 0001d000 fc:00 2542                           /system/lib64/libimg_utils.so
@@ -1330,7 +1330,7 @@
 70e9b0b000-70e9b0c000 rw-p 00030000 fc:00 2950                           /system/lib64/libmemunreachable.so
 70e9b0c000-70e9b0d000 r--s 00000000 fc:00 1088                           /system/usr/hyphen-data/hyph-ta.hyb
 70e9b0d000-70e9b0f000 r--s 00000000 fc:00 72                             /system/fonts/NotoSansOlChiki-Regular.ttf
-70e9b0f000-70e9b2f000 rw-p 00000000 00:05 10271010                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70e9b0f000-70e9b2f000 rw-p 00000000 00:05 10271010                       [anon:dalvik-CompilerMetadata]
 70e9b2f000-70e9b4f000 r--s 00000000 00:10 16633                          /dev/__properties__/u:object_r:persist_debug_prop:s0
 70e9b4f000-70e9b65000 r-xp 00000000 fc:00 2920                           /system/lib64/android.hardware.cas.native@1.0.so
 70e9b65000-70e9b7b000 ---p 00000000 00:00 0 
@@ -1363,13 +1363,13 @@
 70e9d01000-70e9d1e000 ---p 00000000 00:00 0 
 70e9d1e000-70e9d2e000 r--p 00080000 fc:00 2665                           /system/lib64/libbinder.so
 70e9d2e000-70e9d2f000 rw-p 00090000 fc:00 2665                           /system/lib64/libbinder.so
-70e9d2f000-70e9d4f000 rw-p 00000000 00:05 10271009                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70e9d2f000-70e9d4f000 rw-p 00000000 00:05 10271009                       [anon:dalvik-CompilerMetadata]
 70e9d4f000-70e9d53000 r-xp 00000000 fc:00 2454                           /system/lib64/libaudiomanager.so
 70e9d53000-70e9d6e000 ---p 00000000 00:00 0 
 70e9d6e000-70e9d6f000 r--p 0000f000 fc:00 2454                           /system/lib64/libaudiomanager.so
 70e9d6f000-70e9d70000 rw-p 00010000 fc:00 2454                           /system/lib64/libaudiomanager.so
 70e9d70000-70e9d71000 r--s 00000000 fc:00 1087                           /system/usr/hyphen-data/hyph-or.hyb
-70e9d71000-70e9d91000 rw-p 00000000 00:05 10271008                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70e9d71000-70e9d91000 rw-p 00000000 00:05 10271008                       [anon:dalvik-CompilerMetadata]
 70e9d91000-70e9e21000 r-xp 00000000 fc:00 2627                           /system/lib64/libft2.so
 70e9e21000-70e9e37000 ---p 00000000 00:00 0 
 70e9e37000-70e9e3c000 r--p 0009b000 fc:00 2627                           /system/lib64/libft2.so
@@ -1437,15 +1437,15 @@
 70eaa12000-70eaa16000 r--s 00000000 fc:00 87                             /system/fonts/NotoSansThaana-Regular.ttf
 70eaa16000-70eaa1b000 r--s 00000000 fc:00 218                            /system/fonts/NotoSansGeorgian-Bold.ttf
 70eaa1b000-70eaa20000 r--s 00000000 fc:00 125                            /system/fonts/NotoSansGeorgian-Regular.ttf
-70eaa20000-70eaa40000 rw-p 00000000 00:05 10271007                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70eaa20000-70eaa40000 rw-p 00000000 00:05 10271007                       [anon:dalvik-CompilerMetadata]
 70eaa40000-70eaaa0000 r-xp 00000000 fc:00 2384                           /system/lib64/libhidltransport.so
 70eaaa0000-70eaabe000 ---p 00000000 00:00 0 
 70eaabe000-70eaac6000 r--p 00068000 fc:00 2384                           /system/lib64/libhidltransport.so
 70eaac6000-70eaac7000 rw-p 00070000 fc:00 2384                           /system/lib64/libhidltransport.so
 70eaac7000-70eaacb000 r--s 00000000 fc:00 192                            /system/fonts/NotoSerifArmenian-Bold.ttf
 70eaacb000-70eaad0000 r--s 00000000 fc:00 210                            /system/fonts/NotoSansThaiUI-Bold.ttf
-70eaad0000-70eaaf0000 rw-p 00000000 00:05 10271006                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
-70eaaf0000-70eab10000 rw-p 00000000 00:05 10271005                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70eaad0000-70eaaf0000 rw-p 00000000 00:05 10271006                       [anon:dalvik-CompilerMetadata]
+70eaaf0000-70eab10000 rw-p 00000000 00:05 10271005                       [anon:dalvik-CompilerMetadata]
 70eab10000-70eab57000 r-xp 00000000 fc:00 2546                           /system/lib64/libmedia_omx.so
 70eab57000-70eab6d000 ---p 00000000 00:00 0 
 70eab6d000-70eab7a000 r--p 00053000 fc:00 2546                           /system/lib64/libmedia_omx.so
@@ -1453,7 +1453,7 @@
 70eab7f000-70eab80000 r--s 00000000 fc:00 1119                           /system/usr/hyphen-data/hyph-kn.hyb
 70eab80000-70eab86000 r--s 00000000 fc:00 224                            /system/fonts/NotoSansThaiUI-Regular.ttf
 70eab86000-70eab8b000 r--s 00000000 fc:00 300                            /system/fonts/NotoSerifThai-Bold.ttf
-70eab8b000-70eabab000 rw-p 00000000 00:05 10271004                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70eab8b000-70eabab000 rw-p 00000000 00:05 10271004                       [anon:dalvik-CompilerMetadata]
 70eabab000-70eac21000 r-xp 00000000 fc:00 2385                           /system/lib64/libvintf.so
 70eac21000-70eac31000 ---p 00000000 00:00 0 
 70eac31000-70eac36000 r--p 0007b000 fc:00 2385                           /system/lib64/libvintf.so
@@ -1468,7 +1468,7 @@
 70eacd8000-70eacd9000 rw-p 00080000 fc:00 2606                           /system/lib64/android.hardware.media.omx@1.0.so
 70eacd9000-70eacdf000 r--s 00000000 fc:00 169                            /system/fonts/NotoSansThai-Regular.ttf
 70eacdf000-70eace9000 r--s 00000000 fc:00 140                            /system/fonts/CarroisGothicSC-Regular.ttf
-70eace9000-70ead09000 rw-p 00000000 00:05 10271003                       /dev/ashmem/dalvik-CompilerMetadata (deleted)
+70eace9000-70ead09000 rw-p 00000000 00:05 10271003                       [anon:dalvik-CompilerMetadata]
 70ead09000-70ead22000 r-xp 00000000 fc:00 2539                           /system/lib64/android.hardware.graphics.mapper@2.1.so
 70ead22000-70ead34000 ---p 00000000 00:00 0 
 70ead34000-70ead37000 r--p 0001d000 fc:00 2539                           /system/lib64/android.hardware.graphics.mapper@2.1.so
@@ -1503,7 +1503,7 @@
 70eae5f000-70eae62000 r--s 00000000 fc:00 103                            /system/fonts/NotoSansLimbu-Regular.ttf
 70eae62000-70eae67000 r--s 00000000 fc:00 236                            /system/fonts/NotoSansHebrew-Bold.ttf
 70eae67000-70eae84000 r--s 001c2000 fc:00 990                            /system/framework/ext.jar
-70eae84000-70eaea4000 rw-p 00000000 00:05 10269720                       /dev/ashmem/dalvik-LinearAlloc (deleted)
+70eae84000-70eaea4000 rw-p 00000000 00:05 10269720                       [anon:dalvik-LinearAlloc]
 70eaea4000-70eaede000 r-xp 00000000 fc:00 2924                           /system/lib64/libwilhelm.so
 70eaede000-70eaefa000 ---p 00000000 00:00 0 
 70eaefa000-70eaeff000 r--p 0003b000 fc:00 2924                           /system/lib64/libwilhelm.so
@@ -1579,395 +1579,395 @@
 70eb0ca000-70eb0cb000 ---p 00000000 00:00 0                              [anon:bionic TLS guard]
 70eb0cb000-70eb0ce000 rw-p 00000000 00:00 0                              [anon:bionic TLS]
 70eb0ce000-70eb0cf000 ---p 00000000 00:00 0                              [anon:bionic TLS guard]
-70eb0cf000-70eb0ef000 rw-p 00000000 00:05 10270988                       /dev/ashmem/dalvik-LinearAlloc (deleted)
+70eb0cf000-70eb0ef000 rw-p 00000000 00:05 10270988                       [anon:dalvik-LinearAlloc]
 70eb0ef000-70eb5bb000 r-xp 00000000 fc:00 2374                           /system/lib64/libpdfium.so
 70eb5bb000-70eb5cf000 ---p 00000000 00:00 0 
 70eb5cf000-70eb5e6000 r--p 004d9000 fc:00 2374                           /system/lib64/libpdfium.so
 70eb5e6000-70eb5ea000 rw-p 004f0000 fc:00 2374                           /system/lib64/libpdfium.so
 70eb5ea000-70eb5f1000 rw-p 00000000 00:00 0                              [anon:.bss]
 70eb5f1000-70eb5f2000 r--s 00000000 fc:00 1094                           /system/usr/hyphen-data/hyph-hi.hyb
-70eb5f2000-70eb5f6000 rw-p 00000000 00:05 10270982                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb5f6000-70eb5fa000 rw-p 00000000 00:05 10270981                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb5fa000-70eb5fe000 rw-p 00000000 00:05 10270980                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb5fe000-70eb602000 rw-p 00000000 00:05 10270979                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb602000-70eb606000 rw-p 00000000 00:05 10270978                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb606000-70eb60a000 rw-p 00000000 00:05 10270977                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb60a000-70eb60e000 rw-p 00000000 00:05 10270976                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb60e000-70eb612000 rw-p 00000000 00:05 10270975                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb612000-70eb616000 rw-p 00000000 00:05 10270974                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb5f2000-70eb5f6000 rw-p 00000000 00:05 10270982                       [anon:dalvik-thread local mark stack]
+70eb5f6000-70eb5fa000 rw-p 00000000 00:05 10270981                       [anon:dalvik-thread local mark stack]
+70eb5fa000-70eb5fe000 rw-p 00000000 00:05 10270980                       [anon:dalvik-thread local mark stack]
+70eb5fe000-70eb602000 rw-p 00000000 00:05 10270979                       [anon:dalvik-thread local mark stack]
+70eb602000-70eb606000 rw-p 00000000 00:05 10270978                       [anon:dalvik-thread local mark stack]
+70eb606000-70eb60a000 rw-p 00000000 00:05 10270977                       [anon:dalvik-thread local mark stack]
+70eb60a000-70eb60e000 rw-p 00000000 00:05 10270976                       [anon:dalvik-thread local mark stack]
+70eb60e000-70eb612000 rw-p 00000000 00:05 10270975                       [anon:dalvik-thread local mark stack]
+70eb612000-70eb616000 rw-p 00000000 00:05 10270974                       [anon:dalvik-thread local mark stack]
 70eb616000-70eb61a000 r-xp 00000000 fc:00 2479                           /system/lib64/libspeexresampler.so
 70eb61a000-70eb635000 ---p 00000000 00:00 0 
 70eb635000-70eb636000 r--p 0000f000 fc:00 2479                           /system/lib64/libspeexresampler.so
 70eb636000-70eb637000 rw-p 00010000 fc:00 2479                           /system/lib64/libspeexresampler.so
 70eb637000-70eb639000 r--s 00000000 fc:00 299                            /system/fonts/NotoSansImperialAramaic-Regular.ttf
-70eb639000-70eb63d000 rw-p 00000000 00:05 10270973                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb63d000-70eb641000 rw-p 00000000 00:05 10270972                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb641000-70eb645000 rw-p 00000000 00:05 10270971                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb645000-70eb649000 rw-p 00000000 00:05 10270970                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb649000-70eb64d000 rw-p 00000000 00:05 10270969                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb64d000-70eb651000 rw-p 00000000 00:05 10270968                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb651000-70eb655000 rw-p 00000000 00:05 10270967                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb655000-70eb659000 rw-p 00000000 00:05 10270966                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb659000-70eb65d000 rw-p 00000000 00:05 10270965                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb65d000-70eb661000 rw-p 00000000 00:05 10270964                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb639000-70eb63d000 rw-p 00000000 00:05 10270973                       [anon:dalvik-thread local mark stack]
+70eb63d000-70eb641000 rw-p 00000000 00:05 10270972                       [anon:dalvik-thread local mark stack]
+70eb641000-70eb645000 rw-p 00000000 00:05 10270971                       [anon:dalvik-thread local mark stack]
+70eb645000-70eb649000 rw-p 00000000 00:05 10270970                       [anon:dalvik-thread local mark stack]
+70eb649000-70eb64d000 rw-p 00000000 00:05 10270969                       [anon:dalvik-thread local mark stack]
+70eb64d000-70eb651000 rw-p 00000000 00:05 10270968                       [anon:dalvik-thread local mark stack]
+70eb651000-70eb655000 rw-p 00000000 00:05 10270967                       [anon:dalvik-thread local mark stack]
+70eb655000-70eb659000 rw-p 00000000 00:05 10270966                       [anon:dalvik-thread local mark stack]
+70eb659000-70eb65d000 rw-p 00000000 00:05 10270965                       [anon:dalvik-thread local mark stack]
+70eb65d000-70eb661000 rw-p 00000000 00:05 10270964                       [anon:dalvik-thread local mark stack]
 70eb661000-70eb6c5000 r-xp 00000000 fc:00 2461                           /system/lib64/libhidl-gen-utils.so
 70eb6c5000-70eb6df000 ---p 00000000 00:00 0 
 70eb6df000-70eb6e1000 r--p 0006e000 fc:00 2461                           /system/lib64/libhidl-gen-utils.so
 70eb6e1000-70eb6e2000 rw-p 00070000 fc:00 2461                           /system/lib64/libhidl-gen-utils.so
-70eb6e2000-70eb6e6000 rw-p 00000000 00:05 10270963                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb6e6000-70eb6ea000 rw-p 00000000 00:05 10270962                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb6ea000-70eb6ee000 rw-p 00000000 00:05 10270961                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb6ee000-70eb6f2000 rw-p 00000000 00:05 10270960                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb6f2000-70eb6f6000 rw-p 00000000 00:05 10270959                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb6f6000-70eb6fa000 rw-p 00000000 00:05 10270958                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb6fa000-70eb6fe000 rw-p 00000000 00:05 10270957                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb6fe000-70eb702000 rw-p 00000000 00:05 10270956                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb702000-70eb706000 rw-p 00000000 00:05 10270955                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb706000-70eb70a000 rw-p 00000000 00:05 10270954                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb70a000-70eb70e000 rw-p 00000000 00:05 10270953                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb70e000-70eb712000 rw-p 00000000 00:05 10270952                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb6e2000-70eb6e6000 rw-p 00000000 00:05 10270963                       [anon:dalvik-thread local mark stack]
+70eb6e6000-70eb6ea000 rw-p 00000000 00:05 10270962                       [anon:dalvik-thread local mark stack]
+70eb6ea000-70eb6ee000 rw-p 00000000 00:05 10270961                       [anon:dalvik-thread local mark stack]
+70eb6ee000-70eb6f2000 rw-p 00000000 00:05 10270960                       [anon:dalvik-thread local mark stack]
+70eb6f2000-70eb6f6000 rw-p 00000000 00:05 10270959                       [anon:dalvik-thread local mark stack]
+70eb6f6000-70eb6fa000 rw-p 00000000 00:05 10270958                       [anon:dalvik-thread local mark stack]
+70eb6fa000-70eb6fe000 rw-p 00000000 00:05 10270957                       [anon:dalvik-thread local mark stack]
+70eb6fe000-70eb702000 rw-p 00000000 00:05 10270956                       [anon:dalvik-thread local mark stack]
+70eb702000-70eb706000 rw-p 00000000 00:05 10270955                       [anon:dalvik-thread local mark stack]
+70eb706000-70eb70a000 rw-p 00000000 00:05 10270954                       [anon:dalvik-thread local mark stack]
+70eb70a000-70eb70e000 rw-p 00000000 00:05 10270953                       [anon:dalvik-thread local mark stack]
+70eb70e000-70eb712000 rw-p 00000000 00:05 10270952                       [anon:dalvik-thread local mark stack]
 70eb712000-70eb71a000 r-xp 00000000 fc:00 2652                           /system/lib64/libcamera_metadata.so
 70eb71a000-70eb72f000 ---p 00000000 00:00 0 
 70eb72f000-70eb730000 r--p 0000f000 fc:00 2652                           /system/lib64/libcamera_metadata.so
 70eb730000-70eb732000 rw-p 00010000 fc:00 2652                           /system/lib64/libcamera_metadata.so
 70eb732000-70eb734000 r--s 00000000 fc:00 131                            /system/fonts/NotoSansHanunoo-Regular.ttf
-70eb734000-70eb738000 rw-p 00000000 00:05 10270951                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb738000-70eb73c000 rw-p 00000000 00:05 10270950                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb73c000-70eb740000 rw-p 00000000 00:05 10270949                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb740000-70eb744000 rw-p 00000000 00:05 10270948                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb744000-70eb748000 rw-p 00000000 00:05 10270947                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb748000-70eb74c000 rw-p 00000000 00:05 10270946                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb74c000-70eb750000 rw-p 00000000 00:05 10270945                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb750000-70eb754000 rw-p 00000000 00:05 10270944                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb754000-70eb758000 rw-p 00000000 00:05 10270943                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb758000-70eb75c000 rw-p 00000000 00:05 10270942                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb75c000-70eb760000 rw-p 00000000 00:05 10270941                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb760000-70eb764000 rw-p 00000000 00:05 10270940                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb734000-70eb738000 rw-p 00000000 00:05 10270951                       [anon:dalvik-thread local mark stack]
+70eb738000-70eb73c000 rw-p 00000000 00:05 10270950                       [anon:dalvik-thread local mark stack]
+70eb73c000-70eb740000 rw-p 00000000 00:05 10270949                       [anon:dalvik-thread local mark stack]
+70eb740000-70eb744000 rw-p 00000000 00:05 10270948                       [anon:dalvik-thread local mark stack]
+70eb744000-70eb748000 rw-p 00000000 00:05 10270947                       [anon:dalvik-thread local mark stack]
+70eb748000-70eb74c000 rw-p 00000000 00:05 10270946                       [anon:dalvik-thread local mark stack]
+70eb74c000-70eb750000 rw-p 00000000 00:05 10270945                       [anon:dalvik-thread local mark stack]
+70eb750000-70eb754000 rw-p 00000000 00:05 10270944                       [anon:dalvik-thread local mark stack]
+70eb754000-70eb758000 rw-p 00000000 00:05 10270943                       [anon:dalvik-thread local mark stack]
+70eb758000-70eb75c000 rw-p 00000000 00:05 10270942                       [anon:dalvik-thread local mark stack]
+70eb75c000-70eb760000 rw-p 00000000 00:05 10270941                       [anon:dalvik-thread local mark stack]
+70eb760000-70eb764000 rw-p 00000000 00:05 10270940                       [anon:dalvik-thread local mark stack]
 70eb764000-70eb767000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70eb767000-70eb768000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70eb768000-70eb76c000 rw-p 00000000 00:05 10270939                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb76c000-70eb770000 rw-p 00000000 00:05 10270938                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb768000-70eb76c000 rw-p 00000000 00:05 10270939                       [anon:dalvik-thread local mark stack]
+70eb76c000-70eb770000 rw-p 00000000 00:05 10270938                       [anon:dalvik-thread local mark stack]
 70eb770000-70eb771000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70eb771000-70eb774000 r--s 00000000 fc:00 231                            /system/fonts/NotoSansDeseret-Regular.ttf
-70eb774000-70eb778000 rw-p 00000000 00:05 10270937                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb778000-70eb77c000 rw-p 00000000 00:05 10270936                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb77c000-70eb780000 rw-p 00000000 00:05 10270935                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb780000-70eb784000 rw-p 00000000 00:05 10270934                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb784000-70eb788000 rw-p 00000000 00:05 10270933                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb788000-70eb78c000 rw-p 00000000 00:05 10270932                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb78c000-70eb790000 rw-p 00000000 00:05 10270931                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb790000-70eb794000 rw-p 00000000 00:05 10270930                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb794000-70eb798000 rw-p 00000000 00:05 10270929                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb798000-70eb79c000 rw-p 00000000 00:05 10270928                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb79c000-70eb7a0000 rw-p 00000000 00:05 10270927                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb774000-70eb778000 rw-p 00000000 00:05 10270937                       [anon:dalvik-thread local mark stack]
+70eb778000-70eb77c000 rw-p 00000000 00:05 10270936                       [anon:dalvik-thread local mark stack]
+70eb77c000-70eb780000 rw-p 00000000 00:05 10270935                       [anon:dalvik-thread local mark stack]
+70eb780000-70eb784000 rw-p 00000000 00:05 10270934                       [anon:dalvik-thread local mark stack]
+70eb784000-70eb788000 rw-p 00000000 00:05 10270933                       [anon:dalvik-thread local mark stack]
+70eb788000-70eb78c000 rw-p 00000000 00:05 10270932                       [anon:dalvik-thread local mark stack]
+70eb78c000-70eb790000 rw-p 00000000 00:05 10270931                       [anon:dalvik-thread local mark stack]
+70eb790000-70eb794000 rw-p 00000000 00:05 10270930                       [anon:dalvik-thread local mark stack]
+70eb794000-70eb798000 rw-p 00000000 00:05 10270929                       [anon:dalvik-thread local mark stack]
+70eb798000-70eb79c000 rw-p 00000000 00:05 10270928                       [anon:dalvik-thread local mark stack]
+70eb79c000-70eb7a0000 rw-p 00000000 00:05 10270927                       [anon:dalvik-thread local mark stack]
 70eb7a0000-70eb7a1000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70eb7a1000-70eb7a3000 r--s 00000000 fc:00 176                            /system/fonts/NotoSansGothic-Regular.ttf
-70eb7a3000-70eb7a7000 rw-p 00000000 00:05 10270926                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb7a3000-70eb7a7000 rw-p 00000000 00:05 10270926                       [anon:dalvik-thread local mark stack]
 70eb7a7000-70eb7a8000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70eb7a8000-70eb7a9000 r--s 00000000 fc:00 1109                           /system/usr/hyphen-data/hyph-gu.hyb
-70eb7a9000-70eb7ad000 rw-p 00000000 00:05 10270925                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb7a9000-70eb7ad000 rw-p 00000000 00:05 10270925                       [anon:dalvik-thread local mark stack]
 70eb7ad000-70eb7ae000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70eb7ae000-70eb7af000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70eb7af000-70eb7b0000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70eb7b0000-70eb7b2000 r--s 00000000 fc:00 191                            /system/fonts/NotoSansCypriot-Regular.ttf
-70eb7b2000-70eb7b6000 rw-p 00000000 00:05 10270924                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7b6000-70eb7ba000 rw-p 00000000 00:05 10270923                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7ba000-70eb7be000 rw-p 00000000 00:05 10270922                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7be000-70eb7c2000 rw-p 00000000 00:05 10270921                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7c2000-70eb7c6000 rw-p 00000000 00:05 10270920                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7c6000-70eb7ca000 rw-p 00000000 00:05 10270919                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7ca000-70eb7ce000 rw-p 00000000 00:05 10270918                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7ce000-70eb7d2000 rw-p 00000000 00:05 10270917                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7d2000-70eb7d6000 rw-p 00000000 00:05 10270916                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb7b2000-70eb7b6000 rw-p 00000000 00:05 10270924                       [anon:dalvik-thread local mark stack]
+70eb7b6000-70eb7ba000 rw-p 00000000 00:05 10270923                       [anon:dalvik-thread local mark stack]
+70eb7ba000-70eb7be000 rw-p 00000000 00:05 10270922                       [anon:dalvik-thread local mark stack]
+70eb7be000-70eb7c2000 rw-p 00000000 00:05 10270921                       [anon:dalvik-thread local mark stack]
+70eb7c2000-70eb7c6000 rw-p 00000000 00:05 10270920                       [anon:dalvik-thread local mark stack]
+70eb7c6000-70eb7ca000 rw-p 00000000 00:05 10270919                       [anon:dalvik-thread local mark stack]
+70eb7ca000-70eb7ce000 rw-p 00000000 00:05 10270918                       [anon:dalvik-thread local mark stack]
+70eb7ce000-70eb7d2000 rw-p 00000000 00:05 10270917                       [anon:dalvik-thread local mark stack]
+70eb7d2000-70eb7d6000 rw-p 00000000 00:05 10270916                       [anon:dalvik-thread local mark stack]
 70eb7d6000-70eb7d7000 rw-p 00000000 00:00 0                              [anon:linker_alloc_small_objects]
-70eb7d7000-70eb7db000 rw-p 00000000 00:05 10270915                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7db000-70eb7df000 rw-p 00000000 00:05 10270914                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7df000-70eb7e3000 rw-p 00000000 00:05 10270913                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7e3000-70eb7e7000 rw-p 00000000 00:05 10270912                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb7d7000-70eb7db000 rw-p 00000000 00:05 10270915                       [anon:dalvik-thread local mark stack]
+70eb7db000-70eb7df000 rw-p 00000000 00:05 10270914                       [anon:dalvik-thread local mark stack]
+70eb7df000-70eb7e3000 rw-p 00000000 00:05 10270913                       [anon:dalvik-thread local mark stack]
+70eb7e3000-70eb7e7000 rw-p 00000000 00:05 10270912                       [anon:dalvik-thread local mark stack]
 70eb7e7000-70eb7e8000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70eb7e8000-70eb7ea000 r--s 00000000 fc:00 174                            /system/fonts/NotoSansCarian-Regular.ttf
-70eb7ea000-70eb7ee000 rw-p 00000000 00:05 10270911                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7ee000-70eb7f2000 rw-p 00000000 00:05 10270910                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7f2000-70eb7f6000 rw-p 00000000 00:05 10270909                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb7ea000-70eb7ee000 rw-p 00000000 00:05 10270911                       [anon:dalvik-thread local mark stack]
+70eb7ee000-70eb7f2000 rw-p 00000000 00:05 10270910                       [anon:dalvik-thread local mark stack]
+70eb7f2000-70eb7f6000 rw-p 00000000 00:05 10270909                       [anon:dalvik-thread local mark stack]
 70eb7f6000-70eb7f7000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70eb7f7000-70eb7f8000 r--s 00000000 fc:00 1096                           /system/usr/hyphen-data/hyph-eu.hyb
-70eb7f8000-70eb7fc000 rw-p 00000000 00:05 10270908                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb7fc000-70eb800000 rw-p 00000000 00:05 10270907                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb800000-70eb804000 rw-p 00000000 00:05 10270906                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb804000-70eb808000 rw-p 00000000 00:05 10270905                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb808000-70eb80c000 rw-p 00000000 00:05 10270904                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb80c000-70eb810000 rw-p 00000000 00:05 10270903                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb810000-70eb814000 rw-p 00000000 00:05 10270902                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb7f8000-70eb7fc000 rw-p 00000000 00:05 10270908                       [anon:dalvik-thread local mark stack]
+70eb7fc000-70eb800000 rw-p 00000000 00:05 10270907                       [anon:dalvik-thread local mark stack]
+70eb800000-70eb804000 rw-p 00000000 00:05 10270906                       [anon:dalvik-thread local mark stack]
+70eb804000-70eb808000 rw-p 00000000 00:05 10270905                       [anon:dalvik-thread local mark stack]
+70eb808000-70eb80c000 rw-p 00000000 00:05 10270904                       [anon:dalvik-thread local mark stack]
+70eb80c000-70eb810000 rw-p 00000000 00:05 10270903                       [anon:dalvik-thread local mark stack]
+70eb810000-70eb814000 rw-p 00000000 00:05 10270902                       [anon:dalvik-thread local mark stack]
 70eb814000-70eb815000 rw-p 00000000 00:00 0                              [anon:linker_alloc_vector]
-70eb815000-70eb819000 rw-p 00000000 00:05 10270901                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb819000-70eb81d000 rw-p 00000000 00:05 10270900                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb815000-70eb819000 rw-p 00000000 00:05 10270901                       [anon:dalvik-thread local mark stack]
+70eb819000-70eb81d000 rw-p 00000000 00:05 10270900                       [anon:dalvik-thread local mark stack]
 70eb81d000-70eb81e000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
-70eb81e000-70eb822000 rw-p 00000000 00:05 10270899                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb822000-70eb826000 rw-p 00000000 00:05 10270898                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb826000-70eb82a000 rw-p 00000000 00:05 10270897                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb82a000-70eb82e000 rw-p 00000000 00:05 10270896                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb82e000-70eb832000 rw-p 00000000 00:05 10270895                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb832000-70eb836000 rw-p 00000000 00:05 10270894                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb81e000-70eb822000 rw-p 00000000 00:05 10270899                       [anon:dalvik-thread local mark stack]
+70eb822000-70eb826000 rw-p 00000000 00:05 10270898                       [anon:dalvik-thread local mark stack]
+70eb826000-70eb82a000 rw-p 00000000 00:05 10270897                       [anon:dalvik-thread local mark stack]
+70eb82a000-70eb82e000 rw-p 00000000 00:05 10270896                       [anon:dalvik-thread local mark stack]
+70eb82e000-70eb832000 rw-p 00000000 00:05 10270895                       [anon:dalvik-thread local mark stack]
+70eb832000-70eb836000 rw-p 00000000 00:05 10270894                       [anon:dalvik-thread local mark stack]
 70eb836000-70eb837000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70eb837000-70eb83b000 rw-p 00000000 00:05 10270893                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb83b000-70eb83f000 rw-p 00000000 00:05 10270892                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb83f000-70eb843000 rw-p 00000000 00:05 10270891                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb843000-70eb847000 rw-p 00000000 00:05 10270890                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb847000-70eb84b000 rw-p 00000000 00:05 10270889                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb84b000-70eb84f000 rw-p 00000000 00:05 10270888                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb837000-70eb83b000 rw-p 00000000 00:05 10270893                       [anon:dalvik-thread local mark stack]
+70eb83b000-70eb83f000 rw-p 00000000 00:05 10270892                       [anon:dalvik-thread local mark stack]
+70eb83f000-70eb843000 rw-p 00000000 00:05 10270891                       [anon:dalvik-thread local mark stack]
+70eb843000-70eb847000 rw-p 00000000 00:05 10270890                       [anon:dalvik-thread local mark stack]
+70eb847000-70eb84b000 rw-p 00000000 00:05 10270889                       [anon:dalvik-thread local mark stack]
+70eb84b000-70eb84f000 rw-p 00000000 00:05 10270888                       [anon:dalvik-thread local mark stack]
 70eb84f000-70eb850000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
-70eb850000-70eb854000 rw-p 00000000 00:05 10270887                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb854000-70eb858000 rw-p 00000000 00:05 10270886                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb858000-70eb85c000 rw-p 00000000 00:05 10270885                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb85c000-70eb860000 rw-p 00000000 00:05 10270884                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb860000-70eb864000 rw-p 00000000 00:05 10270883                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb864000-70eb868000 rw-p 00000000 00:05 10270882                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb850000-70eb854000 rw-p 00000000 00:05 10270887                       [anon:dalvik-thread local mark stack]
+70eb854000-70eb858000 rw-p 00000000 00:05 10270886                       [anon:dalvik-thread local mark stack]
+70eb858000-70eb85c000 rw-p 00000000 00:05 10270885                       [anon:dalvik-thread local mark stack]
+70eb85c000-70eb860000 rw-p 00000000 00:05 10270884                       [anon:dalvik-thread local mark stack]
+70eb860000-70eb864000 rw-p 00000000 00:05 10270883                       [anon:dalvik-thread local mark stack]
+70eb864000-70eb868000 rw-p 00000000 00:05 10270882                       [anon:dalvik-thread local mark stack]
 70eb868000-70eb869000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70eb869000-70eb86d000 rw-p 00000000 00:05 10270881                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb86d000-70eb871000 rw-p 00000000 00:05 10270880                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb871000-70eb875000 rw-p 00000000 00:05 10270879                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb875000-70eb879000 rw-p 00000000 00:05 10270878                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb879000-70eb87d000 rw-p 00000000 00:05 10270877                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb87d000-70eb881000 rw-p 00000000 00:05 10270876                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb881000-70eb885000 rw-p 00000000 00:05 10270875                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb885000-70eb889000 rw-p 00000000 00:05 10270874                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb889000-70eb88d000 rw-p 00000000 00:05 10270873                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb88d000-70eb891000 rw-p 00000000 00:05 10270872                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb869000-70eb86d000 rw-p 00000000 00:05 10270881                       [anon:dalvik-thread local mark stack]
+70eb86d000-70eb871000 rw-p 00000000 00:05 10270880                       [anon:dalvik-thread local mark stack]
+70eb871000-70eb875000 rw-p 00000000 00:05 10270879                       [anon:dalvik-thread local mark stack]
+70eb875000-70eb879000 rw-p 00000000 00:05 10270878                       [anon:dalvik-thread local mark stack]
+70eb879000-70eb87d000 rw-p 00000000 00:05 10270877                       [anon:dalvik-thread local mark stack]
+70eb87d000-70eb881000 rw-p 00000000 00:05 10270876                       [anon:dalvik-thread local mark stack]
+70eb881000-70eb885000 rw-p 00000000 00:05 10270875                       [anon:dalvik-thread local mark stack]
+70eb885000-70eb889000 rw-p 00000000 00:05 10270874                       [anon:dalvik-thread local mark stack]
+70eb889000-70eb88d000 rw-p 00000000 00:05 10270873                       [anon:dalvik-thread local mark stack]
+70eb88d000-70eb891000 rw-p 00000000 00:05 10270872                       [anon:dalvik-thread local mark stack]
 70eb891000-70eb892000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70eb892000-70eb896000 rw-p 00000000 00:05 10270871                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb896000-70eb89a000 rw-p 00000000 00:05 10270870                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb892000-70eb896000 rw-p 00000000 00:05 10270871                       [anon:dalvik-thread local mark stack]
+70eb896000-70eb89a000 rw-p 00000000 00:05 10270870                       [anon:dalvik-thread local mark stack]
 70eb89a000-70eb89b000 rw-p 00000000 00:00 0                              [anon:linker_alloc_vector]
 70eb89b000-70eb89c000 rw-p 00000000 00:00 0                              [anon:linker_alloc_small_objects]
-70eb89c000-70eb8a0000 rw-p 00000000 00:05 10270869                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8a0000-70eb8a4000 rw-p 00000000 00:05 10270868                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb89c000-70eb8a0000 rw-p 00000000 00:05 10270869                       [anon:dalvik-thread local mark stack]
+70eb8a0000-70eb8a4000 rw-p 00000000 00:05 10270868                       [anon:dalvik-thread local mark stack]
 70eb8a4000-70eb8a5000 r--p 00000000 00:00 0                              [anon:atexit handlers]
-70eb8a5000-70eb8a9000 rw-p 00000000 00:05 10270867                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8a9000-70eb8ad000 rw-p 00000000 00:05 10270866                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8ad000-70eb8b1000 rw-p 00000000 00:05 10270865                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8b1000-70eb8b5000 rw-p 00000000 00:05 10270864                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8b5000-70eb8b9000 rw-p 00000000 00:05 10270863                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8b9000-70eb8bd000 rw-p 00000000 00:05 10270862                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb8a5000-70eb8a9000 rw-p 00000000 00:05 10270867                       [anon:dalvik-thread local mark stack]
+70eb8a9000-70eb8ad000 rw-p 00000000 00:05 10270866                       [anon:dalvik-thread local mark stack]
+70eb8ad000-70eb8b1000 rw-p 00000000 00:05 10270865                       [anon:dalvik-thread local mark stack]
+70eb8b1000-70eb8b5000 rw-p 00000000 00:05 10270864                       [anon:dalvik-thread local mark stack]
+70eb8b5000-70eb8b9000 rw-p 00000000 00:05 10270863                       [anon:dalvik-thread local mark stack]
+70eb8b9000-70eb8bd000 rw-p 00000000 00:05 10270862                       [anon:dalvik-thread local mark stack]
 70eb8bd000-70eb8be000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70eb8be000-70eb8c1000 r--s 00000000 fc:00 168                            /system/fonts/NotoSansAvestan-Regular.ttf
-70eb8c1000-70eb8c5000 rw-p 00000000 00:05 10270861                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8c5000-70eb8c9000 rw-p 00000000 00:05 10270860                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8c9000-70eb8cd000 rw-p 00000000 00:05 10270859                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8cd000-70eb8d1000 rw-p 00000000 00:05 10270858                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8d1000-70eb8d5000 rw-p 00000000 00:05 10270857                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb8c1000-70eb8c5000 rw-p 00000000 00:05 10270861                       [anon:dalvik-thread local mark stack]
+70eb8c5000-70eb8c9000 rw-p 00000000 00:05 10270860                       [anon:dalvik-thread local mark stack]
+70eb8c9000-70eb8cd000 rw-p 00000000 00:05 10270859                       [anon:dalvik-thread local mark stack]
+70eb8cd000-70eb8d1000 rw-p 00000000 00:05 10270858                       [anon:dalvik-thread local mark stack]
+70eb8d1000-70eb8d5000 rw-p 00000000 00:05 10270857                       [anon:dalvik-thread local mark stack]
 70eb8d5000-70eb8d7000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70eb8d7000-70eb8db000 rw-p 00000000 00:05 10270856                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8db000-70eb8df000 rw-p 00000000 00:05 10270855                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8df000-70eb8e3000 rw-p 00000000 00:05 10270854                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8e3000-70eb8e7000 rw-p 00000000 00:05 10270853                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8e7000-70eb8eb000 rw-p 00000000 00:05 10270852                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb8d7000-70eb8db000 rw-p 00000000 00:05 10270856                       [anon:dalvik-thread local mark stack]
+70eb8db000-70eb8df000 rw-p 00000000 00:05 10270855                       [anon:dalvik-thread local mark stack]
+70eb8df000-70eb8e3000 rw-p 00000000 00:05 10270854                       [anon:dalvik-thread local mark stack]
+70eb8e3000-70eb8e7000 rw-p 00000000 00:05 10270853                       [anon:dalvik-thread local mark stack]
+70eb8e7000-70eb8eb000 rw-p 00000000 00:05 10270852                       [anon:dalvik-thread local mark stack]
 70eb8eb000-70eb8ec000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70eb8ec000-70eb8ed000 r--s 00000000 fc:00 1099                           /system/usr/hyphen-data/hyph-bn.hyb
-70eb8ed000-70eb8f1000 rw-p 00000000 00:05 10270851                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8f1000-70eb8f5000 rw-p 00000000 00:05 10270850                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8f5000-70eb8f9000 rw-p 00000000 00:05 10270849                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8f9000-70eb8fd000 rw-p 00000000 00:05 10270848                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb8fd000-70eb901000 rw-p 00000000 00:05 10270847                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb901000-70eb905000 rw-p 00000000 00:05 10270846                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb905000-70eb909000 rw-p 00000000 00:05 10270845                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb909000-70eb90d000 rw-p 00000000 00:05 10270844                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb90d000-70eb911000 rw-p 00000000 00:05 10270843                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb911000-70eb915000 rw-p 00000000 00:05 10270842                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb8ed000-70eb8f1000 rw-p 00000000 00:05 10270851                       [anon:dalvik-thread local mark stack]
+70eb8f1000-70eb8f5000 rw-p 00000000 00:05 10270850                       [anon:dalvik-thread local mark stack]
+70eb8f5000-70eb8f9000 rw-p 00000000 00:05 10270849                       [anon:dalvik-thread local mark stack]
+70eb8f9000-70eb8fd000 rw-p 00000000 00:05 10270848                       [anon:dalvik-thread local mark stack]
+70eb8fd000-70eb901000 rw-p 00000000 00:05 10270847                       [anon:dalvik-thread local mark stack]
+70eb901000-70eb905000 rw-p 00000000 00:05 10270846                       [anon:dalvik-thread local mark stack]
+70eb905000-70eb909000 rw-p 00000000 00:05 10270845                       [anon:dalvik-thread local mark stack]
+70eb909000-70eb90d000 rw-p 00000000 00:05 10270844                       [anon:dalvik-thread local mark stack]
+70eb90d000-70eb911000 rw-p 00000000 00:05 10270843                       [anon:dalvik-thread local mark stack]
+70eb911000-70eb915000 rw-p 00000000 00:05 10270842                       [anon:dalvik-thread local mark stack]
 70eb915000-70eb916000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70eb916000-70eb917000 r--s 00000000 fc:00 1114                           /system/usr/hyphen-data/hyph-bg.hyb
-70eb917000-70eb91b000 rw-p 00000000 00:05 10270841                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb917000-70eb91b000 rw-p 00000000 00:05 10270841                       [anon:dalvik-thread local mark stack]
 70eb91b000-70eb91c000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70eb91c000-70eb91d000 r--s 00000000 fc:00 1133                           /system/usr/hyphen-data/hyph-as.hyb
-70eb91d000-70eb921000 rw-p 00000000 00:05 10270840                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb921000-70eb925000 rw-p 00000000 00:05 10270839                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb91d000-70eb921000 rw-p 00000000 00:05 10270840                       [anon:dalvik-thread local mark stack]
+70eb921000-70eb925000 rw-p 00000000 00:05 10270839                       [anon:dalvik-thread local mark stack]
 70eb925000-70eb926000 rw-p 00000000 00:00 0                              [anon:linker_alloc_small_objects]
 70eb926000-70eb927000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70eb927000-70eb929000 r--s 00000000 fc:00 203                            /system/fonts/NotoSansBuhid-Regular.ttf
-70eb929000-70eb92d000 rw-p 00000000 00:05 10270838                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb92d000-70eb931000 rw-p 00000000 00:05 10270837                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb931000-70eb935000 rw-p 00000000 00:05 10270836                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb935000-70eb939000 rw-p 00000000 00:05 10270835                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb939000-70eb93d000 rw-p 00000000 00:05 10270834                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb93d000-70eb941000 rw-p 00000000 00:05 10270833                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb941000-70eb945000 rw-p 00000000 00:05 10270832                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb945000-70eb949000 rw-p 00000000 00:05 10270831                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb949000-70eb94d000 rw-p 00000000 00:05 10270830                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb94d000-70eb951000 rw-p 00000000 00:05 10270829                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb951000-70eb991000 rw-p 00000000 00:05 10270722                       /dev/ashmem/dalvik-mark stack (deleted)
+70eb929000-70eb92d000 rw-p 00000000 00:05 10270838                       [anon:dalvik-thread local mark stack]
+70eb92d000-70eb931000 rw-p 00000000 00:05 10270837                       [anon:dalvik-thread local mark stack]
+70eb931000-70eb935000 rw-p 00000000 00:05 10270836                       [anon:dalvik-thread local mark stack]
+70eb935000-70eb939000 rw-p 00000000 00:05 10270835                       [anon:dalvik-thread local mark stack]
+70eb939000-70eb93d000 rw-p 00000000 00:05 10270834                       [anon:dalvik-thread local mark stack]
+70eb93d000-70eb941000 rw-p 00000000 00:05 10270833                       [anon:dalvik-thread local mark stack]
+70eb941000-70eb945000 rw-p 00000000 00:05 10270832                       [anon:dalvik-thread local mark stack]
+70eb945000-70eb949000 rw-p 00000000 00:05 10270831                       [anon:dalvik-thread local mark stack]
+70eb949000-70eb94d000 rw-p 00000000 00:05 10270830                       [anon:dalvik-thread local mark stack]
+70eb94d000-70eb951000 rw-p 00000000 00:05 10270829                       [anon:dalvik-thread local mark stack]
+70eb951000-70eb991000 rw-p 00000000 00:05 10270722                       [anon:dalvik-mark stack]
 70eb991000-70eb992000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
-70eb992000-70eb996000 rw-p 00000000 00:05 10270828                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb996000-70eb99a000 rw-p 00000000 00:05 10270827                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb99a000-70eb99e000 rw-p 00000000 00:05 10270826                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb99e000-70eb9a2000 rw-p 00000000 00:05 10270825                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb992000-70eb996000 rw-p 00000000 00:05 10270828                       [anon:dalvik-thread local mark stack]
+70eb996000-70eb99a000 rw-p 00000000 00:05 10270827                       [anon:dalvik-thread local mark stack]
+70eb99a000-70eb99e000 rw-p 00000000 00:05 10270826                       [anon:dalvik-thread local mark stack]
+70eb99e000-70eb9a2000 rw-p 00000000 00:05 10270825                       [anon:dalvik-thread local mark stack]
 70eb9a2000-70eb9a4000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70eb9a4000-70eb9a8000 rw-p 00000000 00:05 10270824                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9a8000-70eb9ac000 rw-p 00000000 00:05 10270823                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9ac000-70eb9b0000 rw-p 00000000 00:05 10270822                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb9a4000-70eb9a8000 rw-p 00000000 00:05 10270824                       [anon:dalvik-thread local mark stack]
+70eb9a8000-70eb9ac000 rw-p 00000000 00:05 10270823                       [anon:dalvik-thread local mark stack]
+70eb9ac000-70eb9b0000 rw-p 00000000 00:05 10270822                       [anon:dalvik-thread local mark stack]
 70eb9b0000-70eb9b1000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70eb9b1000-70eb9b2000 r--s 00021000 fc:01 1180                           /vendor/overlay/framework-res__auto_generated_rro.apk
-70eb9b2000-70eb9b6000 rw-p 00000000 00:05 10270821                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9b6000-70eb9ba000 rw-p 00000000 00:05 10270820                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9ba000-70eb9be000 rw-p 00000000 00:05 10270819                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9be000-70eb9c2000 rw-p 00000000 00:05 10270818                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9c2000-70eb9c6000 rw-p 00000000 00:05 10270817                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9c6000-70eb9ca000 rw-p 00000000 00:05 10270816                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9ca000-70eb9ce000 rw-p 00000000 00:05 10270815                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb9b2000-70eb9b6000 rw-p 00000000 00:05 10270821                       [anon:dalvik-thread local mark stack]
+70eb9b6000-70eb9ba000 rw-p 00000000 00:05 10270820                       [anon:dalvik-thread local mark stack]
+70eb9ba000-70eb9be000 rw-p 00000000 00:05 10270819                       [anon:dalvik-thread local mark stack]
+70eb9be000-70eb9c2000 rw-p 00000000 00:05 10270818                       [anon:dalvik-thread local mark stack]
+70eb9c2000-70eb9c6000 rw-p 00000000 00:05 10270817                       [anon:dalvik-thread local mark stack]
+70eb9c6000-70eb9ca000 rw-p 00000000 00:05 10270816                       [anon:dalvik-thread local mark stack]
+70eb9ca000-70eb9ce000 rw-p 00000000 00:05 10270815                       [anon:dalvik-thread local mark stack]
 70eb9ce000-70eb9cf000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70eb9cf000-70eb9d1000 r--s 00000000 fc:00 213                            /system/fonts/NotoSansBuginese-Regular.ttf
-70eb9d1000-70eb9d5000 rw-p 00000000 00:05 10270814                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9d5000-70eb9d9000 rw-p 00000000 00:05 10270813                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9d9000-70eb9dd000 rw-p 00000000 00:05 10270812                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9dd000-70eb9e1000 rw-p 00000000 00:05 10270811                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9e1000-70eb9e5000 rw-p 00000000 00:05 10270810                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9e5000-70eb9e9000 rw-p 00000000 00:05 10270809                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9e9000-70eb9ed000 rw-p 00000000 00:05 10270808                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9ed000-70eb9f1000 rw-p 00000000 00:05 10270807                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eb9f1000-70eb9f5000 rw-p 00000000 00:05 10270806                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb9d1000-70eb9d5000 rw-p 00000000 00:05 10270814                       [anon:dalvik-thread local mark stack]
+70eb9d5000-70eb9d9000 rw-p 00000000 00:05 10270813                       [anon:dalvik-thread local mark stack]
+70eb9d9000-70eb9dd000 rw-p 00000000 00:05 10270812                       [anon:dalvik-thread local mark stack]
+70eb9dd000-70eb9e1000 rw-p 00000000 00:05 10270811                       [anon:dalvik-thread local mark stack]
+70eb9e1000-70eb9e5000 rw-p 00000000 00:05 10270810                       [anon:dalvik-thread local mark stack]
+70eb9e5000-70eb9e9000 rw-p 00000000 00:05 10270809                       [anon:dalvik-thread local mark stack]
+70eb9e9000-70eb9ed000 rw-p 00000000 00:05 10270808                       [anon:dalvik-thread local mark stack]
+70eb9ed000-70eb9f1000 rw-p 00000000 00:05 10270807                       [anon:dalvik-thread local mark stack]
+70eb9f1000-70eb9f5000 rw-p 00000000 00:05 10270806                       [anon:dalvik-thread local mark stack]
 70eb9f5000-70eb9f6000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70eb9f6000-70eb9f8000 rw-p 00000000 00:05 10271002                       /dev/ashmem/dalvik-indirect ref table (deleted)
-70eb9f8000-70eb9fc000 rw-p 00000000 00:05 10270805                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eb9f6000-70eb9f8000 rw-p 00000000 00:05 10271002                       [anon:dalvik-indirect ref table]
+70eb9f8000-70eb9fc000 rw-p 00000000 00:05 10270805                       [anon:dalvik-thread local mark stack]
 70eb9fc000-70eb9fd000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
-70eb9fd000-70eb9ff000 rw-p 00000000 00:05 10270999                       /dev/ashmem/dalvik-indirect ref table (deleted)
+70eb9fd000-70eb9ff000 rw-p 00000000 00:05 10270999                       [anon:dalvik-indirect ref table]
 70eb9ff000-70eba00000 r--s 00000000 fc:00 983                            /system/framework/com.google.vr.platform.jar
-70eba00000-70eba04000 rw-p 00000000 00:05 10270804                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba04000-70eba08000 rw-p 00000000 00:05 10270803                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba08000-70eba0c000 rw-p 00000000 00:05 10270802                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba0c000-70eba10000 rw-p 00000000 00:05 10270801                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba10000-70eba14000 rw-p 00000000 00:05 10270800                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba14000-70eba18000 rw-p 00000000 00:05 10270799                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba18000-70eba1c000 rw-p 00000000 00:05 10270798                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba1c000-70eba20000 rw-p 00000000 00:05 10270797                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba20000-70eba24000 rw-p 00000000 00:05 10270796                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba24000-70eba28000 rw-p 00000000 00:05 10270795                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba28000-70eba2c000 rw-p 00000000 00:05 10270794                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eba00000-70eba04000 rw-p 00000000 00:05 10270804                       [anon:dalvik-thread local mark stack]
+70eba04000-70eba08000 rw-p 00000000 00:05 10270803                       [anon:dalvik-thread local mark stack]
+70eba08000-70eba0c000 rw-p 00000000 00:05 10270802                       [anon:dalvik-thread local mark stack]
+70eba0c000-70eba10000 rw-p 00000000 00:05 10270801                       [anon:dalvik-thread local mark stack]
+70eba10000-70eba14000 rw-p 00000000 00:05 10270800                       [anon:dalvik-thread local mark stack]
+70eba14000-70eba18000 rw-p 00000000 00:05 10270799                       [anon:dalvik-thread local mark stack]
+70eba18000-70eba1c000 rw-p 00000000 00:05 10270798                       [anon:dalvik-thread local mark stack]
+70eba1c000-70eba20000 rw-p 00000000 00:05 10270797                       [anon:dalvik-thread local mark stack]
+70eba20000-70eba24000 rw-p 00000000 00:05 10270796                       [anon:dalvik-thread local mark stack]
+70eba24000-70eba28000 rw-p 00000000 00:05 10270795                       [anon:dalvik-thread local mark stack]
+70eba28000-70eba2c000 rw-p 00000000 00:05 10270794                       [anon:dalvik-thread local mark stack]
 70eba2c000-70eba2d000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70eba2d000-70eba2e000 r--s 00000000 fc:00 881                            /system/framework/android.test.base.jar
 70eba2e000-70eba2f000 r--s 00000000 fc:00 707                            /system/framework/framework-oahl-backward-compatibility.jar
 70eba2f000-70eba30000 r--s 00000000 fc:00 705                            /system/framework/android.hidl.manager-V1.0-java.jar
-70eba30000-70eba34000 rw-p 00000000 00:05 10270793                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba34000-70eba38000 rw-p 00000000 00:05 10270792                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba38000-70eba3c000 rw-p 00000000 00:05 10270791                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba3c000-70eba40000 rw-p 00000000 00:05 10270790                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba40000-70eba44000 rw-p 00000000 00:05 10270789                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba44000-70eba48000 rw-p 00000000 00:05 10270788                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba48000-70eba4c000 rw-p 00000000 00:05 10270787                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba4c000-70eba50000 rw-p 00000000 00:05 10270786                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eba30000-70eba34000 rw-p 00000000 00:05 10270793                       [anon:dalvik-thread local mark stack]
+70eba34000-70eba38000 rw-p 00000000 00:05 10270792                       [anon:dalvik-thread local mark stack]
+70eba38000-70eba3c000 rw-p 00000000 00:05 10270791                       [anon:dalvik-thread local mark stack]
+70eba3c000-70eba40000 rw-p 00000000 00:05 10270790                       [anon:dalvik-thread local mark stack]
+70eba40000-70eba44000 rw-p 00000000 00:05 10270789                       [anon:dalvik-thread local mark stack]
+70eba44000-70eba48000 rw-p 00000000 00:05 10270788                       [anon:dalvik-thread local mark stack]
+70eba48000-70eba4c000 rw-p 00000000 00:05 10270787                       [anon:dalvik-thread local mark stack]
+70eba4c000-70eba50000 rw-p 00000000 00:05 10270786                       [anon:dalvik-thread local mark stack]
 70eba50000-70eba52000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70eba52000-70eba53000 r--s 00000000 fc:00 971                            /system/framework/android.hidl.base-V1.0-java.jar
-70eba53000-70eba57000 rw-p 00000000 00:05 10270785                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba57000-70eba5b000 rw-p 00000000 00:05 10270784                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba5b000-70eba5f000 rw-p 00000000 00:05 10270783                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba5f000-70eba63000 rw-p 00000000 00:05 10270782                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70eba53000-70eba57000 rw-p 00000000 00:05 10270785                       [anon:dalvik-thread local mark stack]
+70eba57000-70eba5b000 rw-p 00000000 00:05 10270784                       [anon:dalvik-thread local mark stack]
+70eba5b000-70eba5f000 rw-p 00000000 00:05 10270783                       [anon:dalvik-thread local mark stack]
+70eba5f000-70eba63000 rw-p 00000000 00:05 10270782                       [anon:dalvik-thread local mark stack]
 70eba63000-70eba64000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70eba64000-70eba65000 r--s 00000000 fc:00 889                            /system/framework/ims-common.jar
-70eba65000-70eba69000 rw-p 00000000 00:05 10270781                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba69000-70eba6d000 rw-p 00000000 00:05 10270780                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba6d000-70eba71000 rw-p 00000000 00:05 10270779                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba71000-70eba75000 rw-p 00000000 00:05 10270778                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70eba75000-70eba95000 rw-p 00000000 00:05 10267647                       /dev/ashmem/dalvik-large marked objects (deleted)
-70eba95000-70ebab5000 rw-p 00000000 00:05 10267646                       /dev/ashmem/dalvik-large live objects (deleted)
+70eba65000-70eba69000 rw-p 00000000 00:05 10270781                       [anon:dalvik-thread local mark stack]
+70eba69000-70eba6d000 rw-p 00000000 00:05 10270780                       [anon:dalvik-thread local mark stack]
+70eba6d000-70eba71000 rw-p 00000000 00:05 10270779                       [anon:dalvik-thread local mark stack]
+70eba71000-70eba75000 rw-p 00000000 00:05 10270778                       [anon:dalvik-thread local mark stack]
+70eba75000-70eba95000 rw-p 00000000 00:05 10267647                       [anon:dalvik-large marked objects]
+70eba95000-70ebab5000 rw-p 00000000 00:05 10267646                       [anon:dalvik-large live objects]
 70ebab5000-70ebab6000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70ebab6000-70ebab7000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
-70ebab7000-70ebabb000 rw-p 00000000 00:05 10270777                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebab7000-70ebabb000 rw-p 00000000 00:05 10270777                       [anon:dalvik-thread local mark stack]
 70ebabb000-70ebadb000 r--s 00000000 00:10 16603                          /dev/__properties__/u:object_r:exported_fingerprint_prop:s0
 70ebadb000-70ebadc000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70ebadc000-70ebadd000 r--s 00000000 fc:00 878                            /system/framework/voip-common.jar
-70ebadd000-70ebadf000 rw-p 00000000 00:05 10270995                       /dev/ashmem/dalvik-indirect ref table (deleted)
-70ebadf000-70ebae3000 rw-p 00000000 00:05 10270776                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebae3000-70ebae7000 rw-p 00000000 00:05 10270775                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebae7000-70ebaeb000 rw-p 00000000 00:05 10270774                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebaeb000-70ebaef000 rw-p 00000000 00:05 10270773                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebadd000-70ebadf000 rw-p 00000000 00:05 10270995                       [anon:dalvik-indirect ref table]
+70ebadf000-70ebae3000 rw-p 00000000 00:05 10270776                       [anon:dalvik-thread local mark stack]
+70ebae3000-70ebae7000 rw-p 00000000 00:05 10270775                       [anon:dalvik-thread local mark stack]
+70ebae7000-70ebaeb000 rw-p 00000000 00:05 10270774                       [anon:dalvik-thread local mark stack]
+70ebaeb000-70ebaef000 rw-p 00000000 00:05 10270773                       [anon:dalvik-thread local mark stack]
 70ebaef000-70ebb0f000 r--s 00000000 00:10 16582                          /dev/__properties__/u:object_r:debug_prop:s0
 70ebb0f000-70ebb10000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70ebb10000-70ebb11000 r--s 00000000 fc:00 703                            /system/framework/telephony-common.jar
-70ebb11000-70ebb13000 rw-p 00000000 00:05 10270994                       /dev/ashmem/dalvik-indirect ref table (deleted)
-70ebb13000-70ebb17000 rw-p 00000000 00:05 10270772                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebb11000-70ebb13000 rw-p 00000000 00:05 10270994                       [anon:dalvik-indirect ref table]
+70ebb13000-70ebb17000 rw-p 00000000 00:05 10270772                       [anon:dalvik-thread local mark stack]
 70ebb17000-70ebb19000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70ebb19000-70ebb1d000 rw-p 00000000 00:05 10270771                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebb19000-70ebb1d000 rw-p 00000000 00:05 10270771                       [anon:dalvik-thread local mark stack]
 70ebb1d000-70ebb3d000 r--s 00000000 00:10 16600                          /dev/__properties__/u:object_r:exported_default_prop:s0
 70ebb3d000-70ebb5d000 r--s 00000000 00:10 16650                          /dev/__properties__/u:object_r:system_prop:s0
 70ebb5d000-70ebb7d000 r--s 00000000 00:10 16610                          /dev/__properties__/u:object_r:exported_vold_prop:s0
 70ebb7d000-70ebb9d000 r--s 00000000 00:10 16598                          /dev/__properties__/u:object_r:exported_config_prop:s0
 70ebb9d000-70ebb9e000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
-70ebb9e000-70ebba2000 rw-p 00000000 00:05 10270770                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebba2000-70ebba6000 rw-p 00000000 00:05 10270769                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebb9e000-70ebba2000 rw-p 00000000 00:05 10270770                       [anon:dalvik-thread local mark stack]
+70ebba2000-70ebba6000 rw-p 00000000 00:05 10270769                       [anon:dalvik-thread local mark stack]
 70ebba6000-70ebba7000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70ebba7000-70ebba8000 r--s 00000000 fc:00 1004                           /system/framework/framework.jar
-70ebba8000-70ebbac000 rw-p 00000000 00:05 10270768                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebbac000-70ebbb0000 rw-p 00000000 00:05 10270767                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebbb0000-70ebbb4000 rw-p 00000000 00:05 10270766                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebbb4000-70ebbb8000 rw-p 00000000 00:05 10270765                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebbb8000-70ebbbc000 rw-p 00000000 00:05 10270764                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebbbc000-70ebbc0000 rw-p 00000000 00:05 10270763                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebbc0000-70ebbc4000 rw-p 00000000 00:05 10270762                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebba8000-70ebbac000 rw-p 00000000 00:05 10270768                       [anon:dalvik-thread local mark stack]
+70ebbac000-70ebbb0000 rw-p 00000000 00:05 10270767                       [anon:dalvik-thread local mark stack]
+70ebbb0000-70ebbb4000 rw-p 00000000 00:05 10270766                       [anon:dalvik-thread local mark stack]
+70ebbb4000-70ebbb8000 rw-p 00000000 00:05 10270765                       [anon:dalvik-thread local mark stack]
+70ebbb8000-70ebbbc000 rw-p 00000000 00:05 10270764                       [anon:dalvik-thread local mark stack]
+70ebbbc000-70ebbc0000 rw-p 00000000 00:05 10270763                       [anon:dalvik-thread local mark stack]
+70ebbc0000-70ebbc4000 rw-p 00000000 00:05 10270762                       [anon:dalvik-thread local mark stack]
 70ebbc4000-70ebbe4000 r--s 00000000 00:10 16581                          /dev/__properties__/u:object_r:dalvik_prop:s0
 70ebbe4000-70ebbe5000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70ebbe5000-70ebbe6000 r--s 00004000 fc:00 877                            /system/framework/apache-xml.jar
-70ebbe6000-70ebbe8000 rw-p 00000000 00:05 10270993                       /dev/ashmem/dalvik-indirect ref table (deleted)
-70ebbe8000-70ebbec000 rw-p 00000000 00:05 10270761                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebbec000-70ebbf0000 rw-p 00000000 00:05 10270760                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebbf0000-70ebbf4000 rw-p 00000000 00:05 10270759                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebbf4000-70ebbf8000 rw-p 00000000 00:05 10270758                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebbe6000-70ebbe8000 rw-p 00000000 00:05 10270993                       [anon:dalvik-indirect ref table]
+70ebbe8000-70ebbec000 rw-p 00000000 00:05 10270761                       [anon:dalvik-thread local mark stack]
+70ebbec000-70ebbf0000 rw-p 00000000 00:05 10270760                       [anon:dalvik-thread local mark stack]
+70ebbf0000-70ebbf4000 rw-p 00000000 00:05 10270759                       [anon:dalvik-thread local mark stack]
+70ebbf4000-70ebbf8000 rw-p 00000000 00:05 10270758                       [anon:dalvik-thread local mark stack]
 70ebbf8000-70ebbf9000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70ebbf9000-70ebbfa000 r--s 00000000 fc:00 968                            /system/framework/bouncycastle.jar
-70ebbfa000-70ebbfc000 rw-p 00000000 00:05 10270992                       /dev/ashmem/dalvik-indirect ref table (deleted)
-70ebbfc000-70ebc00000 rw-p 00000000 00:05 10270757                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc00000-70ebc04000 rw-p 00000000 00:05 10270756                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc04000-70ebc08000 rw-p 00000000 00:05 10270755                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc08000-70ebc0c000 rw-p 00000000 00:05 10270754                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc0c000-70ebc10000 rw-p 00000000 00:05 10270753                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc10000-70ebc14000 rw-p 00000000 00:05 10270752                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebbfa000-70ebbfc000 rw-p 00000000 00:05 10270992                       [anon:dalvik-indirect ref table]
+70ebbfc000-70ebc00000 rw-p 00000000 00:05 10270757                       [anon:dalvik-thread local mark stack]
+70ebc00000-70ebc04000 rw-p 00000000 00:05 10270756                       [anon:dalvik-thread local mark stack]
+70ebc04000-70ebc08000 rw-p 00000000 00:05 10270755                       [anon:dalvik-thread local mark stack]
+70ebc08000-70ebc0c000 rw-p 00000000 00:05 10270754                       [anon:dalvik-thread local mark stack]
+70ebc0c000-70ebc10000 rw-p 00000000 00:05 10270753                       [anon:dalvik-thread local mark stack]
+70ebc10000-70ebc14000 rw-p 00000000 00:05 10270752                       [anon:dalvik-thread local mark stack]
 70ebc14000-70ebc15000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70ebc15000-70ebc16000 r--s 00000000 fc:00 960                            /system/framework/okhttp.jar
-70ebc16000-70ebc1a000 rw-p 00000000 00:05 10270751                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc1a000-70ebc1e000 rw-p 00000000 00:05 10270750                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebc16000-70ebc1a000 rw-p 00000000 00:05 10270751                       [anon:dalvik-thread local mark stack]
+70ebc1a000-70ebc1e000 rw-p 00000000 00:05 10270750                       [anon:dalvik-thread local mark stack]
 70ebc1e000-70ebc3e000 r--s 00000000 00:10 16584                          /dev/__properties__/u:object_r:default_prop:s0
 70ebc3e000-70ebc3f000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70ebc3f000-70ebc40000 r--s 00000000 fc:00 974                            /system/framework/conscrypt.jar
-70ebc40000-70ebc42000 rw-p 00000000 00:05 10269719                       /dev/ashmem/dalvik-indirect ref table (deleted)
-70ebc42000-70ebc46000 rw-p 00000000 00:05 10270749                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc46000-70ebc4a000 rw-p 00000000 00:05 10270748                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc4a000-70ebc4e000 rw-p 00000000 00:05 10270747                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebc40000-70ebc42000 rw-p 00000000 00:05 10269719                       [anon:dalvik-indirect ref table]
+70ebc42000-70ebc46000 rw-p 00000000 00:05 10270749                       [anon:dalvik-thread local mark stack]
+70ebc46000-70ebc4a000 rw-p 00000000 00:05 10270748                       [anon:dalvik-thread local mark stack]
+70ebc4a000-70ebc4e000 rw-p 00000000 00:05 10270747                       [anon:dalvik-thread local mark stack]
 70ebc4e000-70ebc4f000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
-70ebc4f000-70ebc51000 rw-p 00000000 00:05 10269718                       /dev/ashmem/dalvik-indirect ref table (deleted)
-70ebc51000-70ebc55000 rw-p 00000000 00:05 10270746                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc55000-70ebc59000 rw-p 00000000 00:05 10270745                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc59000-70ebc5d000 rw-p 00000000 00:05 10270744                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebc4f000-70ebc51000 rw-p 00000000 00:05 10269718                       [anon:dalvik-indirect ref table]
+70ebc51000-70ebc55000 rw-p 00000000 00:05 10270746                       [anon:dalvik-thread local mark stack]
+70ebc55000-70ebc59000 rw-p 00000000 00:05 10270745                       [anon:dalvik-thread local mark stack]
+70ebc59000-70ebc5d000 rw-p 00000000 00:05 10270744                       [anon:dalvik-thread local mark stack]
 70ebc5d000-70ebc7d000 r--s 00000000 00:10 16599                          /dev/__properties__/u:object_r:exported_dalvik_prop:s0
 70ebc7d000-70ebc7e000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70ebc7e000-70ebc7f000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70ebc7f000-70ebc80000 r--s 00004000 fc:00 963                            /system/framework/core-libart.jar
 70ebc80000-70ebc81000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70ebc81000-70ebc85000 rw-p 00000000 00:05 10270743                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc85000-70ebc89000 rw-p 00000000 00:05 10270742                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebc81000-70ebc85000 rw-p 00000000 00:05 10270743                       [anon:dalvik-thread local mark stack]
+70ebc85000-70ebc89000 rw-p 00000000 00:05 10270742                       [anon:dalvik-thread local mark stack]
 70ebc89000-70ebc8a000 rw-p 00000000 00:00 0                              [anon:linker_alloc_small_objects]
 70ebc8a000-70ebc8c000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70ebc8c000-70ebc8d000 r--s 0001e000 fc:00 699                            /system/framework/core-oj.jar
-70ebc8d000-70ebc91000 rw-p 00000000 00:05 10270741                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc91000-70ebc95000 rw-p 00000000 00:05 10270740                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebc95000-70ebc99000 rw-p 00000000 00:05 10270739                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebc8d000-70ebc91000 rw-p 00000000 00:05 10270741                       [anon:dalvik-thread local mark stack]
+70ebc91000-70ebc95000 rw-p 00000000 00:05 10270740                       [anon:dalvik-thread local mark stack]
+70ebc95000-70ebc99000 rw-p 00000000 00:05 10270739                       [anon:dalvik-thread local mark stack]
 70ebc99000-70ebc9b000 r--p 00000000 00:00 0                              [anon:linker_alloc]
 70ebc9b000-70ebc9c000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
-70ebc9c000-70ebca0000 rw-p 00000000 00:05 10270738                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebca0000-70ebca4000 rw-p 00000000 00:05 10270737                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebca4000-70ebca8000 rw-p 00000000 00:05 10270736                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebca8000-70ebcac000 rw-p 00000000 00:05 10270735                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebcac000-70ebcb0000 rw-p 00000000 00:05 10270734                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebc9c000-70ebca0000 rw-p 00000000 00:05 10270738                       [anon:dalvik-thread local mark stack]
+70ebca0000-70ebca4000 rw-p 00000000 00:05 10270737                       [anon:dalvik-thread local mark stack]
+70ebca4000-70ebca8000 rw-p 00000000 00:05 10270736                       [anon:dalvik-thread local mark stack]
+70ebca8000-70ebcac000 rw-p 00000000 00:05 10270735                       [anon:dalvik-thread local mark stack]
+70ebcac000-70ebcb0000 rw-p 00000000 00:05 10270734                       [anon:dalvik-thread local mark stack]
 70ebcb0000-70ebcd0000 r--s 00000000 00:10 16592                          /dev/__properties__/u:object_r:exported2_system_prop:s0
 70ebcd0000-70ebcd1000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
-70ebcd1000-70ebcd5000 rw-p 00000000 00:05 10270733                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebcd5000-70ebcd9000 rw-p 00000000 00:05 10270732                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebcd9000-70ebcdd000 rw-p 00000000 00:05 10270731                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebcdd000-70ebce1000 rw-p 00000000 00:05 10270730                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebce1000-70ebce5000 rw-p 00000000 00:05 10270729                       /dev/ashmem/dalvik-thread local mark stack (deleted)
-70ebce5000-70ebce9000 rw-p 00000000 00:05 10270728                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebcd1000-70ebcd5000 rw-p 00000000 00:05 10270733                       [anon:dalvik-thread local mark stack]
+70ebcd5000-70ebcd9000 rw-p 00000000 00:05 10270732                       [anon:dalvik-thread local mark stack]
+70ebcd9000-70ebcdd000 rw-p 00000000 00:05 10270731                       [anon:dalvik-thread local mark stack]
+70ebcdd000-70ebce1000 rw-p 00000000 00:05 10270730                       [anon:dalvik-thread local mark stack]
+70ebce1000-70ebce5000 rw-p 00000000 00:05 10270729                       [anon:dalvik-thread local mark stack]
+70ebce5000-70ebce9000 rw-p 00000000 00:05 10270728                       [anon:dalvik-thread local mark stack]
 70ebce9000-70ebcea000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70ebcea000-70ebcec000 rw-p 00000000 00:05 10270987                       /dev/ashmem/dalvik-indirect ref table (deleted)
+70ebcea000-70ebcec000 rw-p 00000000 00:05 10270987                       [anon:dalvik-indirect ref table]
 70ebcec000-70ebcf9000 r--p 00646000 103:1d 639532                        /data/dalvik-cache/arm64/system@framework@boot-framework.art
 70ebcf9000-70ebd19000 r--s 00000000 00:10 16620                          /dev/__properties__/u:object_r:log_tag_prop:s0
 70ebd19000-70ebd39000 r--s 00000000 00:10 16621                          /dev/__properties__/u:object_r:logd_prop:s0
 70ebd39000-70ebd3a000 rw-p 00000000 00:00 0                              [anon:linker_alloc]
 70ebd3a000-70ebd3b000 r--p 00000000 00:00 0                              [anon:linker_alloc]
-70ebd3b000-70ebd3f000 rw-p 00000000 00:05 10270727                       /dev/ashmem/dalvik-thread local mark stack (deleted)
+70ebd3b000-70ebd3f000 rw-p 00000000 00:05 10270727                       [anon:dalvik-thread local mark stack]
 70ebd3f000-70ebd40000 r--p 00002000 103:1d 639556                        /data/dalvik-cache/arm64/system@framework@boot-com.google.vr.platform.art
 70ebd40000-70ebd41000 r--p 00005000 103:1d 639553                        /data/dalvik-cache/arm64/system@framework@boot-android.test.base.art
 70ebd41000-70ebd42000 r--p 00000000 00:00 0                              [anon:linker_alloc]
diff --git a/libsysutils/Android.bp b/libsysutils/Android.bp
index 3a12292..29d23c8 100644
--- a/libsysutils/Android.bp
+++ b/libsysutils/Android.bp
@@ -27,3 +27,16 @@
 
     export_include_dirs: ["include"],
 }
+
+cc_test {
+    name: "libsysutils_tests",
+    test_suites: ["device-tests"],
+    srcs: [
+        "src/SocketListener_test.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "libsysutils",
+    ],
+}
diff --git a/libsysutils/include/sysutils/FrameworkListener.h b/libsysutils/include/sysutils/FrameworkListener.h
index 2137069..d9a561a 100644
--- a/libsysutils/include/sysutils/FrameworkListener.h
+++ b/libsysutils/include/sysutils/FrameworkListener.h
@@ -38,13 +38,13 @@
     FrameworkListener(const char *socketName);
     FrameworkListener(const char *socketName, bool withSeq);
     FrameworkListener(int sock);
-    virtual ~FrameworkListener() {}
+    ~FrameworkListener() override {}
 
-protected:
+  protected:
     void registerCmd(FrameworkCommand *cmd);
-    virtual bool onDataAvailable(SocketClient *c);
+    bool onDataAvailable(SocketClient* c) override;
 
-private:
+  private:
     void dispatchCommand(SocketClient *c, char *data);
     void init(const char *socketName, bool withSeq);
 };
diff --git a/libsysutils/include/sysutils/OWNERS b/libsysutils/include/sysutils/OWNERS
index b78918e..645baf4 100644
--- a/libsysutils/include/sysutils/OWNERS
+++ b/libsysutils/include/sysutils/OWNERS
@@ -1,2 +1 @@
-per-file Netlink* = ek@google.com
-per-file Netlink* = lorenzo@google.com
+per-file OWNERS,Netlink* = ek@google.com,lorenzo@google.com
diff --git a/libsysutils/include/sysutils/SocketClient.h b/libsysutils/include/sysutils/SocketClient.h
index 1004f06..c657526 100644
--- a/libsysutils/include/sysutils/SocketClient.h
+++ b/libsysutils/include/sysutils/SocketClient.h
@@ -1,8 +1,6 @@
 #ifndef _SOCKET_CLIENT_H
 #define _SOCKET_CLIENT_H
 
-#include "List.h"
-
 #include <pthread.h>
 #include <cutils/atomic.h>
 #include <sys/types.h>
@@ -35,7 +33,7 @@
     SocketClient(int sock, bool owned, bool useCmdNum);
     virtual ~SocketClient();
 
-    int getSocket() { return mSocket; }
+    int getSocket() const { return mSocket; }
     pid_t getPid() const { return mPid; }
     uid_t getUid() const { return mUid; }
     gid_t getGid() const { return mGid; }
@@ -84,5 +82,4 @@
     int sendDataLockedv(struct iovec *iov, int iovcnt);
 };
 
-typedef android::sysutils::List<SocketClient *> SocketClientCollection;
 #endif
diff --git a/libsysutils/include/sysutils/SocketListener.h b/libsysutils/include/sysutils/SocketListener.h
index bc93b86..67a691a 100644
--- a/libsysutils/include/sysutils/SocketListener.h
+++ b/libsysutils/include/sysutils/SocketListener.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2014 The Android Open Source Project
+ * 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.
@@ -18,6 +18,8 @@
 
 #include <pthread.h>
 
+#include <unordered_map>
+
 #include <sysutils/SocketClient.h>
 #include "SocketClientCommand.h"
 
@@ -25,7 +27,7 @@
     bool                    mListen;
     const char              *mSocketName;
     int                     mSock;
-    SocketClientCollection  *mClients;
+    std::unordered_map<int, SocketClient*> mClients;
     pthread_mutex_t         mClientsLock;
     int                     mCtrlPipe[2];
     pthread_t               mThread;
@@ -51,8 +53,13 @@
     virtual bool onDataAvailable(SocketClient *c) = 0;
 
 private:
-    bool release(SocketClient *c, bool wakeup);
     static void *threadStart(void *obj);
+
+    // Add all clients to a separate list, so we don't have to hold the lock
+    // while processing it.
+    std::vector<SocketClient*> snapshotClients();
+
+    bool release(SocketClient *c, bool wakeup);
     void runListener();
     void init(const char *socketName, int socketFd, bool listen, bool useCmdNum);
 };
diff --git a/libsysutils/src/OWNERS b/libsysutils/src/OWNERS
index b78918e..645baf4 100644
--- a/libsysutils/src/OWNERS
+++ b/libsysutils/src/OWNERS
@@ -1,2 +1 @@
-per-file Netlink* = ek@google.com
-per-file Netlink* = lorenzo@google.com
+per-file OWNERS,Netlink* = ek@google.com,lorenzo@google.com
diff --git a/libsysutils/src/SocketListener.cpp b/libsysutils/src/SocketListener.cpp
index 0c8a688..ded5adb 100644
--- a/libsysutils/src/SocketListener.cpp
+++ b/libsysutils/src/SocketListener.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2014 The Android Open Source Project
+ * 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.
@@ -19,13 +19,15 @@
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <sys/select.h>
+#include <sys/poll.h>
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/un.h>
 #include <unistd.h>
 
+#include <vector>
+
 #include <cutils/sockets.h>
 #include <log/log.h>
 #include <sysutils/SocketListener.h>
@@ -52,7 +54,6 @@
     mSock = socketFd;
     mUseCmdNum = useCmdNum;
     pthread_mutex_init(&mClientsLock, nullptr);
-    mClients = new SocketClientCollection();
 }
 
 SocketListener::~SocketListener() {
@@ -63,12 +64,9 @@
         close(mCtrlPipe[0]);
         close(mCtrlPipe[1]);
     }
-    SocketClientCollection::iterator it;
-    for (it = mClients->begin(); it != mClients->end();) {
-        (*it)->decRef();
-        it = mClients->erase(it);
+    for (auto pair : mClients) {
+        pair.second->decRef();
     }
-    delete mClients;
 }
 
 int SocketListener::startListener() {
@@ -95,7 +93,7 @@
         SLOGE("Unable to listen on socket (%s)", strerror(errno));
         return -1;
     } else if (!mListen)
-        mClients->push_back(new SocketClient(mSock, false, mUseCmdNum));
+        mClients[mSock] = new SocketClient(mSock, false, mUseCmdNum);
 
     if (pipe(mCtrlPipe)) {
         SLOGE("pipe failed (%s)", strerror(errno));
@@ -135,11 +133,10 @@
         mSock = -1;
     }
 
-    SocketClientCollection::iterator it;
-    for (it = mClients->begin(); it != mClients->end();) {
-        delete (*it);
-        it = mClients->erase(it);
+    for (auto pair : mClients) {
+        delete pair.second;
     }
+    mClients.clear();
     return 0;
 }
 
@@ -152,47 +149,30 @@
 }
 
 void SocketListener::runListener() {
-
-    SocketClientCollection pendingList;
-
-    while(1) {
-        SocketClientCollection::iterator it;
-        fd_set read_fds;
-        int rc = 0;
-        int max = -1;
-
-        FD_ZERO(&read_fds);
-
-        if (mListen) {
-            max = mSock;
-            FD_SET(mSock, &read_fds);
-        }
-
-        FD_SET(mCtrlPipe[0], &read_fds);
-        if (mCtrlPipe[0] > max)
-            max = mCtrlPipe[0];
+    while (true) {
+        std::vector<pollfd> fds;
 
         pthread_mutex_lock(&mClientsLock);
-        for (it = mClients->begin(); it != mClients->end(); ++it) {
+        fds.reserve(2 + mClients.size());
+        fds.push_back({.fd = mCtrlPipe[0], .events = POLLIN});
+        if (mListen) fds.push_back({.fd = mSock, .events = POLLIN});
+        for (auto pair : mClients) {
             // NB: calling out to an other object with mClientsLock held (safe)
-            int fd = (*it)->getSocket();
-            FD_SET(fd, &read_fds);
-            if (fd > max) {
-                max = fd;
-            }
+            const int fd = pair.second->getSocket();
+            if (fd != pair.first) SLOGE("fd mismatch: %d != %d", fd, pair.first);
+            fds.push_back({.fd = fd, .events = POLLIN});
         }
         pthread_mutex_unlock(&mClientsLock);
-        SLOGV("mListen=%d, max=%d, mSocketName=%s", mListen, max, mSocketName);
-        if ((rc = select(max + 1, &read_fds, nullptr, nullptr, nullptr)) < 0) {
-            if (errno == EINTR)
-                continue;
-            SLOGE("select failed (%s) mListen=%d, max=%d", strerror(errno), mListen, max);
+
+        SLOGV("mListen=%d, mSocketName=%s", mListen, mSocketName);
+        int rc = TEMP_FAILURE_RETRY(poll(fds.data(), fds.size(), -1));
+        if (rc < 0) {
+            SLOGE("poll failed (%s) mListen=%d", strerror(errno), mListen);
             sleep(1);
             continue;
-        } else if (!rc)
-            continue;
+        }
 
-        if (FD_ISSET(mCtrlPipe[0], &read_fds)) {
+        if (fds[0].revents & (POLLIN | POLLERR)) {
             char c = CtrlPipe_Shutdown;
             TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
             if (c == CtrlPipe_Shutdown) {
@@ -200,7 +180,7 @@
             }
             continue;
         }
-        if (mListen && FD_ISSET(mSock, &read_fds)) {
+        if (mListen && (fds[1].revents & (POLLIN | POLLERR))) {
             int c = TEMP_FAILURE_RETRY(accept4(mSock, nullptr, nullptr, SOCK_CLOEXEC));
             if (c < 0) {
                 SLOGE("accept failed (%s)", strerror(errno));
@@ -208,32 +188,33 @@
                 continue;
             }
             pthread_mutex_lock(&mClientsLock);
-            mClients->push_back(new SocketClient(c, true, mUseCmdNum));
+            mClients[c] = new SocketClient(c, true, mUseCmdNum);
             pthread_mutex_unlock(&mClientsLock);
         }
 
-        /* Add all active clients to the pending list first */
-        pendingList.clear();
+        // Add all active clients to the pending list first, so we can release
+        // the lock before invoking the callbacks.
+        std::vector<SocketClient*> pending;
         pthread_mutex_lock(&mClientsLock);
-        for (it = mClients->begin(); it != mClients->end(); ++it) {
-            SocketClient* c = *it;
-            // NB: calling out to an other object with mClientsLock held (safe)
-            int fd = c->getSocket();
-            if (FD_ISSET(fd, &read_fds)) {
-                pendingList.push_back(c);
+        const int size = fds.size();
+        for (int i = mListen ? 2 : 1; i < size; ++i) {
+            const struct pollfd& p = fds[i];
+            if (p.revents & (POLLIN | POLLERR)) {
+                auto it = mClients.find(p.fd);
+                if (it == mClients.end()) {
+                    SLOGE("fd vanished: %d", p.fd);
+                    continue;
+                }
+                SocketClient* c = it->second;
+                pending.push_back(c);
                 c->incRef();
             }
         }
         pthread_mutex_unlock(&mClientsLock);
 
-        /* Process the pending list, since it is owned by the thread,
-         * there is no need to lock it */
-        while (!pendingList.empty()) {
-            /* Pop the first item from the list */
-            it = pendingList.begin();
-            SocketClient* c = *it;
-            pendingList.erase(it);
-            /* Process it, if false is returned, remove from list */
+        for (SocketClient* c : pending) {
+            // Process it, if false is returned, remove from the map
+            SLOGV("processing fd %d", c->getSocket());
             if (!onDataAvailable(c)) {
                 release(c, false);
             }
@@ -246,17 +227,10 @@
     bool ret = false;
     /* if our sockets are connection-based, remove and destroy it */
     if (mListen && c) {
-        /* Remove the client from our array */
+        /* Remove the client from our map */
         SLOGV("going to zap %d for %s", c->getSocket(), mSocketName);
         pthread_mutex_lock(&mClientsLock);
-        SocketClientCollection::iterator it;
-        for (it = mClients->begin(); it != mClients->end(); ++it) {
-            if (*it == c) {
-                mClients->erase(it);
-                ret = true;
-                break;
-            }
-        }
+        ret = (mClients.erase(c->getSocket()) != 0);
         pthread_mutex_unlock(&mClientsLock);
         if (ret) {
             ret = c->decRef();
@@ -269,26 +243,22 @@
     return ret;
 }
 
-void SocketListener::sendBroadcast(int code, const char *msg, bool addErrno) {
-    SocketClientCollection safeList;
-
-    /* Add all active clients to the safe list first */
-    safeList.clear();
+std::vector<SocketClient*> SocketListener::snapshotClients() {
+    std::vector<SocketClient*> clients;
     pthread_mutex_lock(&mClientsLock);
-    SocketClientCollection::iterator i;
-
-    for (i = mClients->begin(); i != mClients->end(); ++i) {
-        SocketClient* c = *i;
+    clients.reserve(mClients.size());
+    for (auto pair : mClients) {
+        SocketClient* c = pair.second;
         c->incRef();
-        safeList.push_back(c);
+        clients.push_back(c);
     }
     pthread_mutex_unlock(&mClientsLock);
 
-    while (!safeList.empty()) {
-        /* Pop the first item from the list */
-        i = safeList.begin();
-        SocketClient* c = *i;
-        safeList.erase(i);
+    return clients;
+}
+
+void SocketListener::sendBroadcast(int code, const char *msg, bool addErrno) {
+    for (SocketClient* c : snapshotClients()) {
         // broadcasts are unsolicited and should not include a cmd number
         if (c->sendMsg(code, msg, addErrno, false)) {
             SLOGW("Error sending broadcast (%s)", strerror(errno));
@@ -298,25 +268,7 @@
 }
 
 void SocketListener::runOnEachSocket(SocketClientCommand *command) {
-    SocketClientCollection safeList;
-
-    /* Add all active clients to the safe list first */
-    safeList.clear();
-    pthread_mutex_lock(&mClientsLock);
-    SocketClientCollection::iterator i;
-
-    for (i = mClients->begin(); i != mClients->end(); ++i) {
-        SocketClient* c = *i;
-        c->incRef();
-        safeList.push_back(c);
-    }
-    pthread_mutex_unlock(&mClientsLock);
-
-    while (!safeList.empty()) {
-        /* Pop the first item from the list */
-        i = safeList.begin();
-        SocketClient* c = *i;
-        safeList.erase(i);
+    for (SocketClient* c : snapshotClients()) {
         command->runSocketCommand(c);
         c->decRef();
     }
diff --git a/libsysutils/src/SocketListener_test.cpp b/libsysutils/src/SocketListener_test.cpp
new file mode 100644
index 0000000..fed4546
--- /dev/null
+++ b/libsysutils/src/SocketListener_test.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2018 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 <sysutils/FrameworkListener.h>
+
+#include <poll.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <algorithm>
+#include <memory>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <cutils/sockets.h>
+#include <gtest/gtest.h>
+
+using android::base::unique_fd;
+
+namespace {
+
+std::string testSocketPath() {
+    const testing::TestInfo* const test_info =
+            testing::UnitTest::GetInstance()->current_test_info();
+    return std::string(ANDROID_SOCKET_DIR "/") + std::string(test_info->test_case_name()) +
+           std::string(".") + std::string(test_info->name());
+}
+
+unique_fd serverSocket(const std::string& path) {
+    unlink(path.c_str());
+
+    unique_fd fd(socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
+    EXPECT_GE(fd.get(), 0);
+
+    struct sockaddr_un addr = {.sun_family = AF_UNIX};
+    strlcpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path));
+
+    EXPECT_EQ(bind(fd.get(), reinterpret_cast<sockaddr*>(&addr), sizeof(addr)), 0)
+            << "bind() to " << path << " failed: " << strerror(errno);
+    EXPECT_EQ(android_get_control_socket(path.c_str()), -1);
+
+    return fd;
+}
+
+unique_fd clientSocket(const std::string& path) {
+    unique_fd fd(socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
+    EXPECT_GE(fd.get(), 0);
+
+    struct sockaddr_un addr = {.sun_family = AF_UNIX};
+    strlcpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path));
+
+    EXPECT_EQ(0, connect(fd.get(), reinterpret_cast<sockaddr*>(&addr), sizeof(addr)))
+            << "connect() to " << path << " failed: " << strerror(errno);
+
+    return fd;
+}
+
+void sendCmd(int fd, const char* cmd) {
+    EXPECT_TRUE(android::base::WriteFully(fd, cmd, strlen(cmd) + 1))
+            << "write() to socket failed: " << strerror(errno);
+}
+
+std::string recvReply(int fd) {
+    pollfd fds = {.fd = fd, .events = POLLIN};
+    int poll_events = poll(&fds, 1, -1);
+    EXPECT_EQ(1, poll_events);
+
+    // Technically, this one-shot read() is incorrect: we should keep on
+    // reading the socket until we get a \0. But this is also how
+    // FrameworkListener::onDataAvailable() reads, and it works because
+    // replies are always send with a single write() call, and clients
+    // always read replies before queueing the next command.
+    char buf[1024];
+    ssize_t len = read(fd, buf, sizeof(buf));
+    EXPECT_GE(len, 0) << "read() from socket failed: " << strerror(errno);
+    return len > 0 ? std::string(buf, buf + len) : "";
+}
+
+// Test command which echoes back all its arguments as a comma-separated list.
+// Always returns error code 42
+//
+// TODO: enable testing replies with addErrno=true and useCmdNum=true
+class TestCommand : public FrameworkCommand {
+  public:
+    TestCommand() : FrameworkCommand("test") {}
+    ~TestCommand() override {}
+
+    int runCommand(SocketClient* cli, int argc, char** argv) {
+        std::vector<std::string> args(argv, argv + argc);
+        std::string reply = android::base::Join(args, ',');
+        cli->sendMsg(42, reply.c_str(), /*addErrno=*/false, /*useCmdNum=*/false);
+        return 0;
+    }
+};
+
+// A test listener with a single command.
+class TestListener : public FrameworkListener {
+  public:
+    TestListener(int fd) : FrameworkListener(fd) {
+        registerCmd(new TestCommand);  // Leaked :-(
+    }
+};
+
+}  // unnamed namespace
+
+class FrameworkListenerTest : public testing::Test {
+  public:
+    FrameworkListenerTest() {
+        mSocketPath = testSocketPath();
+        mSserverFd = serverSocket(mSocketPath);
+        mListener = std::make_unique<TestListener>(mSserverFd.get());
+        EXPECT_EQ(0, mListener->startListener());
+    }
+
+    ~FrameworkListenerTest() override {
+        EXPECT_EQ(0, mListener->stopListener());
+
+        // Wouldn't it be cool if unique_fd had an option for taking care of this?
+        unlink(mSocketPath.c_str());
+    }
+
+    void testCommand(const char* command, const char* expected) {
+        unique_fd client_fd = clientSocket(mSocketPath);
+        sendCmd(client_fd.get(), command);
+
+        std::string reply = recvReply(client_fd.get());
+        EXPECT_EQ(std::string(expected) + '\0', reply);
+    }
+
+  protected:
+    std::string mSocketPath;
+    unique_fd mSserverFd;
+    std::unique_ptr<TestListener> mListener;
+};
+
+TEST_F(FrameworkListenerTest, DoesNothing) {
+    // Let the test harness start and stop a FrameworkListener
+    // without sending any commands through it.
+}
+
+TEST_F(FrameworkListenerTest, DispatchesValidCommands) {
+    testCommand("test", "42 test");
+    testCommand("test arg1 arg2", "42 test,arg1,arg2");
+    testCommand("test \"arg1 still_arg1\" arg2", "42 test,arg1 still_arg1,arg2");
+    testCommand("test \"escaped quote: '\\\"'\"", "42 test,escaped quote: '\"'");
+
+    // Perhaps this behavior was unintended, but would be good to detect any
+    // changes, in case anyone depends on it.
+    testCommand("test   ", "42 test,,,");
+}
+
+TEST_F(FrameworkListenerTest, RejectsInvalidCommands) {
+    testCommand("unknown arg1 arg2", "500 Command not recognized");
+    testCommand("test \"arg1 arg2", "500 Unclosed quotes error");
+    testCommand("test \\a", "500 Unsupported escape sequence");
+}
+
+TEST_F(FrameworkListenerTest, MultipleClients) {
+    unique_fd client1 = clientSocket(mSocketPath);
+    unique_fd client2 = clientSocket(mSocketPath);
+    sendCmd(client1.get(), "test 1");
+    sendCmd(client2.get(), "test 2");
+
+    EXPECT_EQ(std::string("42 test,2") + '\0', recvReply(client2.get()));
+    EXPECT_EQ(std::string("42 test,1") + '\0', recvReply(client1.get()));
+}
diff --git a/libutils/Android.bp b/libutils/Android.bp
index d635e65..1c1bdf7 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -58,7 +58,6 @@
         "-Wall",
         "-Werror",
     ],
-    include_dirs: ["external/safe-iop/include"],
     header_libs: [
         "libutils_headers",
     ],
diff --git a/libutils/VectorImpl.cpp b/libutils/VectorImpl.cpp
index 00a904d..e16f88d 100644
--- a/libutils/VectorImpl.cpp
+++ b/libutils/VectorImpl.cpp
@@ -24,8 +24,6 @@
 
 #include <log/log.h>
 
-#include <safe_iop.h>
-
 #include "SharedBuffer.h"
 
 /*****************************************************************************/
@@ -342,7 +340,7 @@
     }
 
     size_t new_allocation_size = 0;
-    LOG_ALWAYS_FATAL_IF(!safe_mul(&new_allocation_size, new_capacity, mItemSize));
+    LOG_ALWAYS_FATAL_IF(__builtin_mul_overflow(new_capacity, mItemSize, &new_allocation_size));
     SharedBuffer* sb = SharedBuffer::alloc(new_allocation_size);
     if (sb) {
         void* array = sb->data();
@@ -386,7 +384,7 @@
             this, (int)where, (int)amount, (int)mCount); // caller already checked
 
     size_t new_size;
-    LOG_ALWAYS_FATAL_IF(!safe_add(&new_size, mCount, amount), "new_size overflow");
+    LOG_ALWAYS_FATAL_IF(__builtin_add_overflow(mCount, amount, &new_size), "new_size overflow");
 
     if (capacity() < new_size) {
         // NOTE: This implementation used to resize vectors as per ((3*x + 1) / 2)
@@ -397,17 +395,18 @@
         //
         // This approximates the old calculation, using (x + (x/2) + 1) instead.
         size_t new_capacity = 0;
-        LOG_ALWAYS_FATAL_IF(!safe_add(&new_capacity, new_size, (new_size / 2)),
+        LOG_ALWAYS_FATAL_IF(__builtin_add_overflow(new_size, (new_size / 2), &new_capacity),
                             "new_capacity overflow");
-        LOG_ALWAYS_FATAL_IF(!safe_add(&new_capacity, new_capacity, static_cast<size_t>(1u)),
-                            "new_capacity overflow");
+        LOG_ALWAYS_FATAL_IF(
+                __builtin_add_overflow(new_capacity, static_cast<size_t>(1u), &new_capacity),
+                "new_capacity overflow");
         new_capacity = max(kMinVectorCapacity, new_capacity);
 
         size_t new_alloc_size = 0;
-        LOG_ALWAYS_FATAL_IF(!safe_mul(&new_alloc_size, new_capacity, mItemSize),
+        LOG_ALWAYS_FATAL_IF(__builtin_mul_overflow(new_capacity, mItemSize, &new_alloc_size),
                             "new_alloc_size overflow");
 
-//        ALOGV("grow vector %p, new_capacity=%d", this, (int)new_capacity);
+        // ALOGV("grow vector %p, new_capacity=%d", this, (int)new_capacity);
         if ((mStorage) &&
             (mCount==where) &&
             (mFlags & HAS_TRIVIAL_COPY) &&
@@ -464,7 +463,7 @@
             this, (int)where, (int)amount, (int)mCount); // caller already checked
 
     size_t new_size;
-    LOG_ALWAYS_FATAL_IF(!safe_sub(&new_size, mCount, amount));
+    LOG_ALWAYS_FATAL_IF(__builtin_sub_overflow(mCount, amount, &new_size));
 
     if (new_size < (capacity() / 2)) {
         // NOTE: (new_size * 2) is safe because capacity didn't overflow and
diff --git a/libziparchive/Android.bp b/libziparchive/Android.bp
index 2606aa9..fd3f602 100644
--- a/libziparchive/Android.bp
+++ b/libziparchive/Android.bp
@@ -158,4 +158,5 @@
         "libbase",
         "libziparchive",
     ],
+    recovery_available: true,
 }
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index 9536fc7..add6e14 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -41,6 +41,7 @@
 #include <android-base/logging.h>
 #include <android-base/macros.h>  // TEMP_FAILURE_RETRY may or may not be in unistd
 #include <android-base/memory.h>
+#include <android-base/utf8.h>
 #include <log/log.h>
 #include <utils/Compat.h>
 #include <utils/FileMap.h>
@@ -471,7 +472,7 @@
 }
 
 int32_t OpenArchive(const char* fileName, ZipArchiveHandle* handle) {
-  const int fd = open(fileName, O_RDONLY | O_BINARY, 0);
+  const int fd = ::android::base::utf8::open(fileName, O_RDONLY | O_BINARY, 0);
   ZipArchive* archive = new ZipArchive(fd, true);
   *handle = archive;
 
diff --git a/llkd/Android.bp b/llkd/Android.bp
index a6edd26..62a637d 100644
--- a/llkd/Android.bp
+++ b/llkd/Android.bp
@@ -20,6 +20,12 @@
     export_include_dirs: ["include"],
 
     cflags: ["-Werror"],
+
+    product_variables: {
+        debuggable: {
+            cppflags: ["-D__PTRACE_ENABLED__"],
+        },
+    },
 }
 
 cc_binary {
@@ -39,4 +45,9 @@
     cflags: ["-Werror"],
 
     init_rc: ["llkd.rc"],
+    product_variables: {
+        debuggable: {
+            init_rc: ["llkd-debuggable.rc"],
+	},
+    },
 }
diff --git a/llkd/README.md b/llkd/README.md
index 2314583..1f69718 100644
--- a/llkd/README.md
+++ b/llkd/README.md
@@ -23,6 +23,9 @@
 Operations
 ----------
 
+There are two detection scenarios. Persistent D or Z state, and persistent
+stack signature.
+
 If a thread is in D or Z state with no forward progress for longer than
 ro.llk.timeout_ms, or ro.llk.[D|Z].timeout_ms, kill the process or parent
 process respectively.  If another scan shows the same process continues to
@@ -32,6 +35,26 @@
 double the expected time to flow through the mainloop.  Sampling is every
 ro.llk_sample_ms.
 
+For usedebug releases only, persistent stack signature checking is enabled.
+If a thread in any state but Z, has a persistent listed ro.llk.stack kernel
+symbol always being reported, even if there is forward scheduling progress, for
+longer than ro.llk.timeout_ms, or ro.llk.stack.timeout_ms, then issue a kill
+to the process.  If another scan shows the same process continues to exist,
+then have a confirmed live-lock condition and need to panic.  There is no
+ABA detection since forward scheduling progress is allowed, thus the condition
+for the symbols are:
+
+- Check is looking for " " + __symbol__+ "0x" in /proc/<pid>/stack.
+- The __symbol__ should be rare and short lived enough that on a typical
+  system the function is seen at most only once in a sample over the timeout
+  period of ro.llk.stack.timeout_ms, samples occur every ro.llk.check_ms. This
+  can be the only way to prevent a false trigger as there is no ABA protection.
+- Persistent continuously when the live lock condition exists.
+- Should be just below the function that is calling the lock that could
+  contend, because if the lock is below or in the symbol function, the
+  symbol will show in all affected processes, not just the one that
+  caused the lockup.
+
 Default will not monitor init, or [kthreadd] and all that [kthreadd] spawns.
 This reduces the effectiveness of llkd by limiting its coverage.  If there is
 value in covering [kthreadd] spawned threads, the requirement will be that
@@ -40,7 +63,9 @@
 coding hygiene, a common request to add such to publicly reviewed kernel.org
 maintained drivers).  For instance use wait_event_interruptible() instead of
 wait_event().  The blacklists can be adjusted accordingly if these
-conditions are met to cover kernel components.
+conditions are met to cover kernel components.  For the stack symbol checking,
+there is an additional process blacklist so that we do not incide sepolicy
+violations on services that block ptrace operations.
 
 An accompanying gTest set have been added, and will setup a persistent D or Z
 process, with and without forward progress, but not in a live-lock state
@@ -93,14 +118,31 @@
 #### ro.llk.Z.timeout_ms
 default ro.llk.timeout_ms, Z maximum timelimit.
 
+#### ro.llk.stack.timeout_ms
+default ro.llk.timeout_ms,
+checking for persistent stack symbols maximum timelimit.
+Only active on userdebug and eng builds.
+
 #### ro.llk.check_ms
 default 2 minutes samples of threads for D or Z.
 
+#### ro.llk.stack
+default *empty* or false, comma separated list of kernel symbols.
+The string "*false*" is the equivalent to an *empty* list.
+Look for kernel stack symbols that if ever persistently present can
+indicate a subsystem is locked up.
+Beware, check does not on purpose do forward scheduling ABA except by polling
+every ro.llk_check_ms over the period ro.llk.stack.timeout_ms, so stack symbol
+should be exceptionally rare and fleeting.
+One must be convinced that it is virtually *impossible* for symbol to show up
+persistently in all samples of the stack.
+Only active on userdebug and eng builds.
+
 #### ro.llk.blacklist.process
 default 0,1,2 (kernel, init and [kthreadd]) plus process names
 init,[kthreadd],[khungtaskd],lmkd,lmkd.llkd,llkd,watchdogd,
 [watchdogd],[watchdogd/0],...,[watchdogd/***get_nprocs**-1*].
-The string false is the equivalent to an empty list.
+The string "*false*" is the equivalent to an *empty* list.
 Do not watch these processes.  A process can be comm, cmdline or pid reference.
 NB: automated default here can be larger than the current maximum property
 size of 92.
@@ -108,15 +150,23 @@
 
 #### ro.llk.blacklist.parent
 default 0,2 (kernel and [kthreadd]).
-The string false is the equivalent to an empty list.
+The string "*false*" is the equivalent to an *empty* list.
 Do not watch processes that have this parent.
 A parent process can be comm, cmdline or pid reference.
 
 #### ro.llk.blacklist.uid
 default *empty* or false, comma separated list of uid numbers or names.
-The string false is the equivalent to an empty list.
+The string "*false*" is the equivalent to an *empty* list.
 Do not watch processes that match this uid.
 
+#### ro.llk.blacklist.process.stack
+default process names init,lmkd,lmkd.llkd,llkd,keystore,logd.
+The string "*false*" is the equivalent to an *empty* list.
+This subset of processes are not monitored for live lock stack signatures.
+Also prevents the sepolicy violation associated with processes that block
+ptrace, as these can not be checked anyways.
+Only active on userdebug and eng builds.
+
 Architectural Concerns
 ----------------------
 
diff --git a/llkd/include/llkd.h b/llkd/include/llkd.h
index e3ae4bb..d0188ec 100644
--- a/llkd/include/llkd.h
+++ b/llkd/include/llkd.h
@@ -43,9 +43,12 @@
 #define KHT_TIMEOUT_PROPERTY           "ro.khungtask.timeout"
 #define LLK_D_TIMEOUT_MS_PROPERTY      "ro.llk.D.timeout_ms"
 #define LLK_Z_TIMEOUT_MS_PROPERTY      "ro.llk.Z.timeout_ms"
+#define LLK_STACK_TIMEOUT_MS_PROPERTY  "ro.llk.stack.timeout_ms"
 #define LLK_CHECK_MS_PROPERTY          "ro.llk.check_ms"
 /* LLK_CHECK_MS_DEFAULT = actual timeout_ms / LLK_CHECKS_PER_TIMEOUT_DEFAULT */
 #define LLK_CHECKS_PER_TIMEOUT_DEFAULT 5
+#define LLK_CHECK_STACK_PROPERTY       "ro.llk.stack"
+#define LLK_CHECK_STACK_DEFAULT        ""
 #define LLK_BLACKLIST_PROCESS_PROPERTY "ro.llk.blacklist.process"
 #define LLK_BLACKLIST_PROCESS_DEFAULT  \
     "0,1,2,init,[kthreadd],[khungtaskd],lmkd,lmkd.llkd,llkd,watchdogd,[watchdogd],[watchdogd/0]"
@@ -53,6 +56,8 @@
 #define LLK_BLACKLIST_PARENT_DEFAULT   "0,2,[kthreadd]"
 #define LLK_BLACKLIST_UID_PROPERTY     "ro.llk.blacklist.uid"
 #define LLK_BLACKLIST_UID_DEFAULT      ""
+#define LLK_BLACKLIST_STACK_PROPERTY   "ro.llk.blacklist.process.stack"
+#define LLK_BLACKLIST_STACK_DEFAULT    "init,lmkd.llkd,llkd,keystore,/system/bin/keystore"
 /* clang-format on */
 
 __END_DECLS
diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp
index 48551f2..58c2ba8 100644
--- a/llkd/libllkd.cpp
+++ b/llkd/libllkd.cpp
@@ -73,7 +73,14 @@
 bool llkMlockall = LLK_MLOCKALL_DEFAULT;             // run mlocked
 bool llkTestWithKill = LLK_KILLTEST_DEFAULT;         // issue test kills
 milliseconds llkTimeoutMs = LLK_TIMEOUT_MS_DEFAULT;  // default timeout
-enum { llkStateD, llkStateZ, llkNumStates };         // state indexes
+enum {                                               // enum of state indexes
+    llkStateD,                                       // Persistent 'D' state
+    llkStateZ,                                       // Persistent 'Z' state
+#ifdef __PTRACE_ENABLED__                            // Extra privileged states
+    llkStateStack,                                   // stack signature
+#endif                                               // End of extra privilege
+    llkNumStates,                                    // Maxumum number of states
+};                                                   // state indexes
 milliseconds llkStateTimeoutMs[llkNumStates];        // timeout override for each detection state
 milliseconds llkCheckMs;                             // checking interval to inspect any
                                                      // persistent live-locked states
@@ -83,6 +90,10 @@
 // Provides a wide angle of margin b/c khtTimeout is also its granularity.
 seconds khtTimeout = duration_cast<seconds>(llkTimeoutMs * (1 + LLK_CHECKS_PER_TIMEOUT_DEFAULT) /
                                             LLK_CHECKS_PER_TIMEOUT_DEFAULT);
+#ifdef __PTRACE_ENABLED__
+// list of stack symbols to search for persistence.
+std::unordered_set<std::string> llkCheckStackSymbols;
+#endif
 
 // Blacklist variables, initialized with comma separated lists of high false
 // positive and/or dangerous references, e.g. without self restart, for pid,
@@ -97,6 +108,11 @@
 std::unordered_set<std::string> llkBlacklistParent;
 // list of uids, and uid names, to skip, default nothing
 std::unordered_set<std::string> llkBlacklistUid;
+#ifdef __PTRACE_ENABLED__
+// list of names to skip stack checking. "init", "lmkd", "llkd", "keystore" or
+// "logd" (if not userdebug).
+std::unordered_set<std::string> llkBlacklistStack;
+#endif
 
 class dir {
   public:
@@ -263,6 +279,9 @@
                                    // forward scheduling progress.
     milliseconds update;           // llkUpdate millisecond signature of last.
     milliseconds count;            // duration in state.
+#ifdef __PTRACE_ENABLED__          // Privileged state checking
+    milliseconds count_stack;      // duration where stack is stagnant.
+#endif                             // End privilege
     pid_t pid;                     // /proc/<pid> before iterating through
                                    // /proc/<pid>/task/<tid> for threads.
     pid_t ppid;                    // /proc/<tid>/stat field 4 parent pid.
@@ -272,6 +291,9 @@
     std::string cmdline;           // cached /cmdline content
     char state;                    // /proc/<tid>/stat field 3: Z or D
                                    // (others we do not monitor: S, R, T or ?)
+#ifdef __PTRACE_ENABLED__          // Privileged state checking
+    char stack;                    // index in llkCheckStackSymbols for matches
+#endif                             // and with maximum index PROP_VALUE_MAX/2.
     char comm[TASK_COMM_LEN + 3];  // space for adding '[' and ']'
     bool exeMissingValid;          // exeMissing has been cached
     bool cmdlineValid;             // cmdline has been cached
@@ -286,11 +308,17 @@
           nrSwitches(0),
           update(llkUpdate),
           count(0ms),
+#ifdef __PTRACE_ENABLED__
+          count_stack(0ms),
+#endif
           pid(pid),
           ppid(ppid),
           uid(-1),
           time(time),
           state(state),
+#ifdef __PTRACE_ENABLED__
+          stack(-1),
+#endif
           exeMissingValid(false),
           cmdlineValid(false),
           updated(true),
@@ -343,6 +371,10 @@
     void reset(void) {  // reset cache, if we detected pid rollover
         uid = -1;
         state = '?';
+#ifdef __PTRACE_ENABLED__
+        count_stack = 0ms;
+        stack = -1;
+#endif
         cmdline = "";
         comm[0] = '\0';
         exeMissingValid = false;
@@ -667,6 +699,48 @@
     return ret;
 }
 
+#ifdef __PTRACE_ENABLED__
+bool llkCheckStack(proc* procp, const std::string& piddir) {
+    if (llkCheckStackSymbols.empty()) return false;
+    if (procp->state == 'Z') {  // No brains for Zombies
+        procp->stack = -1;
+        procp->count_stack = 0ms;
+        return false;
+    }
+
+    // Don't check process that are known to block ptrace, save sepolicy noise.
+    if (llkSkipName(std::to_string(procp->pid), llkBlacklistStack)) return false;
+    if (llkSkipName(procp->getComm(), llkBlacklistStack)) return false;
+    if (llkSkipName(procp->getCmdline(), llkBlacklistStack)) return false;
+
+    auto kernel_stack = ReadFile(piddir + "/stack");
+    if (kernel_stack.empty()) {
+        LOG(INFO) << piddir << "/stack empty comm=" << procp->getComm()
+                  << " cmdline=" << procp->getCmdline();
+        return false;
+    }
+    // A scheduling incident that should not reset count_stack
+    if (kernel_stack.find(" cpu_worker_pools+0x") != std::string::npos) return false;
+    char idx = -1;
+    char match = -1;
+    for (const auto& stack : llkCheckStackSymbols) {
+        if (++idx < 0) break;
+        if (kernel_stack.find(" "s + stack + "+0x") != std::string::npos) {
+            match = idx;
+            break;
+        }
+    }
+    if (procp->stack != match) {
+        procp->stack = match;
+        procp->count_stack = 0ms;
+        return false;
+    }
+    if (match == char(-1)) return false;
+    procp->count_stack += llkCycle;
+    return procp->count_stack >= llkStateTimeoutMs[llkStateStack];
+}
+#endif
+
 // Primary ABA mitigation watching last time schedule activity happened
 void llkCheckSchedUpdate(proc* procp, const std::string& piddir) {
     // Audit finds /proc/<tid>/sched is just over 1K, and
@@ -731,13 +805,23 @@
               << LLK_TIMEOUT_MS_PROPERTY "=" << llkFormat(llkTimeoutMs) << "\n"
               << LLK_D_TIMEOUT_MS_PROPERTY "=" << llkFormat(llkStateTimeoutMs[llkStateD]) << "\n"
               << LLK_Z_TIMEOUT_MS_PROPERTY "=" << llkFormat(llkStateTimeoutMs[llkStateZ]) << "\n"
+#ifdef __PTRACE_ENABLED__
+              << LLK_STACK_TIMEOUT_MS_PROPERTY "=" << llkFormat(llkStateTimeoutMs[llkStateStack])
+              << "\n"
+#endif
               << LLK_CHECK_MS_PROPERTY "=" << llkFormat(llkCheckMs) << "\n"
+#ifdef __PTRACE_ENABLED__
+              << LLK_CHECK_STACK_PROPERTY "=" << llkFormat(llkCheckStackSymbols) << "\n"
+              << LLK_BLACKLIST_STACK_PROPERTY "=" << llkFormat(llkBlacklistStack) << "\n"
+#endif
               << LLK_BLACKLIST_PROCESS_PROPERTY "=" << llkFormat(llkBlacklistProcess) << "\n"
               << LLK_BLACKLIST_PARENT_PROPERTY "=" << llkFormat(llkBlacklistParent) << "\n"
               << LLK_BLACKLIST_UID_PROPERTY "=" << llkFormat(llkBlacklistUid);
 }
 
 void* llkThread(void* obj) {
+    prctl(PR_SET_DUMPABLE, 0);
+
     LOG(INFO) << "started";
 
     std::string name = std::to_string(::gettid());
@@ -890,9 +974,14 @@
             if (pid == myPid) {
                 break;
             }
-            if (!llkIsMonitorState(state)) {
+#ifdef __PTRACE_ENABLED__
+            // if no stack monitoring, we can quickly exit here
+            if (!llkIsMonitorState(state) && llkCheckStackSymbols.empty()) {
                 continue;
             }
+#else
+            if (!llkIsMonitorState(state)) continue;
+#endif
             if ((tid == myTid) || llkSkipPid(tid)) {
                 continue;
             }
@@ -923,12 +1012,26 @@
             // ABA mitigation watching last time schedule activity happened
             llkCheckSchedUpdate(procp, piddir);
 
-            // Can only fall through to here if registered D or Z state !!!
-            if (procp->count < llkStateTimeoutMs[(state == 'Z') ? llkStateZ : llkStateD]) {
-                LOG(VERBOSE) << state << ' ' << llkFormat(procp->count) << ' ' << ppid << "->"
-                             << pid << "->" << tid << ' ' << procp->getComm();
+#ifdef __PTRACE_ENABLED__
+            auto stuck = llkCheckStack(procp, piddir);
+            if (llkIsMonitorState(state)) {
+                if (procp->count >= llkStateTimeoutMs[(state == 'Z') ? llkStateZ : llkStateD]) {
+                    stuck = true;
+                } else if (procp->count != 0ms) {
+                    LOG(VERBOSE) << state << ' ' << llkFormat(procp->count) << ' ' << ppid << "->"
+                                 << pid << "->" << tid << ' ' << procp->getComm();
+                }
+            }
+            if (!stuck) continue;
+#else
+            if (procp->count >= llkStateTimeoutMs[(state == 'Z') ? llkStateZ : llkStateD]) {
+                if (procp->count != 0ms) {
+                    LOG(VERBOSE) << state << ' ' << llkFormat(procp->count) << ' ' << ppid << "->"
+                                 << pid << "->" << tid << ' ' << procp->getComm();
+                }
                 continue;
             }
+#endif
 
             // We have to kill it to determine difference between live lock
             // and persistent state blocked on a resource.  Is there something
@@ -967,12 +1070,13 @@
                         // not working is we kill a process that likes to
                         // stay in 'D' state, instead of panicing the
                         // kernel (worse).
-                        LOG(WARNING) << "D " << llkFormat(procp->count) << ' ' << pid << "->" << tid
-                                     << ' ' << procp->getComm() << " [kill]";
+                    default:
+                        LOG(WARNING) << state << ' ' << llkFormat(procp->count) << ' ' << pid
+                                     << "->" << tid << ' ' << procp->getComm() << " [kill]";
                         if ((llkKillOneProcess(llkTidLookup(pid), procp) >= 0) ||
-                            (llkKillOneProcess(pid, 'D', tid) >= 0) ||
+                            (llkKillOneProcess(pid, state, tid) >= 0) ||
                             (llkKillOneProcess(procp, procp) >= 0) ||
-                            (llkKillOneProcess(tid, 'D', tid) >= 0)) {
+                            (llkKillOneProcess(tid, state, tid) >= 0)) {
                             continue;
                         }
                         break;
@@ -981,7 +1085,8 @@
             // We are here because we have confirmed kernel live-lock
             LOG(ERROR) << state << ' ' << llkFormat(procp->count) << ' ' << ppid << "->" << pid
                        << "->" << tid << ' ' << procp->getComm() << " [panic]";
-            llkPanicKernel(true, tid, (state == 'Z') ? "zombie" : "driver");
+            llkPanicKernel(true, tid,
+                           (state == 'Z') ? "zombie" : (state == 'D') ? "driver" : "sleeping");
         }
         LOG(VERBOSE) << "+closedir()";
     }
@@ -1039,8 +1144,9 @@
 }
 
 bool llkInit(const char* threadname) {
+    auto debuggable = android::base::GetBoolProperty("ro.debuggable", false);
     llkLowRam = android::base::GetBoolProperty("ro.config.low_ram", false);
-    if (!LLK_ENABLE_DEFAULT && android::base::GetBoolProperty("ro.debuggable", false)) {
+    if (!LLK_ENABLE_DEFAULT && debuggable) {
         llkEnable = android::base::GetProperty(LLK_ENABLE_PROPERTY, "eng") == "eng";
         khtEnable = android::base::GetProperty(KHT_ENABLE_PROPERTY, "eng") == "eng";
     }
@@ -1067,8 +1173,21 @@
     llkValidate();  // validate llkTimeoutMs, llkCheckMs and llkCycle
     llkStateTimeoutMs[llkStateD] = GetUintProperty(LLK_D_TIMEOUT_MS_PROPERTY, llkTimeoutMs);
     llkStateTimeoutMs[llkStateZ] = GetUintProperty(LLK_Z_TIMEOUT_MS_PROPERTY, llkTimeoutMs);
+#ifdef __PTRACE_ENABLED__
+    llkStateTimeoutMs[llkStateStack] = GetUintProperty(LLK_STACK_TIMEOUT_MS_PROPERTY, llkTimeoutMs);
+#endif
     llkCheckMs = GetUintProperty(LLK_CHECK_MS_PROPERTY, llkCheckMs);
     llkValidate();  // validate all (effectively minus llkTimeoutMs)
+#ifdef __PTRACE_ENABLED__
+    if (debuggable) {
+        llkCheckStackSymbols = llkSplit(
+                android::base::GetProperty(LLK_CHECK_STACK_PROPERTY, LLK_CHECK_STACK_DEFAULT));
+    }
+    std::string defaultBlacklistStack(LLK_BLACKLIST_STACK_DEFAULT);
+    if (!debuggable) defaultBlacklistStack += ",logd,/system/bin/logd";
+    llkBlacklistStack = llkSplit(
+            android::base::GetProperty(LLK_BLACKLIST_STACK_PROPERTY, defaultBlacklistStack));
+#endif
     std::string defaultBlacklistProcess(
         std::to_string(kernelPid) + "," + std::to_string(initPid) + "," +
         std::to_string(kthreaddPid) + "," + std::to_string(::getpid()) + "," +
diff --git a/llkd/llkd-debuggable.rc b/llkd/llkd-debuggable.rc
new file mode 100644
index 0000000..724cb5e
--- /dev/null
+++ b/llkd/llkd-debuggable.rc
@@ -0,0 +1,19 @@
+on property:ro.debuggable=1
+    setprop llk.enable ${ro.llk.enable:-1}
+    setprop khungtask.enable ${ro.khungtask.enable:-1}
+
+on property:ro.llk.enable=eng
+    setprop llk.enable ${ro.debuggable:-0}
+
+on property:ro.khungtask.enable=eng
+    setprop khungtask.enable ${ro.debuggable:-0}
+
+service llkd-1 /system/bin/llkd
+    class late_start
+    disabled
+    user llkd
+    group llkd readproc
+    capabilities KILL IPC_LOCK SYS_PTRACE DAC_OVERRIDE
+    file /dev/kmsg w
+    file /proc/sysrq-trigger w
+    writepid /dev/cpuset/system-background/tasks
diff --git a/llkd/llkd.cpp b/llkd/llkd.cpp
index f10253d..1920198 100644
--- a/llkd/llkd.cpp
+++ b/llkd/llkd.cpp
@@ -17,6 +17,7 @@
 #include "llkd.h"
 
 #include <sched.h>
+#include <sys/prctl.h>
 #include <unistd.h>
 
 #include <chrono>
@@ -26,6 +27,8 @@
 using namespace std::chrono;
 
 int main(int, char**) {
+    prctl(PR_SET_DUMPABLE, 0);
+
     LOG(INFO) << "started";
 
     bool enabled = llkInit();
diff --git a/llkd/llkd.rc b/llkd/llkd.rc
index e538cdb..b1f96a8 100644
--- a/llkd/llkd.rc
+++ b/llkd/llkd.rc
@@ -3,15 +3,8 @@
     setprop llk.enable ${ro.llk.enable:-0}
     setprop khungtask.enable ${ro.khungtask.enable:-0}
 
-on property:ro.debuggable=1
-    setprop llk.enable ${ro.llk.enable:-1}
-    setprop khungtask.enable ${ro.khungtask.enable:-1}
-
-on property:ro.llk.enable=eng
-    setprop llk.enable ${ro.debuggable:-0}
-
-on property:ro.khungtask.enable=eng
-    setprop khungtask.enable ${ro.debuggable:-0}
+on property:ro.llk.enable=true
+    setprop llk.enable true
 
 on property:llk.enable=1
     setprop llk.enable true
@@ -19,6 +12,9 @@
 on property:llk.enable=0
     setprop llk.enable false
 
+on property:ro.khungtask.enable=true
+    setprop khungtask.enable true
+
 on property:khungtask.enable=1
     setprop khungtask.enable true
 
@@ -36,9 +32,9 @@
     write /proc/sys/kernel/hung_task_panic 0
 
 on property:llk.enable=true
-    start llkd
+    start llkd-${ro.debuggable:-0}
 
-service llkd /system/bin/llkd
+service llkd-0 /system/bin/llkd
     class late_start
     disabled
     user llkd
diff --git a/llkd/tests/llkd_test.cpp b/llkd/tests/llkd_test.cpp
index 3a15ff1..f54932b 100644
--- a/llkd/tests/llkd_test.cpp
+++ b/llkd/tests/llkd_test.cpp
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
+#include <fcntl.h>
 #include <signal.h>
 #include <stdint.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -81,9 +83,12 @@
         (GetUintProperty(LLK_CHECK_MS_PROPERTY,
                          LLK_TIMEOUT_MS_DEFAULT / LLK_CHECKS_PER_TIMEOUT_DEFAULT) !=
          duration_cast<milliseconds>(10s))) {
-        execute("stop llkd");
+        execute("stop llkd-0");
+        execute("stop llkd-1");
         rest();
         std::string setprop("setprop ");
+        execute((setprop + LLK_CHECK_STACK_PROPERTY + " SyS_openat").c_str());
+        rest();
         execute((setprop + LLK_ENABLE_WRITEABLE_PROPERTY + " false").c_str());
         rest();
         execute((setprop + LLK_TIMEOUT_MS_PROPERTY + " 120000").c_str());
@@ -92,8 +97,10 @@
         rest();
         execute((setprop + LLK_CHECK_MS_PROPERTY + " 10000").c_str());
         rest();
-        execute((setprop + LLK_ENABLE_PROPERTY + " true").c_str());
-        rest();
+        if (!default_enable) {
+            execute((setprop + LLK_ENABLE_PROPERTY + " true").c_str());
+            rest();
+        }
         execute((setprop + LLK_ENABLE_WRITEABLE_PROPERTY + " true").c_str());
         rest();
     }
@@ -104,7 +111,7 @@
     }
     default_enable = android::base::GetBoolProperty(LLK_ENABLE_PROPERTY, default_enable);
     if (default_enable) {
-        execute("start llkd");
+        execute("start llkd-1");
         rest();
         GTEST_LOG_INFO << "llkd enabled\n";
     } else {
@@ -123,8 +130,10 @@
         llkTimeoutMs = LLK_TIMEOUT_MS_MINIMUM;
     }
     milliseconds llkCheckMs = llkTimeoutMs / LLK_CHECKS_PER_TIMEOUT_DEFAULT;
-    auto timeout = GetUintProperty(
-        (state == 'Z') ? LLK_Z_TIMEOUT_MS_PROPERTY : LLK_D_TIMEOUT_MS_PROPERTY, llkTimeoutMs);
+    auto timeout = GetUintProperty((state == 'Z') ? LLK_Z_TIMEOUT_MS_PROPERTY
+                                                  : (state == 'S') ? LLK_STACK_TIMEOUT_MS_PROPERTY
+                                                                   : LLK_D_TIMEOUT_MS_PROPERTY,
+                                   llkTimeoutMs);
     if (timeout < LLK_TIMEOUT_MS_MINIMUM) {
         timeout = LLK_TIMEOUT_MS_MINIMUM;
     }
@@ -285,3 +294,41 @@
 
     waitForPid(child_pid);
 }
+
+TEST(llkd, sleep) {
+    if (checkKill("kernel_panic,sysrq,livelock,sleeping")) {
+        return;
+    }
+    if (!android::base::GetBoolProperty("ro.debuggable", false)) {
+        GTEST_LOG_WARNING << "Features not available on user builds\n";
+    }
+
+    const auto period = llkdSleepPeriod('S');
+
+    /* Create a Persistent SyS_openat for single-ended pipe */
+    static constexpr char stack_pipe_file[] = "/dev/stack_pipe_file";
+    unlink(stack_pipe_file);
+    auto pipe_ret = mknod(stack_pipe_file, S_IFIFO | 0666, 0);
+    ASSERT_LE(0, pipe_ret);
+
+    auto child_pid = fork();
+    ASSERT_LE(0, child_pid);
+    if (!child_pid) {
+        child_pid = fork();
+        ASSERT_LE(0, child_pid);
+        if (!child_pid) {
+            sleep(period.count());
+            auto fd = open(stack_pipe_file, O_RDONLY | O_CLOEXEC);
+            close(fd);
+            exit(0);
+        } else {
+            auto fd = open(stack_pipe_file, O_WRONLY | O_CLOEXEC);
+            close(fd);
+            exit(42);
+        }
+    }
+
+    waitForPid(child_pid);
+
+    unlink(stack_pipe_file);
+}
diff --git a/logd/main.cpp b/logd/main.cpp
index b697d44..8c38d9a 100644
--- a/logd/main.cpp
+++ b/logd/main.cpp
@@ -104,7 +104,8 @@
         return -1;
     }
 
-    if (__android_logger_property_get_bool("ro.debuggable", BOOL_DEFAULT_FALSE) &&
+    if (!__android_logger_property_get_bool("ro.debuggable",
+                                            BOOL_DEFAULT_FALSE) &&
         prctl(PR_SET_DUMPABLE, 0) == -1) {
         android::prdebug("failed to clear PR_SET_DUMPABLE");
         return -1;
diff --git a/platform_tools_tool_version.mk b/platform_tools_tool_version.mk
deleted file mode 100644
index eed2ab5..0000000
--- a/platform_tools_tool_version.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (C) 2017 Google Inc.
-#
-# 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.
-
-# We rewrite ${PLATFORM_SDK_VERSION} with 0 rather than $(PLATFORM_SDK_VERSION)
-# because on the actual platform tools release branches the file contains a
-# literal instead. Using 0 lets us easily distinguish non-canonical builds.
-platform_tools_version := $(shell sed \
-    's/$${PLATFORM_SDK_VERSION}/0/ ; s/^Pkg.Revision=\(.*\)/\1/p ; d' \
-    development/sdk/plat_tools_source.prop_template \
-  )
-tool_version := $(platform_tools_version)-$(BUILD_NUMBER_FROM_FILE)
diff --git a/rootdir/init.rc b/rootdir/init.rc
index bf22951..1c2ef64 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -49,6 +49,10 @@
     copy /proc/cmdline /dev/urandom
     copy /default.prop /dev/urandom
 
+    symlink /proc/self/fd/0 /dev/stdin
+    symlink /proc/self/fd/1 /dev/stdout
+    symlink /proc/self/fd/2 /dev/stderr
+
     symlink /system/bin /bin
     symlink /system/etc /etc
 
@@ -712,12 +716,12 @@
     class_start main
 
 on property:vold.decrypt=trigger_restart_framework
-    stop surfaceflinger
-    start surfaceflinger
     # A/B update verifier that marks a successful boot.
     exec_start update_verifier
     class_start main
     class_start late_start
+    setprop service.bootanim.exit 0
+    start bootanim
 
 on property:vold.decrypt=trigger_shutdown_framework
     class_reset late_start
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index 3d7521c..2d4a26f 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -21,6 +21,7 @@
         "tcpdump",
         "toolbox",
         "toybox",
+        "unzip",
     ],
 }
 
@@ -31,6 +32,7 @@
         "sh.recovery",
         "toolbox.recovery",
         "toybox.recovery",
+        "unzip.recovery",
     ],
 }
 
diff --git a/toolbox/getevent.c b/toolbox/getevent.c
index e6def6b..39033ad 100644
--- a/toolbox/getevent.c
+++ b/toolbox/getevent.c
@@ -321,7 +321,7 @@
     char idstr[80];
     struct input_id id;
 
-    fd = open(device, O_RDWR);
+    fd = open(device, O_RDONLY | O_CLOEXEC);
     if(fd < 0) {
         if(print_flags & PRINT_DEVICE_ERRORS)
             fprintf(stderr, "could not open %s, %s\n", device, strerror(errno));
diff --git a/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp b/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
index 8c5cff6..0956fe6 100644
--- a/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
+++ b/trusty/keymaster/ipc/trusty_keymaster_ipc.cpp
@@ -167,7 +167,7 @@
         // TODO(swillden): Distinguish permanent from transient errors and set error_ appropriately.
         return translate_error(rc);
     } else {
-        ALOGE("Received %d byte response\n", rsp_size);
+        ALOGV("Received %d byte response\n", rsp_size);
     }
 
     const uint8_t* p = recv_buf;
diff --git a/trusty/keymaster/legacy/trusty_keymaster_device.cpp b/trusty/keymaster/legacy/trusty_keymaster_device.cpp
index ea00a92..afdf43b 100644
--- a/trusty/keymaster/legacy/trusty_keymaster_device.cpp
+++ b/trusty/keymaster/legacy/trusty_keymaster_device.cpp
@@ -122,10 +122,10 @@
 void AddClientAndAppData(const keymaster_blob_t* client_id, const keymaster_blob_t* app_data,
                          RequestType* request) {
     request->additional_params.Clear();
-    if (client_id) {
+    if (client_id && client_id->data_length > 0) {
         request->additional_params.push_back(TAG_APPLICATION_ID, *client_id);
     }
-    if (app_data) {
+    if (app_data && app_data->data_length > 0) {
         request->additional_params.push_back(TAG_APPLICATION_DATA, *app_data);
     }
 }