Merge "adb: fix use after free of atransport."
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 9b663be..460ddde 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -1167,7 +1167,7 @@
         std::string host;
         int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
         std::string error;
-        if (address.starts_with("vsock:")) {
+        if (address.starts_with("vsock:") || address.starts_with("localfilesystem:")) {
             serial = address;
         } else if (!android::base::ParseNetAddress(address, &host, &port, &serial, &error)) {
             SendFail(reply_fd, android::base::StringPrintf("couldn't parse '%s': %s",
diff --git a/adb/adb.h b/adb/adb.h
index c6cb06a..e7fcc91 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -200,7 +200,7 @@
 #define ADB_SUBCLASS 0x42
 #define ADB_PROTOCOL 0x1
 
-void local_init(int port);
+void local_init(const std::string& addr);
 bool local_connect(int port);
 int local_connect_arbitrary_ports(int console_port, int adb_port, std::string* error);
 
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 6465ffe..813a8a9 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -137,8 +137,8 @@
         "     run remote shell command (interactive shell if no command given)\n"
         "     -e: choose escape character, or \"none\"; default '~'\n"
         "     -n: don't read from stdin\n"
-        "     -T: disable PTY allocation\n"
-        "     -t: force PTY allocation\n"
+        "     -T: disable pty allocation\n"
+        "     -t: allocate a pty if on a tty (-tt: force pty allocation)\n"
         "     -x: disable remote exit codes and stdout/stderr separation\n"
         " emu COMMAND              run emulator console command\n"
         "\n"
diff --git a/adb/client/main.cpp b/adb/client/main.cpp
index 0c5c28f..e5ffe4c 100644
--- a/adb/client/main.cpp
+++ b/adb/client/main.cpp
@@ -129,7 +129,7 @@
     }
 
     if (!getenv("ADB_EMU") || strcmp(getenv("ADB_EMU"), "0") != 0) {
-        local_init(DEFAULT_ADB_LOCAL_TRANSPORT_PORT);
+        local_init(android::base::StringPrintf("tcp:%d", DEFAULT_ADB_LOCAL_TRANSPORT_PORT));
     }
 
     std::string error;
diff --git a/adb/daemon/main.cpp b/adb/daemon/main.cpp
index 7277cc8..3322574 100644
--- a/adb/daemon/main.cpp
+++ b/adb/daemon/main.cpp
@@ -32,11 +32,13 @@
 #include <sys/prctl.h>
 
 #include <memory>
+#include <vector>
 
 #include <android-base/logging.h>
 #include <android-base/macros.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 
 #if defined(__ANDROID__)
 #include <libminijail.h>
@@ -51,6 +53,7 @@
 #include "adb_auth.h"
 #include "adb_listeners.h"
 #include "adb_utils.h"
+#include "socket_spec.h"
 #include "transport.h"
 
 #include "mdns.h"
@@ -179,12 +182,26 @@
 }
 #endif
 
-static void setup_port(int port) {
-    LOG(INFO) << "adbd listening on port " << port;
-    local_init(port);
+static void setup_adb(const std::vector<std::string>& addrs) {
 #if defined(__ANDROID__)
+    // Get the first valid port from addrs and setup mDNS.
+    int port = -1;
+    std::string error;
+    for (const auto& addr : addrs) {
+        port = get_host_socket_spec_port(addr, &error);
+        if (port != -1) {
+            break;
+        }
+    }
+    if (port == -1) {
+        port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
+    }
     setup_mdns(port);
 #endif
+    for (const auto& addr : addrs) {
+        LOG(INFO) << "adbd listening on " << addr;
+        local_init(addr);
+    }
 }
 
 int adbd_main(int server_port) {
@@ -248,25 +265,38 @@
     // If one of these properties is set, also listen on that port.
     // If one of the properties isn't set and we couldn't listen on usb, listen
     // on the default port.
-    std::string prop_port = android::base::GetProperty("service.adb.tcp.port", "");
-    if (prop_port.empty()) {
-        prop_port = android::base::GetProperty("persist.adb.tcp.port", "");
-    }
+    std::vector<std::string> addrs;
+    std::string prop_addr = android::base::GetProperty("service.adb.listen_addrs", "");
+    if (prop_addr.empty()) {
+        std::string prop_port = android::base::GetProperty("service.adb.tcp.port", "");
+        if (prop_port.empty()) {
+            prop_port = android::base::GetProperty("persist.adb.tcp.port", "");
+        }
 
 #if !defined(__ANDROID__)
-    if (prop_port.empty() && getenv("ADBD_PORT")) {
-        prop_port = getenv("ADBD_PORT");
-    }
+        if (prop_port.empty() && getenv("ADBD_PORT")) {
+            prop_port = getenv("ADBD_PORT");
+        }
 #endif
 
-    int port;
-    if (sscanf(prop_port.c_str(), "%d", &port) == 1 && port > 0) {
-        D("using port=%d", port);
-        // Listen on TCP port specified by service.adb.tcp.port property.
-        setup_port(port);
-    } else if (!is_usb) {
-        // Listen on default port.
-        setup_port(DEFAULT_ADB_LOCAL_TRANSPORT_PORT);
+        int port;
+        if (sscanf(prop_port.c_str(), "%d", &port) == 1 && port > 0) {
+            D("using tcp port=%d", port);
+            // Listen on TCP and VSOCK port specified by service.adb.tcp.port property.
+            addrs.push_back(android::base::StringPrintf("tcp:%d", port));
+            addrs.push_back(android::base::StringPrintf("vsock:%d", port));
+            setup_adb(addrs);
+        } else if (!is_usb) {
+            // Listen on default port.
+            addrs.push_back(
+                    android::base::StringPrintf("tcp:%d", DEFAULT_ADB_LOCAL_TRANSPORT_PORT));
+            addrs.push_back(
+                    android::base::StringPrintf("vsock:%d", DEFAULT_ADB_LOCAL_TRANSPORT_PORT));
+            setup_adb(addrs);
+        }
+    } else {
+        addrs = android::base::Split(prop_addr, ",");
+        setup_adb(addrs);
     }
 
     D("adbd_main(): pre init_jdwp()");
diff --git a/adb/daemon/transport_qemu.cpp b/adb/daemon/transport_qemu.cpp
index aa760bc..901efee 100644
--- a/adb/daemon/transport_qemu.cpp
+++ b/adb/daemon/transport_qemu.cpp
@@ -18,6 +18,7 @@
 #include <qemu_pipe.h>
 
 #define TRACE_TAG TRANSPORT
+#include "socket_spec.h"
 #include "sysdeps.h"
 #include "transport.h"
 
@@ -55,7 +56,7 @@
  *   the transport registration is completed. That's why we need to send the
  *   'start' request after the transport is registered.
  */
-void qemu_socket_thread(int port) {
+void qemu_socket_thread(std::string_view addr) {
     /* 'accept' request to the adb QEMUD service. */
     static const char _accept_req[] = "accept";
     /* 'start' request to the adb QEMUD service. */
@@ -69,6 +70,12 @@
     adb_thread_setname("qemu socket");
     D("transport: qemu_socket_thread() starting");
 
+    std::string error;
+    int port = get_host_socket_spec_port(addr, &error);
+    if (port == -1) {
+        port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
+    }
+
     /* adb QEMUD service connection request. */
     snprintf(con_name, sizeof(con_name), "pipe:qemud:adb:%d", port);
 
@@ -78,7 +85,7 @@
         /* This could be an older version of the emulator, that doesn't
          * implement adb QEMUD service. Fall back to the old TCP way. */
         D("adb service is not available. Falling back to TCP socket.");
-        std::thread(server_socket_thread, tcp_listen_inaddr_any, port).detach();
+        std::thread(server_socket_thread, adb_listen, addr).detach();
         return;
     }
 
diff --git a/adb/daemon/usb_ffs.cpp b/adb/daemon/usb_ffs.cpp
index 338d776..b19fa5d 100644
--- a/adb/daemon/usb_ffs.cpp
+++ b/adb/daemon/usb_ffs.cpp
@@ -84,7 +84,7 @@
 using usb_os_desc_guid_t = usb_os_desc_ext_prop<20, 39>;
 usb_os_desc_guid_t os_desc_guid = {
     .bPropertyName = "DeviceInterfaceGUID",
-    .bProperty = "{64379D6C-D531-4BED-BBEC-5A16FC07D6BC}",
+    .bProperty = "{F72FE0D4-CBCB-407D-8814-9ED673D0DD6B}",
 };
 
 struct usb_ext_prop_values {
diff --git a/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp b/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp
index 3dc5e50..932d579 100644
--- a/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp
+++ b/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp
@@ -36,7 +36,7 @@
     FileRegion(borrowed_fd fd, off64_t offset, size_t length)
         : mapped_(android::base::MappedFile::FromOsHandle(adb_get_os_handle(fd), offset, length,
                                                           PROT_READ)) {
-        if (mapped_.data() != nullptr) {
+        if (mapped_ != nullptr) {
             return;
         }
 
@@ -50,14 +50,14 @@
         }
     }
 
-    const char* data() const { return mapped_.data() ? mapped_.data() : buffer_.data(); }
-    size_t size() const { return mapped_.data() ? mapped_.size() : buffer_.size(); }
+    const char* data() const { return mapped_ ? mapped_->data() : buffer_.data(); }
+    size_t size() const { return mapped_ ? mapped_->size() : buffer_.size(); }
 
   private:
     FileRegion() = default;
     DISALLOW_COPY_AND_ASSIGN(FileRegion);
 
-    android::base::MappedFile mapped_;
+    std::unique_ptr<android::base::MappedFile> mapped_;
     std::string buffer_;
 };
 }  // namespace
diff --git a/adb/socket_spec.cpp b/adb/socket_spec.cpp
index 9ce443e..d17036c 100644
--- a/adb/socket_spec.cpp
+++ b/adb/socket_spec.cpp
@@ -122,6 +122,41 @@
     return true;
 }
 
+int get_host_socket_spec_port(std::string_view spec, std::string* error) {
+    int port;
+    if (spec.starts_with("tcp:")) {
+        if (!parse_tcp_socket_spec(spec, nullptr, &port, nullptr, error)) {
+            return -1;
+        }
+    } else if (spec.starts_with("vsock:")) {
+#if ADB_LINUX
+        std::string spec_str(spec);
+        std::vector<std::string> fragments = android::base::Split(spec_str, ":");
+        if (fragments.size() != 2) {
+            *error = "given vsock server socket string was invalid";
+            return -1;
+        }
+        if (!android::base::ParseInt(fragments[1], &port)) {
+            *error = "could not parse vsock port";
+            errno = EINVAL;
+            return -1;
+        }
+        if (port < 0) {
+            *error = "vsock port was negative.";
+            errno = EINVAL;
+            return -1;
+        }
+#else   // ADB_LINUX
+        *error = "vsock is only supported on linux";
+        return -1;
+#endif  // ADB_LINUX
+    } else {
+        *error = "given socket spec string was invalid";
+        return -1;
+    }
+    return port;
+}
+
 static bool tcp_host_is_local(std::string_view hostname) {
     // FIXME
     return hostname.empty() || hostname == "localhost";
@@ -254,6 +289,14 @@
 
             fd->reset(network_local_client(&address[prefix.length()], it.second.socket_namespace,
                                            SOCK_STREAM, error));
+
+            if (fd->get() < 0) {
+                *error =
+                        android::base::StringPrintf("could not connect to %s address '%s'",
+                                                    it.first.c_str(), std::string(address).c_str());
+                return false;
+            }
+
             if (serial) {
                 *serial = address;
             }
@@ -275,7 +318,11 @@
         }
 
         int result;
+#if ADB_HOST
         if (hostname.empty() && gListenAll) {
+#else
+        if (hostname.empty()) {
+#endif
             result = network_inaddr_any_server(port, SOCK_STREAM, error);
         } else if (tcp_host_is_local(hostname)) {
             result = network_loopback_server(port, SOCK_STREAM, error, true);
diff --git a/adb/socket_spec.h b/adb/socket_spec.h
index 7cc2fac..94719c8 100644
--- a/adb/socket_spec.h
+++ b/adb/socket_spec.h
@@ -31,3 +31,5 @@
 
 bool parse_tcp_socket_spec(std::string_view spec, std::string* hostname, int* port,
                            std::string* serial, std::string* error);
+
+int get_host_socket_spec_port(std::string_view spec, std::string* error);
diff --git a/adb/socket_spec_test.cpp b/adb/socket_spec_test.cpp
index 3a2f60c..e9d5270 100644
--- a/adb/socket_spec_test.cpp
+++ b/adb/socket_spec_test.cpp
@@ -18,6 +18,10 @@
 
 #include <string>
 
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
 #include <gtest/gtest.h>
 
 TEST(socket_spec, parse_tcp_socket_spec_just_port) {
@@ -88,3 +92,63 @@
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:[::1]:", &hostname, &port, &serial, &error));
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:[::1]:-1", &hostname, &port, &serial, &error));
 }
+
+TEST(socket_spec, get_host_socket_spec_port) {
+    std::string error;
+    EXPECT_EQ(5555, get_host_socket_spec_port("tcp:5555", &error));
+    EXPECT_EQ(5555, get_host_socket_spec_port("tcp:localhost:5555", &error));
+    EXPECT_EQ(5555, get_host_socket_spec_port("tcp:[::1]:5555", &error));
+    EXPECT_EQ(5555, get_host_socket_spec_port("vsock:5555", &error));
+}
+
+TEST(socket_spec, get_host_socket_spec_port_no_port) {
+    std::string error;
+    EXPECT_EQ(5555, get_host_socket_spec_port("tcp:localhost", &error));
+    EXPECT_EQ(-1, get_host_socket_spec_port("vsock:localhost", &error));
+}
+
+TEST(socket_spec, get_host_socket_spec_port_bad_ports) {
+    std::string error;
+    EXPECT_EQ(-1, get_host_socket_spec_port("tcp:65536", &error));
+    EXPECT_EQ(-1, get_host_socket_spec_port("tcp:-5", &error));
+    EXPECT_EQ(-1, get_host_socket_spec_port("vsock:-5", &error));
+    EXPECT_EQ(-1, get_host_socket_spec_port("vsock:5:5555", &error));
+}
+
+TEST(socket_spec, get_host_socket_spec_port_bad_string) {
+    std::string error;
+    EXPECT_EQ(-1, get_host_socket_spec_port("tcpz:5555", &error));
+    EXPECT_EQ(-1, get_host_socket_spec_port("vsockz:5555", &error));
+    EXPECT_EQ(-1, get_host_socket_spec_port("abcd:5555", &error));
+    EXPECT_EQ(-1, get_host_socket_spec_port("abcd", &error));
+}
+
+TEST(socket_spec, socket_spec_listen_connect_tcp) {
+    std::string error, serial;
+    int port;
+    unique_fd server_fd, client_fd;
+    EXPECT_FALSE(socket_spec_connect(&client_fd, "tcp:localhost:7777", &port, &serial, &error));
+    server_fd.reset(socket_spec_listen("tcp:7777", &error, &port));
+    EXPECT_NE(server_fd.get(), -1);
+    EXPECT_TRUE(socket_spec_connect(&client_fd, "tcp:localhost:7777", &port, &serial, &error));
+    EXPECT_NE(client_fd.get(), -1);
+}
+
+TEST(socket_spec, socket_spec_listen_connect_localfilesystem) {
+    std::string error, serial;
+    int port;
+    unique_fd server_fd, client_fd;
+    TemporaryDir sock_dir;
+
+    // Only run this test if the created directory is writable.
+    int result = access(sock_dir.path, W_OK);
+    if (result == 0) {
+        std::string sock_addr =
+                android::base::StringPrintf("localfilesystem:%s/af_unix_socket", sock_dir.path);
+        EXPECT_FALSE(socket_spec_connect(&client_fd, sock_addr, &port, &serial, &error));
+        server_fd.reset(socket_spec_listen(sock_addr, &error, &port));
+        EXPECT_NE(server_fd.get(), -1);
+        EXPECT_TRUE(socket_spec_connect(&client_fd, sock_addr, &port, &serial, &error));
+        EXPECT_NE(client_fd.get(), -1);
+    }
+}
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index 7d5bf17..423af67 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -625,7 +625,8 @@
         return true;
     };
 
-    static constexpr std::string_view prefixes[] = {"usb:", "product:", "model:", "device:"};
+    static constexpr std::string_view prefixes[] = {
+            "usb:", "product:", "model:", "device:", "localfilesystem:"};
     for (std::string_view prefix : prefixes) {
         if (command.starts_with(prefix)) {
             consume(prefix.size());
diff --git a/adb/transport.h b/adb/transport.h
index 7a41601..5a750ee 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -425,11 +425,12 @@
 asocket* create_device_tracker(bool long_output);
 
 #if !ADB_HOST
-unique_fd tcp_listen_inaddr_any(int port, std::string* error);
-void server_socket_thread(std::function<unique_fd(int, std::string*)> listen_func, int port);
+unique_fd adb_listen(std::string_view addr, std::string* error);
+void server_socket_thread(std::function<unique_fd(std::string_view, std::string*)> listen_func,
+                          std::string_view addr);
 
 #if defined(__ANDROID__)
-void qemu_socket_thread(int port);
+void qemu_socket_thread(std::string_view addr);
 bool use_qemu_goldfish();
 #endif
 
diff --git a/adb/transport_local.cpp b/adb/transport_local.cpp
index b9f738d..c726186 100644
--- a/adb/transport_local.cpp
+++ b/adb/transport_local.cpp
@@ -85,22 +85,6 @@
     return local_connect_arbitrary_ports(port - 1, port, &dummy) == 0;
 }
 
-std::tuple<unique_fd, int, std::string> tcp_connect(const std::string& address,
-                                                    std::string* response) {
-    unique_fd fd;
-    int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
-    std::string serial;
-    std::string prefix_addr = address.starts_with("vsock:") ? address : "tcp:" + address;
-    if (socket_spec_connect(&fd, prefix_addr, &port, &serial, response)) {
-        close_on_exec(fd);
-        if (!set_tcp_keepalive(fd, 1)) {
-            D("warning: failed to configure TCP keepalives (%s)", strerror(errno));
-        }
-        return std::make_tuple(std::move(fd), port, serial);
-    }
-    return std::make_tuple(unique_fd(), 0, serial);
-}
-
 void connect_device(const std::string& address, std::string* response) {
     if (address.empty()) {
         *response = "empty address";
@@ -110,17 +94,25 @@
     D("connection requested to '%s'", address.c_str());
     unique_fd fd;
     int port;
-    std::string serial;
-    std::tie(fd, port, serial) = tcp_connect(address, response);
+    std::string serial, prefix_addr;
+
+    // If address does not match any socket type, it should default to TCP.
+    if (address.starts_with("vsock:") || address.starts_with("localfilesystem:")) {
+        prefix_addr = address;
+    } else {
+        prefix_addr = "tcp:" + address;
+    }
+
+    socket_spec_connect(&fd, prefix_addr, &port, &serial, response);
     if (fd.get() == -1) {
         return;
     }
-    auto reconnect = [address](atransport* t) {
+    auto reconnect = [prefix_addr](atransport* t) {
         std::string response;
         unique_fd fd;
         int port;
         std::string serial;
-        std::tie(fd, port, serial) = tcp_connect(address, &response);
+        socket_spec_connect(&fd, prefix_addr, &port, &serial, &response);
         if (fd == -1) {
             D("reconnect failed: %s", response.c_str());
             return ReconnectResult::Retry;
@@ -203,7 +195,7 @@
 std::mutex &retry_ports_lock = *new std::mutex;
 std::condition_variable &retry_ports_cond = *new std::condition_variable;
 
-static void client_socket_thread(int) {
+static void client_socket_thread(std::string_view) {
     adb_thread_setname("client_socket_thread");
     D("transport: client_socket_thread() starting");
     PollAllLocalPortsForEmulator();
@@ -248,7 +240,8 @@
 
 #else  // !ADB_HOST
 
-void server_socket_thread(std::function<unique_fd(int, std::string*)> listen_func, int port) {
+void server_socket_thread(std::function<unique_fd(std::string_view, std::string*)> listen_func,
+                          std::string_view addr) {
     adb_thread_setname("server socket");
 
     unique_fd serverfd;
@@ -256,7 +249,7 @@
 
     while (serverfd == -1) {
         errno = 0;
-        serverfd = listen_func(port, &error);
+        serverfd = listen_func(addr, &error);
         if (errno == EAFNOSUPPORT || errno == EINVAL || errno == EPROTONOSUPPORT) {
             D("unrecoverable error: '%s'", error.c_str());
             return;
@@ -276,7 +269,9 @@
             close_on_exec(fd.get());
             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,
+            // We don't care about port value in "register_socket_transport" as it is used
+            // only from ADB_HOST. "server_socket_thread" is never called from ADB_HOST.
+            register_socket_transport(std::move(fd), std::move(serial), 0, 1,
                                       [](atransport*) { return ReconnectResult::Abort; });
         }
     }
@@ -285,38 +280,30 @@
 
 #endif
 
-unique_fd tcp_listen_inaddr_any(int port, std::string* error) {
-    return unique_fd{network_inaddr_any_server(port, SOCK_STREAM, error)};
-}
-
 #if !ADB_HOST
-static unique_fd vsock_listen(int port, std::string* error) {
-    return unique_fd{
-        socket_spec_listen(android::base::StringPrintf("vsock:%d", port), error, nullptr)
-    };
+unique_fd adb_listen(std::string_view addr, std::string* error) {
+    return unique_fd{socket_spec_listen(addr, error, nullptr)};
 }
 #endif
 
-void local_init(int port) {
+void local_init(const std::string& addr) {
 #if ADB_HOST
     D("transport: local client init");
-    std::thread(client_socket_thread, port).detach();
+    std::thread(client_socket_thread, addr).detach();
     adb_local_transport_max_port_env_override();
 #elif !defined(__ANDROID__)
     // Host adbd.
     D("transport: local server init");
-    std::thread(server_socket_thread, tcp_listen_inaddr_any, port).detach();
-    std::thread(server_socket_thread, vsock_listen, port).detach();
+    std::thread(server_socket_thread, adb_listen, addr).detach();
 #else
     D("transport: local server init");
     // For the adbd daemon in the system image we need to distinguish
     // between the device, and the emulator.
-    if (use_qemu_goldfish()) {
-        std::thread(qemu_socket_thread, port).detach();
+    if (addr.starts_with("tcp:") && use_qemu_goldfish()) {
+        std::thread(qemu_socket_thread, addr).detach();
     } else {
-        std::thread(server_socket_thread, tcp_listen_inaddr_any, port).detach();
+        std::thread(server_socket_thread, adb_listen, addr).detach();
     }
-    std::thread(server_socket_thread, vsock_listen, port).detach();
 #endif // !ADB_HOST
 }
 
diff --git a/base/include/android-base/mapped_file.h b/base/include/android-base/mapped_file.h
index 6a19f1b..8c37f43 100644
--- a/base/include/android-base/mapped_file.h
+++ b/base/include/android-base/mapped_file.h
@@ -53,7 +53,8 @@
   /**
    * Same thing, but using the raw OS file handle instead of a CRT wrapper.
    */
-  static MappedFile FromOsHandle(os_handle h, off64_t offset, size_t length, int prot);
+  static std::unique_ptr<MappedFile> FromOsHandle(os_handle h, off64_t offset, size_t length,
+                                                  int prot);
 
   /**
    * Removes the mapping.
@@ -69,10 +70,6 @@
   char* data() const { return base_ + offset_; }
   size_t size() const { return size_; }
 
-  bool isValid() const { return base_ != nullptr; }
-
-  explicit operator bool() const { return isValid(); }
-
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(MappedFile);
 
diff --git a/base/include/android-base/test_utils.h b/base/include/android-base/test_utils.h
index b20f278..f3d7cb0 100644
--- a/base/include/android-base/test_utils.h
+++ b/base/include/android-base/test_utils.h
@@ -53,30 +53,34 @@
   CapturedStdout() : CapturedStdFd(STDOUT_FILENO) {}
 };
 
-#define ASSERT_MATCH(str, pattern)                                             \
-  do {                                                                         \
-    if (!std::regex_search((str), std::regex((pattern)))) {                    \
-      FAIL() << "regex mismatch: expected " << (pattern) << " in:\n" << (str); \
-    }                                                                          \
+#define ASSERT_MATCH(str, pattern)                                           \
+  do {                                                                       \
+    auto __s = (str);                                                        \
+    if (!std::regex_search(__s, std::regex((pattern)))) {                    \
+      FAIL() << "regex mismatch: expected " << (pattern) << " in:\n" << __s; \
+    }                                                                        \
   } while (0)
 
-#define ASSERT_NOT_MATCH(str, pattern)                                                     \
-  do {                                                                                     \
-    if (std::regex_search((str), std::regex((pattern)))) {                                 \
-      FAIL() << "regex mismatch: expected to not find " << (pattern) << " in:\n" << (str); \
-    }                                                                                      \
+#define ASSERT_NOT_MATCH(str, pattern)                                                   \
+  do {                                                                                   \
+    auto __s = (str);                                                                    \
+    if (std::regex_search(__s, std::regex((pattern)))) {                                 \
+      FAIL() << "regex mismatch: expected to not find " << (pattern) << " in:\n" << __s; \
+    }                                                                                    \
   } while (0)
 
-#define EXPECT_MATCH(str, pattern)                                                    \
-  do {                                                                                \
-    if (!std::regex_search((str), std::regex((pattern)))) {                           \
-      ADD_FAILURE() << "regex mismatch: expected " << (pattern) << " in:\n" << (str); \
-    }                                                                                 \
+#define EXPECT_MATCH(str, pattern)                                                  \
+  do {                                                                              \
+    auto __s = (str);                                                               \
+    if (!std::regex_search(__s, std::regex((pattern)))) {                           \
+      ADD_FAILURE() << "regex mismatch: expected " << (pattern) << " in:\n" << __s; \
+    }                                                                               \
   } while (0)
 
-#define EXPECT_NOT_MATCH(str, pattern)                                                            \
-  do {                                                                                            \
-    if (std::regex_search((str), std::regex((pattern)))) {                                        \
-      ADD_FAILURE() << "regex mismatch: expected to not find " << (pattern) << " in:\n" << (str); \
-    }                                                                                             \
+#define EXPECT_NOT_MATCH(str, pattern)                                                          \
+  do {                                                                                          \
+    auto __s = (str);                                                                           \
+    if (std::regex_search(__s, std::regex((pattern)))) {                                        \
+      ADD_FAILURE() << "regex mismatch: expected to not find " << (pattern) << " in:\n" << __s; \
+    }                                                                                           \
   } while (0)
diff --git a/base/mapped_file.cpp b/base/mapped_file.cpp
index 862b73b..fff3453 100644
--- a/base/mapped_file.cpp
+++ b/base/mapped_file.cpp
@@ -38,15 +38,14 @@
 std::unique_ptr<MappedFile> MappedFile::FromFd(borrowed_fd fd, off64_t offset, size_t length,
                                                int prot) {
 #if defined(_WIN32)
-  auto file =
-      FromOsHandle(reinterpret_cast<HANDLE>(_get_osfhandle(fd.get())), offset, length, prot);
+  return FromOsHandle(reinterpret_cast<HANDLE>(_get_osfhandle(fd.get())), offset, length, prot);
 #else
-  auto file = FromOsHandle(fd.get(), offset, length, prot);
+  return FromOsHandle(fd.get(), offset, length, prot);
 #endif
-  return file ? std::make_unique<MappedFile>(std::move(file)) : std::unique_ptr<MappedFile>{};
 }
 
-MappedFile MappedFile::FromOsHandle(os_handle h, off64_t offset, size_t length, int prot) {
+std::unique_ptr<MappedFile> MappedFile::FromOsHandle(os_handle h, off64_t offset, size_t length,
+                                                     int prot) {
   static const off64_t page_size = InitPageSize();
   size_t slop = offset % page_size;
   off64_t file_offset = offset - slop;
@@ -59,28 +58,30 @@
     // http://b/119818070 "app crashes when reading asset of zero length".
     // Return a MappedFile that's only valid for reading the size.
     if (length == 0 && ::GetLastError() == ERROR_FILE_INVALID) {
-      return MappedFile{const_cast<char*>(kEmptyBuffer), 0, 0, nullptr};
+      return std::unique_ptr<MappedFile>(
+          new MappedFile(const_cast<char*>(kEmptyBuffer), 0, 0, nullptr));
     }
-    return MappedFile(nullptr, 0, 0, nullptr);
+    return nullptr;
   }
   void* base = MapViewOfFile(handle, (prot & PROT_WRITE) ? FILE_MAP_ALL_ACCESS : FILE_MAP_READ, 0,
                              file_offset, file_length);
   if (base == nullptr) {
     CloseHandle(handle);
-    return MappedFile(nullptr, 0, 0, nullptr);
+    return nullptr;
   }
-  return MappedFile{static_cast<char*>(base), length, slop, handle};
+  return std::unique_ptr<MappedFile>(
+      new MappedFile(static_cast<char*>(base), length, slop, handle));
 #else
   void* base = mmap(nullptr, file_length, prot, MAP_SHARED, h, file_offset);
   if (base == MAP_FAILED) {
     // http://b/119818070 "app crashes when reading asset of zero length".
     // mmap fails with EINVAL for a zero length region.
     if (errno == EINVAL && length == 0) {
-      return MappedFile{const_cast<char*>(kEmptyBuffer), 0, 0};
+      return std::unique_ptr<MappedFile>(new MappedFile(const_cast<char*>(kEmptyBuffer), 0, 0));
     }
-    return MappedFile(nullptr, 0, 0);
+    return nullptr;
   }
-  return MappedFile{static_cast<char*>(base), length, slop};
+  return std::unique_ptr<MappedFile>(new MappedFile(static_cast<char*>(base), length, slop));
 #endif
 }
 
diff --git a/base/mapped_file_test.cpp b/base/mapped_file_test.cpp
index 3629108..d21703c 100644
--- a/base/mapped_file_test.cpp
+++ b/base/mapped_file_test.cpp
@@ -44,8 +44,6 @@
   ASSERT_TRUE(tf.fd != -1);
 
   auto m = android::base::MappedFile::FromFd(tf.fd, 4096, 0, PROT_READ);
-  ASSERT_NE(nullptr, m);
-  EXPECT_TRUE((bool)*m);
   EXPECT_EQ(0u, m->size());
   EXPECT_NE(nullptr, m->data());
 }
diff --git a/cli-test/.clang-format b/cli-test/.clang-format
new file mode 120000
index 0000000..fd0645f
--- /dev/null
+++ b/cli-test/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2
\ No newline at end of file
diff --git a/cli-test/Android.bp b/cli-test/Android.bp
new file mode 100644
index 0000000..37a1d1b
--- /dev/null
+++ b/cli-test/Android.bp
@@ -0,0 +1,7 @@
+cc_binary {
+    name: "cli-test",
+    host_supported: true,
+    srcs: ["cli-test.cpp"],
+    cflags: ["-Wall", "-Werror"],
+    shared_libs: ["libbase"],
+}
diff --git a/cli-test/README.md b/cli-test/README.md
new file mode 100644
index 0000000..643eb74
--- /dev/null
+++ b/cli-test/README.md
@@ -0,0 +1,90 @@
+# cli-test
+
+## What?
+
+`cli-test` makes integration testing of command-line tools easier.
+
+## Goals
+
+* Readable syntax. Common cases should be concise, and pretty much anyone
+  should be able to read tests even if they've never seen this tool before.
+
+* Minimal issues with quoting. The toybox tests -- being shell scripts --
+  quickly become a nightmare of quoting. Using a non ad hoc format (such as
+  JSON) would have introduced similar but different quoting issues. A custom
+  format, while annoying, side-steps this.
+
+* Sensible defaults. We expect your exit status to be 0 unless you say
+  otherwise. We expect nothing on stderr unless you say otherwise. And so on.
+
+* Convention over configuration. Related to sensible defaults, we don't let you
+  configure things that aren't absolutely necessary. So you can't keep your test
+  data anywhere except in the `files/` subdirectory of the directory containing
+  your test, for example.
+
+## Non Goals
+
+* Portability. Just being able to run on Linux (host and device) is sufficient
+  for our needs. macOS is probably easy enough if we ever need it, but Windows
+  probably doesn't make sense.
+
+## Syntax
+
+Any all-whitespace line, or line starting with `#` is ignored.
+
+A test looks like this:
+```
+name: unzip -l
+command: unzip -l $FILES/example.zip d1/d2/x.txt
+after: [ ! -f d1/d2/x.txt ]
+expected-stdout:
+	Archive:  $FILES/example.zip
+	  Length      Date    Time    Name
+	---------  ---------- -----   ----
+	     1024  2017-06-04 08:45   d1/d2/x.txt
+	---------                     -------
+	     1024                     1 file
+---
+```
+
+The `name:` line names the test, and is only for human consumption.
+
+The `command:` line is the command to be run. Additional commands can be
+supplied as zero or more `before:` lines (run before `command:`) and zero or
+more `after:` lines (run after `command:`). These are useful for both
+setup/teardown but also for testing post conditions (as in the example above).
+
+Any `command:`, `before:`, or `after:` line is expected to exit with status 0.
+Anything else is considered a test failure.
+
+The `expected-stdout:` line is followed by zero or more tab-prefixed lines that
+are otherwise the exact output expected from the command. (There's magic behind
+the scenes to rewrite the test files directory to `$FILES` because otherwise any
+path in the output would depend on the temporary directory used to run the test.)
+
+There is currently no `expected-stderr:` line. Standard error is implicitly
+expected to be empty, and any output will cause a test failure. (The support is
+there, but not wired up because we haven't needed it yet.)
+
+The fields can appear in any order, but every test must contain at least a
+`name:` line and a `command:` line.
+
+## Output
+
+The output is intended to resemble gtest.
+
+## Future Directions
+
+* It's often useful to be able to *match* against stdout/stderr/a file rather
+  than give exact expected output. We might want to add explicit support for
+  this. In the meantime, it's possible to use an `after:` with `grep -q` if
+  you redirect in your `command:`.
+
+* In addition to using a `before:` (which will fail a test), it can be useful
+  to be able to specify tests that would cause us to *skip* a test. An example
+  would be "am I running as root?".
+
+* It might be useful to be able to make exit status assertions other than 0?
+
+* There's currently no way (other than the `files/` directory) to share repeated
+  setup between tests.
diff --git a/cli-test/cli-test.cpp b/cli-test/cli-test.cpp
new file mode 100644
index 0000000..d6e27ee
--- /dev/null
+++ b/cli-test/cli-test.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+
+// Example:
+
+// name: unzip -n
+// before: mkdir -p d1/d2
+// before: echo b > d1/d2/a.txt
+// command: unzip -q -n $FILES/zip/example.zip d1/d2/a.txt && cat d1/d2/a.txt
+// expected-stdout:
+// 	b
+
+struct Test {
+  std::string test_filename;
+  std::string name;
+  std::string command;
+  std::vector<std::string> befores;
+  std::vector<std::string> afters;
+  std::string expected_stdout;
+  std::string expected_stderr;
+  int exit_status = 0;
+};
+
+static const char* g_progname;
+static bool g_verbose;
+
+static const char* g_file;
+static size_t g_line;
+
+enum Color { kRed, kGreen };
+
+static void Print(Color c, const char* lhs, const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  if (isatty(0)) printf("%s", (c == kRed) ? "\e[31m" : "\e[32m");
+  printf("%s%s", lhs, isatty(0) ? "\e[0m" : "");
+  vfprintf(stdout, fmt, ap);
+  putchar('\n');
+  va_end(ap);
+}
+
+static void Die(int error, const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  fprintf(stderr, "%s: ", g_progname);
+  vfprintf(stderr, fmt, ap);
+  if (error != 0) fprintf(stderr, ": %s", strerror(error));
+  fprintf(stderr, "\n");
+  va_end(ap);
+  _exit(1);
+}
+
+static void V(const char* fmt, ...) {
+  if (!g_verbose) return;
+
+  va_list ap;
+  va_start(ap, fmt);
+  fprintf(stderr, "           - ");
+  vfprintf(stderr, fmt, ap);
+  fprintf(stderr, "\n");
+  va_end(ap);
+}
+
+static void SetField(const char* what, std::string* field, std::string_view value) {
+  if (!field->empty()) {
+    Die(0, "%s:%zu: %s already set to '%s'", g_file, g_line, what, field->c_str());
+  }
+  field->assign(value);
+}
+
+// Similar to ConsumePrefix, but also trims, so "key:value" and "key: value"
+// are equivalent.
+static bool Match(std::string* s, const std::string& prefix) {
+  if (!android::base::StartsWith(*s, prefix)) return false;
+  s->assign(android::base::Trim(s->substr(prefix.length())));
+  return true;
+}
+
+static void CollectTests(std::vector<Test>* tests, const char* test_filename) {
+  std::string absolute_test_filename;
+  if (!android::base::Realpath(test_filename, &absolute_test_filename)) {
+    Die(errno, "realpath '%s'", test_filename);
+  }
+
+  std::string content;
+  if (!android::base::ReadFileToString(test_filename, &content)) {
+    Die(errno, "couldn't read '%s'", test_filename);
+  }
+
+  size_t count = 0;
+  g_file = test_filename;
+  g_line = 0;
+  auto lines = android::base::Split(content, "\n");
+  std::unique_ptr<Test> test(new Test);
+  while (g_line < lines.size()) {
+    auto line = lines[g_line++];
+    if (line.empty() || line[0] == '#') continue;
+
+    if (line[0] == '-') {
+      if (test->name.empty() || test->command.empty()) {
+        Die(0, "%s:%zu: each test requires both a name and a command", g_file, g_line);
+      }
+      test->test_filename = absolute_test_filename;
+      tests->push_back(*test.release());
+      test.reset(new Test);
+      ++count;
+    } else if (Match(&line, "name:")) {
+      SetField("name", &test->name, line);
+    } else if (Match(&line, "command:")) {
+      SetField("command", &test->command, line);
+    } else if (Match(&line, "before:")) {
+      test->befores.push_back(line);
+    } else if (Match(&line, "after:")) {
+      test->afters.push_back(line);
+    } else if (Match(&line, "expected-stdout:")) {
+      // Collect tab-indented lines.
+      std::string text;
+      while (g_line < lines.size() && !lines[g_line].empty() && lines[g_line][0] == '\t') {
+        text += lines[g_line++].substr(1) + "\n";
+      }
+      SetField("expected stdout", &test->expected_stdout, text);
+    } else {
+      Die(0, "%s:%zu: syntax error: \"%s\"", g_file, g_line, line.c_str());
+    }
+  }
+  if (count == 0) Die(0, "no tests found in '%s'", g_file);
+}
+
+static const char* Plural(size_t n) {
+  return (n == 1) ? "" : "s";
+}
+
+static std::string ExitStatusToString(int status) {
+  if (WIFSIGNALED(status)) {
+    return android::base::StringPrintf("was killed by signal %d (%s)", WTERMSIG(status),
+                                       strsignal(WTERMSIG(status)));
+  }
+  if (WIFSTOPPED(status)) {
+    return android::base::StringPrintf("was stopped by signal %d (%s)", WSTOPSIG(status),
+                                       strsignal(WSTOPSIG(status)));
+  }
+  return android::base::StringPrintf("exited with status %d", WEXITSTATUS(status));
+}
+
+static bool RunCommands(const char* what, const std::vector<std::string>& commands) {
+  bool result = true;
+  for (auto& command : commands) {
+    V("running %s \"%s\"", what, command.c_str());
+    int exit_status = system(command.c_str());
+    if (exit_status != 0) {
+      result = false;
+      fprintf(stderr, "Command (%s) \"%s\" %s\n", what, command.c_str(),
+              ExitStatusToString(exit_status).c_str());
+    }
+  }
+  return result;
+}
+
+static bool CheckOutput(const char* what, std::string actual_output,
+                        const std::string& expected_output, const std::string& FILES) {
+  // Rewrite the output to reverse any expansion of $FILES.
+  actual_output = android::base::StringReplace(actual_output, FILES, "$FILES", true);
+
+  bool result = (actual_output == expected_output);
+  if (!result) {
+    fprintf(stderr, "Incorrect %s.\nExpected:\n%s\nActual:\n%s\n", what, expected_output.c_str(),
+            actual_output.c_str());
+  }
+  return result;
+}
+
+static int RunTests(const std::vector<Test>& tests) {
+  std::vector<std::string> failures;
+
+  Print(kGreen, "[==========]", " Running %zu tests.", tests.size());
+  android::base::Timer total_timer;
+  for (const auto& test : tests) {
+    bool failed = false;
+
+    Print(kGreen, "[ RUN      ]", " %s", test.name.c_str());
+    android::base::Timer test_timer;
+
+    // Set $FILES for this test.
+    std::string FILES = android::base::Dirname(test.test_filename) + "/files";
+    V("setenv(\"FILES\", \"%s\")", FILES.c_str());
+    setenv("FILES", FILES.c_str(), 1);
+
+    // Make a safe space to run the test.
+    TemporaryDir td;
+    V("chdir(\"%s\")", td.path);
+    if (chdir(td.path)) Die(errno, "chdir(\"%s\")", td.path);
+
+    // Perform any setup specified for this test.
+    if (!RunCommands("before", test.befores)) failed = true;
+
+    if (!failed) {
+      V("running command \"%s\"", test.command.c_str());
+      CapturedStdout test_stdout;
+      CapturedStderr test_stderr;
+      int exit_status = system(test.command.c_str());
+      test_stdout.Stop();
+      test_stderr.Stop();
+
+      V("exit status %d", exit_status);
+      if (exit_status != test.exit_status) {
+        failed = true;
+        fprintf(stderr, "Incorrect exit status: expected %d but %s\n", test.exit_status,
+                ExitStatusToString(exit_status).c_str());
+      }
+
+      if (!CheckOutput("stdout", test_stdout.str(), test.expected_stdout, FILES)) failed = true;
+      if (!CheckOutput("stderr", test_stderr.str(), test.expected_stderr, FILES)) failed = true;
+
+      if (!RunCommands("after", test.afters)) failed = true;
+    }
+
+    std::stringstream duration;
+    duration << test_timer;
+    if (failed) {
+      failures.push_back(test.name);
+      Print(kRed, "[  FAILED  ]", " %s (%s)", test.name.c_str(), duration.str().c_str());
+    } else {
+      Print(kGreen, "[       OK ]", " %s (%s)", test.name.c_str(), duration.str().c_str());
+    }
+  }
+
+  // Summarize the whole run and explicitly list all the failures.
+
+  std::stringstream duration;
+  duration << total_timer;
+  Print(kGreen, "[==========]", " %zu tests ran. (%s total)", tests.size(), duration.str().c_str());
+
+  size_t fail_count = failures.size();
+  size_t pass_count = tests.size() - fail_count;
+  Print(kGreen, "[  PASSED  ]", " %zu test%s.", pass_count, Plural(pass_count));
+  if (!failures.empty()) {
+    Print(kRed, "[  FAILED  ]", " %zu test%s.", fail_count, Plural(fail_count));
+    for (auto& failure : failures) {
+      Print(kRed, "[  FAILED  ]", " %s", failure.c_str());
+    }
+  }
+  return (fail_count == 0) ? 0 : 1;
+}
+
+static void ShowHelp(bool full) {
+  fprintf(full ? stdout : stderr, "usage: %s [-v] FILE...\n", g_progname);
+  if (!full) exit(EXIT_FAILURE);
+
+  printf(
+      "\n"
+      "Run tests.\n"
+      "\n"
+      "-v\tVerbose (show workings)\n");
+  exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char* argv[]) {
+  g_progname = basename(argv[0]);
+
+  static const struct option opts[] = {
+      {"help", no_argument, 0, 'h'},
+      {"verbose", no_argument, 0, 'v'},
+      {},
+  };
+
+  int opt;
+  while ((opt = getopt_long(argc, argv, "hv", opts, nullptr)) != -1) {
+    switch (opt) {
+      case 'h':
+        ShowHelp(true);
+        break;
+      case 'v':
+        g_verbose = true;
+        break;
+      default:
+        ShowHelp(false);
+        break;
+    }
+  }
+
+  argv += optind;
+  if (!*argv) Die(0, "no test files provided");
+  std::vector<Test> tests;
+  for (; *argv; ++argv) CollectTests(&tests, *argv);
+  return RunTests(tests);
+}
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index cbd42b1..7fdc28b 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -1226,7 +1226,7 @@
     std::string merge_status = "none";
     if (fb->GetVar(FB_VAR_SNAPSHOT_UPDATE_STATUS, &merge_status) == fastboot::SUCCESS &&
         merge_status != "none") {
-        fb->SnapshotUpdateCommand("Cancel");
+        fb->SnapshotUpdateCommand("cancel");
     }
 }
 
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 7b686f0..5c0401e 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -1358,24 +1358,17 @@
     return ret;
 }
 
-static std::string GetBlockDeviceForMountPoint(const std::string& mount_point) {
-    Fstab mounts;
-    if (!ReadFstabFromFile("/proc/mounts", &mounts)) {
-        LERROR << "Can't read /proc/mounts";
-        return "";
-    }
-    auto entry = GetEntryForMountPoint(&mounts, mount_point);
-    if (entry == nullptr) {
-        LWARNING << mount_point << " is not mounted";
-        return "";
-    }
-    return entry->blk_device;
-}
-
 // TODO(b/143970043): return different error codes based on which step failed.
 int fs_mgr_remount_userdata_into_checkpointing(Fstab* fstab) {
-    std::string block_device = GetBlockDeviceForMountPoint("/data");
-    if (block_device.empty()) {
+    Fstab proc_mounts;
+    if (!ReadFstabFromFile("/proc/mounts", &proc_mounts)) {
+        LERROR << "Can't read /proc/mounts";
+        return -1;
+    }
+    std::string block_device;
+    if (auto entry = GetEntryForMountPoint(&proc_mounts, "/data"); entry != nullptr) {
+        block_device = entry->blk_device;
+    } else {
         LERROR << "/data is not mounted";
         return -1;
     }
@@ -1408,6 +1401,15 @@
         }
     } else {
         LINFO << "Unmounting /data before remounting into checkpointing mode";
+        // First make sure that all the bind-mounts on top of /data are unmounted.
+        for (const auto& entry : proc_mounts) {
+            if (entry.blk_device == block_device && entry.mount_point != "/data") {
+                LINFO << "Unmounting bind-mount " << entry.mount_point;
+                if (umount2(entry.mount_point.c_str(), UMOUNT_NOFOLLOW) != 0) {
+                    PWARNING << "Failed to unmount " << entry.mount_point;
+                }
+            }
+        }
         if (umount2("/data", UMOUNT_NOFOLLOW) != 0) {
             PERROR << "Failed to umount /data";
             return -1;
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 2337065..27971da 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -440,13 +440,9 @@
     rmdir(kScratchMountPoint.c_str());
 }
 
-// reduce 'DM_DEV_STATUS failed for scratch: No such device or address' noise
-std::string scratch_device_cache;
-
 bool fs_mgr_overlayfs_teardown_scratch(const std::string& overlay, bool* change) {
     // umount and delete kScratchMountPoint storage if we have logical partitions
     if (overlay != kScratchMountPoint) return true;
-    scratch_device_cache.erase();
     auto slot_number = fs_mgr_overlayfs_slot_number();
     auto super_device = fs_mgr_overlayfs_super_device(slot_number);
     if (!fs_mgr_rw_access(super_device)) return true;
@@ -825,7 +821,8 @@
     kSystemOther
 };
 
-static ScratchStrategy GetScratchStrategy(std::string* backing_device) {
+// Return the strategy this device must use for creating a scratch partition.
+static ScratchStrategy GetScratchStrategy(std::string* backing_device = nullptr) {
     auto slot_number = fs_mgr_overlayfs_slot_number();
     auto super_device = fs_mgr_overlayfs_super_device(slot_number);
     auto path = fs_mgr_overlayfs_super_device(slot_number == 0);
@@ -834,11 +831,11 @@
         // wouldn't have registed by-name symlinks for the device as it's
         // normally not needed. The access checks elsewhere in this function
         // are safe because system/super are always required.
-        *backing_device = path;
+        if (backing_device) *backing_device = path;
         return ScratchStrategy::kSuperOther;
     }
     if (fs_mgr_access(super_device)) {
-        *backing_device = super_device;
+        if (backing_device) *backing_device = super_device;
         return ScratchStrategy::kDynamicPartition;
     }
 
@@ -846,7 +843,7 @@
     if (!other_slot.empty()) {
         path = kPhysicalDevice + "system" + other_slot;
         if (fs_mgr_access(path)) {
-            *backing_device = path;
+            if (backing_device) *backing_device = path;
             return ScratchStrategy::kSystemOther;
         }
     }
@@ -876,13 +873,6 @@
     }
 }
 
-std::string fs_mgr_overlayfs_scratch_device() {
-    if (!scratch_device_cache.empty()) return scratch_device_cache;
-
-    scratch_device_cache = GetScratchDevice();
-    return scratch_device_cache;
-}
-
 bool fs_mgr_overlayfs_make_scratch(const std::string& scratch_device, const std::string& mnt_type) {
     // Force mkfs by design for overlay support of adb remount, simplify and
     // thus do not rely on fsck to correct problems that could creep in.
@@ -924,16 +914,15 @@
     }
 }
 
-// This is where we find and steal backing storage from the system.
-bool fs_mgr_overlayfs_create_scratch(const Fstab& fstab, std::string* scratch_device,
-                                     bool* partition_exists, bool* change) {
-    *scratch_device = fs_mgr_overlayfs_scratch_device();
-    *partition_exists = fs_mgr_rw_access(*scratch_device);
+// Create or update a scratch partition within super.
+static bool CreateDynamicScratch(const Fstab& fstab, std::string* scratch_device,
+                                 bool* partition_exists, bool* change) {
+    const auto partition_name = android::base::Basename(kScratchMountPoint);
+
+    auto& dm = DeviceMapper::Instance();
+    *partition_exists = dm.GetState(partition_name) != DmDeviceState::INVALID;
+
     auto partition_create = !*partition_exists;
-    // Do we need to create a logical "scratch" partition?
-    if (!partition_create && android::base::StartsWith(*scratch_device, kPhysicalDevice)) {
-        return true;
-    }
     auto slot_number = fs_mgr_overlayfs_slot_number();
     auto super_device = fs_mgr_overlayfs_super_device(slot_number);
     if (!fs_mgr_rw_access(super_device)) return false;
@@ -943,7 +932,6 @@
         LERROR << "open " << super_device << " metadata";
         return false;
     }
-    const auto partition_name = android::base::Basename(kScratchMountPoint);
     auto partition = builder->FindPartition(partition_name);
     *partition_exists = partition != nullptr;
     auto changed = false;
@@ -1024,6 +1012,25 @@
     return true;
 }
 
+bool fs_mgr_overlayfs_create_scratch(const Fstab& fstab, std::string* scratch_device,
+                                     bool* partition_exists, bool* change) {
+    auto strategy = GetScratchStrategy();
+    if (strategy == ScratchStrategy::kDynamicPartition) {
+        return CreateDynamicScratch(fstab, scratch_device, partition_exists, change);
+    }
+
+    // The scratch partition can only be landed on a physical partition if we
+    // get here. If there are no viable candidates that are R/W, just return
+    // that there is no device.
+    *scratch_device = GetScratchDevice();
+    if (scratch_device->empty()) {
+        errno = ENXIO;
+        return false;
+    }
+    *partition_exists = true;
+    return true;
+}
+
 // Create and mount kScratchMountPoint storage if we have logical partitions
 bool fs_mgr_overlayfs_setup_scratch(const Fstab& fstab, bool* change) {
     if (fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) return true;
@@ -1113,7 +1120,7 @@
 }
 
 static void TryMountScratch() {
-    auto scratch_device = fs_mgr_overlayfs_scratch_device();
+    auto scratch_device = GetScratchDevice();
     if (!fs_mgr_overlayfs_scratch_can_be_mounted(scratch_device)) {
         return;
     }
@@ -1162,7 +1169,7 @@
     for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
         if (fs_mgr_is_verity_enabled(entry)) continue;
         if (fs_mgr_overlayfs_already_mounted(fs_mgr_mount_point(entry.mount_point))) continue;
-        auto device = fs_mgr_overlayfs_scratch_device();
+        auto device = GetScratchDevice();
         if (!fs_mgr_overlayfs_scratch_can_be_mounted(device)) break;
         return {device};
     }
@@ -1234,6 +1241,27 @@
     return ret;
 }
 
+static bool GetAndMapScratchDeviceIfNeeded(std::string* device) {
+    *device = GetScratchDevice();
+    if (!device->empty()) {
+        return true;
+    }
+
+    auto strategy = GetScratchStrategy();
+    if (strategy == ScratchStrategy::kDynamicPartition) {
+        auto metadata_slot = fs_mgr_overlayfs_slot_number();
+        CreateLogicalPartitionParams params = {
+                .block_device = fs_mgr_overlayfs_super_device(metadata_slot),
+                .metadata_slot = metadata_slot,
+                .partition_name = android::base::Basename(kScratchMountPoint),
+                .force_writable = true,
+                .timeout_ms = 10s,
+        };
+        return CreateLogicalPartition(params, device);
+    }
+    return false;
+}
+
 // Returns false if teardown not permitted, errno set to last error.
 // If something is altered, set *change.
 bool fs_mgr_overlayfs_teardown(const char* mount_point, bool* change) {
@@ -1243,20 +1271,11 @@
     // specific override entries.
     auto mount_scratch = false;
     if ((mount_point != nullptr) && !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
-        auto scratch_device = fs_mgr_overlayfs_scratch_device();
-        if (scratch_device.empty()) {
-            auto metadata_slot = fs_mgr_overlayfs_slot_number();
-            CreateLogicalPartitionParams params = {
-                    .block_device = fs_mgr_overlayfs_super_device(metadata_slot),
-                    .metadata_slot = metadata_slot,
-                    .partition_name = android::base::Basename(kScratchMountPoint),
-                    .force_writable = true,
-                    .timeout_ms = 10s,
-            };
-            CreateLogicalPartition(params, &scratch_device);
+        std::string scratch_device;
+        if (GetAndMapScratchDeviceIfNeeded(&scratch_device)) {
+            mount_scratch = fs_mgr_overlayfs_mount_scratch(scratch_device,
+                                                           fs_mgr_overlayfs_scratch_mount_type());
         }
-        mount_scratch = fs_mgr_overlayfs_mount_scratch(scratch_device,
-                                                       fs_mgr_overlayfs_scratch_mount_type());
     }
     for (const auto& overlay_mount_point : kOverlayMountPoints) {
         ret &= fs_mgr_overlayfs_teardown_one(
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 809318c..254fbed 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -575,5 +575,9 @@
     return "/dev/block/" + sub_device_name;
 }
 
+bool DeviceMapper::TargetInfo::IsOverflowSnapshot() const {
+    return spec.target_type == "snapshot"s && data == "Overflow"s;
+}
+
 }  // namespace dm
 }  // namespace android
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 418210c..abe9c4c 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -205,6 +205,8 @@
         TargetInfo() {}
         TargetInfo(const struct dm_target_spec& spec, const std::string& data)
             : spec(spec), data(data) {}
+
+        bool IsOverflowSnapshot() const;
     };
     bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table);
 
diff --git a/fs_mgr/libfiemap/Android.bp b/fs_mgr/libfiemap/Android.bp
new file mode 100644
index 0000000..8dbbf4c
--- /dev/null
+++ b/fs_mgr/libfiemap/Android.bp
@@ -0,0 +1,135 @@
+//
+// 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.
+//
+
+cc_library_headers {
+    name: "libfiemap_headers",
+    recovery_available: true,
+    export_include_dirs: ["include"],
+}
+
+cc_defaults {
+    name: "libfiemap_defaults",
+    defaults: ["fs_mgr_defaults"],
+    cflags: [
+        "-D_FILE_OFFSET_BITS=64",
+        "-Wall",
+        "-Werror",
+    ],
+
+    srcs: [
+        "fiemap_writer.cpp",
+        "image_manager.cpp",
+        "metadata.cpp",
+        "split_fiemap_writer.cpp",
+        "utility.cpp",
+    ],
+
+    static_libs: [
+        "libdm",
+        "libext2_uuid",
+        "libext4_utils",
+        "liblp",
+        "libfs_mgr",
+    ],
+
+    shared_libs: [
+        "libbase",
+    ],
+
+    header_libs: [
+        "libfiemap_headers",
+        "liblog_headers",
+    ],
+
+    export_shared_lib_headers: [
+        "libbase",
+    ],
+
+    export_header_lib_headers: [
+        "libfiemap_headers",
+    ],
+}
+
+// Open up a binder IImageManager interface.
+cc_library_static {
+    name: "libfiemap_binder",
+    defaults: ["libfiemap_defaults"],
+    srcs: [
+        "binder.cpp",
+    ],
+    whole_static_libs: [
+        "gsi_aidl_interface-cpp",
+        "libgsi",
+    ],
+    shared_libs: [
+        "libbinder",
+    ],
+}
+
+// Open up a passthrough IImageManager interface. Use libfiemap_binder whenever
+// possible. This should only be used when binder is not available.
+cc_library_static {
+    name: "libfiemap_passthrough",
+    defaults: ["libfiemap_defaults"],
+    recovery_available: true,
+    srcs: [
+        "passthrough.cpp",
+    ],
+}
+
+cc_test {
+    name: "fiemap_writer_test",
+    defaults: ["libfiemap_defaults"],
+    static_libs: [
+        "libbase",
+        "libdm",
+        "liblog",
+    ],
+
+    data: [
+        "testdata/unaligned_file",
+        "testdata/file_4k",
+        "testdata/file_32k",
+    ],
+
+    srcs: [
+        "fiemap_writer_test.cpp",
+    ],
+}
+
+cc_test {
+    name: "fiemap_image_test",
+    defaults: ["libfiemap_defaults"],
+    static_libs: [
+        "libdm",
+        "libext4_utils",
+        "libfs_mgr",
+        "liblp",
+    ],
+    shared_libs: [
+        "libcrypto",
+        "libcrypto_utils",
+        "libcutils",
+        "liblog",
+    ],
+    srcs: [
+        "image_test.cpp",
+    ],
+}
+
+vts_config {
+    name: "VtsFiemapWriterTest",
+}
diff --git a/fs_mgr/libfiemap/AndroidTest.xml b/fs_mgr/libfiemap/AndroidTest.xml
new file mode 100644
index 0000000..44c80fc
--- /dev/null
+++ b/fs_mgr/libfiemap/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Config for VTS VtsFiemapWriterTest">
+    <option name="config-descriptor:metadata" key="plan" value="vts-kernel" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="abort-on-push-failure" value="false"/>
+        <option name="push-group" value="HostDrivenTest.push"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+      <option name="test-module-name" value="VtsFiemapWriterTest"/>
+        <option name="binary-test-source" value="_32bit::DATA/nativetest/fiemap_writer_test/fiemap_writer_test" />
+        <option name="binary-test-source" value="_64bit::DATA/nativetest64/fiemap_writer_test/fiemap_writer_test" />
+        <option name="binary-test-type" value="gtest"/>
+        <option name="precondition-first-api-level" value="29" />
+        <option name="test-timeout" value="1m"/>
+    </test>
+</configuration>
diff --git a/fs_mgr/libfiemap/README.md b/fs_mgr/libfiemap/README.md
new file mode 100644
index 0000000..62d610a
--- /dev/null
+++ b/fs_mgr/libfiemap/README.md
@@ -0,0 +1,75 @@
+libfiemap
+=============
+
+`libfiemap` is a library for creating block-devices that are backed by
+storage in read-write partitions. It exists primary for gsid. Generally, the
+library works by using `libfiemap_writer` to allocate large files within
+filesystem, and then tracks their extents.
+
+There are three main uses for `libfiemap`:
+ - Creating images that will act as block devices. For example, gsid needs to
+   create a `system_gsi` image to store Dynamic System Updates.
+ - Mapping the image as a block device while /data is mounted. This is fairly
+   tricky and is described in more detail below.
+ - Mapping the image as a block device during first-stage init. This is simple
+   because it uses the same logic from dynamic partitions.
+
+Image creation is done through `SplitFiemap`. Depending on the file system,
+a large image may have to be split into multiple files. On Ext4 the limit is
+16GiB and on FAT32 it's 4GiB. Images are saved into `/data/gsi/<name>/`
+where `<name>` is chosen by the process requesting the image.
+
+At the same time, a file called `/metadata/gsi/<name>/lp_metadata` is created.
+This is a super partition header that allows first-stage init to create dynamic
+partitions from the image files. It also tracks the canonical size of the image,
+since the file size may be larger due to alignment.
+
+Mapping
+-------
+
+It is easy to make block devices out of blocks on `/data` when it is not
+mounted, so first-stage init has no issues mapping dynamic partitions from
+images. After `/data` is mounted however, there are two problems:
+ - `/data` is encrypted.
+ - `/dev/block/by-name/data` may be marked as in-use.
+
+We break the problem down into three scenarios.
+
+### FDE and Metadata Encrypted Devices
+
+When FDE or metadata encryption is used, `/data` is not mounted from
+`/dev/block/by-name/data`. Instead, it is mounted from an intermediate
+`dm-crypt` or `dm-default-key` device. This means the underlying device is
+not marked in use, and we can create new dm-linear devices on top of it.
+
+On these devices, a block device for an image will consist of a single
+device-mapper device with a `dm-linear` table entry for each extent in the
+backing file.
+
+### Unencrypted and FBE-encrypted Devices
+
+When a device is unencrypted, or is encrypted with FBE but not metadata
+encryption, we instead use a loop device with `LOOP_SET_DIRECT_IO` enabled.
+Since `/data/gsi` has encryption disabled, this means the raw blocks will be
+unencrypted as well.
+
+### Split Images
+
+If an image was too large to store a single file on the underlying filesystem,
+on an FBE/unencrypted device we will have multiple loop devices. In this case,
+we create a device-mapper device as well. For each loop device it will have one
+`dm-linear` table entry spanning the length of the device.
+
+State Tracking
+--------------
+
+It's important that we know whether or not an image is currently in-use by a
+block device. It could be catastrophic to write to a dm-linear device if the
+underlying blocks are no longer owned by the original file. Thus, when mapping
+an image, we create a property called `gsid.mapped_image.<name>` and set it to
+the path of the block device.
+
+Additionally, we create a `/metadata/gsi/<subdir>/<name>.status` file. Each
+line in this file denotes a dependency on either a device-mapper node or a loop
+device. When deleting a block device, this file is used to release all
+resources.
diff --git a/fs_mgr/libfiemap/binder.cpp b/fs_mgr/libfiemap/binder.cpp
new file mode 100644
index 0000000..49779f4
--- /dev/null
+++ b/fs_mgr/libfiemap/binder.cpp
@@ -0,0 +1,224 @@
+//
+// Copyright (C) 2019 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.
+//
+
+#if !defined(__ANDROID_RECOVERY__)
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/gsi/IGsiService.h>
+#include <android/gsi/IGsid.h>
+#include <binder/IServiceManager.h>
+#include <libfiemap/image_manager.h>
+#include <libgsi/libgsi.h>
+
+namespace android {
+namespace fiemap {
+
+using namespace android::gsi;
+using namespace std::chrono_literals;
+
+class ImageManagerBinder final : public IImageManager {
+  public:
+    ImageManagerBinder(android::sp<IGsiService>&& service, android::sp<IImageService>&& manager);
+    bool CreateBackingImage(const std::string& name, uint64_t size, int flags) override;
+    bool DeleteBackingImage(const std::string& name) override;
+    bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
+                        std::string* path) override;
+    bool UnmapImageDevice(const std::string& name) override;
+    bool BackingImageExists(const std::string& name) override;
+    bool IsImageMapped(const std::string& name) override;
+    bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
+                                  std::string* dev) override;
+    bool ZeroFillNewImage(const std::string& name, uint64_t bytes) override;
+    bool RemoveAllImages() override;
+
+    std::vector<std::string> GetAllBackingImages() override;
+
+  private:
+    android::sp<IGsiService> service_;
+    android::sp<IImageService> manager_;
+};
+
+ImageManagerBinder::ImageManagerBinder(android::sp<IGsiService>&& service,
+                                       android::sp<IImageService>&& manager)
+    : service_(std::move(service)), manager_(std::move(manager)) {}
+
+bool ImageManagerBinder::CreateBackingImage(const std::string& name, uint64_t size, int flags) {
+    auto status = manager_->createBackingImage(name, size, flags);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return true;
+}
+
+bool ImageManagerBinder::DeleteBackingImage(const std::string& name) {
+    auto status = manager_->deleteBackingImage(name);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return true;
+}
+
+bool ImageManagerBinder::MapImageDevice(const std::string& name,
+                                        const std::chrono::milliseconds& timeout_ms,
+                                        std::string* path) {
+    int32_t timeout_ms_count =
+            static_cast<int32_t>(std::clamp<typename std::chrono::milliseconds::rep>(
+                    timeout_ms.count(), INT32_MIN, INT32_MAX));
+    MappedImage map;
+    auto status = manager_->mapImageDevice(name, timeout_ms_count, &map);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    *path = map.path;
+    return true;
+}
+
+bool ImageManagerBinder::UnmapImageDevice(const std::string& name) {
+    auto status = manager_->unmapImageDevice(name);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return true;
+}
+
+bool ImageManagerBinder::BackingImageExists(const std::string& name) {
+    bool retval;
+    auto status = manager_->backingImageExists(name, &retval);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return retval;
+}
+
+bool ImageManagerBinder::IsImageMapped(const std::string& name) {
+    bool retval;
+    auto status = manager_->isImageMapped(name, &retval);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return retval;
+}
+
+bool ImageManagerBinder::MapImageWithDeviceMapper(const IPartitionOpener& opener,
+                                                  const std::string& name, std::string* dev) {
+    (void)opener;
+    (void)name;
+    (void)dev;
+    LOG(ERROR) << "MapImageWithDeviceMapper is not available over binder.";
+    return false;
+}
+
+std::vector<std::string> ImageManagerBinder::GetAllBackingImages() {
+    std::vector<std::string> retval;
+    auto status = manager_->getAllBackingImages(&retval);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+    }
+    return retval;
+}
+
+bool ImageManagerBinder::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
+    auto status = manager_->zeroFillNewImage(name, bytes);
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return true;
+}
+
+bool ImageManagerBinder::RemoveAllImages() {
+    auto status = manager_->removeAllImages();
+    if (!status.isOk()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__
+                   << " binder returned: " << status.exceptionMessage().string();
+        return false;
+    }
+    return true;
+}
+
+static android::sp<IGsid> AcquireIGsid(const std::chrono::milliseconds& timeout_ms) {
+    if (android::base::GetProperty("init.svc.gsid", "") != "running") {
+        if (!android::base::SetProperty("ctl.start", "gsid") ||
+            !android::base::WaitForProperty("init.svc.gsid", "running", timeout_ms)) {
+            LOG(ERROR) << "Could not start the gsid service";
+            return nullptr;
+        }
+        // Sleep for 250ms to give the service time to register.
+        usleep(250 * 1000);
+    }
+    auto sm = android::defaultServiceManager();
+    auto name = android::String16(kGsiServiceName);
+    auto service = sm->checkService(name);
+    return android::interface_cast<IGsid>(service);
+}
+
+static android::sp<IGsid> GetGsiService(const std::chrono::milliseconds& timeout_ms) {
+    auto start_time = std::chrono::steady_clock::now();
+
+    std::chrono::milliseconds elapsed = std::chrono::milliseconds::zero();
+    do {
+        if (auto gsid = AcquireIGsid(timeout_ms - elapsed); gsid != nullptr) {
+            return gsid;
+        }
+        auto now = std::chrono::steady_clock::now();
+        elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
+    } while (elapsed <= timeout_ms);
+
+    LOG(ERROR) << "Timed out trying to acquire IGsid interface";
+    return nullptr;
+}
+
+std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir,
+                                                   const std::chrono::milliseconds& timeout_ms) {
+    auto gsid = GetGsiService(timeout_ms);
+    if (!gsid) {
+        return nullptr;
+    }
+
+    android::sp<IGsiService> service;
+    auto status = gsid->getClient(&service);
+    if (!status.isOk() || !service) {
+        LOG(ERROR) << "Could not acquire IGsiService";
+        return nullptr;
+    }
+
+    android::sp<IImageService> manager;
+    status = service->openImageService(dir, &manager);
+    if (!status.isOk() || !manager) {
+        LOG(ERROR) << "Could not acquire IImageManager: " << status.exceptionMessage().string();
+        return nullptr;
+    }
+    return std::make_unique<ImageManagerBinder>(std::move(service), std::move(manager));
+}
+
+}  // namespace fiemap
+}  // namespace android
+
+#endif  // __ANDROID_RECOVERY__
diff --git a/fs_mgr/libfiemap/fiemap_writer.cpp b/fs_mgr/libfiemap/fiemap_writer.cpp
new file mode 100644
index 0000000..b5794d3
--- /dev/null
+++ b/fs_mgr/libfiemap/fiemap_writer.cpp
@@ -0,0 +1,795 @@
+/*
+ * 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 <libfiemap/fiemap_writer.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <libdm/dm.h>
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using namespace android::dm;
+
+// We cap the maximum number of extents as a sanity measure.
+static constexpr uint32_t kMaxExtents = 50000;
+
+// TODO: Fallback to using fibmap if FIEMAP_EXTENT_MERGED is set.
+static constexpr const uint32_t kUnsupportedExtentFlags =
+        FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_DELALLOC |
+        FIEMAP_EXTENT_NOT_ALIGNED | FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_DATA_TAIL |
+        FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_SHARED | FIEMAP_EXTENT_MERGED;
+
+// Large file support must be enabled.
+static_assert(sizeof(off_t) == sizeof(uint64_t));
+
+static inline void cleanup(const std::string& file_path, bool created) {
+    if (created) {
+        unlink(file_path.c_str());
+    }
+}
+
+static bool ValidateDmTarget(const DeviceMapper::TargetInfo& target) {
+    const auto& entry = target.spec;
+    if (entry.sector_start != 0) {
+        LOG(INFO) << "Stopping at target with non-zero starting sector";
+        return false;
+    }
+
+    auto target_type = DeviceMapper::GetTargetType(entry);
+    if (target_type == "bow" || target_type == "default-key" || target_type == "crypt") {
+        return true;
+    }
+    if (target_type == "linear") {
+        auto pieces = android::base::Split(target.data, " ");
+        if (pieces[1] != "0") {
+            LOG(INFO) << "Stopping at complex linear target with non-zero starting sector: "
+                      << pieces[1];
+            return false;
+        }
+        return true;
+    }
+
+    LOG(INFO) << "Stopping at complex target type " << target_type;
+    return false;
+}
+
+static bool DeviceMapperStackPop(const std::string& bdev, std::string* bdev_raw) {
+    *bdev_raw = bdev;
+
+    if (!::android::base::StartsWith(bdev, "dm-")) {
+        // We are at the bottom of the device mapper stack.
+        return true;
+    }
+
+    // Get the device name.
+    auto dm_name_file = "/sys/block/" + bdev + "/dm/name";
+    std::string dm_name;
+    if (!android::base::ReadFileToString(dm_name_file, &dm_name)) {
+        PLOG(ERROR) << "Could not read file: " << dm_name_file;
+        return false;
+    }
+    dm_name = android::base::Trim(dm_name);
+
+    auto& dm = DeviceMapper::Instance();
+    std::vector<DeviceMapper::TargetInfo> table;
+    if (!dm.GetTableInfo(dm_name, &table)) {
+        LOG(ERROR) << "Could not read device-mapper table for " << dm_name << " at " << bdev;
+        return false;
+    }
+
+    // The purpose of libfiemap is to provide an extent-based view into
+    // a file. This is difficult if devices are not layered in a 1:1 manner;
+    // we would have to translate and break up extents based on the actual
+    // block mapping. Since this is too complex, we simply stop processing
+    // the device-mapper stack if we encounter a complex case.
+    //
+    // It is up to the caller to decide whether stopping at a virtual block
+    // device is allowable. In most cases it is not, because we want either
+    // "userdata" or an external volume. It is useful for tests however.
+    // Callers can check by comparing the device number to that of userdata,
+    // or by checking whether is a device-mapper node.
+    if (table.size() > 1) {
+        LOG(INFO) << "Stopping at complex table for " << dm_name << " at " << bdev;
+        return true;
+    }
+    if (!ValidateDmTarget(table[0])) {
+        return true;
+    }
+
+    auto dm_leaf_dir = "/sys/block/" + bdev + "/slaves";
+    auto d = std::unique_ptr<DIR, decltype(&closedir)>(opendir(dm_leaf_dir.c_str()), closedir);
+    if (d == nullptr) {
+        PLOG(ERROR) << "Failed to open: " << dm_leaf_dir;
+        return false;
+    }
+
+    struct dirent* de;
+    uint32_t num_leaves = 0;
+    std::string bdev_next = "";
+    while ((de = readdir(d.get())) != nullptr) {
+        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
+            continue;
+        }
+
+        // We set the first name we find here
+        if (bdev_next.empty()) {
+            bdev_next = de->d_name;
+        }
+        num_leaves++;
+    }
+
+    // if we have more than one leaves, we return immediately. We can't continue to create the
+    // file since we don't know how to write it out using fiemap, so it will be readable via the
+    // underlying block devices later. The reader will also have to construct the same device mapper
+    // target in order read the file out.
+    if (num_leaves > 1) {
+        LOG(ERROR) << "Found " << num_leaves << " leaf block devices under device mapper device "
+                   << bdev;
+        return false;
+    }
+
+    // recursively call with the block device we found in order to pop the device mapper stack.
+    return DeviceMapperStackPop(bdev_next, bdev_raw);
+}
+
+bool FiemapWriter::GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path,
+                                         bool* uses_dm) {
+    struct stat sb;
+    if (stat(file_path.c_str(), &sb)) {
+        PLOG(ERROR) << "Failed to get stat for: " << file_path;
+        return false;
+    }
+
+    std::string bdev;
+    if (!BlockDeviceToName(major(sb.st_dev), minor(sb.st_dev), &bdev)) {
+        LOG(ERROR) << "Failed to get block device name for " << major(sb.st_dev) << ":"
+                   << minor(sb.st_dev);
+        return false;
+    }
+
+    std::string bdev_raw;
+    if (!DeviceMapperStackPop(bdev, &bdev_raw)) {
+        LOG(ERROR) << "Failed to get the bottom of the device mapper stack for device: " << bdev;
+        return false;
+    }
+
+    if (uses_dm) {
+        *uses_dm = (bdev_raw != bdev);
+    }
+
+    LOG(DEBUG) << "Popped device (" << bdev_raw << ") from device mapper stack starting with ("
+               << bdev << ")";
+
+    *bdev_path = ::android::base::StringPrintf("/dev/block/%s", bdev_raw.c_str());
+
+    // Make sure we are talking to a block device before calling it a success.
+    if (stat(bdev_path->c_str(), &sb)) {
+        PLOG(ERROR) << "Failed to get stat for block device: " << *bdev_path;
+        return false;
+    }
+
+    if ((sb.st_mode & S_IFMT) != S_IFBLK) {
+        PLOG(ERROR) << "File: " << *bdev_path << " is not a block device";
+        return false;
+    }
+
+    return true;
+}
+
+static bool GetBlockDeviceSize(int bdev_fd, const std::string& bdev_path, uint64_t* bdev_size) {
+    uint64_t size_in_bytes = 0;
+    if (ioctl(bdev_fd, BLKGETSIZE64, &size_in_bytes)) {
+        PLOG(ERROR) << "Failed to get total size for: " << bdev_path;
+        return false;
+    }
+
+    *bdev_size = size_in_bytes;
+
+    return true;
+}
+
+static uint64_t GetFileSize(const std::string& file_path) {
+    struct stat sb;
+    if (stat(file_path.c_str(), &sb)) {
+        PLOG(ERROR) << "Failed to get size for file: " << file_path;
+        return 0;
+    }
+
+    return sb.st_size;
+}
+
+static bool PerformFileChecks(const std::string& file_path, uint64_t file_size, uint64_t* blocksz,
+                              uint32_t* fs_type) {
+    struct statfs64 sfs;
+    if (statfs64(file_path.c_str(), &sfs)) {
+        PLOG(ERROR) << "Failed to read file system status at: " << file_path;
+        return false;
+    }
+
+    if (!sfs.f_bsize) {
+        LOG(ERROR) << "Unsupported block size: " << sfs.f_bsize;
+        return false;
+    }
+
+    // Check if the filesystem is of supported types.
+    // Only ext4, f2fs, and vfat are tested and supported.
+    switch (sfs.f_type) {
+        case EXT4_SUPER_MAGIC:
+        case F2FS_SUPER_MAGIC:
+        case MSDOS_SUPER_MAGIC:
+            break;
+        default:
+            LOG(ERROR) << "Unsupported file system type: 0x" << std::hex << sfs.f_type;
+            return false;
+    }
+
+    uint64_t available_bytes = sfs.f_bsize * sfs.f_bavail;
+    if (access(file_path.c_str(), F_OK) != 0 && available_bytes <= file_size) {
+        LOG(ERROR) << "Not enough free space in file system to create file of size : " << file_size;
+        return false;
+    }
+
+    *blocksz = sfs.f_bsize;
+    *fs_type = sfs.f_type;
+    return true;
+}
+
+static bool FallocateFallback(int file_fd, uint64_t block_size, uint64_t file_size,
+                              const std::string& file_path,
+                              const std::function<bool(uint64_t, uint64_t)>& on_progress) {
+    // Even though this is much faster than writing zeroes, it is still slow
+    // enough that we need to fire the progress callback periodically. To
+    // easily achieve this, we seek in chunks. We use 1000 chunks since
+    // normally we only fire the callback on 1/1000th increments.
+    uint64_t bytes_per_chunk = std::max(file_size / 1000, block_size);
+
+    // Seek just to the end of each chunk and write a single byte, causing
+    // the filesystem to allocate blocks.
+    off_t cursor = 0;
+    off_t end = static_cast<off_t>(file_size);
+    while (cursor < end) {
+        cursor = std::min(static_cast<off_t>(cursor + bytes_per_chunk), end);
+        auto rv = TEMP_FAILURE_RETRY(lseek(file_fd, cursor - 1, SEEK_SET));
+        if (rv < 0) {
+            PLOG(ERROR) << "Failed to lseek " << file_path;
+            return false;
+        }
+        if (rv != cursor - 1) {
+            LOG(ERROR) << "Seek returned wrong offset " << rv << " for file " << file_path;
+            return false;
+        }
+        char buffer[] = {0};
+        if (!android::base::WriteFully(file_fd, buffer, 1)) {
+            PLOG(ERROR) << "Write failed: " << file_path;
+            return false;
+        }
+        if (on_progress && !on_progress(cursor, file_size)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// F2FS-specific ioctl
+// It requires the below kernel commit merged in v4.16-rc1.
+//   1ad71a27124c ("f2fs: add an ioctl to disable GC for specific file")
+// In android-4.4,
+//   56ee1e817908 ("f2fs: updates on v4.16-rc1")
+// In android-4.9,
+//   2f17e34672a8 ("f2fs: updates on v4.16-rc1")
+// In android-4.14,
+//   ce767d9a55bc ("f2fs: updates on v4.16-rc1")
+#ifndef F2FS_IOC_SET_PIN_FILE
+#ifndef F2FS_IOCTL_MAGIC
+#define F2FS_IOCTL_MAGIC 0xf5
+#endif
+#define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32)
+#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
+#endif
+
+static bool IsFilePinned(int file_fd, const std::string& file_path, uint32_t fs_type) {
+    if (fs_type != F2FS_SUPER_MAGIC) {
+        // No pinning necessary for ext4 or vfat. The blocks, once allocated,
+        // are expected to be fixed.
+        return true;
+    }
+
+    // f2fs: export FS_NOCOW_FL flag to user
+    uint32_t flags;
+    int error = ioctl(file_fd, FS_IOC_GETFLAGS, &flags);
+    if (error < 0) {
+        if ((errno == ENOTTY) || (errno == ENOTSUP)) {
+            PLOG(ERROR) << "Failed to get flags, not supported by kernel: " << file_path;
+        } else {
+            PLOG(ERROR) << "Failed to get flags: " << file_path;
+        }
+        return false;
+    }
+    if (!(flags & FS_NOCOW_FL)) {
+        return false;
+    }
+
+    // F2FS_IOC_GET_PIN_FILE returns the number of blocks moved.
+    uint32_t moved_blocks_nr;
+    error = ioctl(file_fd, F2FS_IOC_GET_PIN_FILE, &moved_blocks_nr);
+    if (error < 0) {
+        if ((errno == ENOTTY) || (errno == ENOTSUP)) {
+            PLOG(ERROR) << "Failed to get file pin status, not supported by kernel: " << file_path;
+        } else {
+            PLOG(ERROR) << "Failed to get file pin status: " << file_path;
+        }
+        return false;
+    }
+
+    if (moved_blocks_nr) {
+        LOG(WARNING) << moved_blocks_nr << " blocks moved in file " << file_path;
+    }
+    return moved_blocks_nr == 0;
+}
+
+static bool PinFile(int file_fd, const std::string& file_path, uint32_t fs_type) {
+    if (IsFilePinned(file_fd, file_path, fs_type)) {
+        return true;
+    }
+    if (fs_type != F2FS_SUPER_MAGIC) {
+        // No pinning necessary for ext4/msdos. The blocks, once allocated, are
+        // expected to be fixed.
+        return true;
+    }
+
+    uint32_t pin_status = 1;
+    int error = ioctl(file_fd, F2FS_IOC_SET_PIN_FILE, &pin_status);
+    if (error < 0) {
+        if ((errno == ENOTTY) || (errno == ENOTSUP)) {
+            PLOG(ERROR) << "Failed to pin file, not supported by kernel: " << file_path;
+        } else {
+            PLOG(ERROR) << "Failed to pin file: " << file_path;
+        }
+        return false;
+    }
+
+    return true;
+}
+
+// write zeroes in 'blocksz' byte increments until we reach file_size to make sure the data
+// blocks are actually written to by the file system and thus getting rid of the holes in the
+// file.
+static bool WriteZeroes(int file_fd, const std::string& file_path, size_t blocksz,
+                        uint64_t file_size,
+                        const std::function<bool(uint64_t, uint64_t)>& on_progress) {
+    auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksz), free);
+    if (buffer == nullptr) {
+        LOG(ERROR) << "failed to allocate memory for writing file";
+        return false;
+    }
+
+    off64_t offset = lseek64(file_fd, 0, SEEK_SET);
+    if (offset < 0) {
+        PLOG(ERROR) << "Failed to seek at the beginning of : " << file_path;
+        return false;
+    }
+
+    int permille = -1;
+    while (offset < file_size) {
+        if (!::android::base::WriteFully(file_fd, buffer.get(), blocksz)) {
+            PLOG(ERROR) << "Failed to write" << blocksz << " bytes at offset" << offset
+                        << " in file " << file_path;
+            return false;
+        }
+
+        offset += blocksz;
+
+        // Don't invoke the callback every iteration - wait until a significant
+        // chunk (here, 1/1000th) of the data has been processed.
+        int new_permille = (static_cast<uint64_t>(offset) * 1000) / file_size;
+        if (new_permille != permille && static_cast<uint64_t>(offset) != file_size) {
+            if (on_progress && !on_progress(offset, file_size)) {
+                return false;
+            }
+            permille = new_permille;
+        }
+    }
+
+    if (lseek64(file_fd, 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "Failed to reset offset at the beginning of : " << file_path;
+        return false;
+    }
+    return true;
+}
+
+// Reserve space for the file on the file system and write it out to make sure the extents
+// don't come back unwritten. Return from this function with the kernel file offset set to 0.
+// If the filesystem is f2fs, then we also PIN the file on disk to make sure the blocks
+// aren't moved around.
+static bool AllocateFile(int file_fd, const std::string& file_path, uint64_t blocksz,
+                         uint64_t file_size, unsigned int fs_type,
+                         std::function<bool(uint64_t, uint64_t)> on_progress) {
+    bool need_explicit_writes = true;
+    switch (fs_type) {
+        case EXT4_SUPER_MAGIC:
+            break;
+        case F2FS_SUPER_MAGIC: {
+            bool supported;
+            if (!F2fsPinBeforeAllocate(file_fd, &supported)) {
+                return false;
+            }
+            if (supported) {
+                if (!PinFile(file_fd, file_path, fs_type)) {
+                    return false;
+                }
+                need_explicit_writes = false;
+            }
+            break;
+        }
+        case MSDOS_SUPER_MAGIC:
+            // fallocate() is not supported, and not needed, since VFAT does not support holes.
+            // Instead we can perform a much faster allocation.
+            return FallocateFallback(file_fd, blocksz, file_size, file_path, on_progress);
+        default:
+            LOG(ERROR) << "Missing fallocate() support for file system " << fs_type;
+            return false;
+    }
+
+    if (fallocate(file_fd, 0, 0, file_size)) {
+        PLOG(ERROR) << "Failed to allocate space for file: " << file_path << " size: " << file_size;
+        return false;
+    }
+
+    if (need_explicit_writes && !WriteZeroes(file_fd, file_path, blocksz, file_size, on_progress)) {
+        return false;
+    }
+
+    // flush all writes here ..
+    if (fsync(file_fd)) {
+        PLOG(ERROR) << "Failed to synchronize written file:" << file_path;
+        return false;
+    }
+
+    // Send one last progress notification.
+    if (on_progress && !on_progress(file_size, file_size)) {
+        return false;
+    }
+    return true;
+}
+
+bool FiemapWriter::HasPinnedExtents(const std::string& file_path) {
+    android::base::unique_fd fd(open(file_path.c_str(), O_NOFOLLOW | O_CLOEXEC | O_RDONLY));
+    if (fd < 0) {
+        PLOG(ERROR) << "open: " << file_path;
+        return false;
+    }
+
+    struct statfs64 sfs;
+    if (fstatfs64(fd, &sfs)) {
+        PLOG(ERROR) << "fstatfs64: " << file_path;
+        return false;
+    }
+    return IsFilePinned(fd, file_path, sfs.f_type);
+}
+
+static bool CountFiemapExtents(int file_fd, const std::string& file_path, uint32_t* num_extents) {
+    struct fiemap fiemap = {};
+    fiemap.fm_start = 0;
+    fiemap.fm_length = UINT64_MAX;
+    fiemap.fm_flags = FIEMAP_FLAG_SYNC;
+    fiemap.fm_extent_count = 0;
+
+    if (ioctl(file_fd, FS_IOC_FIEMAP, &fiemap)) {
+        PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
+        return false;
+    }
+
+    if (num_extents) {
+        *num_extents = fiemap.fm_mapped_extents;
+    }
+    return true;
+}
+
+static bool IsValidExtent(const fiemap_extent* extent, std::string_view file_path) {
+    if (extent->fe_flags & kUnsupportedExtentFlags) {
+        LOG(ERROR) << "Extent at location " << extent->fe_logical << " of file " << file_path
+                   << " has unsupported flags";
+        return false;
+    }
+    return true;
+}
+
+static bool IsLastExtent(const fiemap_extent* extent) {
+    if (!(extent->fe_flags & FIEMAP_EXTENT_LAST)) {
+        LOG(ERROR) << "Extents are being received out-of-order";
+        return false;
+    }
+    return true;
+}
+
+static bool FiemapToExtents(struct fiemap* fiemap, std::vector<struct fiemap_extent>* extents,
+                            uint32_t num_extents, std::string_view file_path) {
+    if (num_extents == 0) return false;
+
+    const struct fiemap_extent* last_extent = &fiemap->fm_extents[num_extents - 1];
+    if (!IsLastExtent(last_extent)) {
+        LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path;
+        return false;
+    }
+
+    // Iterate through each extent, read and make sure its valid before adding it to the vector
+    // merging contiguous extents.
+    fiemap_extent* prev = &fiemap->fm_extents[0];
+    if (!IsValidExtent(prev, file_path)) return false;
+
+    for (uint32_t i = 1; i < num_extents; i++) {
+        fiemap_extent* next = &fiemap->fm_extents[i];
+
+        // Make sure extents are returned in order
+        if (next != last_extent && IsLastExtent(next)) return false;
+
+        // Check if extent's flags are valid
+        if (!IsValidExtent(next, file_path)) return false;
+
+        // Check if the current extent is contiguous with the previous one.
+        // An extent can be combined with its predecessor only if:
+        //  1. There is no physical space between the previous and the current
+        //  extent, and
+        //  2. The physical distance between the previous and current extent
+        //  corresponds to their logical distance (contiguous mapping).
+        if (prev->fe_physical + prev->fe_length == next->fe_physical &&
+            next->fe_physical - prev->fe_physical == next->fe_logical - prev->fe_logical) {
+            prev->fe_length += next->fe_length;
+        } else {
+            extents->emplace_back(*prev);
+            prev = next;
+        }
+    }
+    extents->emplace_back(*prev);
+
+    return true;
+}
+
+static bool ReadFiemap(int file_fd, const std::string& file_path,
+                       std::vector<struct fiemap_extent>* extents) {
+    uint32_t num_extents;
+    if (!CountFiemapExtents(file_fd, file_path, &num_extents)) {
+        return false;
+    }
+    if (num_extents == 0) {
+        LOG(ERROR) << "File " << file_path << " has zero extents";
+        return false;
+    }
+    if (num_extents > kMaxExtents) {
+        LOG(ERROR) << "File has " << num_extents << ", maximum is " << kMaxExtents << ": "
+                   << file_path;
+        return false;
+    }
+
+    uint64_t fiemap_size =
+            sizeof(struct fiemap_extent) + num_extents * sizeof(struct fiemap_extent);
+    auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, fiemap_size), free);
+    if (buffer == nullptr) {
+        LOG(ERROR) << "Failed to allocate memory for fiemap";
+        return false;
+    }
+
+    struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(buffer.get());
+    fiemap->fm_start = 0;
+    fiemap->fm_length = UINT64_MAX;
+    // make sure file is synced to disk before we read the fiemap
+    fiemap->fm_flags = FIEMAP_FLAG_SYNC;
+    fiemap->fm_extent_count = num_extents;
+
+    if (ioctl(file_fd, FS_IOC_FIEMAP, fiemap)) {
+        PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
+        return false;
+    }
+    if (fiemap->fm_mapped_extents != num_extents) {
+        LOG(ERROR) << "FIEMAP returned unexpected extent count (" << num_extents
+                   << " expected, got " << fiemap->fm_mapped_extents << ") for file: " << file_path;
+        return false;
+    }
+
+    return FiemapToExtents(fiemap, extents, num_extents, file_path);
+}
+
+static bool ReadFibmap(int file_fd, const std::string& file_path,
+                       std::vector<struct fiemap_extent>* extents) {
+    struct stat s;
+    if (fstat(file_fd, &s)) {
+        PLOG(ERROR) << "Failed to stat " << file_path;
+        return false;
+    }
+
+    unsigned int blksize;
+    if (ioctl(file_fd, FIGETBSZ, &blksize) < 0) {
+        PLOG(ERROR) << "Failed to get FIGETBSZ for " << file_path;
+        return false;
+    }
+    if (!blksize) {
+        LOG(ERROR) << "Invalid filesystem block size: " << blksize;
+        return false;
+    }
+
+    uint64_t num_blocks = (s.st_size + blksize - 1) / blksize;
+    if (num_blocks > std::numeric_limits<uint32_t>::max()) {
+        LOG(ERROR) << "Too many blocks for FIBMAP (" << num_blocks << ")";
+        return false;
+    }
+
+    for (uint32_t last_block, block_number = 0; block_number < num_blocks; block_number++) {
+        uint32_t block = block_number;
+        if (ioctl(file_fd, FIBMAP, &block)) {
+            PLOG(ERROR) << "Failed to get FIBMAP for file " << file_path;
+            return false;
+        }
+        if (!block) {
+            LOG(ERROR) << "Logical block " << block_number << " is a hole, which is not supported";
+            return false;
+        }
+
+        if (!extents->empty() && block == last_block + 1) {
+            extents->back().fe_length += blksize;
+        } else {
+            extents->push_back(fiemap_extent{.fe_logical = block_number,
+                                             .fe_physical = static_cast<uint64_t>(block) * blksize,
+                                             .fe_length = static_cast<uint64_t>(blksize),
+                                             .fe_flags = 0});
+            if (extents->size() > kMaxExtents) {
+                LOG(ERROR) << "File has more than " << kMaxExtents << "extents: " << file_path;
+                return false;
+            }
+        }
+        last_block = block;
+    }
+    return true;
+}
+
+FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_size, bool create,
+                                   std::function<bool(uint64_t, uint64_t)> progress) {
+    // if 'create' is false, open an existing file and do not truncate.
+    int open_flags = O_RDWR | O_CLOEXEC;
+    if (create) {
+        if (access(file_path.c_str(), F_OK) == 0) {
+            LOG(WARNING) << "File " << file_path << " already exists, truncating";
+        }
+        open_flags |= O_CREAT | O_TRUNC;
+    }
+    ::android::base::unique_fd file_fd(
+            TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags, S_IRUSR | S_IWUSR)));
+    if (file_fd < 0) {
+        PLOG(ERROR) << "Failed to create file at: " << file_path;
+        return nullptr;
+    }
+
+    std::string abs_path;
+    if (!::android::base::Realpath(file_path, &abs_path)) {
+        PLOG(ERROR) << "Invalid file path: " << file_path;
+        cleanup(file_path, create);
+        return nullptr;
+    }
+
+    std::string bdev_path;
+    if (!GetBlockDeviceForFile(abs_path, &bdev_path)) {
+        LOG(ERROR) << "Failed to get block dev path for file: " << file_path;
+        cleanup(abs_path, create);
+        return nullptr;
+    }
+
+    ::android::base::unique_fd bdev_fd(
+            TEMP_FAILURE_RETRY(open(bdev_path.c_str(), O_RDONLY | O_CLOEXEC)));
+    if (bdev_fd < 0) {
+        PLOG(ERROR) << "Failed to open block device: " << bdev_path;
+        cleanup(file_path, create);
+        return nullptr;
+    }
+
+    uint64_t bdevsz;
+    if (!GetBlockDeviceSize(bdev_fd, bdev_path, &bdevsz)) {
+        LOG(ERROR) << "Failed to get block device size for : " << bdev_path;
+        cleanup(file_path, create);
+        return nullptr;
+    }
+
+    if (!create) {
+        file_size = GetFileSize(abs_path);
+        if (file_size == 0) {
+            LOG(ERROR) << "Invalid file size of zero bytes for file: " << abs_path;
+            return nullptr;
+        }
+    }
+
+    uint64_t blocksz;
+    uint32_t fs_type;
+    if (!PerformFileChecks(abs_path, file_size, &blocksz, &fs_type)) {
+        LOG(ERROR) << "Failed to validate file or file system for file:" << abs_path;
+        cleanup(abs_path, create);
+        return nullptr;
+    }
+
+    // Align up to the nearest block size.
+    if (file_size % blocksz) {
+        file_size += blocksz - (file_size % blocksz);
+    }
+
+    if (create) {
+        if (!AllocateFile(file_fd, abs_path, blocksz, file_size, fs_type, std::move(progress))) {
+            LOG(ERROR) << "Failed to allocate file: " << abs_path << " of size: " << file_size
+                       << " bytes";
+            cleanup(abs_path, create);
+            return nullptr;
+        }
+    }
+
+    // f2fs may move the file blocks around.
+    if (!PinFile(file_fd, abs_path, fs_type)) {
+        cleanup(abs_path, create);
+        LOG(ERROR) << "Failed to pin the file in storage";
+        return nullptr;
+    }
+
+    // now allocate the FiemapWriter and start setting it up
+    FiemapUniquePtr fmap(new FiemapWriter());
+    switch (fs_type) {
+        case EXT4_SUPER_MAGIC:
+        case F2FS_SUPER_MAGIC:
+            if (!ReadFiemap(file_fd, abs_path, &fmap->extents_)) {
+                LOG(ERROR) << "Failed to read fiemap of file: " << abs_path;
+                cleanup(abs_path, create);
+                return nullptr;
+            }
+            break;
+        case MSDOS_SUPER_MAGIC:
+            if (!ReadFibmap(file_fd, abs_path, &fmap->extents_)) {
+                LOG(ERROR) << "Failed to read fibmap of file: " << abs_path;
+                cleanup(abs_path, create);
+                return nullptr;
+            }
+            break;
+    }
+
+    fmap->file_path_ = abs_path;
+    fmap->bdev_path_ = bdev_path;
+    fmap->file_size_ = file_size;
+    fmap->bdev_size_ = bdevsz;
+    fmap->fs_type_ = fs_type;
+    fmap->block_size_ = blocksz;
+
+    LOG(VERBOSE) << "Successfully created FiemapWriter for file " << abs_path << " on block device "
+                 << bdev_path;
+    return fmap;
+}
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/libfiemap/fiemap_writer_test.cpp b/fs_mgr/libfiemap/fiemap_writer_test.cpp
new file mode 100644
index 0000000..4ac7161
--- /dev/null
+++ b/fs_mgr/libfiemap/fiemap_writer_test.cpp
@@ -0,0 +1,541 @@
+/*
+ * 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 <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <libdm/loop_control.h>
+#include <libfiemap/fiemap_writer.h>
+#include <libfiemap/split_fiemap_writer.h>
+
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using namespace std;
+using namespace std::string_literals;
+using namespace android::fiemap;
+using unique_fd = android::base::unique_fd;
+using LoopDevice = android::dm::LoopDevice;
+
+std::string gTestDir;
+uint64_t testfile_size = 536870912;  // default of 512MiB
+size_t gBlockSize = 0;
+
+class FiemapWriterTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+        testfile = gTestDir + "/"s + tinfo->name();
+    }
+
+    void TearDown() override { unlink(testfile.c_str()); }
+
+    // name of the file we use for testing
+    std::string testfile;
+};
+
+class SplitFiemapTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+        testfile = gTestDir + "/"s + tinfo->name();
+    }
+
+    void TearDown() override {
+        std::string message;
+        if (!SplitFiemap::RemoveSplitFiles(testfile, &message)) {
+            cerr << "Could not remove all split files: " << message;
+        }
+    }
+
+    // name of the file we use for testing
+    std::string testfile;
+};
+
+TEST_F(FiemapWriterTest, CreateImpossiblyLargeFile) {
+    // Try creating a file of size ~100TB but aligned to
+    // 512 byte to make sure block alignment tests don't
+    // fail.
+    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 1099511627997184);
+    EXPECT_EQ(fptr, nullptr);
+    EXPECT_EQ(access(testfile.c_str(), F_OK), -1);
+    EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_F(FiemapWriterTest, CreateUnalignedFile) {
+    // Try creating a file of size 4097 bytes which is guaranteed
+    // to be unaligned to all known block sizes.
+    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize + 1);
+    ASSERT_NE(fptr, nullptr);
+    ASSERT_EQ(fptr->size(), gBlockSize * 2);
+}
+
+TEST_F(FiemapWriterTest, CheckFilePath) {
+    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize);
+    ASSERT_NE(fptr, nullptr);
+    EXPECT_EQ(fptr->size(), gBlockSize);
+    EXPECT_EQ(fptr->file_path(), testfile);
+    EXPECT_EQ(access(testfile.c_str(), F_OK), 0);
+}
+
+TEST_F(FiemapWriterTest, CheckFileSize) {
+    // Create a large-ish file and test that the expected size matches.
+    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 1024 * 1024 * 16);
+    ASSERT_NE(fptr, nullptr);
+
+    struct stat s;
+    ASSERT_EQ(stat(testfile.c_str(), &s), 0);
+    EXPECT_EQ(static_cast<uint64_t>(s.st_size), fptr->size());
+}
+
+TEST_F(FiemapWriterTest, CheckProgress) {
+    std::vector<uint64_t> expected;
+    size_t invocations = 0;
+    auto callback = [&](uint64_t done, uint64_t total) -> bool {
+        if (invocations >= expected.size()) {
+            return false;
+        }
+        EXPECT_EQ(done, expected[invocations]);
+        EXPECT_EQ(total, gBlockSize);
+        invocations++;
+        return true;
+    };
+
+    expected.push_back(gBlockSize);
+
+    auto ptr = FiemapWriter::Open(testfile, gBlockSize, true, std::move(callback));
+    EXPECT_NE(ptr, nullptr);
+    EXPECT_EQ(invocations, expected.size());
+}
+
+TEST_F(FiemapWriterTest, CheckPinning) {
+    auto ptr = FiemapWriter::Open(testfile, 4096);
+    ASSERT_NE(ptr, nullptr);
+    EXPECT_TRUE(FiemapWriter::HasPinnedExtents(testfile));
+}
+
+TEST_F(FiemapWriterTest, CheckBlockDevicePath) {
+    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize);
+    EXPECT_EQ(fptr->size(), gBlockSize);
+    EXPECT_EQ(fptr->bdev_path().find("/dev/block/"), size_t(0));
+    EXPECT_EQ(fptr->bdev_path().find("/dev/block/dm-"), string::npos);
+}
+
+TEST_F(FiemapWriterTest, CheckFileCreated) {
+    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 32768);
+    ASSERT_NE(fptr, nullptr);
+    unique_fd fd(open(testfile.c_str(), O_RDONLY));
+    EXPECT_GT(fd, -1);
+}
+
+TEST_F(FiemapWriterTest, CheckFileSizeActual) {
+    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, testfile_size);
+    ASSERT_NE(fptr, nullptr);
+
+    struct stat sb;
+    ASSERT_EQ(stat(testfile.c_str(), &sb), 0);
+    EXPECT_GE(sb.st_size, testfile_size);
+}
+
+TEST_F(FiemapWriterTest, CheckFileExtents) {
+    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, testfile_size);
+    ASSERT_NE(fptr, nullptr);
+    EXPECT_GT(fptr->extents().size(), 0);
+}
+
+TEST_F(FiemapWriterTest, ExistingFile) {
+    // Create the file.
+    { ASSERT_NE(FiemapWriter::Open(testfile, gBlockSize), nullptr); }
+    // Test that we can still open it.
+    {
+        auto ptr = FiemapWriter::Open(testfile, 0, false);
+        ASSERT_NE(ptr, nullptr);
+        EXPECT_GT(ptr->extents().size(), 0);
+    }
+}
+
+TEST_F(FiemapWriterTest, FileDeletedOnError) {
+    auto callback = [](uint64_t, uint64_t) -> bool { return false; };
+    auto ptr = FiemapWriter::Open(testfile, gBlockSize, true, std::move(callback));
+    EXPECT_EQ(ptr, nullptr);
+    EXPECT_EQ(access(testfile.c_str(), F_OK), -1);
+    EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_F(FiemapWriterTest, MaxBlockSize) {
+    ASSERT_GT(DetermineMaximumFileSize(testfile), 0);
+}
+
+TEST_F(FiemapWriterTest, FibmapBlockAddressing) {
+    FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize);
+    ASSERT_NE(fptr, nullptr);
+
+    switch (fptr->fs_type()) {
+        case F2FS_SUPER_MAGIC:
+        case EXT4_SUPER_MAGIC:
+            // Skip the test for FIEMAP supported filesystems. This is really
+            // because f2fs/ext4 have caches that seem to defeat reading back
+            // directly from the block device, and writing directly is too
+            // dangerous.
+            std::cout << "Skipping test, filesystem does not use FIBMAP\n";
+            return;
+    }
+
+    bool uses_dm;
+    std::string bdev_path;
+    ASSERT_TRUE(FiemapWriter::GetBlockDeviceForFile(testfile, &bdev_path, &uses_dm));
+
+    if (uses_dm) {
+        // We could use a device-mapper wrapper here to bypass encryption, but
+        // really this test is for FIBMAP correctness on VFAT (where encryption
+        // is never used), so we don't bother.
+        std::cout << "Skipping test, block device is metadata encrypted\n";
+        return;
+    }
+
+    std::string data(fptr->size(), '\0');
+    for (size_t i = 0; i < data.size(); i++) {
+        data[i] = 'A' + static_cast<char>(data.size() % 26);
+    }
+
+    {
+        unique_fd fd(open(testfile.c_str(), O_WRONLY | O_CLOEXEC));
+        ASSERT_GE(fd, 0);
+        ASSERT_TRUE(android::base::WriteFully(fd, data.data(), data.size()));
+        ASSERT_EQ(fsync(fd), 0);
+    }
+
+    ASSERT_FALSE(fptr->extents().empty());
+    const auto& first_extent = fptr->extents()[0];
+
+    unique_fd bdev(open(fptr->bdev_path().c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_GE(bdev, 0);
+
+    off_t where = first_extent.fe_physical;
+    ASSERT_EQ(lseek(bdev, where, SEEK_SET), where);
+
+    // Note: this will fail on encrypted folders.
+    std::string actual(data.size(), '\0');
+    ASSERT_GE(first_extent.fe_length, data.size());
+    ASSERT_TRUE(android::base::ReadFully(bdev, actual.data(), actual.size()));
+    EXPECT_EQ(memcmp(actual.data(), data.data(), data.size()), 0);
+}
+
+TEST_F(SplitFiemapTest, Create) {
+    auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32);
+    ASSERT_NE(ptr, nullptr);
+
+    auto extents = ptr->extents();
+
+    // Destroy the fiemap, closing file handles. This should not delete them.
+    ptr = nullptr;
+
+    std::vector<std::string> files;
+    ASSERT_TRUE(SplitFiemap::GetSplitFileList(testfile, &files));
+    for (const auto& path : files) {
+        EXPECT_EQ(access(path.c_str(), F_OK), 0);
+    }
+
+    ASSERT_GE(extents.size(), files.size());
+}
+
+TEST_F(SplitFiemapTest, Open) {
+    {
+        auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32);
+        ASSERT_NE(ptr, nullptr);
+    }
+
+    auto ptr = SplitFiemap::Open(testfile);
+    ASSERT_NE(ptr, nullptr);
+
+    auto extents = ptr->extents();
+    ASSERT_GE(extents.size(), 24);
+}
+
+TEST_F(SplitFiemapTest, DeleteOnFail) {
+    auto ptr = SplitFiemap::Create(testfile, 1024 * 1024 * 100, 1);
+    ASSERT_EQ(ptr, nullptr);
+
+    std::string first_file = testfile + ".0001";
+    ASSERT_NE(access(first_file.c_str(), F_OK), 0);
+    ASSERT_EQ(errno, ENOENT);
+    ASSERT_NE(access(testfile.c_str(), F_OK), 0);
+    ASSERT_EQ(errno, ENOENT);
+}
+
+static string ReadSplitFiles(const std::string& base_path, size_t num_files) {
+    std::string result;
+    for (int i = 0; i < num_files; i++) {
+        std::string path = base_path + android::base::StringPrintf(".%04d", i);
+        std::string data;
+        if (!android::base::ReadFileToString(path, &data)) {
+            return {};
+        }
+        result += data;
+    }
+    return result;
+}
+
+TEST_F(SplitFiemapTest, WriteWholeFile) {
+    static constexpr size_t kChunkSize = 32768;
+    static constexpr size_t kSize = kChunkSize * 3;
+    auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize);
+    ASSERT_NE(ptr, nullptr);
+
+    auto buffer = std::make_unique<int[]>(kSize / sizeof(int));
+    for (size_t i = 0; i < kSize / sizeof(int); i++) {
+        buffer[i] = i;
+    }
+    ASSERT_TRUE(ptr->Write(buffer.get(), kSize));
+
+    std::string expected(reinterpret_cast<char*>(buffer.get()), kSize);
+    auto actual = ReadSplitFiles(testfile, 3);
+    ASSERT_EQ(expected.size(), actual.size());
+    EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0);
+}
+
+TEST_F(SplitFiemapTest, WriteFileInChunks1) {
+    static constexpr size_t kChunkSize = 32768;
+    static constexpr size_t kSize = kChunkSize * 3;
+    auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize);
+    ASSERT_NE(ptr, nullptr);
+
+    auto buffer = std::make_unique<int[]>(kSize / sizeof(int));
+    for (size_t i = 0; i < kSize / sizeof(int); i++) {
+        buffer[i] = i;
+    }
+
+    // Write in chunks of 1000 (so some writes straddle the boundary of two
+    // files).
+    size_t bytes_written = 0;
+    while (bytes_written < kSize) {
+        size_t to_write = std::min(kSize - bytes_written, (size_t)1000);
+        char* data = reinterpret_cast<char*>(buffer.get()) + bytes_written;
+        ASSERT_TRUE(ptr->Write(data, to_write));
+        bytes_written += to_write;
+    }
+
+    std::string expected(reinterpret_cast<char*>(buffer.get()), kSize);
+    auto actual = ReadSplitFiles(testfile, 3);
+    ASSERT_EQ(expected.size(), actual.size());
+    EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0);
+}
+
+TEST_F(SplitFiemapTest, WriteFileInChunks2) {
+    static constexpr size_t kChunkSize = 32768;
+    static constexpr size_t kSize = kChunkSize * 3;
+    auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize);
+    ASSERT_NE(ptr, nullptr);
+
+    auto buffer = std::make_unique<int[]>(kSize / sizeof(int));
+    for (size_t i = 0; i < kSize / sizeof(int); i++) {
+        buffer[i] = i;
+    }
+
+    // Write in chunks of 32KiB so every write is exactly at the end of the
+    // current file.
+    size_t bytes_written = 0;
+    while (bytes_written < kSize) {
+        size_t to_write = std::min(kSize - bytes_written, kChunkSize);
+        char* data = reinterpret_cast<char*>(buffer.get()) + bytes_written;
+        ASSERT_TRUE(ptr->Write(data, to_write));
+        bytes_written += to_write;
+    }
+
+    std::string expected(reinterpret_cast<char*>(buffer.get()), kSize);
+    auto actual = ReadSplitFiles(testfile, 3);
+    ASSERT_EQ(expected.size(), actual.size());
+    EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0);
+}
+
+TEST_F(SplitFiemapTest, WritePastEnd) {
+    static constexpr size_t kChunkSize = 32768;
+    static constexpr size_t kSize = kChunkSize * 3;
+    auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize);
+    ASSERT_NE(ptr, nullptr);
+
+    auto buffer = std::make_unique<int[]>(kSize / sizeof(int));
+    for (size_t i = 0; i < kSize / sizeof(int); i++) {
+        buffer[i] = i;
+    }
+    ASSERT_TRUE(ptr->Write(buffer.get(), kSize));
+    ASSERT_FALSE(ptr->Write(buffer.get(), kSize));
+}
+
+class VerifyBlockWritesExt4 : public ::testing::Test {
+    // 2GB Filesystem and 4k block size by default
+    static constexpr uint64_t block_size = 4096;
+    static constexpr uint64_t fs_size = 2147483648;
+
+  protected:
+    void SetUp() override {
+        fs_path = std::string(getenv("TMPDIR")) + "/ext4_2G.img";
+        uint64_t count = fs_size / block_size;
+        std::string dd_cmd =
+                ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
+                                              " count=%" PRIu64 " > /dev/null 2>&1",
+                                              fs_path.c_str(), block_size, count);
+        std::string mkfs_cmd =
+                ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path.c_str());
+        // create mount point
+        mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
+        ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
+        // create file for the file system
+        int ret = system(dd_cmd.c_str());
+        ASSERT_EQ(ret, 0);
+        // Get and attach a loop device to the filesystem we created
+        LoopDevice loop_dev(fs_path, 10s);
+        ASSERT_TRUE(loop_dev.valid());
+        // create file system
+        ret = system(mkfs_cmd.c_str());
+        ASSERT_EQ(ret, 0);
+
+        // mount the file system
+        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "ext4", 0, nullptr), 0);
+    }
+
+    void TearDown() override {
+        umount(mntpoint.c_str());
+        rmdir(mntpoint.c_str());
+        unlink(fs_path.c_str());
+    }
+
+    std::string mntpoint;
+    std::string fs_path;
+};
+
+class VerifyBlockWritesF2fs : public ::testing::Test {
+    // 2GB Filesystem and 4k block size by default
+    static constexpr uint64_t block_size = 4096;
+    static constexpr uint64_t fs_size = 2147483648;
+
+  protected:
+    void SetUp() override {
+        fs_path = std::string(getenv("TMPDIR")) + "/f2fs_2G.img";
+        uint64_t count = fs_size / block_size;
+        std::string dd_cmd =
+                ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
+                                              " count=%" PRIu64 " > /dev/null 2>&1",
+                                              fs_path.c_str(), block_size, count);
+        std::string mkfs_cmd =
+                ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path.c_str());
+        // create mount point
+        mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
+        ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
+        // create file for the file system
+        int ret = system(dd_cmd.c_str());
+        ASSERT_EQ(ret, 0);
+        // Get and attach a loop device to the filesystem we created
+        LoopDevice loop_dev(fs_path, 10s);
+        ASSERT_TRUE(loop_dev.valid());
+        // create file system
+        ret = system(mkfs_cmd.c_str());
+        ASSERT_EQ(ret, 0);
+
+        // mount the file system
+        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "f2fs", 0, nullptr), 0);
+    }
+
+    void TearDown() override {
+        umount(mntpoint.c_str());
+        rmdir(mntpoint.c_str());
+        unlink(fs_path.c_str());
+    }
+
+    std::string mntpoint;
+    std::string fs_path;
+};
+
+bool DetermineBlockSize() {
+    struct statfs s;
+    if (statfs(gTestDir.c_str(), &s)) {
+        std::cerr << "Could not call statfs: " << strerror(errno) << "\n";
+        return false;
+    }
+    if (!s.f_bsize) {
+        std::cerr << "Invalid block size: " << s.f_bsize << "\n";
+        return false;
+    }
+
+    gBlockSize = s.f_bsize;
+    return true;
+}
+
+}  // namespace fiemap
+}  // namespace android
+
+using namespace android::fiemap;
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    if (argc > 1 && argv[1] == "-h"s) {
+        cerr << "Usage: [test_dir] [file_size]\n";
+        cerr << "\n";
+        cerr << "Note: test_dir must be a writable, unencrypted directory.\n";
+        exit(EXIT_FAILURE);
+    }
+    ::android::base::InitLogging(argv, ::android::base::StderrLogger);
+
+    std::string root_dir = "/data/local/unencrypted";
+    if (access(root_dir.c_str(), F_OK)) {
+        root_dir = "/data";
+    }
+
+    std::string tempdir = root_dir + "/XXXXXX"s;
+    if (!mkdtemp(tempdir.data())) {
+        cerr << "unable to create tempdir on " << root_dir << "\n";
+        exit(EXIT_FAILURE);
+    }
+    if (!android::base::Realpath(tempdir, &gTestDir)) {
+        cerr << "unable to find realpath for " << tempdir;
+        exit(EXIT_FAILURE);
+    }
+
+    if (argc > 2) {
+        testfile_size = strtoull(argv[2], NULL, 0);
+        if (testfile_size == ULLONG_MAX) {
+            testfile_size = 512 * 1024 * 1024;
+        }
+    }
+
+    if (!DetermineBlockSize()) {
+        exit(EXIT_FAILURE);
+    }
+
+    auto result = RUN_ALL_TESTS();
+
+    std::string cmd = "rm -rf " + gTestDir;
+    system(cmd.c_str());
+
+    return result;
+}
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
new file mode 100644
index 0000000..fe2018d
--- /dev/null
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -0,0 +1,674 @@
+//
+// Copyright (C) 2019 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 <libfiemap/image_manager.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.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/file_wait.h>
+#include <fs_mgr_dm_linear.h>
+#include <libdm/loop_control.h>
+#include <libfiemap/split_fiemap_writer.h>
+
+#include "metadata.h"
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using namespace std::literals;
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::dm::DmTable;
+using android::dm::DmTargetLinear;
+using android::dm::LoopControl;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::GetPartitionName;
+
+static constexpr char kTestImageMetadataDir[] = "/metadata/gsi/test";
+
+std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix) {
+    auto metadata_dir = "/metadata/gsi/" + dir_prefix;
+    auto data_dir = "/data/gsi/" + dir_prefix;
+    return Open(metadata_dir, data_dir);
+}
+
+std::unique_ptr<ImageManager> ImageManager::Open(const std::string& metadata_dir,
+                                                 const std::string& data_dir) {
+    return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir));
+}
+
+ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir)
+    : metadata_dir_(metadata_dir), data_dir_(data_dir) {
+    partition_opener_ = std::make_unique<android::fs_mgr::PartitionOpener>();
+}
+
+std::string ImageManager::GetImageHeaderPath(const std::string& name) {
+    return JoinPaths(data_dir_, name) + ".img";
+}
+
+// The status file has one entry per line, with each entry formatted as one of:
+//   dm:<name>
+//   loop:<path>
+//
+// This simplifies the process of tearing down a mapping, since we can simply
+// unmap each entry in the order it appears.
+std::string ImageManager::GetStatusFilePath(const std::string& image_name) {
+    return JoinPaths(metadata_dir_, image_name) + ".status";
+}
+
+static std::string GetStatusPropertyName(const std::string& image_name) {
+    // Note: we don't prefix |image_name|, because CreateLogicalPartition won't
+    // prefix the name either. There are no plans to change this at the moment,
+    // consumers of the image API must take care to use globally-unique image
+    // names.
+    return "gsid.mapped_image." + image_name;
+}
+
+void ImageManager::set_partition_opener(std::unique_ptr<IPartitionOpener>&& opener) {
+    partition_opener_ = std::move(opener);
+}
+
+bool ImageManager::IsImageMapped(const std::string& image_name) {
+    auto prop_name = GetStatusPropertyName(image_name);
+    if (android::base::GetProperty(prop_name, "").empty()) {
+        // If mapped in first-stage init, the dm-device will exist but not the
+        // property.
+        auto& dm = DeviceMapper::Instance();
+        return dm.GetState(image_name) != DmDeviceState::INVALID;
+    }
+    return true;
+}
+
+std::vector<std::string> ImageManager::GetAllBackingImages() {
+    std::vector<std::string> images;
+    if (!MetadataExists(metadata_dir_)) {
+        return images;
+    }
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (metadata) {
+        for (auto&& partition : metadata->partitions) {
+            images.push_back(partition.name);
+        }
+    }
+    return images;
+}
+
+bool ImageManager::PartitionExists(const std::string& name) {
+    if (!MetadataExists(metadata_dir_)) {
+        return false;
+    }
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return false;
+    }
+    return !!FindPartition(*metadata.get(), name);
+}
+
+bool ImageManager::BackingImageExists(const std::string& name) {
+    auto header_file = GetImageHeaderPath(name);
+    return access(header_file.c_str(), F_OK) == 0;
+}
+
+bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, int flags) {
+    return CreateBackingImage(name, size, flags, nullptr);
+}
+
+static bool IsUnreliablePinningAllowed(const std::string& path) {
+    return android::base::StartsWith(path, "/data/gsi/dsu/") ||
+           android::base::StartsWith(path, "/data/gsi/test/") ||
+           android::base::StartsWith(path, "/data/gsi/ota/test/");
+}
+
+bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, int flags,
+                                      std::function<bool(uint64_t, uint64_t)>&& on_progress) {
+    auto data_path = GetImageHeaderPath(name);
+    auto fw = SplitFiemap::Create(data_path, size, 0, on_progress);
+    if (!fw) {
+        return false;
+    }
+
+    bool reliable_pinning;
+    if (!FilesystemHasReliablePinning(data_path, &reliable_pinning)) {
+        return false;
+    }
+    if (!reliable_pinning && !IsUnreliablePinningAllowed(data_path)) {
+        // For historical reasons, we allow unreliable pinning for certain use
+        // cases (DSUs, testing) because the ultimate use case is either
+        // developer-oriented or ephemeral (the intent is to boot immediately
+        // into DSUs). For everything else - such as snapshots/OTAs or adb
+        // remount, we have a higher bar, and require the filesystem to support
+        // proper pinning.
+        LOG(ERROR) << "File system does not have reliable block pinning";
+        SplitFiemap::RemoveSplitFiles(data_path);
+        return false;
+    }
+
+    // Except for testing, we do not allow persisting metadata that references
+    // device-mapper devices. It just doesn't make sense, because the device
+    // numbering may change on reboot. We allow it for testing since the images
+    // are not meant to survive reboot. Outside of tests, this can only happen
+    // if device-mapper is stacked in some complex way not supported by
+    // FiemapWriter.
+    auto device_path = GetDevicePathForFile(fw.get());
+    if (android::base::StartsWith(device_path, "/dev/block/dm-") &&
+        !android::base::StartsWith(metadata_dir_, kTestImageMetadataDir)) {
+        LOG(ERROR) << "Cannot persist images against device-mapper device: " << device_path;
+
+        fw = {};
+        SplitFiemap::RemoveSplitFiles(data_path);
+        return false;
+    }
+
+    bool readonly = !!(flags & CREATE_IMAGE_READONLY);
+    if (!UpdateMetadata(metadata_dir_, name, fw.get(), size, readonly)) {
+        return false;
+    }
+
+    if (flags & CREATE_IMAGE_ZERO_FILL) {
+        if (!ZeroFillNewImage(name, 0)) {
+            DeleteBackingImage(name);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
+    auto data_path = GetImageHeaderPath(name);
+
+    // See the comment in MapImageDevice() about how this works.
+    std::string block_device;
+    bool can_use_devicemapper;
+    if (!FiemapWriter::GetBlockDeviceForFile(data_path, &block_device, &can_use_devicemapper)) {
+        LOG(ERROR) << "Could not determine block device for " << data_path;
+        return false;
+    }
+
+    if (!can_use_devicemapper) {
+        // We've backed with loop devices, and since we store files in an
+        // unencrypted folder, the initial zeroes we wrote will suffice.
+        return true;
+    }
+
+    // data is dm-crypt, or FBE + dm-default-key. This means the zeroes written
+    // by libfiemap were encrypted, so we need to map the image in and correct
+    // this.
+    auto device = MappedDevice::Open(this, 10s, name);
+    if (!device) {
+        return false;
+    }
+
+    static constexpr size_t kChunkSize = 4096;
+    std::string zeroes(kChunkSize, '\0');
+
+    uint64_t remaining;
+    if (bytes) {
+        remaining = bytes;
+    } else {
+        remaining = get_block_device_size(device->fd());
+        if (!remaining) {
+            PLOG(ERROR) << "Could not get block device size for " << device->path();
+            return false;
+        }
+    }
+    while (remaining) {
+        uint64_t to_write = std::min(static_cast<uint64_t>(zeroes.size()), remaining);
+        if (!android::base::WriteFully(device->fd(), zeroes.data(),
+                                       static_cast<size_t>(to_write))) {
+            PLOG(ERROR) << "write failed: " << device->path();
+            return false;
+        }
+        remaining -= to_write;
+    }
+    return true;
+}
+
+bool ImageManager::DeleteBackingImage(const std::string& name) {
+    // For dm-linear devices sitting on top of /data, we cannot risk deleting
+    // the file. The underlying blocks could be reallocated by the filesystem.
+    if (IsImageMapped(name)) {
+        LOG(ERROR) << "Backing image " << name << " is currently mapped to a block device";
+        return false;
+    }
+
+    std::string message;
+    auto header_file = GetImageHeaderPath(name);
+    if (!SplitFiemap::RemoveSplitFiles(header_file, &message)) {
+        // This is fatal, because we don't want to leave these files dangling.
+        LOG(ERROR) << "Error removing image " << name << ": " << message;
+        return false;
+    }
+
+    auto status_file = GetStatusFilePath(name);
+    if (!android::base::RemoveFileIfExists(status_file)) {
+        LOG(ERROR) << "Error removing " << status_file << ": " << message;
+    }
+    return RemoveImageMetadata(metadata_dir_, name);
+}
+
+// Create a block device for an image file, using its extents in its
+// lp_metadata.
+bool ImageManager::MapWithDmLinear(const IPartitionOpener& opener, const std::string& name,
+                                   const std::chrono::milliseconds& timeout_ms, std::string* path) {
+    // :TODO: refresh extents in metadata file until f2fs is fixed.
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return false;
+    }
+
+    auto super = android::fs_mgr::GetMetadataSuperBlockDevice(*metadata.get());
+    auto block_device = android::fs_mgr::GetBlockDevicePartitionName(*super);
+
+    CreateLogicalPartitionParams params = {
+            .block_device = block_device,
+            .metadata = metadata.get(),
+            .partition_name = name,
+            .force_writable = true,
+            .timeout_ms = timeout_ms,
+            .partition_opener = &opener,
+    };
+    if (!CreateLogicalPartition(params, path)) {
+        LOG(ERROR) << "Error creating device-mapper node for image " << name;
+        return false;
+    }
+
+    auto status_string = "dm:" + name;
+    auto status_file = GetStatusFilePath(name);
+    if (!android::base::WriteStringToFile(status_string, status_file)) {
+        PLOG(ERROR) << "Could not write status file: " << status_file;
+        DestroyLogicalPartition(name);
+        return false;
+    }
+    return true;
+}
+
+// Helper to create a loop device for a file.
+static bool CreateLoopDevice(LoopControl& control, const std::string& file,
+                             const std::chrono::milliseconds& timeout_ms, std::string* path) {
+    static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
+    android::base::unique_fd file_fd(open(file.c_str(), kOpenFlags));
+    if (file_fd < 0) {
+        PLOG(ERROR) << "Could not open file: " << file;
+        return false;
+    }
+    if (!control.Attach(file_fd, timeout_ms, path)) {
+        LOG(ERROR) << "Could not create loop device for: " << file;
+        return false;
+    }
+    LOG(INFO) << "Created loop device " << *path << " for file " << file;
+    return true;
+}
+
+class AutoDetachLoopDevices final {
+  public:
+    AutoDetachLoopDevices(LoopControl& control, const std::vector<std::string>& devices)
+        : control_(control), devices_(devices), commit_(false) {}
+
+    ~AutoDetachLoopDevices() {
+        if (commit_) return;
+        for (const auto& device : devices_) {
+            control_.Detach(device);
+        }
+    }
+
+    void Commit() { commit_ = true; }
+
+  private:
+    LoopControl& control_;
+    const std::vector<std::string>& devices_;
+    bool commit_;
+};
+
+// If an image is stored across multiple files, this takes a list of loop
+// devices and joins them together using device-mapper.
+bool ImageManager::MapWithLoopDeviceList(const std::vector<std::string>& device_list,
+                                         const std::string& name,
+                                         const std::chrono::milliseconds& timeout_ms,
+                                         std::string* path) {
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return false;
+    }
+    auto partition = FindPartition(*metadata.get(), name);
+    if (!partition) {
+        LOG(ERROR) << "Could not find image in metadata: " << name;
+        return false;
+    }
+
+    // Since extent lengths are in sector units, the size should be a multiple
+    // of the sector size.
+    uint64_t partition_size = GetPartitionSize(*metadata.get(), *partition);
+    if (partition_size % LP_SECTOR_SIZE != 0) {
+        LOG(ERROR) << "Partition size not sector aligned: " << name << ", " << partition_size
+                   << " bytes";
+        return false;
+    }
+
+    DmTable table;
+
+    uint64_t start_sector = 0;
+    uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE;
+    for (const auto& block_device : device_list) {
+        // The final block device must be == partition_size, otherwise we
+        // can't find the AVB footer on verified partitions.
+        static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
+        unique_fd fd(open(block_device.c_str(), kOpenFlags));
+        if (fd < 0) {
+            PLOG(ERROR) << "Open failed: " << block_device;
+            return false;
+        }
+
+        uint64_t file_size = get_block_device_size(fd);
+        uint64_t file_sectors = file_size / LP_SECTOR_SIZE;
+        uint64_t segment_size = std::min(file_sectors, sectors_needed);
+
+        table.Emplace<DmTargetLinear>(start_sector, segment_size, block_device, 0);
+
+        start_sector += segment_size;
+        sectors_needed -= segment_size;
+        if (sectors_needed == 0) {
+            break;
+        }
+    }
+
+    auto& dm = DeviceMapper::Instance();
+    if (!dm.CreateDevice(name, table, path, timeout_ms)) {
+        LOG(ERROR) << "Could not create device-mapper device over loop set";
+        return false;
+    }
+
+    // Build the status file.
+    std::vector<std::string> lines;
+    lines.emplace_back("dm:" + name);
+    for (const auto& block_device : device_list) {
+        lines.emplace_back("loop:" + block_device);
+    }
+    auto status_message = android::base::Join(lines, "\n");
+    auto status_file = GetStatusFilePath(name);
+    if (!android::base::WriteStringToFile(status_message, status_file)) {
+        PLOG(ERROR) << "Write failed: " << status_file;
+        dm.DeleteDevice(name);
+        return false;
+    }
+    return true;
+}
+
+static bool OptimizeLoopDevices(const std::vector<std::string>& device_list) {
+    for (const auto& device : device_list) {
+        unique_fd fd(open(device.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW));
+        if (fd < 0) {
+            PLOG(ERROR) << "Open failed: " << device;
+            return false;
+        }
+        if (!LoopControl::EnableDirectIo(fd)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// Helper to use one or more loop devices around image files.
+bool ImageManager::MapWithLoopDevice(const std::string& name,
+                                     const std::chrono::milliseconds& timeout_ms,
+                                     std::string* path) {
+    auto image_header = GetImageHeaderPath(name);
+
+    std::vector<std::string> file_list;
+    if (!SplitFiemap::GetSplitFileList(image_header, &file_list)) {
+        LOG(ERROR) << "Could not get image file list";
+        return false;
+    }
+
+    // Map each image file as a loopback device.
+    LoopControl control;
+    std::vector<std::string> loop_devices;
+    AutoDetachLoopDevices auto_detach(control, loop_devices);
+
+    auto start_time = std::chrono::steady_clock::now();
+    for (const auto& file : file_list) {
+        auto now = std::chrono::steady_clock::now();
+        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
+
+        std::string loop_device;
+        if (!CreateLoopDevice(control, file, timeout_ms - elapsed, &loop_device)) {
+            break;
+        }
+        loop_devices.emplace_back(loop_device);
+    }
+    if (loop_devices.size() != file_list.size()) {
+        // The number of devices will mismatch if CreateLoopDevice() failed.
+        return false;
+    }
+
+    // If OptimizeLoopDevices fails, we'd use double the memory.
+    if (!OptimizeLoopDevices(loop_devices)) {
+        return false;
+    }
+
+    // If there's only one loop device (by far the most common case, splits
+    // will normally only happen on sdcards with FAT32), then just return that
+    // as the block device. Otherwise, we need to use dm-linear to stitch
+    // together all the loop devices we just created.
+    if (loop_devices.size() > 1) {
+        if (!MapWithLoopDeviceList(loop_devices, name, timeout_ms, path)) {
+            return false;
+        }
+    }
+
+    auto status_message = "loop:" + loop_devices.back();
+    auto status_file = GetStatusFilePath(name);
+    if (!android::base::WriteStringToFile(status_message, status_file)) {
+        PLOG(ERROR) << "Write failed: " << status_file;
+        return false;
+    }
+
+    auto_detach.Commit();
+
+    *path = loop_devices.back();
+    return true;
+}
+
+bool ImageManager::MapImageDevice(const std::string& name,
+                                  const std::chrono::milliseconds& timeout_ms, std::string* path) {
+    if (IsImageMapped(name)) {
+        LOG(ERROR) << "Backing image " << name << " is already mapped";
+        return false;
+    }
+
+    auto image_header = GetImageHeaderPath(name);
+
+    // If there is a device-mapper node wrapping the block device, then we're
+    // able to create another node around it; the dm layer does not carry the
+    // exclusion lock down the stack when a mount occurs.
+    //
+    // If there is no intermediate device-mapper node, then partitions cannot be
+    // opened writable due to sepolicy and exclusivity of having a mounted
+    // filesystem. This should only happen on devices with no encryption, or
+    // devices with FBE and no metadata encryption. For these cases it suffices
+    // to perform normal file writes to /data/gsi (which is unencrypted).
+    std::string block_device;
+    bool can_use_devicemapper;
+    if (!FiemapWriter::GetBlockDeviceForFile(image_header, &block_device, &can_use_devicemapper)) {
+        LOG(ERROR) << "Could not determine block device for " << image_header;
+        return false;
+    }
+
+    if (can_use_devicemapper) {
+        if (!MapWithDmLinear(*partition_opener_.get(), name, timeout_ms, path)) {
+            return false;
+        }
+    } else if (!MapWithLoopDevice(name, timeout_ms, path)) {
+        return false;
+    }
+
+    // Set a property so we remember this is mapped.
+    auto prop_name = GetStatusPropertyName(name);
+    if (!android::base::SetProperty(prop_name, *path)) {
+        UnmapImageDevice(name, true);
+        return false;
+    }
+    return true;
+}
+
+bool ImageManager::MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
+                                            std::string* dev) {
+    std::string ignore_path;
+    if (!MapWithDmLinear(opener, name, {}, &ignore_path)) {
+        return false;
+    }
+
+    auto& dm = DeviceMapper::Instance();
+    if (!dm.GetDeviceString(name, dev)) {
+        return false;
+    }
+    return true;
+}
+
+bool ImageManager::UnmapImageDevice(const std::string& name) {
+    return UnmapImageDevice(name, false);
+}
+
+bool ImageManager::UnmapImageDevice(const std::string& name, bool force) {
+    if (!force && !IsImageMapped(name)) {
+        LOG(ERROR) << "Backing image " << name << " is not mapped";
+        return false;
+    }
+    auto& dm = DeviceMapper::Instance();
+    LoopControl loop;
+
+    std::string status;
+    auto status_file = GetStatusFilePath(name);
+    if (!android::base::ReadFileToString(status_file, &status)) {
+        PLOG(ERROR) << "Read failed: " << status_file;
+        return false;
+    }
+
+    auto lines = android::base::Split(status, "\n");
+    for (const auto& line : lines) {
+        auto pieces = android::base::Split(line, ":");
+        if (pieces.size() != 2) {
+            LOG(ERROR) << "Unknown status line";
+            continue;
+        }
+        if (pieces[0] == "dm") {
+            // Failure to remove a dm node is fatal, since we can't safely
+            // remove the file or loop devices.
+            const auto& name = pieces[1];
+            if (!dm.DeleteDeviceIfExists(name)) {
+                return false;
+            }
+        } else if (pieces[0] == "loop") {
+            // Failure to remove a loop device is not fatal, since we can still
+            // remove the backing file if we want.
+            loop.Detach(pieces[1]);
+        } else {
+            LOG(ERROR) << "Unknown status: " << pieces[0];
+        }
+    }
+
+    std::string message;
+    if (!android::base::RemoveFileIfExists(status_file, &message)) {
+        LOG(ERROR) << "Could not remove " << status_file << ": " << message;
+    }
+
+    auto status_prop = GetStatusPropertyName(name);
+    android::base::SetProperty(status_prop, "");
+    return true;
+}
+
+bool ImageManager::RemoveAllImages() {
+    if (!MetadataExists(metadata_dir_)) {
+        return true;
+    }
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return RemoveAllMetadata(metadata_dir_);
+    }
+
+    bool ok = true;
+    for (const auto& partition : metadata->partitions) {
+        auto partition_name = GetPartitionName(partition);
+        ok &= DeleteBackingImage(partition_name);
+    }
+    return ok && RemoveAllMetadata(metadata_dir_);
+}
+
+bool ImageManager::Validate() {
+    auto metadata = OpenMetadata(metadata_dir_);
+    if (!metadata) {
+        return false;
+    }
+
+    for (const auto& partition : metadata->partitions) {
+        auto name = GetPartitionName(partition);
+        auto image_path = GetImageHeaderPath(name);
+        auto fiemap = SplitFiemap::Open(image_path);
+        if (!fiemap || !fiemap->HasPinnedExtents()) {
+            LOG(ERROR) << "Image is missing or was moved: " << image_path;
+            return false;
+        }
+    }
+    return true;
+}
+
+std::unique_ptr<MappedDevice> MappedDevice::Open(IImageManager* manager,
+                                                 const std::chrono::milliseconds& timeout_ms,
+                                                 const std::string& name) {
+    std::string path;
+    if (!manager->MapImageDevice(name, timeout_ms, &path)) {
+        return nullptr;
+    }
+
+    auto device = std::unique_ptr<MappedDevice>(new MappedDevice(manager, name, path));
+    if (device->fd() < 0) {
+        return nullptr;
+    }
+    return device;
+}
+
+MappedDevice::MappedDevice(IImageManager* manager, const std::string& name, const std::string& path)
+    : manager_(manager), name_(name), path_(path) {
+    // The device is already mapped; try and open it.
+    fd_.reset(open(path.c_str(), O_RDWR | O_CLOEXEC));
+}
+
+MappedDevice::~MappedDevice() {
+    fd_ = {};
+    manager_->UnmapImageDevice(name_);
+}
+
+bool IImageManager::UnmapImageIfExists(const std::string& name) {
+    // No lock is needed even though this seems to be vulnerable to TOCTOU. If process A
+    // calls MapImageDevice() while process B calls UnmapImageIfExists(), and MapImageDevice()
+    // happens after process B checks IsImageMapped(), it would be as if MapImageDevice() is called
+    // after process B finishes calling UnmapImageIfExists(), resulting the image to be mapped,
+    // which is a reasonable sequence.
+    if (!IsImageMapped(name)) {
+        return true;
+    }
+    return UnmapImageDevice(name);
+}
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/libfiemap/image_test.cpp b/fs_mgr/libfiemap/image_test.cpp
new file mode 100644
index 0000000..f05825c
--- /dev/null
+++ b/fs_mgr/libfiemap/image_test.cpp
@@ -0,0 +1,251 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <chrono>
+#include <iostream>
+#include <thread>
+
+#include <android-base/file.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/file_wait.h>
+#include <gtest/gtest.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+
+using namespace android::dm;
+using namespace std::literals;
+using android::base::unique_fd;
+using android::fiemap::ImageManager;
+using android::fs_mgr::BlockDeviceInfo;
+using android::fs_mgr::PartitionOpener;
+using android::fs_mgr::WaitForFile;
+
+static std::string gDataPath;
+static std::string gDataMountPath;
+static constexpr char kMetadataPath[] = "/metadata/gsi/test";
+
+static constexpr uint64_t kTestImageSize = 1024 * 1024;
+
+class TestPartitionOpener final : public PartitionOpener {
+  public:
+    android::base::unique_fd Open(const std::string& partition_name, int flags) const override {
+        return PartitionOpener::Open(GetPathForBlockDeviceName(partition_name), flags);
+    }
+    bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const override {
+        return PartitionOpener::GetInfo(GetPathForBlockDeviceName(partition_name), info);
+    }
+    std::string GetDeviceString(const std::string& partition_name) const override {
+        return PartitionOpener::GetDeviceString(GetPathForBlockDeviceName(partition_name));
+    }
+
+  private:
+    static std::string GetPathForBlockDeviceName(const std::string& name) {
+        if (android::base::StartsWith(name, "loop") || android::base::StartsWith(name, "dm-")) {
+            return "/dev/block/"s + name;
+        }
+        return name;
+    }
+};
+
+// This fixture is for tests against the device's native configuration.
+class NativeTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        manager_ = ImageManager::Open(kMetadataPath, gDataPath);
+        ASSERT_NE(manager_, nullptr);
+
+        manager_->set_partition_opener(std::make_unique<TestPartitionOpener>());
+
+        const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+        base_name_ = tinfo->name();
+    }
+
+    void TearDown() override {
+        manager_->UnmapImageDevice(base_name_);
+        manager_->DeleteBackingImage(base_name_);
+    }
+
+    std::string PropertyName() { return "gsid.mapped_image." + base_name_; }
+
+    std::unique_ptr<ImageManager> manager_;
+    std::string base_name_;
+};
+
+TEST_F(NativeTest, CreateAndMap) {
+    ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr));
+
+    std::string path;
+    ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &path));
+    ASSERT_TRUE(manager_->IsImageMapped(base_name_));
+    ASSERT_EQ(android::base::GetProperty(PropertyName(), ""), path);
+
+    {
+        unique_fd fd(open(path.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC));
+        ASSERT_GE(fd, 0);
+        ASSERT_EQ(get_block_device_size(fd), kTestImageSize);
+    }
+
+    ASSERT_TRUE(manager_->UnmapImageDevice(base_name_));
+    ASSERT_FALSE(manager_->IsImageMapped(base_name_));
+    ASSERT_EQ(android::base::GetProperty(PropertyName(), ""), "");
+}
+
+// This fixture is for tests against a simulated device environment. Rather
+// than use /data, we create an image and then layer a new filesystem within
+// it. Each test then decides how to mount and create layered images. This
+// allows us to test FBE vs FDE configurations.
+class ImageTest : public ::testing::Test {
+  public:
+    ImageTest() : dm_(DeviceMapper::Instance()) {}
+
+    void SetUp() override {
+        manager_ = ImageManager::Open(kMetadataPath, gDataPath);
+        ASSERT_NE(manager_, nullptr);
+
+        manager_->set_partition_opener(std::make_unique<TestPartitionOpener>());
+
+        submanager_ = ImageManager::Open(kMetadataPath + "/mnt"s, gDataPath + "/mnt"s);
+        ASSERT_NE(submanager_, nullptr);
+
+        submanager_->set_partition_opener(std::make_unique<TestPartitionOpener>());
+
+        // Ensure that metadata is cleared in between runs.
+        submanager_->RemoveAllImages();
+        manager_->RemoveAllImages();
+
+        const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+        base_name_ = tinfo->name();
+        test_image_name_ = base_name_ + "-base";
+        wrapper_device_name_ = base_name_ + "-wrapper";
+
+        ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize * 16, false, nullptr));
+        ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &base_device_));
+    }
+
+    void TearDown() override {
+        submanager_->UnmapImageDevice(test_image_name_);
+        umount(gDataMountPath.c_str());
+        dm_.DeleteDeviceIfExists(wrapper_device_name_);
+        manager_->UnmapImageDevice(base_name_);
+        manager_->DeleteBackingImage(base_name_);
+    }
+
+  protected:
+    bool DoFormat(const std::string& device) {
+        // clang-format off
+        std::vector<std::string> mkfs_args = {
+            "/system/bin/mke2fs",
+            "-F",
+            "-b 4096",
+            "-t ext4",
+            "-m 0",
+            "-O has_journal",
+            device,
+            ">/dev/null",
+            "2>/dev/null",
+            "</dev/null",
+        };
+        // clang-format on
+        auto command = android::base::Join(mkfs_args, " ");
+        return system(command.c_str()) == 0;
+    }
+
+    std::unique_ptr<ImageManager> manager_;
+    std::unique_ptr<ImageManager> submanager_;
+
+    DeviceMapper& dm_;
+    std::string base_name_;
+    std::string base_device_;
+    std::string test_image_name_;
+    std::string wrapper_device_name_;
+};
+
+TEST_F(ImageTest, DirectMount) {
+    ASSERT_TRUE(DoFormat(base_device_));
+    ASSERT_EQ(mount(base_device_.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0);
+    ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr));
+
+    std::string path;
+    ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path));
+    ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/loop"));
+}
+
+TEST_F(ImageTest, IndirectMount) {
+    // Create a simple wrapper around the base device that we'll mount from
+    // instead. This will simulate the code paths for dm-crypt/default-key/bow
+    // and force us to use device-mapper rather than loop devices.
+    uint64_t device_size = 0;
+    {
+        unique_fd fd(open(base_device_.c_str(), O_RDWR | O_CLOEXEC));
+        ASSERT_GE(fd, 0);
+        device_size = get_block_device_size(fd);
+        ASSERT_EQ(device_size, kTestImageSize * 16);
+    }
+    uint64_t num_sectors = device_size / 512;
+
+    auto& dm = DeviceMapper::Instance();
+
+    DmTable table;
+    table.Emplace<DmTargetLinear>(0, num_sectors, base_device_, 0);
+    ASSERT_TRUE(dm.CreateDevice(wrapper_device_name_, table));
+
+    // Format and mount.
+    std::string wrapper_device;
+    ASSERT_TRUE(dm.GetDmDevicePathByName(wrapper_device_name_, &wrapper_device));
+    ASSERT_TRUE(WaitForFile(wrapper_device, 5s));
+    ASSERT_TRUE(DoFormat(wrapper_device));
+    ASSERT_EQ(mount(wrapper_device.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0);
+
+    ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr));
+
+    std::string path;
+    ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path));
+    ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/dm-"));
+}
+
+bool Mkdir(const std::string& path) {
+    if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
+        std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
+        return false;
+    }
+    return true;
+}
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    if (argc >= 2) {
+        gDataPath = argv[1];
+    } else {
+        gDataPath = "/data/gsi/test";
+    }
+    gDataMountPath = gDataPath + "/mnt"s;
+
+    if (!Mkdir(gDataPath) || !Mkdir(kMetadataPath) || !Mkdir(gDataMountPath) ||
+        !Mkdir(kMetadataPath + "/mnt"s)) {
+        return 1;
+    }
+    return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/libfiemap/include/libfiemap/fiemap_writer.h b/fs_mgr/libfiemap/include/libfiemap/fiemap_writer.h
new file mode 100644
index 0000000..c692265
--- /dev/null
+++ b/fs_mgr/libfiemap/include/libfiemap/fiemap_writer.h
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <linux/fiemap.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace fiemap {
+
+class FiemapWriter;
+using FiemapUniquePtr = std::unique_ptr<FiemapWriter>;
+
+class FiemapWriter final {
+  public:
+    // Factory method for FiemapWriter.
+    // The method returns FiemapUniquePtr that contains all the data necessary to be able to write
+    // to the given file directly using raw block i/o. The optional progress callback will be
+    // invoked, if create is true, while the file is being initialized. It receives the bytes
+    // written and the number of total bytes. If the callback returns false, the operation will
+    // fail.
+    //
+    // Note: when create is true, the file size will be aligned up to the nearest file system
+    // block.
+    static FiemapUniquePtr Open(const std::string& file_path, uint64_t file_size,
+                                bool create = true,
+                                std::function<bool(uint64_t, uint64_t)> progress = {});
+
+    // Check that a file still has the same extents since it was last opened with FiemapWriter,
+    // assuming the file was not resized outside of FiemapWriter. Returns false either on error
+    // or if the file was not pinned.
+    //
+    // This will always return true on Ext4. On F2FS, it will return true if either of the
+    // following cases are true:
+    //   - The file was never pinned.
+    //   - The file is pinned and has not been moved by the GC.
+    // Thus, this method should only be called for pinned files (such as those returned by
+    // FiemapWriter::Open).
+    static bool HasPinnedExtents(const std::string& file_path);
+
+    // Returns the underlying block device of a file. This will look past device-mapper layers
+    // as long as each layer would not change block mappings (i.e., dm-crypt, dm-bow, and dm-
+    // default-key tables are okay; dm-linear is not). If a mapping such as dm-linear is found,
+    // it will be returned in place of any physical block device.
+    //
+    // It is the caller's responsibility to check whether the returned block device is acceptable.
+    // Gsid, for example, will only accept /dev/block/by-name/userdata as the bottom device.
+    // Callers can check the device name (dm- or loop prefix), inspect sysfs, or compare the major
+    // number against a boot device.
+    //
+    // If device-mapper nodes were encountered, then |uses_dm| will be set to true.
+    static bool GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path,
+                                      bool* uses_dm = nullptr);
+
+    ~FiemapWriter() = default;
+
+    const std::string& file_path() const { return file_path_; };
+    uint64_t size() const { return file_size_; };
+    const std::string& bdev_path() const { return bdev_path_; };
+    uint64_t block_size() const { return block_size_; };
+    const std::vector<struct fiemap_extent>& extents() { return extents_; };
+    uint32_t fs_type() const { return fs_type_; }
+
+    // Non-copyable & Non-movable
+    FiemapWriter(const FiemapWriter&) = delete;
+    FiemapWriter& operator=(const FiemapWriter&) = delete;
+    FiemapWriter& operator=(FiemapWriter&&) = delete;
+    FiemapWriter(FiemapWriter&&) = delete;
+
+  private:
+    // Name of the file managed by this class.
+    std::string file_path_;
+    // Block device on which we have created the file.
+    std::string bdev_path_;
+
+    // Size in bytes of the file this class is writing
+    uint64_t file_size_;
+
+    // total size in bytes of the block device
+    uint64_t bdev_size_;
+
+    // Filesystem type where the file is being created.
+    // See: <uapi/linux/magic.h> for filesystem magic numbers
+    uint32_t fs_type_;
+
+    // block size as reported by the kernel of the underlying block device;
+    uint64_t block_size_;
+
+    // This file's fiemap
+    std::vector<struct fiemap_extent> extents_;
+
+    FiemapWriter() = default;
+};
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/libfiemap/include/libfiemap/image_manager.h b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
new file mode 100644
index 0000000..5ff4628
--- /dev/null
+++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
@@ -0,0 +1,185 @@
+//
+// Copyright (C) 2019 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.
+//
+
+#pragma once
+
+#include <stdint.h>
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <string>
+
+#include <android-base/unique_fd.h>
+#include <liblp/partition_opener.h>
+
+namespace android {
+namespace fiemap {
+
+class IImageManager {
+  public:
+    using IPartitionOpener = android::fs_mgr::IPartitionOpener;
+
+    virtual ~IImageManager() {}
+
+    // When linking to libfiemap_binder, the Open() call will use binder.
+    // Otherwise, the Open() call will use the ImageManager implementation
+    // below.
+    static std::unique_ptr<IImageManager> Open(const std::string& dir_prefix,
+                                               const std::chrono::milliseconds& timeout_ms);
+
+    // Flags for CreateBackingImage().
+    static constexpr int CREATE_IMAGE_DEFAULT = 0x0;
+    static constexpr int CREATE_IMAGE_READONLY = 0x1;
+    static constexpr int CREATE_IMAGE_ZERO_FILL = 0x2;
+
+    // Create an image that can be mapped as a block-device. If |force_zero_fill|
+    // is true, the image will be zero-filled. Otherwise, the initial content
+    // of the image is undefined. If zero-fill is requested, and the operation
+    // cannot be completed, the image will be deleted and this function will
+    // return false.
+    virtual bool CreateBackingImage(const std::string& name, uint64_t size, int flags) = 0;
+
+    // Delete an image created with CreateBackingImage. Its entry will be
+    // removed from the associated lp_metadata file.
+    virtual bool DeleteBackingImage(const std::string& name) = 0;
+
+    // Create a block device for an image previously created with
+    // CreateBackingImage. This will wait for at most |timeout_ms| milliseconds
+    // for |path| to be available, and will return false if not available in
+    // the requested time. If |timeout_ms| is zero, this is NOT guaranteed to
+    // return true. A timeout of 10s is recommended.
+    //
+    // Note that snapshots created with a readonly flag are always mapped
+    // writable. The flag is persisted in the lp_metadata file however, so if
+    // fs_mgr::CreateLogicalPartition(s) is used, the flag will be respected.
+    virtual bool MapImageDevice(const std::string& name,
+                                const std::chrono::milliseconds& timeout_ms, std::string* path) = 0;
+
+    // Unmap a block device previously mapped with mapBackingImage.
+    virtual bool UnmapImageDevice(const std::string& name) = 0;
+
+    // Returns true whether the named backing image exists.
+    virtual bool BackingImageExists(const std::string& name) = 0;
+
+    // Returns true if the specified image is mapped to a device.
+    virtual bool IsImageMapped(const std::string& name) = 0;
+
+    // Map an image using device-mapper. This is not available over binder, and
+    // is intended only for first-stage init. The returned device is a major:minor
+    // device string.
+    virtual bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
+                                          std::string* dev) = 0;
+
+    // Get all backing image names.
+    virtual std::vector<std::string> GetAllBackingImages() = 0;
+
+    // Writes |bytes| zeros to |name| file. If |bytes| is 0, then the
+    // whole file if filled with zeros.
+    virtual bool ZeroFillNewImage(const std::string& name, uint64_t bytes) = 0;
+
+    // Find and remove all images and metadata for this manager.
+    virtual bool RemoveAllImages() = 0;
+
+    virtual bool UnmapImageIfExists(const std::string& name);
+};
+
+class ImageManager final : public IImageManager {
+  public:
+    // Return an ImageManager for the given metadata and data directories. Both
+    // directories must already exist.
+    static std::unique_ptr<ImageManager> Open(const std::string& metadata_dir,
+                                              const std::string& data_dir);
+
+    // Helper function that derives the metadata and data dirs given a single
+    // prefix.
+    static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix);
+
+    // Methods that must be implemented from IImageManager.
+    bool CreateBackingImage(const std::string& name, uint64_t size, int flags) override;
+    bool DeleteBackingImage(const std::string& name) override;
+    bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
+                        std::string* path) override;
+    bool UnmapImageDevice(const std::string& name) override;
+    bool BackingImageExists(const std::string& name) override;
+    bool IsImageMapped(const std::string& name) override;
+    bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
+                                  std::string* dev) override;
+    bool RemoveAllImages() override;
+
+    std::vector<std::string> GetAllBackingImages();
+    // Same as CreateBackingImage, but provides a progress notification.
+    bool CreateBackingImage(const std::string& name, uint64_t size, int flags,
+                            std::function<bool(uint64_t, uint64_t)>&& on_progress);
+
+    // Returns true if the named partition exists. This does not check the
+    // consistency of the backing image/data file.
+    bool PartitionExists(const std::string& name);
+
+    // Validates that all images still have pinned extents. This will be removed
+    // once b/134588268 is fixed.
+    bool Validate();
+
+    void set_partition_opener(std::unique_ptr<IPartitionOpener>&& opener);
+
+    // Writes |bytes| zeros at the beginning of the passed image
+    bool ZeroFillNewImage(const std::string& name, uint64_t bytes);
+
+  private:
+    ImageManager(const std::string& metadata_dir, const std::string& data_dir);
+    std::string GetImageHeaderPath(const std::string& name);
+    std::string GetStatusFilePath(const std::string& image_name);
+    bool MapWithLoopDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
+                           std::string* path);
+    bool MapWithLoopDeviceList(const std::vector<std::string>& device_list, const std::string& name,
+                               const std::chrono::milliseconds& timeout_ms, std::string* path);
+    bool MapWithDmLinear(const IPartitionOpener& opener, const std::string& name,
+                         const std::chrono::milliseconds& timeout_ms, std::string* path);
+    bool UnmapImageDevice(const std::string& name, bool force);
+
+    ImageManager(const ImageManager&) = delete;
+    ImageManager& operator=(const ImageManager&) = delete;
+    ImageManager& operator=(ImageManager&&) = delete;
+    ImageManager(ImageManager&&) = delete;
+
+    std::string metadata_dir_;
+    std::string data_dir_;
+    std::unique_ptr<IPartitionOpener> partition_opener_;
+};
+
+// RAII helper class for mapping and opening devices with an ImageManager.
+class MappedDevice final {
+  public:
+    static std::unique_ptr<MappedDevice> Open(IImageManager* manager,
+                                              const std::chrono::milliseconds& timeout_ms,
+                                              const std::string& name);
+
+    ~MappedDevice();
+
+    int fd() const { return fd_; }
+    const std::string& path() const { return path_; }
+
+  protected:
+    MappedDevice(IImageManager* manager, const std::string& name, const std::string& path);
+
+    IImageManager* manager_;
+    std::string name_;
+    std::string path_;
+    android::base::unique_fd fd_;
+};
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/libfiemap/include/libfiemap/split_fiemap_writer.h b/fs_mgr/libfiemap/include/libfiemap/split_fiemap_writer.h
new file mode 100644
index 0000000..feffb3d
--- /dev/null
+++ b/fs_mgr/libfiemap/include/libfiemap/split_fiemap_writer.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+#include "fiemap_writer.h"
+
+namespace android {
+namespace fiemap {
+
+// Wrapper around FiemapWriter that is able to split images across files if
+// necessary.
+class SplitFiemap final {
+  public:
+    using ProgressCallback = std::function<bool(uint64_t, uint64_t)>;
+
+    // Create a new split fiemap file. If |max_piece_size| is 0, the number of
+    // pieces will be determined automatically by detecting the filesystem.
+    // Otherwise, the file will be split evenly (with the remainder in the
+    // final file).
+    static std::unique_ptr<SplitFiemap> Create(const std::string& file_path, uint64_t file_size,
+                                               uint64_t max_piece_size,
+                                               ProgressCallback progress = {});
+
+    // Open an existing split fiemap file.
+    static std::unique_ptr<SplitFiemap> Open(const std::string& file_path);
+
+    ~SplitFiemap();
+
+    // Return a list of all files created for a split file.
+    static bool GetSplitFileList(const std::string& file_path, std::vector<std::string>* list);
+
+    // Destroy all components of a split file. If the root file does not exist,
+    // this returns true and does not report an error.
+    static bool RemoveSplitFiles(const std::string& file_path, std::string* message = nullptr);
+
+    // Return whether all components of a split file still have pinned extents.
+    bool HasPinnedExtents() const;
+
+    // Helper method for writing data that spans files. Note there is no seek
+    // method (yet); this starts at 0 and increments the position by |bytes|.
+    bool Write(const void* data, uint64_t bytes);
+
+    // Flush all writes to all split files.
+    bool Flush();
+
+    const std::vector<struct fiemap_extent>& extents();
+    uint32_t block_size() const;
+    uint64_t size() const { return total_size_; }
+    const std::string& bdev_path() const;
+
+    // Non-copyable & Non-movable
+    SplitFiemap(const SplitFiemap&) = delete;
+    SplitFiemap& operator=(const SplitFiemap&) = delete;
+    SplitFiemap& operator=(SplitFiemap&&) = delete;
+    SplitFiemap(SplitFiemap&&) = delete;
+
+  private:
+    SplitFiemap() = default;
+    void AddFile(FiemapUniquePtr&& file);
+
+    bool creating_ = false;
+    std::string list_file_;
+    std::vector<FiemapUniquePtr> files_;
+    std::vector<struct fiemap_extent> extents_;
+    uint64_t total_size_ = 0;
+
+    // Most recently open file and position for Write().
+    size_t cursor_index_ = 0;
+    uint64_t cursor_file_pos_ = 0;
+    android::base::unique_fd cursor_fd_;
+};
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/libfiemap/metadata.cpp b/fs_mgr/libfiemap/metadata.cpp
new file mode 100644
index 0000000..597efe9
--- /dev/null
+++ b/fs_mgr/libfiemap/metadata.cpp
@@ -0,0 +1,196 @@
+//
+// Copyright (C) 2019 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 "metadata.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <liblp/builder.h>
+
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using namespace android::fs_mgr;
+
+static constexpr uint32_t kMaxMetadataSize = 256 * 1024;
+
+std::string GetMetadataFile(const std::string& metadata_dir) {
+    return JoinPaths(metadata_dir, "lp_metadata");
+}
+
+bool MetadataExists(const std::string& metadata_dir) {
+    auto metadata_file = GetMetadataFile(metadata_dir);
+    return access(metadata_file.c_str(), F_OK) == 0;
+}
+
+std::unique_ptr<LpMetadata> OpenMetadata(const std::string& metadata_dir) {
+    auto metadata_file = GetMetadataFile(metadata_dir);
+    auto metadata = ReadFromImageFile(metadata_file);
+    if (!metadata) {
+        LOG(ERROR) << "Could not read metadata file " << metadata_file;
+        return nullptr;
+    }
+    return metadata;
+}
+
+// :TODO: overwrite on create if open fails
+std::unique_ptr<MetadataBuilder> OpenOrCreateMetadata(const std::string& metadata_dir,
+                                                      SplitFiemap* file) {
+    auto metadata_file = GetMetadataFile(metadata_dir);
+
+    PartitionOpener opener;
+    std::unique_ptr<MetadataBuilder> builder;
+    if (access(metadata_file.c_str(), R_OK)) {
+        if (errno != ENOENT) {
+            PLOG(ERROR) << "access " << metadata_file << " failed:";
+            return nullptr;
+        }
+
+        auto data_device = GetDevicePathForFile(file);
+
+        BlockDeviceInfo device_info;
+        if (!opener.GetInfo(data_device, &device_info)) {
+            LOG(ERROR) << "Could not read partition: " << data_device;
+            return nullptr;
+        }
+
+        std::vector<BlockDeviceInfo> block_devices = {device_info};
+        auto super_name = android::base::Basename(data_device);
+        builder = MetadataBuilder::New(block_devices, super_name, kMaxMetadataSize, 1);
+    } else {
+        auto metadata = OpenMetadata(metadata_dir);
+        if (!metadata) {
+            return nullptr;
+        }
+        builder = MetadataBuilder::New(*metadata.get(), &opener);
+    }
+
+    if (!builder) {
+        LOG(ERROR) << "Could not create metadata builder";
+        return nullptr;
+    }
+    return builder;
+}
+
+bool SaveMetadata(MetadataBuilder* builder, const std::string& metadata_dir) {
+    auto exported = builder->Export();
+    if (!exported) {
+        LOG(ERROR) << "Unable to export new metadata";
+        return false;
+    }
+
+    // If there are no more partitions in the metadata, just delete the file.
+    auto metadata_file = GetMetadataFile(metadata_dir);
+    if (exported->partitions.empty() && android::base::RemoveFileIfExists(metadata_file)) {
+        return true;
+    }
+    if (!WriteToImageFile(metadata_file, *exported.get())) {
+        LOG(ERROR) << "Unable to save new metadata";
+        return false;
+    }
+    return true;
+}
+
+bool RemoveAllMetadata(const std::string& dir) {
+    auto metadata_file = GetMetadataFile(dir);
+    return android::base::RemoveFileIfExists(metadata_file);
+}
+
+bool FillPartitionExtents(MetadataBuilder* builder, Partition* partition, SplitFiemap* file,
+                          uint64_t partition_size) {
+    auto block_device = android::base::Basename(GetDevicePathForFile(file));
+
+    uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE;
+    for (const auto& extent : file->extents()) {
+        if (extent.fe_length % LP_SECTOR_SIZE != 0) {
+            LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length;
+            return false;
+        }
+        if (extent.fe_physical % LP_SECTOR_SIZE != 0) {
+            LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical;
+            return false;
+        }
+
+        uint64_t num_sectors =
+                std::min(static_cast<uint64_t>(extent.fe_length / LP_SECTOR_SIZE), sectors_needed);
+        if (!num_sectors || !sectors_needed) {
+            // This should never happen, but we include it just in case. It would
+            // indicate that the last filesystem block had multiple extents.
+            LOG(WARNING) << "FiemapWriter allocated extra blocks";
+            break;
+        }
+
+        uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE;
+        if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) {
+            LOG(ERROR) << "Could not add extent to lp metadata";
+            return false;
+        }
+
+        sectors_needed -= num_sectors;
+    }
+    return true;
+}
+
+bool RemoveImageMetadata(const std::string& metadata_dir, const std::string& partition_name) {
+    if (!MetadataExists(metadata_dir)) {
+        return true;
+    }
+    auto metadata = OpenMetadata(metadata_dir);
+    if (!metadata) {
+        return false;
+    }
+
+    PartitionOpener opener;
+    auto builder = MetadataBuilder::New(*metadata.get(), &opener);
+    if (!builder) {
+        return false;
+    }
+    builder->RemovePartition(partition_name);
+    return SaveMetadata(builder.get(), metadata_dir);
+}
+
+bool UpdateMetadata(const std::string& metadata_dir, const std::string& partition_name,
+                    SplitFiemap* file, uint64_t partition_size, bool readonly) {
+    auto builder = OpenOrCreateMetadata(metadata_dir, file);
+    if (!builder) {
+        return false;
+    }
+    auto partition = builder->FindPartition(partition_name);
+    if (!partition) {
+        int attrs = 0;
+        if (readonly) attrs |= LP_PARTITION_ATTR_READONLY;
+
+        if ((partition = builder->AddPartition(partition_name, attrs)) == nullptr) {
+            LOG(ERROR) << "Could not add partition " << partition_name << " to metadata";
+            return false;
+        }
+    }
+    partition->RemoveExtents();
+
+    if (!FillPartitionExtents(builder.get(), partition, file, partition_size)) {
+        return false;
+    }
+    return SaveMetadata(builder.get(), metadata_dir);
+}
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/libfiemap/metadata.h b/fs_mgr/libfiemap/metadata.h
new file mode 100644
index 0000000..f0ce23e
--- /dev/null
+++ b/fs_mgr/libfiemap/metadata.h
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include <libfiemap/split_fiemap_writer.h>
+#include <liblp/liblp.h>
+
+namespace android {
+namespace fiemap {
+
+bool MetadataExists(const std::string& metadata_dir);
+std::unique_ptr<android::fs_mgr::LpMetadata> OpenMetadata(const std::string& metadata_dir);
+bool UpdateMetadata(const std::string& metadata_dir, const std::string& partition_name,
+                    SplitFiemap* file, uint64_t partition_size, bool readonly);
+bool RemoveImageMetadata(const std::string& metadata_dir, const std::string& partition_name);
+bool RemoveAllMetadata(const std::string& dir);
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/libfiemap/passthrough.cpp b/fs_mgr/libfiemap/passthrough.cpp
new file mode 100644
index 0000000..1ccd9a0
--- /dev/null
+++ b/fs_mgr/libfiemap/passthrough.cpp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 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 <libfiemap/image_manager.h>
+
+namespace android {
+namespace fiemap {
+
+std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir_prefix,
+                                                   const std::chrono::milliseconds& timeout_ms) {
+    (void)timeout_ms;
+    return ImageManager::Open(dir_prefix);
+}
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/libfiemap/split_fiemap_writer.cpp b/fs_mgr/libfiemap/split_fiemap_writer.cpp
new file mode 100644
index 0000000..cc54f20
--- /dev/null
+++ b/fs_mgr/libfiemap/split_fiemap_writer.cpp
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2019 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 <libfiemap/split_fiemap_writer.h>
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using android::base::unique_fd;
+
+// We use a four-digit suffix at the end of filenames.
+static const size_t kMaxFilePieces = 500;
+
+std::unique_ptr<SplitFiemap> SplitFiemap::Create(const std::string& file_path, uint64_t file_size,
+                                                 uint64_t max_piece_size,
+                                                 ProgressCallback progress) {
+    if (!file_size) {
+        LOG(ERROR) << "Cannot create a fiemap for a 0-length file: " << file_path;
+        return nullptr;
+    }
+
+    if (!max_piece_size) {
+        max_piece_size = DetermineMaximumFileSize(file_path);
+        if (!max_piece_size) {
+            LOG(ERROR) << "Could not determine maximum file size for " << file_path;
+            return nullptr;
+        }
+    }
+
+    // Remove any existing file.
+    RemoveSplitFiles(file_path);
+
+    // Call |progress| only when the total percentage would significantly change.
+    int permille = -1;
+    uint64_t total_bytes_written = 0;
+    auto on_progress = [&](uint64_t written, uint64_t) -> bool {
+        uint64_t actual_written = total_bytes_written + written;
+        int new_permille = (actual_written * 1000) / file_size;
+        if (new_permille != permille && actual_written < file_size) {
+            if (progress && !progress(actual_written, file_size)) {
+                return false;
+            }
+            permille = new_permille;
+        }
+        return true;
+    };
+
+    std::unique_ptr<SplitFiemap> out(new SplitFiemap());
+    out->creating_ = true;
+    out->list_file_ = file_path;
+
+    // Create the split files.
+    uint64_t remaining_bytes = file_size;
+    while (remaining_bytes) {
+        if (out->files_.size() >= kMaxFilePieces) {
+            LOG(ERROR) << "Requested size " << file_size << " created too many split files";
+            return nullptr;
+        }
+        std::string chunk_path =
+                android::base::StringPrintf("%s.%04d", file_path.c_str(), (int)out->files_.size());
+        uint64_t chunk_size = std::min(max_piece_size, remaining_bytes);
+        auto writer = FiemapWriter::Open(chunk_path, chunk_size, true, on_progress);
+        if (!writer) {
+            return nullptr;
+        }
+
+        // To make sure the alignment doesn't create too much inconsistency, we
+        // account the *actual* size, not the requested size.
+        total_bytes_written += writer->size();
+
+        // writer->size() is block size aligned and could be bigger than remaining_bytes
+        // If remaining_bytes is bigger, set remaining_bytes to 0 to avoid underflow error.
+        remaining_bytes = remaining_bytes > writer->size() ? (remaining_bytes - writer->size()) : 0;
+
+        out->AddFile(std::move(writer));
+    }
+
+    // Create the split file list.
+    unique_fd fd(open(out->list_file_.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0660));
+    if (fd < 0) {
+        PLOG(ERROR) << "Failed to open " << file_path;
+        return nullptr;
+    }
+
+    for (const auto& writer : out->files_) {
+        std::string line = android::base::Basename(writer->file_path()) + "\n";
+        if (!android::base::WriteFully(fd, line.data(), line.size())) {
+            PLOG(ERROR) << "Write failed " << file_path;
+            return nullptr;
+        }
+    }
+
+    // Unset this bit, so we don't unlink on destruction.
+    out->creating_ = false;
+    return out;
+}
+
+std::unique_ptr<SplitFiemap> SplitFiemap::Open(const std::string& file_path) {
+    std::vector<std::string> files;
+    if (!GetSplitFileList(file_path, &files)) {
+        return nullptr;
+    }
+
+    std::unique_ptr<SplitFiemap> out(new SplitFiemap());
+    out->list_file_ = file_path;
+
+    for (const auto& file : files) {
+        auto writer = FiemapWriter::Open(file, 0, false);
+        if (!writer) {
+            // Error was logged in Open().
+            return nullptr;
+        }
+        out->AddFile(std::move(writer));
+    }
+    return out;
+}
+
+bool SplitFiemap::GetSplitFileList(const std::string& file_path, std::vector<std::string>* list) {
+    // This is not the most efficient thing, but it is simple and recovering
+    // the fiemap/fibmap is much more expensive.
+    std::string contents;
+    if (!android::base::ReadFileToString(file_path, &contents, true)) {
+        PLOG(ERROR) << "Error reading file: " << file_path;
+        return false;
+    }
+
+    std::vector<std::string> names = android::base::Split(contents, "\n");
+    std::string dir = android::base::Dirname(file_path);
+    for (const auto& name : names) {
+        if (!name.empty()) {
+            list->emplace_back(dir + "/" + name);
+        }
+    }
+    return true;
+}
+
+bool SplitFiemap::RemoveSplitFiles(const std::string& file_path, std::string* message) {
+    // Early exit if this does not exist, and do not report an error.
+    if (access(file_path.c_str(), F_OK) && errno == ENOENT) {
+        return true;
+    }
+
+    bool ok = true;
+    std::vector<std::string> files;
+    if (GetSplitFileList(file_path, &files)) {
+        for (const auto& file : files) {
+            ok &= android::base::RemoveFileIfExists(file, message);
+        }
+    }
+    ok &= android::base::RemoveFileIfExists(file_path, message);
+    return ok;
+}
+
+bool SplitFiemap::HasPinnedExtents() const {
+    for (const auto& file : files_) {
+        if (!FiemapWriter::HasPinnedExtents(file->file_path())) {
+            return false;
+        }
+    }
+    return true;
+}
+
+const std::vector<struct fiemap_extent>& SplitFiemap::extents() {
+    if (extents_.empty()) {
+        for (const auto& file : files_) {
+            const auto& extents = file->extents();
+            extents_.insert(extents_.end(), extents.begin(), extents.end());
+        }
+    }
+    return extents_;
+}
+
+bool SplitFiemap::Write(const void* data, uint64_t bytes) {
+    // Open the current file.
+    FiemapWriter* file = files_[cursor_index_].get();
+
+    const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(data);
+    uint64_t bytes_remaining = bytes;
+    while (bytes_remaining) {
+        // How many bytes can we write into the current file?
+        uint64_t file_bytes_left = file->size() - cursor_file_pos_;
+        if (!file_bytes_left) {
+            if (cursor_index_ == files_.size() - 1) {
+                LOG(ERROR) << "write past end of file requested";
+                return false;
+            }
+
+            // No space left in the current file, but we have more files to
+            // use, so prep the next one.
+            cursor_fd_ = {};
+            cursor_file_pos_ = 0;
+            file = files_[++cursor_index_].get();
+            file_bytes_left = file->size();
+        }
+
+        // Open the current file if it's not open.
+        if (cursor_fd_ < 0) {
+            cursor_fd_.reset(open(file->file_path().c_str(), O_CLOEXEC | O_WRONLY));
+            if (cursor_fd_ < 0) {
+                PLOG(ERROR) << "open failed: " << file->file_path();
+                return false;
+            }
+            CHECK(cursor_file_pos_ == 0);
+        }
+
+        if (!FiemapWriter::HasPinnedExtents(file->file_path())) {
+            LOG(ERROR) << "file is no longer pinned: " << file->file_path();
+            return false;
+        }
+
+        uint64_t bytes_to_write = std::min(file_bytes_left, bytes_remaining);
+        if (!android::base::WriteFully(cursor_fd_, data_ptr, bytes_to_write)) {
+            PLOG(ERROR) << "write failed: " << file->file_path();
+            return false;
+        }
+        data_ptr += bytes_to_write;
+        bytes_remaining -= bytes_to_write;
+        cursor_file_pos_ += bytes_to_write;
+    }
+
+    // If we've reached the end of the current file, close it for sanity.
+    if (cursor_file_pos_ == file->size()) {
+        cursor_fd_ = {};
+    }
+    return true;
+}
+
+bool SplitFiemap::Flush() {
+    for (const auto& file : files_) {
+        unique_fd fd(open(file->file_path().c_str(), O_RDONLY | O_CLOEXEC));
+        if (fd < 0) {
+            PLOG(ERROR) << "open failed: " << file->file_path();
+            return false;
+        }
+        if (fsync(fd)) {
+            PLOG(ERROR) << "fsync failed: " << file->file_path();
+            return false;
+        }
+    }
+    return true;
+}
+
+SplitFiemap::~SplitFiemap() {
+    if (!creating_) {
+        return;
+    }
+
+    // We failed to finish creating, so unlink everything.
+    unlink(list_file_.c_str());
+    for (auto&& file : files_) {
+        std::string path = file->file_path();
+        file = nullptr;
+
+        unlink(path.c_str());
+    }
+}
+
+void SplitFiemap::AddFile(FiemapUniquePtr&& file) {
+    total_size_ += file->size();
+    files_.emplace_back(std::move(file));
+}
+
+uint32_t SplitFiemap::block_size() const {
+    return files_[0]->block_size();
+}
+
+const std::string& SplitFiemap::bdev_path() const {
+    return files_[0]->bdev_path();
+}
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/libfiemap/testdata/file_32k b/fs_mgr/libfiemap/testdata/file_32k
new file mode 100644
index 0000000..12f3be4
--- /dev/null
+++ b/fs_mgr/libfiemap/testdata/file_32k
Binary files differ
diff --git a/fs_mgr/libfiemap/testdata/file_4k b/fs_mgr/libfiemap/testdata/file_4k
new file mode 100644
index 0000000..08e7df1
--- /dev/null
+++ b/fs_mgr/libfiemap/testdata/file_4k
Binary files differ
diff --git a/fs_mgr/libfiemap/testdata/unaligned_file b/fs_mgr/libfiemap/testdata/unaligned_file
new file mode 100644
index 0000000..c107c26
--- /dev/null
+++ b/fs_mgr/libfiemap/testdata/unaligned_file
Binary files differ
diff --git a/fs_mgr/libfiemap/utility.cpp b/fs_mgr/libfiemap/utility.cpp
new file mode 100644
index 0000000..955e544
--- /dev/null
+++ b/fs_mgr/libfiemap/utility.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 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 "utility.h"
+
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <libfiemap/fiemap_writer.h>
+
+namespace android {
+namespace fiemap {
+
+using namespace std::string_literals;
+using android::base::unique_fd;
+
+static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata";
+
+uint64_t DetermineMaximumFileSize(const std::string& file_path) {
+    // Create the smallest file possible (one block).
+    auto writer = FiemapWriter::Open(file_path, 1);
+    if (!writer) {
+        return 0;
+    }
+
+    uint64_t result = 0;
+    switch (writer->fs_type()) {
+        case EXT4_SUPER_MAGIC:
+            // The minimum is 16GiB, so just report that. If we wanted we could parse the
+            // superblock and figure out if 64-bit support is enabled.
+            result = 17179869184ULL;
+            break;
+        case F2FS_SUPER_MAGIC:
+            // Formula is from https://www.kernel.org/doc/Documentation/filesystems/f2fs.txt
+            // 4KB * (923 + 2 * 1018 + 2 * 1018 * 1018 + 1018 * 1018 * 1018) := 3.94TB.
+            result = 4329690886144ULL;
+            break;
+        case MSDOS_SUPER_MAGIC:
+            // 4GB-1, which we want aligned to the block size.
+            result = 4294967295;
+            result -= (result % writer->block_size());
+            break;
+        default:
+            LOG(ERROR) << "Unknown file system type: " << writer->fs_type();
+            break;
+    }
+
+    // Close and delete the temporary file.
+    writer = nullptr;
+    unlink(file_path.c_str());
+
+    return result;
+}
+
+// Given a SplitFiemap, this returns a device path that will work during first-
+// stage init (i.e., its path can be found by InitRequiredDevices).
+std::string GetDevicePathForFile(SplitFiemap* file) {
+    auto bdev_path = file->bdev_path();
+
+    struct stat userdata, given;
+    if (!stat(bdev_path.c_str(), &given) && !stat(kUserdataDevice, &userdata)) {
+        if (S_ISBLK(given.st_mode) && S_ISBLK(userdata.st_mode) &&
+            given.st_rdev == userdata.st_rdev) {
+            return kUserdataDevice;
+        }
+    }
+    return bdev_path;
+}
+
+std::string JoinPaths(const std::string& dir, const std::string& file) {
+    if (android::base::EndsWith(dir, "/")) {
+        return dir + file;
+    }
+    return dir + "/" + file;
+}
+
+bool F2fsPinBeforeAllocate(int file_fd, bool* supported) {
+    struct stat st;
+    if (fstat(file_fd, &st) < 0) {
+        PLOG(ERROR) << "stat failed";
+        return false;
+    }
+    std::string bdev;
+    if (!BlockDeviceToName(major(st.st_dev), minor(st.st_dev), &bdev)) {
+        LOG(ERROR) << "Failed to get block device name for " << major(st.st_dev) << ":"
+                   << minor(st.st_dev);
+        return false;
+    }
+
+    std::string contents;
+    std::string feature_file = "/sys/fs/f2fs/" + bdev + "/features";
+    if (!android::base::ReadFileToString(feature_file, &contents)) {
+        PLOG(ERROR) << "read failed: " << feature_file;
+        return false;
+    }
+    contents = android::base::Trim(contents);
+
+    auto features = android::base::Split(contents, ", ");
+    auto iter = std::find(features.begin(), features.end(), "pin_file"s);
+    *supported = (iter != features.end());
+    return true;
+}
+
+bool BlockDeviceToName(uint32_t major, uint32_t minor, std::string* bdev_name) {
+    // The symlinks in /sys/dev/block point to the block device node under /sys/device/..
+    // The directory name in the target corresponds to the name of the block device. We use
+    // that to extract the block device name.
+    // e.g for block device name 'ram0', there exists a symlink named '1:0' in /sys/dev/block as
+    // follows.
+    //    1:0 -> ../../devices/virtual/block/ram0
+    std::string sysfs_path = ::android::base::StringPrintf("/sys/dev/block/%u:%u", major, minor);
+    std::string sysfs_bdev;
+
+    if (!::android::base::Readlink(sysfs_path, &sysfs_bdev)) {
+        PLOG(ERROR) << "Failed to read link at: " << sysfs_path;
+        return false;
+    }
+
+    *bdev_name = ::android::base::Basename(sysfs_bdev);
+    // Paranoid sanity check to make sure we just didn't get the
+    // input in return as-is.
+    if (sysfs_bdev == *bdev_name) {
+        LOG(ERROR) << "Malformed symlink for block device: " << sysfs_bdev;
+        return false;
+    }
+
+    return true;
+}
+
+bool FilesystemHasReliablePinning(const std::string& file, bool* supported) {
+    struct statfs64 sfs;
+    if (statfs64(file.c_str(), &sfs)) {
+        PLOG(ERROR) << "statfs failed: " << file;
+        return false;
+    }
+    if (sfs.f_type != F2FS_SUPER_MAGIC) {
+        *supported = true;
+        return true;
+    }
+
+    unique_fd fd(open(file.c_str(), O_RDONLY | O_CLOEXEC));
+    if (fd < 0) {
+        PLOG(ERROR) << "open failed: " << file;
+        return false;
+    }
+    return F2fsPinBeforeAllocate(fd, supported);
+}
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/libfiemap/utility.h b/fs_mgr/libfiemap/utility.h
new file mode 100644
index 0000000..24ebc57
--- /dev/null
+++ b/fs_mgr/libfiemap/utility.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <string>
+
+#include <libfiemap/split_fiemap_writer.h>
+
+namespace android {
+namespace fiemap {
+
+// Given a file that will be created, determine the maximum size its containing
+// filesystem allows. Note this is a theoretical maximum size; free space is
+// ignored entirely.
+uint64_t DetermineMaximumFileSize(const std::string& file_path);
+
+// Given a SplitFiemap, this returns a device path that will work during first-
+// stage init (i.e., its path can be found by InitRequiredDevices).
+std::string GetDevicePathForFile(android::fiemap::SplitFiemap* file);
+
+// Combine two path components into a single path.
+std::string JoinPaths(const std::string& dir, const std::string& file);
+
+// Given a file within an F2FS filesystem, return whether or not the filesystem
+// supports the "pin_file" feature, which requires pinning before fallocation.
+bool F2fsPinBeforeAllocate(int file_fd, bool* supported);
+
+// Given a major/minor device number, return its canonical name such that
+// /dev/block/<name> resolves to the device.
+bool BlockDeviceToName(uint32_t major, uint32_t minor, std::string* bdev_name);
+
+// This is the same as F2fsPinBeforeAllocate, however, it will return true
+// (and supported = true) for non-f2fs filesystems. It is intended to be used
+// in conjunction with ImageManager to reject image requests for reliable use
+// cases (such as snapshots or adb remount).
+bool FilesystemHasReliablePinning(const std::string& file, bool* supported);
+
+}  // namespace fiemap
+}  // namespace android
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 54350a5..7e7f393 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -253,7 +253,7 @@
     header_.magic = LP_METADATA_HEADER_MAGIC;
     header_.major_version = LP_METADATA_MAJOR_VERSION;
     header_.minor_version = LP_METADATA_MINOR_VERSION_MIN;
-    header_.header_size = sizeof(header_);
+    header_.header_size = sizeof(LpMetadataHeaderV1_0);
     header_.partitions.entry_size = sizeof(LpMetadataPartition);
     header_.extents.entry_size = sizeof(LpMetadataExtent);
     header_.groups.entry_size = sizeof(LpMetadataPartitionGroup);
@@ -264,6 +264,12 @@
     geometry_ = metadata.geometry;
     block_devices_ = metadata.block_devices;
 
+    // Bump the version as necessary to copy any newer fields.
+    if (metadata.header.minor_version >= LP_METADATA_VERSION_FOR_EXPANDED_HEADER) {
+        RequireExpandedMetadataHeader();
+        header_.flags = metadata.header.flags;
+    }
+
     for (const auto& group : metadata.groups) {
         std::string group_name = GetPartitionGroupName(group);
         if (!AddGroup(group_name, group.maximum_size)) {
@@ -883,6 +889,14 @@
     return metadata;
 }
 
+void MetadataBuilder::RequireExpandedMetadataHeader() {
+    if (header_.minor_version >= LP_METADATA_VERSION_FOR_EXPANDED_HEADER) {
+        return;
+    }
+    header_.minor_version = LP_METADATA_VERSION_FOR_EXPANDED_HEADER;
+    header_.header_size = sizeof(LpMetadataHeaderV1_2);
+}
+
 uint64_t MetadataBuilder::AllocatableSpace() const {
     uint64_t total_size = 0;
     for (const auto& block_device : block_devices_) {
@@ -1111,6 +1125,11 @@
     auto_slot_suffixing_ = true;
 }
 
+void MetadataBuilder::SetVirtualABDeviceFlag() {
+    RequireExpandedMetadataHeader();
+    header_.flags |= LP_HEADER_FLAG_VIRTUAL_AB_DEVICE;
+}
+
 bool MetadataBuilder::IsABDevice() {
     return !IPropertyFetcher::GetInstance()->GetProperty("ro.boot.slot_suffix", "").empty();
 }
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index a67ffa7..ca8df61 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -352,6 +352,7 @@
     EXPECT_EQ(header.magic, LP_METADATA_HEADER_MAGIC);
     EXPECT_EQ(header.major_version, LP_METADATA_MAJOR_VERSION);
     EXPECT_EQ(header.minor_version, LP_METADATA_MINOR_VERSION_MIN);
+    EXPECT_EQ(header.header_size, sizeof(LpMetadataHeaderV1_0));
 
     ASSERT_EQ(exported->partitions.size(), 2);
     ASSERT_EQ(exported->extents.size(), 3);
@@ -917,3 +918,22 @@
                                       std::vector<Interval>{Interval(0, 100, 150)})
                           .size());
 }
+
+TEST_F(BuilderTest, ExpandedHeader) {
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+    ASSERT_NE(builder, nullptr);
+
+    builder->RequireExpandedMetadataHeader();
+
+    unique_ptr<LpMetadata> exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+    EXPECT_EQ(exported->header.header_size, sizeof(LpMetadataHeaderV1_2));
+
+    exported->header.flags = 0x5e5e5e5e;
+
+    builder = MetadataBuilder::New(*exported.get());
+    exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+    EXPECT_EQ(exported->header.header_size, sizeof(LpMetadataHeaderV1_2));
+    EXPECT_EQ(exported->header.flags, 0x5e5e5e5e);
+}
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index 1e9d636..851f041 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -318,6 +318,8 @@
 
     // Set the LP_METADATA_AUTO_SLOT_SUFFIXING flag.
     void SetAutoSlotSuffixing();
+    // Set the LP_HEADER_FLAG_VIRTUAL_AB_DEVICE flag.
+    void SetVirtualABDeviceFlag();
 
     // If set, checks for slot suffixes will be ignored internally.
     void IgnoreSlotSuffixing();
@@ -325,6 +327,10 @@
     bool GetBlockDeviceInfo(const std::string& partition_name, BlockDeviceInfo* info) const;
     bool UpdateBlockDeviceInfo(const std::string& partition_name, const BlockDeviceInfo& info);
 
+    // Require the expanded metadata header. This is exposed for testing, and
+    // is normally only called as needed by other methods.
+    void RequireExpandedMetadataHeader();
+
     // Attempt to preserve the named partitions from an older metadata. If this
     // is not possible (for example, the block device list has changed) then
     // false is returned.
diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h
index 6e928b4..d3c9874 100644
--- a/fs_mgr/liblp/include/liblp/metadata_format.h
+++ b/fs_mgr/liblp/include/liblp/metadata_format.h
@@ -40,11 +40,14 @@
 /* Current metadata version. */
 #define LP_METADATA_MAJOR_VERSION 10
 #define LP_METADATA_MINOR_VERSION_MIN 0
-#define LP_METADATA_MINOR_VERSION_MAX 1
+#define LP_METADATA_MINOR_VERSION_MAX 2
 
 /* Metadata version needed to use the UPDATED partition attribute. */
 #define LP_METADATA_VERSION_FOR_UPDATED_ATTR 1
 
+/* Metadata version needed for the new expanded header struct. */
+#define LP_METADATA_VERSION_FOR_EXPANDED_HEADER 2
+
 /* Attributes for the LpMetadataPartition::attributes field.
  *
  * READONLY - The partition should not be considered writable. When used with
@@ -212,8 +215,27 @@
     LpMetadataTableDescriptor groups;
     /* 116: Block device table. */
     LpMetadataTableDescriptor block_devices;
+
+    /* Everything past here is header version 1.2+, and is only included if
+     * needed. When liblp supporting >= 1.2 reads a < 1.2 header, it must
+     * zero these additional fields.
+     */
+
+    /* 128: See LP_HEADER_FLAG_ constants for possible values. Header flags are
+     * independent of the version number and intended to be informational only.
+     * New flags can be added without bumping the version.
+     */
+    uint32_t flags;
+
+    /* 132: Reserved (zero), pad to 256 bytes. */
+    uint8_t reserved[124];
 } __attribute__((packed)) LpMetadataHeader;
 
+/* This device uses Virtual A/B. Note that on retrofit devices, the expanded
+ * header may not be present.
+ */
+#define LP_HEADER_FLAG_VIRTUAL_AB_DEVICE 0x1
+
 /* This struct defines a logical partition entry, similar to what would be
  * present in a GUID Partition Table.
  */
@@ -351,6 +373,25 @@
  */
 #define LP_BLOCK_DEVICE_SLOT_SUFFIXED (1 << 0)
 
+/* For ease of writing compatibility checks, the original metadata header is
+ * preserved below, and typedefs are provided for the current version.
+ */
+typedef struct LpMetadataHeaderV1_0 {
+    uint32_t magic;
+    uint16_t major_version;
+    uint16_t minor_version;
+    uint32_t header_size;
+    uint8_t header_checksum[32];
+    uint32_t tables_size;
+    uint8_t tables_checksum[32];
+    LpMetadataTableDescriptor partitions;
+    LpMetadataTableDescriptor extents;
+    LpMetadataTableDescriptor groups;
+    LpMetadataTableDescriptor block_devices;
+} __attribute__((packed)) LpMetadataHeaderV1_0;
+
+typedef LpMetadataHeader LpMetadataHeaderV1_2;
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index 22f6746..e67fb33 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -372,7 +372,7 @@
     // Compute the maximum number of partitions we can fit in 512 bytes of
     // metadata. By default there is the header, one partition group, and a
     // block device entry.
-    static const size_t kMaxPartitionTableSize = kMetadataSize - sizeof(LpMetadataHeader) -
+    static const size_t kMaxPartitionTableSize = kMetadataSize - sizeof(LpMetadataHeaderV1_0) -
                                                  sizeof(LpMetadataPartitionGroup) -
                                                  sizeof(LpMetadataBlockDevice);
     size_t max_partitions = kMaxPartitionTableSize / sizeof(LpMetadataPartition);
@@ -742,3 +742,28 @@
     ASSERT_GE(metadata->partitions.size(), 1);
     ASSERT_NE(metadata->partitions[0].attributes & LP_PARTITION_ATTR_UPDATED, 0);
 }
+
+TEST_F(LiblpTest, ReadExpandedHeader) {
+    unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
+    ASSERT_NE(builder, nullptr);
+    ASSERT_TRUE(AddDefaultPartitions(builder.get()));
+
+    builder->RequireExpandedMetadataHeader();
+
+    unique_fd fd = CreateFakeDisk();
+    ASSERT_GE(fd, 0);
+
+    DefaultPartitionOpener opener(fd);
+
+    // Export and flash.
+    unique_ptr<LpMetadata> exported = builder->Export();
+    ASSERT_NE(exported, nullptr);
+    exported->header.flags = 0x5e5e5e5e;
+    ASSERT_TRUE(FlashPartitionTable(opener, "super", *exported.get()));
+
+    unique_ptr<LpMetadata> imported = ReadMetadata(opener, "super", 0);
+    ASSERT_NE(imported, nullptr);
+    EXPECT_EQ(imported->header.header_size, sizeof(LpMetadataHeaderV1_2));
+    EXPECT_EQ(imported->header.header_size, exported->header.header_size);
+    EXPECT_EQ(imported->header.flags, exported->header.flags);
+}
diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp
index aecf685..30c17e4 100644
--- a/fs_mgr/liblp/reader.cpp
+++ b/fs_mgr/liblp/reader.cpp
@@ -31,6 +31,9 @@
 namespace android {
 namespace fs_mgr {
 
+static_assert(sizeof(LpMetadataHeaderV1_0) == offsetof(LpMetadataHeader, flags),
+              "Incorrect LpMetadataHeader v0 size");
+
 // Helper class for reading descriptors and memory buffers in the same manner.
 class Reader {
   public:
@@ -161,30 +164,59 @@
     return true;
 }
 
-static bool ValidateMetadataHeader(const LpMetadataHeader& header) {
-    // To compute the header's checksum, we have to temporarily set its checksum
-    // field to 0.
-    {
-        LpMetadataHeader temp = header;
-        memset(&temp.header_checksum, 0, sizeof(temp.header_checksum));
-        SHA256(&temp, sizeof(temp), temp.header_checksum);
-        if (memcmp(temp.header_checksum, header.header_checksum, sizeof(temp.header_checksum)) != 0) {
-            LERROR << "Logical partition metadata has invalid checksum.";
-            return false;
-        }
+static bool ReadMetadataHeader(Reader* reader, LpMetadata* metadata) {
+    // Note we zero the struct since older files will result in a partial read.
+    LpMetadataHeader& header = metadata->header;
+    memset(&header, 0, sizeof(header));
+
+    if (!reader->ReadFully(&header, sizeof(LpMetadataHeaderV1_0))) {
+        PERROR << __PRETTY_FUNCTION__ << " read failed";
+        return false;
     }
 
-    // Do basic validation of key metadata bits.
+    // Do basic sanity checks before computing the checksum.
     if (header.magic != LP_METADATA_HEADER_MAGIC) {
         LERROR << "Logical partition metadata has invalid magic value.";
         return false;
     }
-    // Check that the version is compatible.
     if (header.major_version != LP_METADATA_MAJOR_VERSION ||
         header.minor_version > LP_METADATA_MINOR_VERSION_MAX) {
         LERROR << "Logical partition metadata has incompatible version.";
         return false;
     }
+
+    // Validate the header struct size against the reported version.
+    uint32_t expected_struct_size = sizeof(header);
+    if (header.minor_version < LP_METADATA_VERSION_FOR_EXPANDED_HEADER) {
+        expected_struct_size = sizeof(LpMetadataHeaderV1_0);
+    }
+    if (header.header_size != expected_struct_size) {
+        LERROR << "Invalid partition metadata header struct size.";
+        return false;
+    }
+
+    // Read in any remaining fields, the last step needed before checksumming.
+    if (size_t remaining_bytes = header.header_size - sizeof(LpMetadataHeaderV1_0)) {
+        uint8_t* offset = reinterpret_cast<uint8_t*>(&header) + sizeof(LpMetadataHeaderV1_0);
+        if (!reader->ReadFully(offset, remaining_bytes)) {
+            PERROR << __PRETTY_FUNCTION__ << " read failed";
+            return false;
+        }
+    }
+
+    // To compute the header's checksum, we have to temporarily set its checksum
+    // field to 0. Note that we must only compute up to |header_size|.
+    {
+        LpMetadataHeader temp = header;
+        memset(&temp.header_checksum, 0, sizeof(temp.header_checksum));
+        SHA256(&temp, temp.header_size, temp.header_checksum);
+        if (memcmp(temp.header_checksum, header.header_checksum, sizeof(temp.header_checksum)) !=
+            0) {
+            LERROR << "Logical partition metadata has invalid checksum.";
+            return false;
+        }
+    }
+
     if (!ValidateTableBounds(header, header.partitions) ||
         !ValidateTableBounds(header, header.extents) ||
         !ValidateTableBounds(header, header.groups) ||
@@ -215,19 +247,22 @@
                                                  Reader* reader) {
     // First read and validate the header.
     std::unique_ptr<LpMetadata> metadata = std::make_unique<LpMetadata>();
-    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;
+    if (!ReadMetadataHeader(reader, metadata.get())) {
+        return nullptr;
+    }
 
     LpMetadataHeader& header = metadata->header;
 
-    // Read the metadata payload. Allocation is fallible in case the metadata is
-    // corrupt and has some huge value.
+    // Sanity check the table size.
+    if (header.tables_size > geometry.metadata_max_size) {
+        LERROR << "Invalid partition metadata header table size.";
+        return nullptr;
+    }
+
+    // Read the metadata payload. Allocation is fallible since the table size
+    // could be large.
     std::unique_ptr<uint8_t[]> buffer(new (std::nothrow) uint8_t[header.tables_size]);
     if (!buffer) {
         LERROR << "Out of memory reading logical partition tables.";
diff --git a/fs_mgr/liblp/writer.cpp b/fs_mgr/liblp/writer.cpp
index bb24069..8bf1ee9 100644
--- a/fs_mgr/liblp/writer.cpp
+++ b/fs_mgr/liblp/writer.cpp
@@ -74,10 +74,10 @@
 
     // Compute header checksum.
     memset(header.header_checksum, 0, sizeof(header.header_checksum));
-    SHA256(&header, sizeof(header), header.header_checksum);
+    SHA256(&header, header.header_size, header.header_checksum);
 
     std::string header_blob =
-            std::string(reinterpret_cast<const char*>(&metadata.header), sizeof(metadata.header));
+            std::string(reinterpret_cast<const char*>(&header), header.header_size);
     return header_blob + tables;
 }
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 7450d19..445e6db 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -155,6 +155,7 @@
     // Mark snapshot writes as having completed. After this, new snapshots cannot
     // be created, and the device must either cancel the OTA (either before
     // rebooting or after rolling back), or merge the OTA.
+    // Before calling this function, all snapshots must be mapped.
     bool FinishedSnapshotWrites();
 
   private:
@@ -197,6 +198,13 @@
     //   - other states indicating an error has occurred
     UpdateState InitiateMergeAndWait();
 
+    // Wait for the merge if rebooted into the new slot. Does NOT initiate a
+    // merge. If the merge has not been initiated (but should be), wait.
+    // Returns:
+    //   - true there is no merge or merge finishes
+    //   - false indicating an error has occurred
+    bool WaitForMerge();
+
     // Find the status of the current update, if any.
     //
     // |progress| depends on the returned status:
@@ -490,6 +498,15 @@
     // This should only be called in recovery.
     bool UnmapAllPartitions();
 
+    // Sanity check no snapshot overflows. Note that this returns false negatives if the snapshot
+    // overflows, then is remapped and not written afterwards. Hence, the function may only serve
+    // as a sanity check.
+    bool EnsureNoOverflowSnapshot(LockedFile* lock);
+
+    enum class Slot { Unknown, Source, Target };
+    friend std::ostream& operator<<(std::ostream& os, SnapshotManager::Slot slot);
+    Slot GetCurrentSlot();
+
     std::string gsid_dir_;
     std::string metadata_dir_;
     std::unique_ptr<IDeviceInfo> device_;
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 830495c..a0ec068 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -74,6 +74,7 @@
 using namespace std::string_literals;
 
 static constexpr char kBootIndicatorPath[] = "/metadata/ota/snapshot-boot";
+static constexpr auto kUpdateStateCheckInterval = 2s;
 
 // Note: IImageManager is an incomplete type in the header, so the default
 // destructor doesn't work.
@@ -171,14 +172,9 @@
 
     if (state == UpdateState::Unverified) {
         // We completed an update, but it can still be canceled if we haven't booted into it.
-        auto boot_file = GetSnapshotBootIndicatorPath();
-        std::string contents;
-        if (!android::base::ReadFileToString(boot_file, &contents)) {
-            PLOG(WARNING) << "Cannot read " << boot_file << ", proceed to canceling the update:";
-            return RemoveAllUpdateState(file.get());
-        }
-        if (device_->GetSlotSuffix() == contents) {
-            LOG(INFO) << "Canceling a previously completed update";
+        auto slot = GetCurrentSlot();
+        if (slot != Slot::Target) {
+            LOG(INFO) << "Canceling previously completed updates (if any)";
             return RemoveAllUpdateState(file.get());
         }
     }
@@ -186,6 +182,19 @@
     return true;
 }
 
+SnapshotManager::Slot SnapshotManager::GetCurrentSlot() {
+    auto boot_file = GetSnapshotBootIndicatorPath();
+    std::string contents;
+    if (!android::base::ReadFileToString(boot_file, &contents)) {
+        PLOG(WARNING) << "Cannot read " << boot_file;
+        return Slot::Unknown;
+    }
+    if (device_->GetSlotSuffix() == contents) {
+        return Slot::Source;
+    }
+    return Slot::Target;
+}
+
 bool SnapshotManager::RemoveAllUpdateState(LockedFile* lock) {
     if (!RemoveAllSnapshots(lock)) {
         LOG(ERROR) << "Could not remove all snapshots";
@@ -214,6 +223,11 @@
         return false;
     }
 
+    if (!EnsureNoOverflowSnapshot(lock.get())) {
+        LOG(ERROR) << "Cannot ensure there are no overflow snapshots.";
+        return false;
+    }
+
     // This file acts as both a quick indicator for init (it can use access(2)
     // to decide how to do first-stage mounts), and it stores the old slot, so
     // we can tell whether or not we performed a rollback.
@@ -500,15 +514,9 @@
         return false;
     }
 
-    std::string old_slot;
-    auto boot_file = GetSnapshotBootIndicatorPath();
-    if (!android::base::ReadFileToString(boot_file, &old_slot)) {
-        LOG(ERROR) << "Could not determine the previous slot; aborting merge";
-        return false;
-    }
-    auto new_slot = device_->GetSlotSuffix();
-    if (new_slot == old_slot) {
-        LOG(ERROR) << "Device cannot merge while booting off old slot " << old_slot;
+    auto slot = GetCurrentSlot();
+    if (slot != Slot::Target) {
+        LOG(ERROR) << "Device cannot merge while not booting from new slot";
         return false;
     }
 
@@ -724,7 +732,7 @@
 
         // This wait is not super time sensitive, so we have a relatively
         // low polling frequency.
-        std::this_thread::sleep_for(2s);
+        std::this_thread::sleep_for(kUpdateStateCheckInterval);
     }
 }
 
@@ -1092,13 +1100,11 @@
 }
 
 bool SnapshotManager::HandleCancelledUpdate(LockedFile* lock) {
-    std::string old_slot;
-    auto boot_file = GetSnapshotBootIndicatorPath();
-    if (!android::base::ReadFileToString(boot_file, &old_slot)) {
-        PLOG(ERROR) << "Unable to read the snapshot indicator file: " << boot_file;
+    auto slot = GetCurrentSlot();
+    if (slot == Slot::Unknown) {
         return false;
     }
-    if (device_->GetSlotSuffix() != old_slot) {
+    if (slot == Slot::Target) {
         // We're booted into the target slot, which means we just rebooted
         // after applying the update.
         if (!HandleCancelledUpdateOnNewSlot(lock)) {
@@ -1266,14 +1272,9 @@
     // ultimately we'll fail to boot. Why not make it a fatal error and have
     // the reason be clearer? Because the indicator file still exists, and
     // if this was FATAL, reverting to the old slot would be broken.
-    std::string old_slot;
-    auto boot_file = GetSnapshotBootIndicatorPath();
-    if (!android::base::ReadFileToString(boot_file, &old_slot)) {
-        PLOG(ERROR) << "Unable to read the snapshot indicator file: " << boot_file;
-        return false;
-    }
-    if (device_->GetSlotSuffix() == old_slot) {
-        LOG(INFO) << "Detected slot rollback, will not mount snapshots.";
+    auto slot = GetCurrentSlot();
+    if (slot != Slot::Target) {
+        LOG(INFO) << "Not booting from new slot. Will not mount snapshots.";
         return false;
     }
 
@@ -2151,6 +2152,17 @@
     return ok;
 }
 
+std::ostream& operator<<(std::ostream& os, SnapshotManager::Slot slot) {
+    switch (slot) {
+        case SnapshotManager::Slot::Unknown:
+            return os << "unknown";
+        case SnapshotManager::Slot::Source:
+            return os << "source";
+        case SnapshotManager::Slot::Target:
+            return os << "target";
+    }
+}
+
 bool SnapshotManager::Dump(std::ostream& os) {
     // Don't actually lock. Dump() is for debugging purposes only, so it is okay
     // if it is racy.
@@ -2161,11 +2173,8 @@
 
     ss << "Update state: " << ReadUpdateState(file.get()) << std::endl;
 
-    auto boot_file = GetSnapshotBootIndicatorPath();
-    std::string boot_indicator;
-    if (android::base::ReadFileToString(boot_file, &boot_indicator)) {
-        ss << "Boot indicator: old slot = " << boot_indicator << std::endl;
-    }
+    ss << "Current slot: " << device_->GetSlotSuffix() << std::endl;
+    ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl;
 
     bool ok = true;
     std::vector<std::string> snapshots;
@@ -2233,6 +2242,21 @@
     return state;
 }
 
+bool SnapshotManager::WaitForMerge() {
+    LOG(INFO) << "Waiting for any previous merge request to complete. "
+              << "This can take up to several minutes.";
+    while (true) {
+        auto state = ProcessUpdateState();
+        if (state == UpdateState::Unverified && GetCurrentSlot() == Slot::Target) {
+            LOG(INFO) << "Wait for merge to be initiated.";
+            std::this_thread::sleep_for(kUpdateStateCheckInterval);
+            continue;
+        }
+        LOG(INFO) << "Wait for merge exits with state " << state;
+        return state == UpdateState::None || state == UpdateState::MergeCompleted;
+    }
+}
+
 bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callback) {
     if (!device_->IsRecovery()) {
         LOG(ERROR) << "Data wipes are only allowed in recovery.";
@@ -2278,11 +2302,9 @@
             //
             // Since the rollback is inevitable, we don't treat a HAL failure
             // as an error here.
-            std::string old_slot;
-            auto boot_file = GetSnapshotBootIndicatorPath();
-            if (android::base::ReadFileToString(boot_file, &old_slot) &&
-                device_->GetSlotSuffix() != old_slot) {
-                LOG(ERROR) << "Reverting to slot " << old_slot << " since update will be deleted.";
+            auto slot = GetCurrentSlot();
+            if (slot == Slot::Target) {
+                LOG(ERROR) << "Reverting to old slot since update will be deleted.";
                 device_->SetSlotAsUnbootable(slot_number);
             }
             break;
@@ -2303,5 +2325,36 @@
     return true;
 }
 
+bool SnapshotManager::EnsureNoOverflowSnapshot(LockedFile* lock) {
+    CHECK(lock);
+
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock, &snapshots)) {
+        LOG(ERROR) << "Could not list snapshots.";
+        return false;
+    }
+
+    auto& dm = DeviceMapper::Instance();
+    for (const auto& snapshot : snapshots) {
+        std::vector<DeviceMapper::TargetInfo> targets;
+        if (!dm.GetTableStatus(snapshot, &targets)) {
+            LOG(ERROR) << "Could not read snapshot device table: " << snapshot;
+            return false;
+        }
+        if (targets.size() != 1) {
+            LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << snapshot
+                       << ", size = " << targets.size();
+            return false;
+        }
+        if (targets[0].IsOverflowSnapshot()) {
+            LOG(ERROR) << "Detected overflow in snapshot " << snapshot
+                       << ", CoW device size computation is wrong!";
+            return false;
+        }
+    }
+
+    return true;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index ff943f2..0f5af14 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -273,6 +273,61 @@
         return AssertionSuccess();
     }
 
+    // Prepare A/B slot for a partition named "test_partition".
+    AssertionResult PrepareOneSnapshot(uint64_t device_size,
+                                       std::string* out_snap_device = nullptr) {
+        std::string base_device, cow_device, snap_device;
+        if (!CreatePartition("test_partition_a", device_size)) {
+            return AssertionFailure();
+        }
+        if (!MapUpdatePartitions()) {
+            return AssertionFailure();
+        }
+        if (!dm_.GetDmDevicePathByName("test_partition_b-base", &base_device)) {
+            return AssertionFailure();
+        }
+        SnapshotStatus status;
+        status.set_name("test_partition_b");
+        status.set_device_size(device_size);
+        status.set_snapshot_size(device_size);
+        status.set_cow_file_size(device_size);
+        if (!sm->CreateSnapshot(lock_.get(), &status)) {
+            return AssertionFailure();
+        }
+        if (!CreateCowImage("test_partition_b")) {
+            return AssertionFailure();
+        }
+        if (!MapCowImage("test_partition_b", 10s, &cow_device)) {
+            return AssertionFailure();
+        }
+        if (!sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s,
+                             &snap_device)) {
+            return AssertionFailure();
+        }
+        if (out_snap_device) {
+            *out_snap_device = std::move(snap_device);
+        }
+        return AssertionSuccess();
+    }
+
+    // Simulate a reboot into the new slot.
+    AssertionResult SimulateReboot() {
+        lock_ = nullptr;
+        if (!sm->FinishedSnapshotWrites()) {
+            return AssertionFailure();
+        }
+        if (!dm_.DeleteDevice("test_partition_b")) {
+            return AssertionFailure();
+        }
+        if (!DestroyLogicalPartition("test_partition_b-base")) {
+            return AssertionFailure();
+        }
+        if (!sm->UnmapCowImage("test_partition_b")) {
+            return AssertionFailure();
+        }
+        return AssertionSuccess();
+    }
+
     DeviceMapper& dm_;
     std::unique_ptr<SnapshotManager::LockedFile> lock_;
     android::fiemap::IImageManager* image_manager_ = nullptr;
@@ -389,21 +444,8 @@
     ASSERT_TRUE(AcquireLock());
 
     static const uint64_t kDeviceSize = 1024 * 1024;
-
-    std::string base_device, cow_device, snap_device;
-    ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
-    ASSERT_TRUE(MapUpdatePartitions());
-    ASSERT_TRUE(dm_.GetDmDevicePathByName("test_partition_b-base", &base_device));
-    SnapshotStatus status;
-    status.set_name("test_partition_b");
-    status.set_device_size(kDeviceSize);
-    status.set_snapshot_size(kDeviceSize);
-    status.set_cow_file_size(kDeviceSize);
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
-    ASSERT_TRUE(CreateCowImage("test_partition_b"));
-    ASSERT_TRUE(MapCowImage("test_partition_b", 10s, &cow_device));
-    ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s,
-                                &snap_device));
+    std::string snap_device;
+    ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &snap_device));
 
     std::string test_string = "This is a test string.";
     {
@@ -455,21 +497,8 @@
     ASSERT_TRUE(AcquireLock());
 
     static const uint64_t kDeviceSize = 1024 * 1024;
-
-    ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
-    ASSERT_TRUE(MapUpdatePartitions());
-    SnapshotStatus status;
-    status.set_name("test_partition_b");
-    status.set_device_size(kDeviceSize);
-    status.set_snapshot_size(kDeviceSize);
-    status.set_cow_file_size(kDeviceSize);
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
-    ASSERT_TRUE(CreateCowImage("test_partition_b"));
-
-    // Simulate a reboot into the new slot.
-    lock_ = nullptr;
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
-    ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base"));
+    ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
+    ASSERT_TRUE(SimulateReboot());
 
     auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
     ASSERT_NE(init, nullptr);
@@ -479,6 +508,7 @@
     ASSERT_TRUE(AcquireLock());
 
     // Validate that we have a snapshot device.
+    SnapshotStatus status;
     ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
     ASSERT_EQ(status.state(), SnapshotState::CREATED);
 
@@ -492,21 +522,8 @@
     ASSERT_TRUE(AcquireLock());
 
     static const uint64_t kDeviceSize = 1024 * 1024;
-
-    ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
-    ASSERT_TRUE(MapUpdatePartitions());
-    SnapshotStatus status;
-    status.set_name("test_partition_b");
-    status.set_device_size(kDeviceSize);
-    status.set_snapshot_size(kDeviceSize);
-    status.set_cow_file_size(kDeviceSize);
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
-    ASSERT_TRUE(CreateCowImage("test_partition_b"));
-
-    // Simulate a reboot into the new slot.
-    lock_ = nullptr;
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
-    ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base"));
+    ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
+    ASSERT_TRUE(SimulateReboot());
 
     // Reflash the super partition.
     FormatFakeSuper();
@@ -519,6 +536,7 @@
 
     ASSERT_TRUE(AcquireLock());
 
+    SnapshotStatus status;
     ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
 
     // We should not get a snapshot device now.
@@ -535,21 +553,8 @@
     ASSERT_TRUE(AcquireLock());
 
     static const uint64_t kDeviceSize = 1024 * 1024;
-
-    ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
-    ASSERT_TRUE(MapUpdatePartitions());
-    SnapshotStatus status;
-    status.set_name("test_partition_b");
-    status.set_device_size(kDeviceSize);
-    status.set_snapshot_size(kDeviceSize);
-    status.set_cow_file_size(kDeviceSize);
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
-    ASSERT_TRUE(CreateCowImage("test_partition_b"));
-
-    // Simulate a reboot into the new slot.
-    lock_ = nullptr;
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
-    ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base"));
+    ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
+    ASSERT_TRUE(SimulateReboot());
 
     auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
     ASSERT_NE(init, nullptr);
@@ -905,6 +910,36 @@
                                   << ", hash: " << hashes_[name];
     }
 
+    AssertionResult MapUpdateSnapshots(const std::vector<std::string>& names = {"sys_b", "vnd_b",
+                                                                                "prd_b"}) {
+        for (const auto& name : names) {
+            auto res = MapUpdateSnapshot(name);
+            if (!res) {
+                return res;
+            }
+        }
+        return AssertionSuccess();
+    }
+
+    // Create fake install operations to grow the COW device size.
+    void AddOperation(PartitionUpdate* partition_update, uint64_t size_bytes = 0) {
+        auto e = partition_update->add_operations()->add_dst_extents();
+        e->set_start_block(0);
+        if (size_bytes == 0) {
+            size_bytes = GetSize(partition_update);
+        }
+        e->set_num_blocks(size_bytes / manifest_.block_size());
+    }
+
+    void AddOperationForPartitions(std::vector<PartitionUpdate*> partitions = {}) {
+        if (partitions.empty()) {
+            partitions = {sys_, vnd_, prd_};
+        }
+        for (auto* partition : partitions) {
+            AddOperation(partition);
+        }
+    }
+
     std::unique_ptr<TestPartitionOpener> opener_;
     DeltaArchiveManifest manifest_;
     std::unique_ptr<MetadataBuilder> src_;
@@ -932,12 +967,7 @@
     SetSize(vnd_, partition_size);
     SetSize(prd_, partition_size);
 
-    // Create fake install operations to grow the COW device size.
-    for (auto& partition : {sys_, vnd_, prd_}) {
-        auto e = partition->add_operations()->add_dst_extents();
-        e->set_start_block(0);
-        e->set_num_blocks(GetSize(partition) / manifest_.block_size());
-    }
+    AddOperationForPartitions();
 
     // Execute the update.
     ASSERT_TRUE(sm->BeginUpdate());
@@ -1064,9 +1094,7 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Check that target partitions can be mapped.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        EXPECT_TRUE(MapUpdateSnapshot(name));
-    }
+    EXPECT_TRUE(MapUpdateSnapshots());
 }
 
 // Test that the old partitions are not modified.
@@ -1075,12 +1103,7 @@
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
 
-    // Create fake install operations to grow the COW device size.
-    for (auto& partition : {sys_, vnd_, prd_}) {
-        auto e = partition->add_operations()->add_dst_extents();
-        e->set_start_block(0);
-        e->set_num_blocks(GetSize(partition) / manifest_.block_size());
-    }
+    AddOperationForPartitions();
 
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
@@ -1142,6 +1165,7 @@
     // Execute the first update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(MapUpdateSnapshots());
     ASSERT_TRUE(sm->FinishedSnapshotWrites());
 
     // Simulate shutting down the device.
@@ -1224,10 +1248,8 @@
     group_->set_size(kRetrofitGroupSize);
     for (auto* partition : {sys_, vnd_, prd_}) {
         SetSize(partition, 2_MiB);
-        auto* e = partition->add_operations()->add_dst_extents();
-        e->set_start_block(0);
-        e->set_num_blocks(2_MiB / manifest_.block_size());
     }
+    AddOperationForPartitions();
 
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
@@ -1270,13 +1292,12 @@
     }
 
     // Add operations for sys. The whole device is written.
-    auto e = sys_->add_operations()->add_dst_extents();
-    e->set_start_block(0);
-    e->set_num_blocks(GetSize(sys_) / manifest_.block_size());
+    AddOperation(sys_);
 
     // Execute the update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(MapUpdateSnapshots());
     ASSERT_TRUE(sm->FinishedSnapshotWrites());
 
     // Simulate shutting down the device.
@@ -1379,6 +1400,7 @@
     // Execute the first update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(MapUpdateSnapshots());
     ASSERT_TRUE(sm->FinishedSnapshotWrites());
 
     // Simulate shutting down the device.
@@ -1410,6 +1432,7 @@
     // Execute the first update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(MapUpdateSnapshots());
     ASSERT_TRUE(sm->FinishedSnapshotWrites());
 
     // Simulate shutting down the device.
@@ -1434,6 +1457,7 @@
     // Execute the first update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(MapUpdateSnapshots());
     ASSERT_TRUE(sm->FinishedSnapshotWrites());
 
     // Simulate shutting down the device.
@@ -1458,10 +1482,7 @@
 
     const auto block_size = manifest_.block_size();
     SetSize(sys_, partition_size);
-
-    auto e = sys_->add_operations()->add_dst_extents();
-    e->set_start_block(0);
-    e->set_num_blocks(data_size / block_size);
+    AddOperation(sys_, data_size);
 
     // Set hastree extents.
     sys_->mutable_hash_tree_data_extent()->set_start_block(0);
@@ -1480,7 +1501,8 @@
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
-    // Write some data to target partition.
+    // Map and write some data to target partition.
+    ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"}));
     ASSERT_TRUE(WriteSnapshotAndHash("sys_b", partition_size));
 
     // Finish update.
@@ -1500,6 +1522,69 @@
     ASSERT_TRUE(IsPartitionUnchanged("sys_b"));
 }
 
+// Test for overflow bit after update
+TEST_F(SnapshotUpdateTest, Overflow) {
+    const auto actual_write_size = GetSize(sys_);
+    const auto declared_write_size = actual_write_size - 1_MiB;
+
+    AddOperation(sys_, declared_write_size);
+
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    // Map and write some data to target partitions.
+    ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"}));
+    ASSERT_TRUE(WriteSnapshotAndHash("sys_b", actual_write_size));
+
+    std::vector<android::dm::DeviceMapper::TargetInfo> table;
+    ASSERT_TRUE(DeviceMapper::Instance().GetTableStatus("sys_b", &table));
+    ASSERT_EQ(1u, table.size());
+    EXPECT_TRUE(table[0].IsOverflowSnapshot());
+
+    ASSERT_FALSE(sm->FinishedSnapshotWrites())
+            << "FinishedSnapshotWrites should detect overflow of CoW device.";
+}
+
+TEST_F(SnapshotUpdateTest, WaitForMerge) {
+    AddOperationForPartitions();
+
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    // Write some data to target partitions.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(WriteSnapshotAndHash(name));
+    }
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    {
+        auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+        ASSERT_NE(nullptr, init);
+        ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+    }
+
+    auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
+    ASSERT_NE(nullptr, new_sm);
+
+    auto waiter = std::async(std::launch::async, [&new_sm] { return new_sm->WaitForMerge(); });
+    ASSERT_EQ(std::future_status::timeout, waiter.wait_for(1s))
+            << "WaitForMerge should block when not initiated";
+
+    auto merger =
+            std::async(std::launch::async, [&new_sm] { return new_sm->InitiateMergeAndWait(); });
+    // Small images, so should be merged pretty quickly.
+    ASSERT_EQ(std::future_status::ready, waiter.wait_for(3s)) << "WaitForMerge did not finish";
+    ASSERT_TRUE(waiter.get());
+    ASSERT_THAT(merger.get(), AnyOf(UpdateState::None, UpdateState::MergeCompleted));
+}
+
 class FlashAfterUpdateTest : public SnapshotUpdateTest,
                              public WithParamInterface<std::tuple<uint32_t, bool>> {
   public:
@@ -1524,7 +1609,7 @@
     // Execute the update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
+    ASSERT_TRUE(MapUpdateSnapshots());
     ASSERT_TRUE(sm->FinishedSnapshotWrites());
 
     // Simulate shutting down the device.
diff --git a/init/Android.bp b/init/Android.bp
index 9529617..42d0b33 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -71,6 +71,7 @@
         "libpropertyinfoserializer",
         "libpropertyinfoparser",
         "libsnapshot_init",
+        "lib_apex_manifest_proto_lite",
     ],
     shared_libs: [
         "libbacktrace",
diff --git a/init/Android.mk b/init/Android.mk
index ee2d89a..07b0f95 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -52,7 +52,6 @@
     first_stage_init.cpp \
     first_stage_main.cpp \
     first_stage_mount.cpp \
-    mount_namespace.cpp \
     reboot_utils.cpp \
     selabel.cpp \
     selinux.cpp \
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index c33e0de..648b3bb 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -27,6 +27,7 @@
 #include <android-base/properties.h>
 #include <android-base/result.h>
 #include <android-base/unique_fd.h>
+#include <apex_manifest.pb.h>
 
 #include "util.h"
 
@@ -90,6 +91,19 @@
     return {};
 }
 
+static Result<std::string> GetApexName(const std::string& apex_dir) {
+    const std::string manifest_path = apex_dir + "/apex_manifest.pb";
+    std::string content;
+    if (!android::base::ReadFileToString(manifest_path, &content)) {
+        return Error() << "Failed to read manifest file: " << manifest_path;
+    }
+    apex::proto::ApexManifest manifest;
+    if (!manifest.ParseFromString(content)) {
+        return Error() << "Can't parse manifest file: " << manifest_path;
+    }
+    return manifest.name();
+}
+
 static Result<void> ActivateFlattenedApexesFrom(const std::string& from_dir,
                                                 const std::string& to_dir) {
     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(from_dir.c_str()), closedir);
@@ -101,7 +115,12 @@
         if (entry->d_name[0] == '.') continue;
         if (entry->d_type == DT_DIR) {
             const std::string apex_path = from_dir + "/" + entry->d_name;
-            const std::string mount_path = to_dir + "/" + entry->d_name;
+            const auto apex_name = GetApexName(apex_path);
+            if (!apex_name) {
+                LOG(ERROR) << apex_path << " is not an APEX directory: " << apex_name.error();
+                continue;
+            }
+            const std::string mount_path = to_dir + "/" + (*apex_name);
             if (auto result = MountDir(apex_path, mount_path); !result) {
                 return result;
             }
@@ -129,26 +148,7 @@
             return false;
         }
     }
-    // Special casing for the ART APEX
-    constexpr const char kArtApexMountPath[] = "/apex/com.android.art";
-    static const std::vector<std::string> kArtApexDirNames = {"com.android.art.release",
-                                                              "com.android.art.debug"};
-    bool success = false;
-    for (const auto& name : kArtApexDirNames) {
-        std::string path = kApexTop + "/" + name;
-        if (access(path.c_str(), F_OK) == 0) {
-            if (auto result = MountDir(path, kArtApexMountPath); !result) {
-                LOG(ERROR) << result.error();
-                return false;
-            }
-            success = true;
-            break;
-        }
-    }
-    if (!success) {
-        PLOG(ERROR) << "Failed to bind mount the ART APEX to " << kArtApexMountPath;
-    }
-    return success;
+    return true;
 }
 
 static android::base::unique_fd bootstrap_ns_fd;
diff --git a/liblog/Android.bp b/liblog/Android.bp
index 656d4dd..de0c636 100644
--- a/liblog/Android.bp
+++ b/liblog/Android.bp
@@ -17,7 +17,6 @@
 liblog_sources = [
     "log_event_list.cpp",
     "log_event_write.cpp",
-    "logger_lock.cpp",
     "logger_name.cpp",
     "logger_read.cpp",
     "logger_write.cpp",
diff --git a/liblog/fake_log_device.cpp b/liblog/fake_log_device.cpp
index 4143fa6..fb3b9bc 100644
--- a/liblog/fake_log_device.cpp
+++ b/liblog/fake_log_device.cpp
@@ -48,21 +48,16 @@
 #define TRACE(...) ((void)0)
 #endif
 
-static int FakeAvailable(log_id_t);
-static int FakeOpen();
 static void FakeClose();
 static int FakeWrite(log_id_t log_id, struct timespec* ts, struct iovec* vec, size_t nr);
 
 struct android_log_transport_write fakeLoggerWrite = {
-    .name = "fake",
-    .logMask = 0,
-    .available = FakeAvailable,
-    .open = FakeOpen,
     .close = FakeClose,
     .write = FakeWrite,
 };
 
 typedef struct LogState {
+  bool initialized = false;
   /* global minimum priority */
   int global_min_priority;
 
@@ -76,19 +71,8 @@
   } tagSet[kTagSetSize];
 } LogState;
 
-/*
- * Locking.  Since we're emulating a device, we need to be prepared
- * to have multiple callers at the same time.  This lock is used
- * to both protect the fd list and to prevent LogStates from being
- * freed out from under a user.
- */
-std::mutex mutex;
-
 static LogState log_state;
-
-static int FakeAvailable(log_id_t) {
-  return 0;
-}
+static std::mutex fake_log_mutex;
 
 /*
  * Configure logging based on ANDROID_LOG_TAGS environment variable.  We
@@ -103,8 +87,8 @@
  * We also want to check ANDROID_PRINTF_LOG to determine how the output
  * will look.
  */
-int FakeOpen() {
-  std::lock_guard guard{mutex};
+void InitializeLogStateLocked() {
+  log_state.initialized = true;
 
   /* global min priority defaults to "info" level */
   log_state.global_min_priority = ANDROID_LOG_INFO;
@@ -129,7 +113,7 @@
       }
       if (i == kMaxTagLen) {
         TRACE("ERROR: env tag too long (%d chars max)\n", kMaxTagLen - 1);
-        return 0;
+        return;
       }
       tagName[i] = '\0';
 
@@ -180,7 +164,7 @@
         if (*tags != '\0' && !isspace(*tags)) {
           TRACE("ERROR: garbage in tag env; expected whitespace\n");
           TRACE("       env='%s'\n", tags);
-          return 0;
+          return;
         }
       }
 
@@ -224,7 +208,6 @@
   }
 
   log_state.output_format = format;
-  return 0;
 }
 
 /*
@@ -474,7 +457,11 @@
    * Also guarantees that only one thread is in showLog() at a given
    * time (if it matters).
    */
-  std::lock_guard guard{mutex};
+  auto lock = std::lock_guard{fake_log_mutex};
+
+  if (!log_state.initialized) {
+    InitializeLogStateLocked();
+  }
 
   if (log_id == LOG_ID_EVENTS || log_id == LOG_ID_STATS || log_id == LOG_ID_SECURITY) {
     TRACE("%s: ignoring binary log\n", android_log_id_to_name(log_id));
@@ -532,7 +519,7 @@
  * help debug HOST tools ...
  */
 static void FakeClose() {
-  std::lock_guard guard{mutex};
+  auto lock = std::lock_guard{fake_log_mutex};
 
   memset(&log_state, 0, sizeof(log_state));
 }
diff --git a/liblog/logd_writer.cpp b/liblog/logd_writer.cpp
index a22c3be..3c6eb69 100644
--- a/liblog/logd_writer.cpp
+++ b/liblog/logd_writer.cpp
@@ -30,97 +30,76 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <shared_mutex>
+
 #include <cutils/sockets.h>
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
 #include "log_portability.h"
 #include "logger.h"
+#include "rwlock.h"
 #include "uio.h"
 
-static int logdAvailable(log_id_t LogId);
-static int logdOpen();
-static void logdClose();
-static int logdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
+static int LogdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
+static void LogdClose();
 
 struct android_log_transport_write logdLoggerWrite = {
-    .name = "logd",
-    .logMask = 0,
-    .context.sock = -EBADF,
-    .available = logdAvailable,
-    .open = logdOpen,
-    .close = logdClose,
-    .write = logdWrite,
+    .close = LogdClose,
+    .write = LogdWrite,
 };
 
-/* log_init_lock assumed */
-static int logdOpen() {
-  int i, ret = 0;
+static int logd_socket;
+static RwLock logd_socket_lock;
 
-  i = atomic_load(&logdLoggerWrite.context.sock);
-  if (i < 0) {
-    int sock = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
-    if (sock < 0) {
-      ret = -errno;
-    } else {
-      struct sockaddr_un un;
-      memset(&un, 0, sizeof(struct sockaddr_un));
-      un.sun_family = AF_UNIX;
-      strcpy(un.sun_path, "/dev/socket/logdw");
-
-      if (TEMP_FAILURE_RETRY(connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) <
-          0) {
-        ret = -errno;
-        switch (ret) {
-          case -ENOTCONN:
-          case -ECONNREFUSED:
-          case -ENOENT:
-            i = atomic_exchange(&logdLoggerWrite.context.sock, ret);
-            [[fallthrough]];
-          default:
-            break;
-        }
-        close(sock);
-      } else {
-        ret = atomic_exchange(&logdLoggerWrite.context.sock, sock);
-        if ((ret >= 0) && (ret != sock)) {
-          close(ret);
-        }
-        ret = 0;
-      }
-    }
+static void OpenSocketLocked() {
+  logd_socket = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
+  if (logd_socket <= 0) {
+    return;
   }
 
-  return ret;
-}
+  sockaddr_un un = {};
+  un.sun_family = AF_UNIX;
+  strcpy(un.sun_path, "/dev/socket/logdw");
 
-static void __logdClose(int negative_errno) {
-  int sock = atomic_exchange(&logdLoggerWrite.context.sock, negative_errno);
-  if (sock >= 0) {
-    close(sock);
+  if (TEMP_FAILURE_RETRY(
+          connect(logd_socket, reinterpret_cast<sockaddr*>(&un), sizeof(sockaddr_un))) < 0) {
+    close(logd_socket);
+    logd_socket = 0;
   }
 }
 
-static void logdClose() {
-  __logdClose(-EBADF);
+static void OpenSocket() {
+  auto lock = std::unique_lock{logd_socket_lock};
+  if (logd_socket > 0) {
+    // Someone raced us and opened the socket already.
+    return;
+  }
+
+  OpenSocketLocked();
 }
 
-static int logdAvailable(log_id_t logId) {
-  if (logId >= LOG_ID_MAX || logId == LOG_ID_KERNEL) {
-    return -EINVAL;
+static void ResetSocket(int old_socket) {
+  auto lock = std::unique_lock{logd_socket_lock};
+  if (old_socket != logd_socket) {
+    // Someone raced us and reset the socket already.
+    return;
   }
-  if (atomic_load(&logdLoggerWrite.context.sock) < 0) {
-    if (access("/dev/socket/logdw", W_OK) == 0) {
-      return 0;
-    }
-    return -EBADF;
-  }
-  return 1;
+  close(logd_socket);
+  logd_socket = 0;
+  OpenSocketLocked();
 }
 
-static int logdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
+static void LogdClose() {
+  auto lock = std::unique_lock{logd_socket_lock};
+  if (logd_socket > 0) {
+    close(logd_socket);
+  }
+  logd_socket = 0;
+}
+
+static int LogdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
   ssize_t ret;
-  int sock;
   static const unsigned headerLength = 1;
   struct iovec newVec[nr + headerLength];
   android_log_header_t header;
@@ -128,15 +107,16 @@
   static atomic_int dropped;
   static atomic_int droppedSecurity;
 
-  sock = atomic_load(&logdLoggerWrite.context.sock);
-  if (sock < 0) switch (sock) {
-      case -ENOTCONN:
-      case -ECONNREFUSED:
-      case -ENOENT:
-        break;
-      default:
-        return -EBADF;
-    }
+  auto lock = std::shared_lock{logd_socket_lock};
+  if (logd_socket <= 0) {
+    lock.unlock();
+    OpenSocket();
+    lock.lock();
+  }
+
+  if (logd_socket <= 0) {
+    return -EBADF;
+  }
 
   /* logd, after initialization and priv drop */
   if (__android_log_uid() == AID_LOGD) {
@@ -155,41 +135,39 @@
   newVec[0].iov_base = (unsigned char*)&header;
   newVec[0].iov_len = sizeof(header);
 
-  if (sock >= 0) {
-    int32_t snapshot = atomic_exchange_explicit(&droppedSecurity, 0, memory_order_relaxed);
-    if (snapshot) {
-      android_log_event_int_t buffer;
+  int32_t snapshot = atomic_exchange_explicit(&droppedSecurity, 0, memory_order_relaxed);
+  if (snapshot) {
+    android_log_event_int_t buffer;
 
-      header.id = LOG_ID_SECURITY;
-      buffer.header.tag = LIBLOG_LOG_TAG;
-      buffer.payload.type = EVENT_TYPE_INT;
-      buffer.payload.data = snapshot;
+    header.id = LOG_ID_SECURITY;
+    buffer.header.tag = LIBLOG_LOG_TAG;
+    buffer.payload.type = EVENT_TYPE_INT;
+    buffer.payload.data = snapshot;
 
-      newVec[headerLength].iov_base = &buffer;
-      newVec[headerLength].iov_len = sizeof(buffer);
+    newVec[headerLength].iov_base = &buffer;
+    newVec[headerLength].iov_len = sizeof(buffer);
 
-      ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
-      if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
-        atomic_fetch_add_explicit(&droppedSecurity, snapshot, memory_order_relaxed);
-      }
+    ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, 2));
+    if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
+      atomic_fetch_add_explicit(&droppedSecurity, snapshot, memory_order_relaxed);
     }
-    snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
-    if (snapshot && __android_log_is_loggable_len(ANDROID_LOG_INFO, "liblog", strlen("liblog"),
-                                                  ANDROID_LOG_VERBOSE)) {
-      android_log_event_int_t buffer;
+  }
+  snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
+  if (snapshot && __android_log_is_loggable_len(ANDROID_LOG_INFO, "liblog", strlen("liblog"),
+                                                ANDROID_LOG_VERBOSE)) {
+    android_log_event_int_t buffer;
 
-      header.id = LOG_ID_EVENTS;
-      buffer.header.tag = LIBLOG_LOG_TAG;
-      buffer.payload.type = EVENT_TYPE_INT;
-      buffer.payload.data = snapshot;
+    header.id = LOG_ID_EVENTS;
+    buffer.header.tag = LIBLOG_LOG_TAG;
+    buffer.payload.type = EVENT_TYPE_INT;
+    buffer.payload.data = snapshot;
 
-      newVec[headerLength].iov_base = &buffer;
-      newVec[headerLength].iov_len = sizeof(buffer);
+    newVec[headerLength].iov_base = &buffer;
+    newVec[headerLength].iov_len = sizeof(buffer);
 
-      ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
-      if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
-        atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
-      }
+    ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, 2));
+    if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
+      atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
     }
   }
 
@@ -208,49 +186,26 @@
     }
   }
 
-  /*
-   * The write below could be lost, but will never block.
-   *
-   * ENOTCONN occurs if logd has died.
-   * ENOENT occurs if logd is not running and socket is missing.
-   * ECONNREFUSED occurs if we can not reconnect to logd.
-   * EAGAIN occurs if logd is overloaded.
-   */
-  if (sock < 0) {
-    ret = sock;
-  } else {
-    ret = TEMP_FAILURE_RETRY(writev(sock, newVec, i));
-    if (ret < 0) {
-      ret = -errno;
-    }
+  // The write below could be lost, but will never block.
+  // EAGAIN occurs if logd is overloaded, other errors indicate that something went wrong with
+  // the connection, so we reset it and try again.
+  ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, i));
+  if (ret < 0 && errno != EAGAIN) {
+    int old_socket = logd_socket;
+    lock.unlock();
+    ResetSocket(old_socket);
+    lock.lock();
+
+    ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, i));
   }
-  switch (ret) {
-    case -ENOTCONN:
-    case -ECONNREFUSED:
-    case -ENOENT:
-      if (__android_log_trylock()) {
-        return ret; /* in a signal handler? try again when less stressed */
-      }
-      __logdClose(ret);
-      ret = logdOpen();
-      __android_log_unlock();
 
-      if (ret < 0) {
-        return ret;
-      }
-
-      ret = TEMP_FAILURE_RETRY(writev(atomic_load(&logdLoggerWrite.context.sock), newVec, i));
-      if (ret < 0) {
-        ret = -errno;
-      }
-      [[fallthrough]];
-    default:
-      break;
+  if (ret < 0) {
+    ret = -errno;
   }
 
   if (ret > (ssize_t)sizeof(header)) {
     ret -= sizeof(header);
-  } else if (ret == -EAGAIN) {
+  } else if (ret < 0) {
     atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);
     if (logId == LOG_ID_SECURITY) {
       atomic_fetch_add_explicit(&droppedSecurity, 1, memory_order_relaxed);
diff --git a/liblog/logger.h b/liblog/logger.h
index 9d74d29..40d5fe5 100644
--- a/liblog/logger.h
+++ b/liblog/logger.h
@@ -26,20 +26,7 @@
 
 __BEGIN_DECLS
 
-/* Union, sock or fd of zero is not allowed unless static initialized */
-union android_log_context_union {
-  void* priv;
-  atomic_int sock;
-  atomic_int fd;
-};
-
 struct android_log_transport_write {
-  const char* name;                  /* human name to describe the transport */
-  unsigned logMask;                  /* mask cache of available() success */
-  union android_log_context_union context; /* Initialized by static allocation */
-
-  int (*available)(log_id_t logId); /* Does not cause resources to be taken */
-  int (*open)();   /* can be called multiple times, reusing current resources */
   void (*close)(); /* free up resources */
   /* write log to transport, returns number of bytes propagated, or -errno */
   int (*write)(log_id_t logId, struct timespec* ts, struct iovec* vec,
@@ -83,8 +70,4 @@
 }
 #endif
 
-void __android_log_lock();
-int __android_log_trylock();
-void __android_log_unlock();
-
 __END_DECLS
diff --git a/liblog/logger_lock.cpp b/liblog/logger_lock.cpp
deleted file mode 100644
index 4636b00..0000000
--- a/liblog/logger_lock.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2007-2016 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.
- */
-
-/*
- * Some OS specific dribs and drabs (locking etc).
- */
-
-#if !defined(_WIN32)
-#include <pthread.h>
-#endif
-
-#include "logger.h"
-
-#if !defined(_WIN32)
-static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER;
-#endif
-
-void __android_log_lock() {
-#if !defined(_WIN32)
-  /*
-   * If we trigger a signal handler in the middle of locked activity and the
-   * signal handler logs a message, we could get into a deadlock state.
-   */
-  pthread_mutex_lock(&log_init_lock);
-#endif
-}
-
-int __android_log_trylock() {
-#if !defined(_WIN32)
-  return pthread_mutex_trylock(&log_init_lock);
-#else
-  return 0;
-#endif
-}
-
-void __android_log_unlock() {
-#if !defined(_WIN32)
-  pthread_mutex_unlock(&log_init_lock);
-#endif
-}
diff --git a/liblog/logger_write.cpp b/liblog/logger_write.cpp
index 85475ec..d38b402 100644
--- a/liblog/logger_write.cpp
+++ b/liblog/logger_write.cpp
@@ -46,11 +46,8 @@
 android_log_transport_write* android_log_persist_write = nullptr;
 #endif
 
-static int __write_to_log_init(log_id_t, struct iovec* vec, size_t nr);
-static int (*write_to_log)(log_id_t, struct iovec* vec, size_t nr) = __write_to_log_init;
-
-static int check_log_uid_permissions() {
 #if defined(__ANDROID__)
+static int check_log_uid_permissions() {
   uid_t uid = __android_log_uid();
 
   /* Matches clientHasLogCredentials() in logd */
@@ -87,43 +84,14 @@
       }
     }
   }
-#endif
   return 0;
 }
-
-static void __android_log_cache_available(struct android_log_transport_write* node) {
-  uint32_t i;
-
-  if (node->logMask) {
-    return;
-  }
-
-  for (i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
-    if (i != LOG_ID_KERNEL && (i != LOG_ID_SECURITY || check_log_uid_permissions() == 0) &&
-        (*node->available)(static_cast<log_id_t>(i)) >= 0) {
-      node->logMask |= 1 << i;
-    }
-  }
-}
+#endif
 
 /*
  * Release any logger resources. A new log write will immediately re-acquire.
  */
 void __android_log_close() {
-  __android_log_lock();
-
-  write_to_log = __write_to_log_init;
-
-  /*
-   * Threads that are actively writing at this point are not held back
-   * by a lock and are at risk of dropping the messages with a return code
-   * -EBADF. Prefer to return error code than add the overhead of a lock to
-   * each log writing call to guarantee delivery. In addition, anyone
-   * calling this is doing so to release the logging resources and shut down,
-   * for them to do so with outstanding log requests in other threads is a
-   * disengenuous use of this function.
-   */
-
   if (android_log_write != nullptr) {
     android_log_write->close();
   }
@@ -132,44 +100,18 @@
     android_log_persist_write->close();
   }
 
-  __android_log_unlock();
 }
 
-static bool transport_initialize(android_log_transport_write* transport) {
-  if (transport == nullptr) {
-    return false;
-  }
-
-  __android_log_cache_available(transport);
-  if (!transport->logMask) {
-    return false;
-  }
-
-  // TODO: Do we actually need to call close() if open() fails?
-  if (transport->open() < 0) {
-    transport->close();
-    return false;
-  }
-
-  return true;
-}
-
-/* log_init_lock assumed */
-static int __write_to_log_initialize() {
-  if (!transport_initialize(android_log_write)) {
-    return -ENODEV;
-  }
-
-  transport_initialize(android_log_persist_write);
-
-  return 1;
-}
-
-static int __write_to_log_daemon(log_id_t log_id, struct iovec* vec, size_t nr) {
+static int write_to_log(log_id_t log_id, struct iovec* vec, size_t nr) {
   int ret, save_errno;
   struct timespec ts;
 
   save_errno = errno;
+
+  if (log_id == LOG_ID_KERNEL) {
+    return -EINVAL;
+  }
+
 #if defined(__ANDROID__)
   clock_gettime(android_log_clockid(), &ts);
 
@@ -215,9 +157,8 @@
 #endif
 
   ret = 0;
-  size_t i = 1 << log_id;
 
-  if (android_log_write != nullptr && (android_log_write->logMask & i)) {
+  if (android_log_write != nullptr) {
     ssize_t retval;
     retval = android_log_write->write(log_id, &ts, vec, nr);
     if (ret >= 0) {
@@ -225,7 +166,7 @@
     }
   }
 
-  if (android_log_persist_write != nullptr && (android_log_persist_write->logMask & i)) {
+  if (android_log_persist_write != nullptr) {
     android_log_persist_write->write(log_id, &ts, vec, nr);
   }
 
@@ -233,29 +174,6 @@
   return ret;
 }
 
-static int __write_to_log_init(log_id_t log_id, struct iovec* vec, size_t nr) {
-  int ret, save_errno = errno;
-
-  __android_log_lock();
-
-  if (write_to_log == __write_to_log_init) {
-    ret = __write_to_log_initialize();
-    if (ret < 0) {
-      __android_log_unlock();
-      errno = save_errno;
-      return ret;
-    }
-
-    write_to_log = __write_to_log_daemon;
-  }
-
-  __android_log_unlock();
-
-  ret = write_to_log(log_id, vec, nr);
-  errno = save_errno;
-  return ret;
-}
-
 int __android_log_write(int prio, const char* tag, const char* msg) {
   return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
 }
diff --git a/liblog/pmsg_writer.cpp b/liblog/pmsg_writer.cpp
index 54980d9..4f45780 100644
--- a/liblog/pmsg_writer.cpp
+++ b/liblog/pmsg_writer.cpp
@@ -25,68 +25,47 @@
 #include <sys/types.h>
 #include <time.h>
 
+#include <shared_mutex>
+
 #include <log/log_properties.h>
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
 #include "log_portability.h"
 #include "logger.h"
+#include "rwlock.h"
 #include "uio.h"
 
-static int pmsgOpen();
-static void pmsgClose();
-static int pmsgAvailable(log_id_t logId);
-static int pmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
+static void PmsgClose();
+static int PmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
 
 struct android_log_transport_write pmsgLoggerWrite = {
-    .name = "pmsg",
-    .logMask = 0,
-    .context.fd = -1,
-    .available = pmsgAvailable,
-    .open = pmsgOpen,
-    .close = pmsgClose,
-    .write = pmsgWrite,
+    .close = PmsgClose,
+    .write = PmsgWrite,
 };
 
-static int pmsgOpen() {
-  int fd = atomic_load(&pmsgLoggerWrite.context.fd);
-  if (fd < 0) {
-    int i;
+static int pmsg_fd;
+static RwLock pmsg_fd_lock;
 
-    fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
-    i = atomic_exchange(&pmsgLoggerWrite.context.fd, fd);
-    if ((i >= 0) && (i != fd)) {
-      close(i);
-    }
+static void PmsgOpen() {
+  auto lock = std::unique_lock{pmsg_fd_lock};
+  if (pmsg_fd > 0) {
+    // Someone raced us and opened the socket already.
+    return;
   }
 
-  return fd;
+  pmsg_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
 }
 
-static void pmsgClose() {
-  int fd = atomic_exchange(&pmsgLoggerWrite.context.fd, -1);
-  if (fd >= 0) {
-    close(fd);
+static void PmsgClose() {
+  auto lock = std::unique_lock{pmsg_fd_lock};
+  if (pmsg_fd > 0) {
+    close(pmsg_fd);
   }
+  pmsg_fd = 0;
 }
 
-static int pmsgAvailable(log_id_t logId) {
-  if (logId > LOG_ID_SECURITY) {
-    return -EINVAL;
-  }
-  if ((logId != LOG_ID_SECURITY) && (logId != LOG_ID_EVENTS) && !__android_log_is_debuggable()) {
-    return -EINVAL;
-  }
-  if (atomic_load(&pmsgLoggerWrite.context.fd) < 0) {
-    if (access("/dev/pmsg0", W_OK) == 0) {
-      return 0;
-    }
-    return -EBADF;
-  }
-  return 1;
-}
-
-static int pmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
+static int PmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
   static const unsigned headerLength = 2;
   struct iovec newVec[nr + headerLength];
   android_log_header_t header;
@@ -94,17 +73,31 @@
   size_t i, payloadSize;
   ssize_t ret;
 
-  if ((logId == LOG_ID_EVENTS) && !__android_log_is_debuggable()) {
-    if (vec[0].iov_len < 4) {
-      return -EINVAL;
+  if (!__android_log_is_debuggable()) {
+    if (logId != LOG_ID_EVENTS && logId != LOG_ID_SECURITY) {
+      return -1;
     }
 
-    if (SNET_EVENT_LOG_TAG != *static_cast<uint32_t*>(vec[0].iov_base)) {
-      return -EPERM;
+    if (logId == LOG_ID_EVENTS) {
+      if (vec[0].iov_len < 4) {
+        return -EINVAL;
+      }
+
+      if (SNET_EVENT_LOG_TAG != *static_cast<uint32_t*>(vec[0].iov_base)) {
+        return -EPERM;
+      }
     }
   }
 
-  if (atomic_load(&pmsgLoggerWrite.context.fd) < 0) {
+  auto lock = std::shared_lock{pmsg_fd_lock};
+
+  if (pmsg_fd <= 0) {
+    lock.unlock();
+    PmsgOpen();
+    lock.lock();
+  }
+
+  if (pmsg_fd <= 0) {
     return -EBADF;
   }
 
@@ -158,7 +151,7 @@
   }
   pmsgHeader.len += payloadSize;
 
-  ret = TEMP_FAILURE_RETRY(writev(atomic_load(&pmsgLoggerWrite.context.fd), newVec, i));
+  ret = TEMP_FAILURE_RETRY(writev(pmsg_fd, newVec, i));
   if (ret < 0) {
     ret = errno ? -errno : -ENOTCONN;
   }
@@ -193,7 +186,6 @@
 /* Write a buffer as filename references (tag = <basedir>:<basename>) */
 ssize_t __android_log_pmsg_file_write(log_id_t logId, char prio, const char* filename,
                                       const char* buf, size_t len) {
-  bool weOpened;
   size_t length, packet_len;
   const char* tag;
   char *cp, *slash;
@@ -233,7 +225,6 @@
   vec[1].iov_base = (unsigned char*)tag;
   vec[1].iov_len = length;
 
-  weOpened = false;
   for (ts.tv_nsec = 0, length = len; length; ts.tv_nsec += ANDROID_LOG_PMSG_FILE_SEQUENCE) {
     ssize_t ret;
     size_t transfer;
@@ -254,37 +245,15 @@
     vec[2].iov_base = (unsigned char*)buf;
     vec[2].iov_len = transfer;
 
-    if (atomic_load(&pmsgLoggerWrite.context.fd) < 0) {
-      if (!weOpened) { /* Impossible for weOpened = true here */
-        __android_log_lock();
-      }
-      weOpened = atomic_load(&pmsgLoggerWrite.context.fd) < 0;
-      if (!weOpened) {
-        __android_log_unlock();
-      } else if (pmsgOpen() < 0) {
-        __android_log_unlock();
-        free(cp);
-        return -EBADF;
-      }
-    }
-
-    ret = pmsgWrite(logId, &ts, vec, sizeof(vec) / sizeof(vec[0]));
+    ret = PmsgWrite(logId, &ts, vec, sizeof(vec) / sizeof(vec[0]));
 
     if (ret <= 0) {
-      if (weOpened) {
-        pmsgClose();
-        __android_log_unlock();
-      }
       free(cp);
       return ret ? ret : (len - length);
     }
     length -= transfer;
     buf += transfer;
   }
-  if (weOpened) {
-    pmsgClose();
-    __android_log_unlock();
-  }
   free(cp);
   return len;
 }
diff --git a/liblog/rwlock.h b/liblog/rwlock.h
new file mode 100644
index 0000000..00f1806
--- /dev/null
+++ b/liblog/rwlock.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <pthread.h>
+
+// As of the end of Dec 2019, std::shared_mutex is *not* simply a pthread_rwlock, but rather a
+// combination of std::mutex and std::condition variable, which is obviously less efficient.  This
+// immitates what std::shared_mutex should be doing and is compatible with std::shared_lock and
+// std::unique_lock.
+
+class RwLock {
+ public:
+  RwLock() {}
+  ~RwLock() {}
+
+  void lock() { pthread_rwlock_wrlock(&rwlock_); }
+  void unlock() { pthread_rwlock_unlock(&rwlock_); }
+
+  void lock_shared() { pthread_rwlock_rdlock(&rwlock_); }
+  void unlock_shared() { pthread_rwlock_unlock(&rwlock_); }
+
+ private:
+  pthread_rwlock_t rwlock_ = PTHREAD_RWLOCK_INITIALIZER;
+};
diff --git a/liblog/tests/Android.bp b/liblog/tests/Android.bp
index 99df4ca..f58c524 100644
--- a/liblog/tests/Android.bp
+++ b/liblog/tests/Android.bp
@@ -62,6 +62,7 @@
         "log_time_test.cpp",
         "log_wrap_test.cpp",
         "logprint_test.cpp",
+        "rwlock_test.cpp",
     ],
     shared_libs: [
         "libcutils",
diff --git a/liblog/tests/rwlock_test.cpp b/liblog/tests/rwlock_test.cpp
new file mode 100644
index 0000000..617d5c4
--- /dev/null
+++ b/liblog/tests/rwlock_test.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 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 "../rwlock.h"
+
+#include <chrono>
+#include <shared_mutex>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+using namespace std::literals;
+
+TEST(rwlock, reader_then_reader_lock) {
+  RwLock lock;
+
+  bool thread_ran = false;
+  auto read_guard = std::shared_lock{lock};
+
+  auto reader_thread = std::thread([&] {
+    auto read_guard = std::shared_lock{lock};
+    thread_ran = true;
+  });
+
+  auto end_time = std::chrono::steady_clock::now() + 1s;
+
+  while (std::chrono::steady_clock::now() < end_time) {
+    if (thread_ran) {
+      break;
+    }
+  }
+
+  EXPECT_EQ(true, thread_ran);
+
+  // Unlock the lock in case something went wrong, to ensure that we can still join() the thread.
+  read_guard.unlock();
+  reader_thread.join();
+}
+
+template <template <typename> typename L1, template <typename> typename L2>
+void TestBlockingLocks() {
+  RwLock lock;
+
+  bool thread_ran = false;
+  auto read_guard = L1{lock};
+
+  auto reader_thread = std::thread([&] {
+    auto read_guard = L2{lock};
+    thread_ran = true;
+  });
+
+  auto end_time = std::chrono::steady_clock::now() + 1s;
+
+  while (std::chrono::steady_clock::now() < end_time) {
+    if (thread_ran) {
+      break;
+    }
+  }
+
+  EXPECT_EQ(false, thread_ran);
+
+  read_guard.unlock();
+  reader_thread.join();
+
+  EXPECT_EQ(true, thread_ran);
+}
+
+TEST(rwlock, reader_then_writer_lock) {
+  TestBlockingLocks<std::shared_lock, std::unique_lock>();
+}
+
+TEST(rwlock, writer_then_reader_lock) {
+  TestBlockingLocks<std::unique_lock, std::shared_lock>();
+}
+
+TEST(rwlock, writer_then_writer_lock) {
+  TestBlockingLocks<std::unique_lock, std::unique_lock>();
+}
diff --git a/libstats/Android.bp b/libstats/Android.bp
index f5ee1da..0440087 100644
--- a/libstats/Android.bp
+++ b/libstats/Android.bp
@@ -22,6 +22,7 @@
     srcs: [
         "stats_event_list.c",
         "statsd_writer.c",
+        "stats_event.c",
     ],
     host_supported: true,
     cflags: [
diff --git a/libstats/include/stats_event.h b/libstats/include/stats_event.h
new file mode 100644
index 0000000..89cb420
--- /dev/null
+++ b/libstats/include/stats_event.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_STATS_LOG_STATS_EVENT_H
+#define ANDROID_STATS_LOG_STATS_EVENT_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/*
+ * Functionality to build and store the buffer sent over the statsd socket.
+ * This code defines and encapsulates the socket protocol.
+ *
+ * Usage:
+ *      struct stats_event* event = stats_event_obtain();
+ *
+ *      stats_event_set_atom_id(event, atomId);
+ *      stats_event_write_int32(event, 24);
+ *      stats_event_add_bool_annotation(event, 1, true); // annotations apply to the previous field
+ *      stats_event_add_int32_annotation(event, 2, 128);
+ *      stats_event_write_float(event, 2.0);
+ *
+ *      stats_event_build(event);
+ *      stats_event_write(event);
+ *      stats_event_release(event);
+ *
+ * Notes:
+ *    (a) write_<type>() and add_<type>_annotation() should be called in the order that fields
+ *        and annotations are defined in the atom.
+ *    (b) set_atom_id() can be called anytime before stats_event_write().
+ *    (c) add_<type>_annotation() calls apply to the previous field.
+ *    (d) If errors occur, stats_event_write() will write a bitmask of the errors to the socket.
+ *    (e) All strings should be encoded using UTF8.
+ */
+
+/* ERRORS */
+#define ERROR_NO_TIMESTAMP 0x1
+#define ERROR_NO_ATOM_ID 0x2
+#define ERROR_OVERFLOW 0x4
+#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
+#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
+#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
+#define ERROR_INVALID_ANNOTATION_ID 0x40
+#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
+#define ERROR_TOO_MANY_ANNOTATIONS 0x100
+#define ERROR_TOO_MANY_FIELDS 0x200
+#define ERROR_INVALID_VALUE_TYPE 0x400
+#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
+
+/* TYPE IDS */
+#define INT32_TYPE 0x00
+#define INT64_TYPE 0x01
+#define STRING_TYPE 0x02
+#define LIST_TYPE 0x03
+#define FLOAT_TYPE 0x04
+#define BOOL_TYPE 0x05
+#define BYTE_ARRAY_TYPE 0x06
+#define OBJECT_TYPE 0x07
+#define KEY_VALUE_PAIRS_TYPE 0x08
+#define ATTRIBUTION_CHAIN_TYPE 0x09
+#define ERROR_TYPE 0x0F
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct stats_event;
+
+/* SYSTEM API */
+struct stats_event* stats_event_obtain();
+// The build function can be called multiple times without error. If the event
+// has been built before, this function is a no-op.
+void stats_event_build(struct stats_event* event);
+void stats_event_write(struct stats_event* event);
+void stats_event_release(struct stats_event* event);
+
+void stats_event_set_atom_id(struct stats_event* event, uint32_t atomId);
+
+void stats_event_write_int32(struct stats_event* event, int32_t value);
+void stats_event_write_int64(struct stats_event* event, int64_t value);
+void stats_event_write_float(struct stats_event* event, float value);
+void stats_event_write_bool(struct stats_event* event, bool value);
+
+void stats_event_write_byte_array(struct stats_event* event, uint8_t* buf, size_t numBytes);
+
+// Buf must be null-terminated.
+void stats_event_write_string8(struct stats_event* event, const char* buf);
+
+// Tags must be null-terminated.
+void stats_event_write_attribution_chain(struct stats_event* event, uint32_t* uids,
+                                         const char** tags, uint8_t numNodes);
+
+/* key_value_pair struct can be constructed as follows:
+ *    struct key_value_pair pair = {.key = key, .valueType = STRING_TYPE,
+ *                                  .stringValue = buf};
+ */
+struct key_value_pair {
+    int32_t key;
+    uint8_t valueType;  // expected to be INT32_TYPE, INT64_TYPE, FLOAT_TYPE, or STRING_TYPE
+    union {
+        int32_t int32Value;
+        int64_t int64Value;
+        float floatValue;
+        const char* stringValue;  // must be null terminated
+    };
+};
+
+void stats_event_write_key_value_pairs(struct stats_event* event, struct key_value_pair* pairs,
+                                       uint8_t numPairs);
+
+void stats_event_add_bool_annotation(struct stats_event* event, uint8_t annotationId, bool value);
+void stats_event_add_int32_annotation(struct stats_event* event, uint8_t annotationId,
+                                      int32_t value);
+
+uint32_t stats_event_get_atom_id(struct stats_event* event);
+uint8_t* stats_event_get_buffer(struct stats_event* event, size_t* size);
+uint32_t stats_event_get_errors(struct stats_event* event);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ANDROID_STATS_LOG_STATS_EVENT_H
diff --git a/libstats/stats_event.c b/libstats/stats_event.c
new file mode 100644
index 0000000..35081dc
--- /dev/null
+++ b/libstats/stats_event.c
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2019 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 "include/stats_event.h"
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "include/stats_event_list.h"
+
+#define STATS_EVENT_TAG 1937006964
+#define LOGGER_ENTRY_MAX_PAYLOAD 4068
+// Max payload size is 4 bytes less as 4 bytes are reserved for stats_eventTag.
+// See android_util_Stats_Log.cpp
+#define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - 4)
+
+/* POSITIONS */
+#define POS_NUM_ELEMENTS 1
+#define POS_TIMESTAMP (POS_NUM_ELEMENTS + sizeof(uint8_t))
+#define POS_ATOM_ID (POS_TIMESTAMP + sizeof(uint8_t) + sizeof(uint64_t))
+#define POS_FIRST_FIELD (POS_ATOM_ID + sizeof(uint8_t) + sizeof(uint32_t))
+
+/* LIMITS */
+#define MAX_ANNOTATION_COUNT 15
+#define MAX_BYTE_VALUE 127  // parsing side requires that lengths fit in 7 bits
+
+// The stats_event struct holds the serialized encoding of an event
+// within a buf. Also includes other required fields.
+struct stats_event {
+    uint8_t buf[MAX_EVENT_PAYLOAD];
+    size_t lastFieldPos;  // location of last field within the buf
+    size_t size;          // number of valid bytes within buffer
+    uint32_t numElements;
+    uint32_t atomId;
+    uint32_t errors;
+    uint32_t tag;
+    bool built;
+};
+
+static int64_t get_elapsed_realtime_ns() {
+    struct timespec t;
+    t.tv_sec = t.tv_nsec = 0;
+    clock_gettime(CLOCK_BOOTTIME, &t);
+    return (int64_t)t.tv_sec * 1000000000LL + t.tv_nsec;
+}
+
+struct stats_event* stats_event_obtain() {
+    struct stats_event* event = malloc(sizeof(struct stats_event));
+
+    memset(event->buf, 0, MAX_EVENT_PAYLOAD);
+    event->buf[0] = OBJECT_TYPE;
+    event->atomId = 0;
+    event->errors = 0;
+    event->tag = STATS_EVENT_TAG;
+    event->built = false;
+
+    // place the timestamp
+    uint64_t timestampNs = get_elapsed_realtime_ns();
+    event->buf[POS_TIMESTAMP] = INT64_TYPE;
+    memcpy(&event->buf[POS_TIMESTAMP + sizeof(uint8_t)], &timestampNs, sizeof(timestampNs));
+
+    event->numElements = 1;
+    event->lastFieldPos = 0;  // 0 since we haven't written a field yet
+    event->size = POS_FIRST_FIELD;
+
+    return event;
+}
+
+void stats_event_release(struct stats_event* event) {
+    free(event);
+}
+
+void stats_event_set_atom_id(struct stats_event* event, uint32_t atomId) {
+    event->atomId = atomId;
+    event->buf[POS_ATOM_ID] = INT32_TYPE;
+    memcpy(&event->buf[POS_ATOM_ID + sizeof(uint8_t)], &atomId, sizeof(atomId));
+    event->numElements++;
+}
+
+// Side-effect: modifies event->errors if the buffer would overflow
+static bool overflows(struct stats_event* event, size_t size) {
+    if (event->size + size > MAX_EVENT_PAYLOAD) {
+        event->errors |= ERROR_OVERFLOW;
+        return true;
+    }
+    return false;
+}
+
+// Side-effect: all append functions increment event->size if there is
+// sufficient space within the buffer to place the value
+static void append_byte(struct stats_event* event, uint8_t value) {
+    if (!overflows(event, sizeof(value))) {
+        event->buf[event->size] = value;
+        event->size += sizeof(value);
+    }
+}
+
+static void append_bool(struct stats_event* event, bool value) {
+    append_byte(event, (uint8_t)value);
+}
+
+static void append_int32(struct stats_event* event, int32_t value) {
+    if (!overflows(event, sizeof(value))) {
+        memcpy(&event->buf[event->size], &value, sizeof(value));
+        event->size += sizeof(value);
+    }
+}
+
+static void append_int64(struct stats_event* event, int64_t value) {
+    if (!overflows(event, sizeof(value))) {
+        memcpy(&event->buf[event->size], &value, sizeof(value));
+        event->size += sizeof(value);
+    }
+}
+
+static void append_float(struct stats_event* event, float value) {
+    if (!overflows(event, sizeof(value))) {
+        memcpy(&event->buf[event->size], &value, sizeof(value));
+        event->size += sizeof(float);
+    }
+}
+
+static void append_byte_array(struct stats_event* event, uint8_t* buf, size_t size) {
+    if (!overflows(event, size)) {
+        memcpy(&event->buf[event->size], buf, size);
+        event->size += size;
+    }
+}
+
+// Side-effect: modifies event->errors if buf is not properly null-terminated
+static void append_string(struct stats_event* event, const char* buf) {
+    size_t size = strnlen(buf, MAX_EVENT_PAYLOAD);
+    if (event->errors) {
+        event->errors |= ERROR_STRING_NOT_NULL_TERMINATED;
+        return;
+    }
+
+    append_int32(event, size);
+    append_byte_array(event, (uint8_t*)buf, size);
+}
+
+static void start_field(struct stats_event* event, uint8_t typeId) {
+    event->lastFieldPos = event->size;
+    append_byte(event, typeId);
+    event->numElements++;
+}
+
+void stats_event_write_int32(struct stats_event* event, int32_t value) {
+    if (event->errors) return;
+
+    start_field(event, INT32_TYPE);
+    append_int32(event, value);
+}
+
+void stats_event_write_int64(struct stats_event* event, int64_t value) {
+    if (event->errors) return;
+
+    start_field(event, INT64_TYPE);
+    append_int64(event, value);
+}
+
+void stats_event_write_float(struct stats_event* event, float value) {
+    if (event->errors) return;
+
+    start_field(event, FLOAT_TYPE);
+    append_float(event, value);
+}
+
+void stats_event_write_bool(struct stats_event* event, bool value) {
+    if (event->errors) return;
+
+    start_field(event, BOOL_TYPE);
+    append_bool(event, value);
+}
+
+void stats_event_write_byte_array(struct stats_event* event, uint8_t* buf, size_t numBytes) {
+    if (event->errors) return;
+
+    start_field(event, BYTE_ARRAY_TYPE);
+    append_int32(event, numBytes);
+    append_byte_array(event, buf, numBytes);
+}
+
+// Buf is assumed to be encoded using UTF8
+void stats_event_write_string8(struct stats_event* event, const char* buf) {
+    if (event->errors) return;
+
+    start_field(event, STRING_TYPE);
+    append_string(event, buf);
+}
+
+// Tags are assumed to be encoded using UTF8
+void stats_event_write_attribution_chain(struct stats_event* event, uint32_t* uids,
+                                         const char** tags, uint8_t numNodes) {
+    if (numNodes > MAX_BYTE_VALUE) event->errors |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG;
+    if (event->errors) return;
+
+    start_field(event, ATTRIBUTION_CHAIN_TYPE);
+    append_byte(event, numNodes);
+
+    for (uint8_t i = 0; i < numNodes; i++) {
+        append_int32(event, uids[i]);
+        append_string(event, tags[i]);
+    }
+}
+
+void stats_event_write_key_value_pairs(struct stats_event* event, struct key_value_pair* pairs,
+                                       uint8_t numPairs) {
+    if (numPairs > MAX_BYTE_VALUE) event->errors |= ERROR_TOO_MANY_KEY_VALUE_PAIRS;
+    if (event->errors) return;
+
+    start_field(event, KEY_VALUE_PAIRS_TYPE);
+    append_byte(event, numPairs);
+
+    for (uint8_t i = 0; i < numPairs; i++) {
+        append_int32(event, pairs[i].key);
+        append_byte(event, pairs[i].valueType);
+        switch (pairs[i].valueType) {
+            case INT32_TYPE:
+                append_int32(event, pairs[i].int32Value);
+                break;
+            case INT64_TYPE:
+                append_int64(event, pairs[i].int64Value);
+                break;
+            case FLOAT_TYPE:
+                append_float(event, pairs[i].floatValue);
+                break;
+            case STRING_TYPE:
+                append_string(event, pairs[i].stringValue);
+                break;
+            default:
+                event->errors |= ERROR_INVALID_VALUE_TYPE;
+                return;
+        }
+    }
+}
+
+// Side-effect: modifies event->errors if field has too many annotations
+static void increment_annotation_count(struct stats_event* event) {
+    uint8_t fieldType = event->buf[event->lastFieldPos] & 0x0F;
+    uint32_t oldAnnotationCount = (event->buf[event->lastFieldPos] & 0xF0) >> 4;
+    uint32_t newAnnotationCount = oldAnnotationCount + 1;
+
+    if (newAnnotationCount > MAX_ANNOTATION_COUNT) {
+        event->errors |= ERROR_TOO_MANY_ANNOTATIONS;
+        return;
+    }
+
+    event->buf[event->lastFieldPos] = (((uint8_t)newAnnotationCount << 4) & 0xF0) | fieldType;
+}
+
+void stats_event_add_bool_annotation(struct stats_event* event, uint8_t annotationId, bool value) {
+    if (event->lastFieldPos == 0) event->errors |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+    if (annotationId > MAX_BYTE_VALUE) event->errors |= ERROR_ANNOTATION_ID_TOO_LARGE;
+    if (event->errors) return;
+
+    append_byte(event, annotationId);
+    append_byte(event, BOOL_TYPE);
+    append_bool(event, value);
+    increment_annotation_count(event);
+}
+
+void stats_event_add_int32_annotation(struct stats_event* event, uint8_t annotationId,
+                                      int32_t value) {
+    if (event->lastFieldPos == 0) event->errors |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+    if (annotationId > MAX_BYTE_VALUE) event->errors |= ERROR_ANNOTATION_ID_TOO_LARGE;
+    if (event->errors) return;
+
+    append_byte(event, annotationId);
+    append_byte(event, INT32_TYPE);
+    append_int32(event, value);
+    increment_annotation_count(event);
+}
+
+uint32_t stats_event_get_atom_id(struct stats_event* event) {
+    return event->atomId;
+}
+
+uint8_t* stats_event_get_buffer(struct stats_event* event, size_t* size) {
+    if (size) *size = event->size;
+    return event->buf;
+}
+
+uint32_t stats_event_get_errors(struct stats_event* event) {
+    return event->errors;
+}
+
+void stats_event_build(struct stats_event* event) {
+    if (event->built) return;
+
+    if (event->atomId == 0) event->errors |= ERROR_NO_ATOM_ID;
+
+    if (event->numElements > MAX_BYTE_VALUE) {
+        event->errors |= ERROR_TOO_MANY_FIELDS;
+    } else {
+        event->buf[POS_NUM_ELEMENTS] = event->numElements;
+    }
+
+    // If there are errors, rewrite buffer.
+    if (event->errors) {
+        event->buf[POS_NUM_ELEMENTS] = 3;
+        event->buf[POS_FIRST_FIELD] = ERROR_TYPE;
+        memcpy(&event->buf[POS_FIRST_FIELD + sizeof(uint8_t)], &event->errors,
+               sizeof(event->errors));
+        event->size = POS_FIRST_FIELD + sizeof(uint8_t) + sizeof(uint32_t);
+    }
+
+    event->built = true;
+}
+
+void stats_event_write(struct stats_event* event) {
+    stats_event_build(event);
+
+    // Prepare iovecs for write to statsd.
+    struct iovec vecs[2];
+    vecs[0].iov_base = &event->tag;
+    vecs[0].iov_len = sizeof(event->tag);
+    vecs[1].iov_base = &event->buf;
+    vecs[1].iov_len = event->size;
+    write_to_statsd(vecs, 2);
+}
diff --git a/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp
index 8fe7854..2351afa 100644
--- a/libsysutils/src/NetlinkEvent.cpp
+++ b/libsysutils/src/NetlinkEvent.cpp
@@ -24,7 +24,6 @@
 #include <linux/if_link.h>
 #include <linux/netfilter/nfnetlink.h>
 #include <linux/netfilter/nfnetlink_log.h>
-#include <linux/netfilter_ipv4/ipt_ULOG.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
 #include <net/if.h>
@@ -39,6 +38,23 @@
 const int LOCAL_QLOG_NL_EVENT = 112;
 const int LOCAL_NFLOG_PACKET = NFNL_SUBSYS_ULOG << 8 | NFULNL_MSG_PACKET;
 
+/* From deprecated ipt_ULOG.h to parse QLOG_NL_EVENT. */
+#define ULOG_MAC_LEN 80
+#define ULOG_PREFIX_LEN 32
+typedef struct ulog_packet_msg {
+    unsigned long mark;
+    long timestamp_sec;
+    long timestamp_usec;
+    unsigned int hook;
+    char indev_name[IFNAMSIZ];
+    char outdev_name[IFNAMSIZ];
+    size_t data_len;
+    char prefix[ULOG_PREFIX_LEN];
+    unsigned char mac_len;
+    unsigned char mac[ULOG_MAC_LEN];
+    unsigned char payload[0];
+} ulog_packet_msg_t;
+
 #include <android-base/parseint.h>
 #include <log/log.h>
 #include <sysutils/NetlinkEvent.h>
diff --git a/libziparchive/Android.bp b/libziparchive/Android.bp
index e3bb2ab..1bbffaf 100644
--- a/libziparchive/Android.bp
+++ b/libziparchive/Android.bp
@@ -177,7 +177,7 @@
 cc_binary {
     name: "ziptool",
     defaults: ["libziparchive_flags"],
-    srcs: ["unzip.cpp"],
+    srcs: ["ziptool.cpp"],
     shared_libs: [
         "libbase",
         "libziparchive",
@@ -198,3 +198,15 @@
     host_supported: true,
     corpus: ["testdata/*"],
 }
+
+sh_test {
+    name: "ziptool-tests",
+    src: "run-ziptool-tests-on-android.sh",
+    filename: "run-ziptool-tests-on-android.sh",
+    test_suites: ["general-tests"],
+    host_supported: true,
+    device_supported: false,
+    test_config: "ziptool-tests.xml",
+    data: ["cli-tests/**/*"],
+    target_required: ["cli-test", "ziptool"],
+}
diff --git a/libziparchive/cli-tests/files/example.zip b/libziparchive/cli-tests/files/example.zip
new file mode 100644
index 0000000..c3292e9
--- /dev/null
+++ b/libziparchive/cli-tests/files/example.zip
Binary files differ
diff --git a/libziparchive/cli-tests/unzip.test b/libziparchive/cli-tests/unzip.test
new file mode 100755
index 0000000..6e5cbf2
--- /dev/null
+++ b/libziparchive/cli-tests/unzip.test
@@ -0,0 +1,148 @@
+# unzip tests.
+
+# Note: since "master key", Android uses libziparchive for all zip file
+# handling, and that scans the whole central directory immediately. Not only
+# lookups by name but also iteration is implemented using the resulting hash
+# table, meaning that any test that makes assumptions about iteration order
+# will fail on Android.
+
+name: unzip -l
+command: unzip -l $FILES/example.zip d1/d2/x.txt
+after: [ ! -f d1/d2/x.txt ]
+expected-stdout:
+	Archive:  $FILES/example.zip
+	  Length      Date    Time    Name
+	---------  ---------- -----   ----
+	     1024  2017-06-04 08:45   d1/d2/x.txt
+	---------                     -------
+	     1024                     1 file
+---
+
+name: unzip -lq
+command: unzip -lq $FILES/example.zip d1/d2/x.txt
+after: [ ! -f d1/d2/x.txt ]
+expected-stdout:
+	  Length      Date    Time    Name
+	---------  ---------- -----   ----
+	     1024  2017-06-04 08:45   d1/d2/x.txt
+	---------                     -------
+	     1024                     1 file
+---
+
+name: unzip -lv
+command: unzip -lv $FILES/example.zip d1/d2/x.txt
+after: [ ! -f d1/d2/file ]
+expected-stdout:
+	Archive:  $FILES/example.zip
+	 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
+	--------  ------  ------- ---- ---------- ----- --------  ----
+	    1024  Defl:N       11  99% 2017-06-04 08:45 48d7f063  d1/d2/x.txt
+	--------          -------  ---                            -------
+	    1024               11  99%                            1 file
+---
+
+name: unzip -v
+command: unzip -v $FILES/example.zip d1/d2/x.txt
+after: [ ! -f d1/d2/file ]
+expected-stdout:
+	Archive:  $FILES/example.zip
+	 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
+	--------  ------  ------- ---- ---------- ----- --------  ----
+	    1024  Defl:N       11  99% 2017-06-04 08:45 48d7f063  d1/d2/x.txt
+	--------          -------  ---                            -------
+	    1024               11  99%                            1 file
+---
+
+name: unzip one file
+command: unzip -q $FILES/example.zip d1/d2/a.txt && cat d1/d2/a.txt
+after: [ ! -f d1/d2/b.txt ]
+expected-stdout:
+	a
+---
+
+name: unzip all files
+command: unzip -q $FILES/example.zip
+after: [ -f d1/d2/a.txt ]
+after: [ -f d1/d2/b.txt ]
+after: [ -f d1/d2/c.txt ]
+after: [ -f d1/d2/empty.txt ]
+after: [ -f d1/d2/x.txt ]
+after: [ -d d1/d2/dir ]
+expected-stdout:
+---
+
+name: unzip -o
+before: mkdir -p d1/d2
+before: echo b > d1/d2/a.txt
+command: unzip -q -o $FILES/example.zip d1/d2/a.txt && cat d1/d2/a.txt
+expected-stdout:
+	a
+---
+
+name: unzip -n
+before: mkdir -p d1/d2
+before: echo b > d1/d2/a.txt
+command: unzip -q -n $FILES/example.zip d1/d2/a.txt && cat d1/d2/a.txt
+expected-stdout:
+	b
+---
+
+# The reference implementation will create *one* level of missing directories,
+# so this succeeds.
+name: unzip -d shallow non-existent
+command: unzip -q -d will-be-created $FILES/example.zip d1/d2/a.txt
+after: [ -d will-be-created ]
+after: [ -f will-be-created/d1/d2/a.txt ]
+---
+
+# The reference implementation will *only* create one level of missing
+# directories, so this fails.
+name: unzip -d deep non-existent
+command: unzip -q -d oh-no/will-not-be-created $FILES/example.zip d1/d2/a.txt 2> stderr ; echo $? > status
+after: [ ! -d oh-no ]
+after: [ ! -d oh-no/will-not-be-created ]
+after: [ ! -f oh-no/will-not-be-created/d1/d2/a.txt ]
+after: grep -q "oh-no/will-not-be-created" stderr
+after: grep -q "No such file or directory" stderr
+# The reference implementation has *lots* of non-zero exit values, but we stick to 0 and 1.
+after: [ $(cat status) -gt 0 ]
+---
+
+name: unzip -d exists
+before: mkdir dir
+command: unzip -q -d dir $FILES/example.zip d1/d2/a.txt && cat dir/d1/d2/a.txt
+after: [ ! -f d1/d2/a.txt ]
+expected-stdout:
+	a
+---
+
+name: unzip -p
+command: unzip -p $FILES/example.zip d1/d2/a.txt
+after: [ ! -f d1/d2/a.txt ]
+expected-stdout:
+	a
+---
+
+name: unzip -x FILE...
+# Note: the RI ignores -x DIR for some reason, but it's not obvious we should.
+command: unzip -q $FILES/example.zip -x d1/d2/a.txt d1/d2/b.txt d1/d2/empty.txt d1/d2/x.txt && cat d1/d2/c.txt
+after: [ ! -f d1/d2/a.txt ]
+after: [ ! -f d1/d2/b.txt ]
+after: [ ! -f d1/d2/empty.txt ]
+after: [ ! -f d1/d2/x.txt ]
+after: [ -d d1/d2/dir ]
+expected-stdout:
+	ccc
+---
+
+name: unzip FILE -x FILE...
+command: unzip -q $FILES/example.zip d1/d2/a.txt d1/d2/b.txt -x d1/d2/a.txt && cat d1/d2/b.txt
+after: [ ! -f d1/d2/a.txt ]
+after: [ -f d1/d2/b.txt ]
+after: [ ! -f d1/d2/c.txt ]
+after: [ ! -f d1/d2/empty.txt ]
+after: [ ! -f d1/d2/x.txt ]
+after: [ ! -d d1/d2/dir ]
+expected-stdout:
+	bb
+---
diff --git a/libziparchive/cli-tests/zipinfo.test b/libziparchive/cli-tests/zipinfo.test
new file mode 100755
index 0000000..d5bce1c
--- /dev/null
+++ b/libziparchive/cli-tests/zipinfo.test
@@ -0,0 +1,53 @@
+# zipinfo tests.
+
+# Note: since "master key", Android uses libziparchive for all zip file
+# handling, and that scans the whole central directory immediately. Not only
+# lookups by name but also iteration is implemented using the resulting hash
+# table, meaning that any test that makes assumptions about iteration order
+# will fail on Android.
+
+name: zipinfo -1
+command: zipinfo -1 $FILES/example.zip | sort
+expected-stdout:
+	d1/
+	d1/d2/a.txt
+	d1/d2/b.txt
+	d1/d2/c.txt
+	d1/d2/dir/
+	d1/d2/empty.txt
+	d1/d2/x.txt
+---
+
+name: zipinfo header
+command: zipinfo $FILES/example.zip | head -2
+expected-stdout:
+	Archive:  $FILES/example.zip
+	Zip file size: 1082 bytes, number of entries: 7
+---
+
+name: zipinfo footer
+command: zipinfo $FILES/example.zip | tail -1
+expected-stdout:
+	7 files, 1033 bytes uncompressed, 20 bytes compressed:  98.1%
+---
+
+name: zipinfo directory
+# The RI doesn't use ISO dates.
+command: zipinfo $FILES/example.zip d1/ | sed s/17-Jun-/2017-06-/
+expected-stdout:
+	drwxr-x---  3.0 unx        0 bx stor 2017-06-04 08:40 d1/
+---
+
+name: zipinfo stored
+# The RI doesn't use ISO dates.
+command: zipinfo $FILES/example.zip d1/d2/empty.txt | sed s/17-Jun-/2017-06-/
+expected-stdout:
+	-rw-r-----  3.0 unx        0 bx stor 2017-06-04 08:43 d1/d2/empty.txt
+---
+
+name: zipinfo deflated
+# The RI doesn't use ISO dates.
+command: zipinfo $FILES/example.zip d1/d2/x.txt | sed s/17-Jun-/2017-06-/
+expected-stdout:
+	-rw-r-----  3.0 unx     1024 tx defN 2017-06-04 08:45 d1/d2/x.txt
+---
diff --git a/libziparchive/run-ziptool-tests-on-android.sh b/libziparchive/run-ziptool-tests-on-android.sh
new file mode 100755
index 0000000..3c23d43
--- /dev/null
+++ b/libziparchive/run-ziptool-tests-on-android.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Copy the tests across.
+adb shell rm -rf /data/local/tmp/ziptool-tests/
+adb shell mkdir /data/local/tmp/ziptool-tests/
+adb push cli-tests/ /data/local/tmp/ziptool-tests/
+#adb push cli-test /data/local/tmp/ziptool-tests/
+
+if tty -s; then
+  dash_t="-t"
+else
+  dash_t=""
+fi
+
+exec adb shell $dash_t cli-test /data/local/tmp/ziptool-tests/cli-tests/*.test
diff --git a/libziparchive/testdata/empty.zip b/libziparchive/testdata/empty.zip
new file mode 100644
index 0000000..15cb0ec
--- /dev/null
+++ b/libziparchive/testdata/empty.zip
Binary files differ
diff --git a/libziparchive/testdata/zero-size-cd.zip b/libziparchive/testdata/zero-size-cd.zip
new file mode 100644
index 0000000..b6c8cbe
--- /dev/null
+++ b/libziparchive/testdata/zero-size-cd.zip
Binary files differ
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index ef29188..68837cc 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -265,14 +265,10 @@
   ALOGV("+++ num_entries=%" PRIu32 " dir_size=%" PRIu32 " dir_offset=%" PRIu32, eocd->num_records,
         eocd->cd_size, eocd->cd_start_offset);
 
-  /*
-   * It all looks good.  Create a mapping for the CD, and set the fields
-   * in archive.
-   */
-
+  // It all looks good.  Create a mapping for the CD, and set the fields
+  // in archive.
   if (!archive->InitializeCentralDirectory(static_cast<off64_t>(eocd->cd_start_offset),
                                            static_cast<size_t>(eocd->cd_size))) {
-    ALOGE("Zip: failed to intialize central directory.\n");
     return kMmapFailed;
   }
 
@@ -354,7 +350,7 @@
   if (archive->hash_table == nullptr) {
     ALOGW("Zip: unable to allocate the %u-entry hash_table, entry size: %zu",
           archive->hash_table_size, sizeof(ZipStringOffset));
-    return -1;
+    return kAllocationFailed;
   }
 
   /*
@@ -365,24 +361,25 @@
   const uint8_t* ptr = cd_ptr;
   for (uint16_t i = 0; i < num_entries; i++) {
     if (ptr > cd_end - sizeof(CentralDirectoryRecord)) {
-      ALOGW("Zip: ran off the end (at %" PRIu16 ")", i);
+      ALOGW("Zip: ran off the end (item #%" PRIu16 ", %zu bytes of central directory)", i,
+            cd_length);
 #if defined(__ANDROID__)
       android_errorWriteLog(0x534e4554, "36392138");
 #endif
-      return -1;
+      return kInvalidFile;
     }
 
     const CentralDirectoryRecord* cdr = reinterpret_cast<const CentralDirectoryRecord*>(ptr);
     if (cdr->record_signature != CentralDirectoryRecord::kSignature) {
       ALOGW("Zip: missed a central dir sig (at %" PRIu16 ")", i);
-      return -1;
+      return kInvalidFile;
     }
 
     const off64_t local_header_offset = cdr->local_file_header_offset;
     if (local_header_offset >= archive->directory_offset) {
       ALOGW("Zip: bad LFH offset %" PRId64 " at entry %" PRIu16,
             static_cast<int64_t>(local_header_offset), i);
-      return -1;
+      return kInvalidFile;
     }
 
     const uint16_t file_name_length = cdr->file_name_length;
@@ -394,12 +391,12 @@
       ALOGW("Zip: file name for entry %" PRIu16
             " exceeds the central directory range, file_name_length: %" PRIu16 ", cd_length: %zu",
             i, file_name_length, cd_length);
-      return -1;
+      return kInvalidEntryName;
     }
     // Check that file name is valid UTF-8 and doesn't contain NUL (U+0000) characters.
     if (!IsValidEntryName(file_name, file_name_length)) {
       ALOGW("Zip: invalid file name at entry %" PRIu16, i);
-      return -1;
+      return kInvalidEntryName;
     }
 
     // Add the CDE filename to the hash table.
@@ -414,7 +411,7 @@
     ptr += sizeof(CentralDirectoryRecord) + file_name_length + extra_length + comment_length;
     if ((ptr - cd_ptr) > static_cast<int64_t>(cd_length)) {
       ALOGW("Zip: bad CD advance (%tu vs %zu) at entry %" PRIu16, ptr - cd_ptr, cd_length, i);
-      return -1;
+      return kInvalidFile;
     }
   }
 
@@ -422,7 +419,7 @@
   if (!archive->mapped_zip.ReadAtOffset(reinterpret_cast<uint8_t*>(&lfh_start_bytes),
                                         sizeof(uint32_t), 0)) {
     ALOGW("Zip: Unable to read header for entry at offset == 0.");
-    return -1;
+    return kInvalidFile;
   }
 
   if (lfh_start_bytes != LocalFileHeader::kSignature) {
@@ -430,7 +427,7 @@
 #if defined(__ANDROID__)
     android_errorWriteLog(0x534e4554, "64211847");
 #endif
-    return -1;
+    return kInvalidFile;
   }
 
   ALOGV("+++ zip good scan %" PRIu16 " entries", num_entries);
@@ -439,16 +436,8 @@
 }
 
 static int32_t OpenArchiveInternal(ZipArchive* archive, const char* debug_file_name) {
-  int32_t result = -1;
-  if ((result = MapCentralDirectory(debug_file_name, archive)) != 0) {
-    return result;
-  }
-
-  if ((result = ParseZipArchive(archive))) {
-    return result;
-  }
-
-  return 0;
+  int32_t result = MapCentralDirectory(debug_file_name, archive);
+  return result != 0 ? result : ParseZipArchive(archive);
 }
 
 int32_t OpenArchiveFd(int fd, const char* debug_file_name, ZipArchiveHandle* handle,
@@ -1185,7 +1174,7 @@
     return result;
   } else {
     if (base_ptr_ == nullptr) {
-      ALOGE("Zip: invalid file map\n");
+      ALOGE("Zip: invalid file map");
       return -1;
     }
     return static_cast<off64_t>(data_length_);
@@ -1196,12 +1185,12 @@
 bool MappedZipFile::ReadAtOffset(uint8_t* buf, size_t len, off64_t off) const {
   if (has_fd_) {
     if (!android::base::ReadFullyAtOffset(fd_, buf, len, off)) {
-      ALOGE("Zip: failed to read at offset %" PRId64 "\n", off);
+      ALOGE("Zip: failed to read at offset %" PRId64, off);
       return false;
     }
   } else {
     if (off < 0 || off > static_cast<off64_t>(data_length_)) {
-      ALOGE("Zip: invalid offset: %" PRId64 ", data length: %" PRId64 "\n", off, data_length_);
+      ALOGE("Zip: invalid offset: %" PRId64 ", data length: %" PRId64, off, data_length_);
       return false;
     }
     memcpy(buf, static_cast<const uint8_t*>(base_ptr_) + off, len);
@@ -1219,13 +1208,17 @@
   if (mapped_zip.HasFd()) {
     directory_map = android::base::MappedFile::FromFd(mapped_zip.GetFileDescriptor(),
                                                       cd_start_offset, cd_size, PROT_READ);
-    if (!directory_map) return false;
+    if (!directory_map) {
+      ALOGE("Zip: failed to map central directory (offset %" PRId64 ", size %zu): %s",
+            cd_start_offset, cd_size, strerror(errno));
+      return false;
+    }
 
     CHECK_EQ(directory_map->size(), cd_size);
     central_directory.Initialize(directory_map->data(), 0 /*offset*/, cd_size);
   } else {
     if (mapped_zip.GetBasePtr() == nullptr) {
-      ALOGE("Zip: Failed to map central directory, bad mapped_zip base pointer\n");
+      ALOGE("Zip: Failed to map central directory, bad mapped_zip base pointer");
       return false;
     }
     if (static_cast<off64_t>(cd_start_offset) + static_cast<off64_t>(cd_size) >
diff --git a/libziparchive/zip_archive_private.h b/libziparchive/zip_archive_private.h
index 60fdec0..1d05fc7 100644
--- a/libziparchive/zip_archive_private.h
+++ b/libziparchive/zip_archive_private.h
@@ -42,6 +42,7 @@
     "Invalid entry name",
     "I/O error",
     "File mapping failed",
+    "Allocation failed",
 };
 
 enum ErrorCodes : int32_t {
@@ -87,7 +88,10 @@
   // We were not able to mmap the central directory or entry contents.
   kMmapFailed = -12,
 
-  kLastErrorCode = kMmapFailed,
+  // An allocation failed.
+  kAllocationFailed = -13,
+
+  kLastErrorCode = kAllocationFailed,
 };
 
 class MappedZipFile {
diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc
index 8781ab7..0916304 100644
--- a/libziparchive/zip_archive_test.cc
+++ b/libziparchive/zip_archive_test.cc
@@ -36,13 +36,9 @@
 
 static std::string test_data_dir = android::base::GetExecutableDirectory() + "/testdata";
 
-static const std::string kMissingZip = "missing.zip";
 static const std::string kValidZip = "valid.zip";
 static const std::string kLargeZip = "large.zip";
 static const std::string kBadCrcZip = "bad_crc.zip";
-static const std::string kCrashApk = "crash.apk";
-static const std::string kBadFilenameZip = "bad_filename.zip";
-static const std::string kUpdateZip = "dummy-update.zip";
 
 static const std::vector<uint8_t> kATxtContents{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a',
                                                 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\n'};
@@ -52,13 +48,6 @@
 
 static const std::vector<uint8_t> kBTxtContents{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\n'};
 
-static const std::string kATxtName("a.txt");
-static const std::string kBTxtName("b.txt");
-static const std::string kNonexistentTxtName("nonexistent.txt");
-static const std::string kEmptyTxtName("empty.txt");
-static const std::string kLargeCompressTxtName("compress.txt");
-static const std::string kLargeUncompressTxtName("uncompress.txt");
-
 static int32_t OpenArchiveWrapper(const std::string& name, ZipArchiveHandle* handle) {
   const std::string abs_path = test_data_dir + "/" + name;
   return OpenArchive(abs_path.c_str(), handle);
@@ -69,19 +58,31 @@
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
   CloseArchive(handle);
 
-  ASSERT_EQ(-1, OpenArchiveWrapper(kBadFilenameZip, &handle));
+  ASSERT_EQ(kInvalidEntryName, OpenArchiveWrapper("bad_filename.zip", &handle));
   CloseArchive(handle);
 }
 
 TEST(ziparchive, OutOfBound) {
   ZipArchiveHandle handle;
-  ASSERT_EQ(-8, OpenArchiveWrapper(kCrashApk, &handle));
+  ASSERT_EQ(kInvalidOffset, OpenArchiveWrapper("crash.apk", &handle));
+  CloseArchive(handle);
+}
+
+TEST(ziparchive, EmptyArchive) {
+  ZipArchiveHandle handle;
+  ASSERT_EQ(kEmptyArchive, OpenArchiveWrapper("empty.zip", &handle));
+  CloseArchive(handle);
+}
+
+TEST(ziparchive, ZeroSizeCentralDirectory) {
+  ZipArchiveHandle handle;
+  ASSERT_EQ(kInvalidFile, OpenArchiveWrapper("zero-size-cd.zip", &handle));
   CloseArchive(handle);
 }
 
 TEST(ziparchive, OpenMissing) {
   ZipArchiveHandle handle;
-  ASSERT_NE(0, OpenArchiveWrapper(kMissingZip, &handle));
+  ASSERT_NE(0, OpenArchiveWrapper("missing.zip", &handle));
 
   // Confirm the file descriptor is not going to be mistaken for a valid one.
   ASSERT_EQ(-1, GetFileDescriptor(handle));
@@ -200,7 +201,7 @@
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
 
   ZipEntry data;
-  ASSERT_EQ(0, FindEntry(handle, kATxtName, &data));
+  ASSERT_EQ(0, FindEntry(handle, "a.txt", &data));
 
   // Known facts about a.txt, from zipinfo -v.
   ASSERT_EQ(63, data.offset);
@@ -211,7 +212,7 @@
   ASSERT_EQ(static_cast<uint32_t>(0x438a8005), data.mod_time);
 
   // An entry that doesn't exist. Should be a negative return code.
-  ASSERT_LT(FindEntry(handle, kNonexistentTxtName, &data), 0);
+  ASSERT_LT(FindEntry(handle, "this file does not exist", &data), 0);
 
   CloseArchive(handle);
 }
@@ -259,7 +260,7 @@
 
   // An entry that's deflated.
   ZipEntry data;
-  ASSERT_EQ(0, FindEntry(handle, kATxtName, &data));
+  ASSERT_EQ(0, FindEntry(handle, "a.txt", &data));
   const uint32_t a_size = data.uncompressed_length;
   ASSERT_EQ(a_size, kATxtContents.size());
   uint8_t* buffer = new uint8_t[a_size];
@@ -268,7 +269,7 @@
   delete[] buffer;
 
   // An entry that's stored.
-  ASSERT_EQ(0, FindEntry(handle, kBTxtName, &data));
+  ASSERT_EQ(0, FindEntry(handle, "b.txt", &data));
   const uint32_t b_size = data.uncompressed_length;
   ASSERT_EQ(b_size, kBTxtContents.size());
   buffer = new uint8_t[b_size];
@@ -323,7 +324,7 @@
   ASSERT_EQ(0, OpenArchiveFd(tmp_file.fd, "EmptyEntriesTest", &handle, false));
 
   ZipEntry entry;
-  ASSERT_EQ(0, FindEntry(handle, kEmptyTxtName, &entry));
+  ASSERT_EQ(0, FindEntry(handle, "empty.txt", &entry));
   ASSERT_EQ(static_cast<uint32_t>(0), entry.uncompressed_length);
   uint8_t buffer[1];
   ASSERT_EQ(0, ExtractToMemory(handle, &entry, buffer, 1));
@@ -403,7 +404,7 @@
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
 
   ZipEntry entry;
-  ASSERT_EQ(0, FindEntry(handle, kATxtName, &entry));
+  ASSERT_EQ(0, FindEntry(handle, "a.txt", &entry));
   ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, tmp_file.fd));
 
   // Assert that the first 8 bytes of the file haven't been clobbered.
@@ -425,7 +426,7 @@
 
 #if !defined(_WIN32)
 TEST(ziparchive, OpenFromMemory) {
-  const std::string zip_path = test_data_dir + "/" + kUpdateZip;
+  const std::string zip_path = test_data_dir + "/dummy-update.zip";
   android::base::unique_fd fd(open(zip_path.c_str(), O_RDONLY | O_BINARY));
   ASSERT_NE(-1, fd);
   struct stat sb;
@@ -510,27 +511,27 @@
 }
 
 TEST(ziparchive, StreamCompressed) {
-  ZipArchiveStreamTestUsingContents(kValidZip, kATxtName, kATxtContents, false);
+  ZipArchiveStreamTestUsingContents(kValidZip, "a.txt", kATxtContents, false);
 }
 
 TEST(ziparchive, StreamUncompressed) {
-  ZipArchiveStreamTestUsingContents(kValidZip, kBTxtName, kBTxtContents, false);
+  ZipArchiveStreamTestUsingContents(kValidZip, "b.txt", kBTxtContents, false);
 }
 
 TEST(ziparchive, StreamRawCompressed) {
-  ZipArchiveStreamTestUsingContents(kValidZip, kATxtName, kATxtContentsCompressed, true);
+  ZipArchiveStreamTestUsingContents(kValidZip, "a.txt", kATxtContentsCompressed, true);
 }
 
 TEST(ziparchive, StreamRawUncompressed) {
-  ZipArchiveStreamTestUsingContents(kValidZip, kBTxtName, kBTxtContents, true);
+  ZipArchiveStreamTestUsingContents(kValidZip, "b.txt", kBTxtContents, true);
 }
 
 TEST(ziparchive, StreamLargeCompressed) {
-  ZipArchiveStreamTestUsingMemory(kLargeZip, kLargeCompressTxtName);
+  ZipArchiveStreamTestUsingMemory(kLargeZip, "compress.txt");
 }
 
 TEST(ziparchive, StreamLargeUncompressed) {
-  ZipArchiveStreamTestUsingMemory(kLargeZip, kLargeUncompressTxtName);
+  ZipArchiveStreamTestUsingMemory(kLargeZip, "uncompress.txt");
 }
 
 TEST(ziparchive, StreamCompressedBadCrc) {
@@ -539,7 +540,7 @@
 
   ZipEntry entry;
   std::vector<uint8_t> read_data;
-  ZipArchiveStreamTest(handle, kATxtName, false, false, &entry, &read_data);
+  ZipArchiveStreamTest(handle, "a.txt", false, false, &entry, &read_data);
 
   CloseArchive(handle);
 }
@@ -550,7 +551,7 @@
 
   ZipEntry entry;
   std::vector<uint8_t> read_data;
-  ZipArchiveStreamTest(handle, kBTxtName, false, false, &entry, &read_data);
+  ZipArchiveStreamTest(handle, "b.txt", false, false, &entry, &read_data);
 
   CloseArchive(handle);
 }
@@ -647,7 +648,8 @@
 
   // Out of bounds.
   ASSERT_STREQ("Unknown return code", ErrorCodeString(1));
-  ASSERT_STREQ("Unknown return code", ErrorCodeString(-13));
+  ASSERT_STRNE("Unknown return code", ErrorCodeString(kLastErrorCode));
+  ASSERT_STREQ("Unknown return code", ErrorCodeString(kLastErrorCode - 1));
 
   ASSERT_STREQ("I/O error", ErrorCodeString(kIoError));
 }
@@ -698,7 +700,7 @@
   ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, &kZipFileWithBrokenLfhSignature[0],
                                         kZipFileWithBrokenLfhSignature.size()));
   ZipArchiveHandle handle;
-  ASSERT_EQ(-1, OpenArchiveFd(tmp_file.fd, "LeadingNonZipBytes", &handle, false));
+  ASSERT_EQ(kInvalidFile, OpenArchiveFd(tmp_file.fd, "LeadingNonZipBytes", &handle, false));
 }
 
 class VectorReader : public zip_archive::Reader {
diff --git a/libziparchive/ziptool-tests.xml b/libziparchive/ziptool-tests.xml
new file mode 100644
index 0000000..211119f
--- /dev/null
+++ b/libziparchive/ziptool-tests.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Config for running ziptool-tests through Atest or in Infra">
+    <option name="test-suite-tag" value="ziptool-tests" />
+    <!-- This test requires a device, so it's not annotated with a null-device. -->
+    <test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
+        <option name="binary" value="run-ziptool-tests-on-android.sh" />
+        <!-- Test script assumes a relative path with the cli-tests/ folders. -->
+        <option name="relative-path-execution" value="true" />
+        <!-- Tests shouldn't be that long but set 15m to be safe. -->
+        <option name="per-binary-timeout" value="15m" />
+    </test>
+</configuration>
diff --git a/libziparchive/unzip.cpp b/libziparchive/ziptool.cpp
similarity index 95%
rename from libziparchive/unzip.cpp
rename to libziparchive/ziptool.cpp
index 11b575e..dd42e90 100644
--- a/libziparchive/unzip.cpp
+++ b/libziparchive/ziptool.cpp
@@ -52,7 +52,7 @@
 static Role role;
 static OverwriteMode overwrite_mode = kPrompt;
 static bool flag_1 = false;
-static const char* flag_d = nullptr;
+static std::string flag_d;
 static bool flag_l = false;
 static bool flag_p = false;
 static bool flag_q = false;
@@ -214,12 +214,9 @@
   }
 
   // Where are we actually extracting to (for human-readable output)?
-  std::string dst;
-  if (flag_d) {
-    dst = flag_d;
-    if (!EndsWith(dst, "/")) dst += '/';
-  }
-  dst += name;
+  // flag_d is the empty string if -d wasn't used, or has a trailing '/'
+  // otherwise.
+  std::string dst = flag_d + name;
 
   // Ensure the directory hierarchy exists.
   if (!MakeDirectoryHierarchy(android::base::Dirname(name))) {
@@ -463,6 +460,7 @@
       switch (opt) {
         case 'd':
           flag_d = optarg;
+          if (!EndsWith(flag_d, "/")) flag_d += '/';
           break;
         case 'l':
           flag_l = true;
@@ -511,9 +509,17 @@
   }
 
   // Implement -d by changing into that directory.
-  // We'll create implicit directories based on paths in the zip file, but we
-  // require that the -d directory already exists.
-  if (flag_d && chdir(flag_d) == -1) die(errno, "couldn't chdir to %s", flag_d);
+  // We'll create implicit directories based on paths in the zip file, and we'll create
+  // the -d directory itself, but we require that *parents* of the -d directory already exists.
+  // This is pretty arbitrary, but it's the behavior of the original unzip.
+  if (!flag_d.empty()) {
+    if (mkdir(flag_d.c_str(), 0777) == -1 && errno != EEXIST) {
+      die(errno, "couldn't created %s", flag_d.c_str());
+    }
+    if (chdir(flag_d.c_str()) == -1) {
+      die(errno, "couldn't chdir to %s", flag_d.c_str());
+    }
+  }
 
   ProcessAll(zah);
 
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index ebc0cde..2dbdb60 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -72,7 +72,7 @@
 #
 # create some directories (some are mount points) and symlinks
 LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
-    dev proc sys system data odm oem acct config storage mnt apex debug_ramdisk \
+    dev proc sys system data data_mirror odm oem acct config storage mnt apex debug_ramdisk \
     linkerconfig $(BOARD_ROOT_EXTRA_FOLDERS)); \
     ln -sf /system/bin $(TARGET_ROOT_OUT)/bin; \
     ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \
diff --git a/rootdir/avb/Android.mk b/rootdir/avb/Android.mk
index 5dc019c..80573fb 100644
--- a/rootdir/avb/Android.mk
+++ b/rootdir/avb/Android.mk
@@ -16,6 +16,21 @@
 include $(BUILD_PREBUILT)
 
 #######################################
+# q-developer-gsi.avbpubkey
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := q-developer-gsi.avbpubkey
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/first_stage_ramdisk/avb
+else
+LOCAL_MODULE_PATH := $(TARGET_RAMDISK_OUT)/avb
+endif
+
+include $(BUILD_PREBUILT)
+
+#######################################
 # r-gsi.avbpubkey
 include $(CLEAR_VARS)
 
diff --git a/rootdir/avb/q-developer-gsi.avbpubkey b/rootdir/avb/q-developer-gsi.avbpubkey
new file mode 100644
index 0000000..0ace69d
--- /dev/null
+++ b/rootdir/avb/q-developer-gsi.avbpubkey
Binary files differ
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 2ec0669..b89c45e 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -650,12 +650,35 @@
 
     mkdir /data/user 0711 system system encryption=None
     mkdir /data/user_de 0711 system system encryption=None
-    symlink /data/data /data/user/0
+
+    # Unlink /data/user/0 if we previously symlink it to /data/data
+    rm /data/user/0
+
+    # Bind mount /data/user/0 to /data/data
+    mkdir /data/user/0 0700 system system encryption=None
+    mount none /data/data /data/user/0 bind rec
 
     # Special-case /data/media/obb per b/64566063
     mkdir /data/media 0770 media_rw media_rw encryption=None
     mkdir /data/media/obb 0770 media_rw media_rw encryption=Attempt
 
+    # A tmpfs directory, which will contain all apps CE DE data directory that
+    # bind mount from the original source.
+    chown root root /data_mirror
+    chmod 0700 /data_mirror
+    mount tmpfs tmpfs /data_mirror mode=0700,uid=0,gid=1000 nodev noexec nosuid
+    restorecon /data_mirror
+    mkdir /data_mirror/data_ce 0700 root root
+    mkdir /data_mirror/data_de 0700 root root
+
+    # Create CE and DE data directory for default volume
+    mkdir /data_mirror/data_ce/null 0700 root root
+    mkdir /data_mirror/data_de/null 0700 root root
+
+    # Bind mount CE and DE data directory to mirror's default volume directory
+    mount none /data/user /data_mirror/data_ce/null bind rec
+    mount none /data/user_de /data_mirror/data_de/null bind rec
+
     mkdir /data/cache 0770 system cache encryption=Require
     mkdir /data/cache/recovery 0770 system cache
     mkdir /data/cache/backup_stage 0700 system system