Merge "Revert "libutils: Introduce StaticString16""
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 66d0c92..cc1978d 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -4,6 +4,9 @@
       "name": "adbd_test"
     },
     {
+      "name": "CtsInitTestCases"
+    },
+    {
       "name": "debuggerd_test"
     },
     {
@@ -13,10 +16,10 @@
       "name": "fs_mgr_vendor_overlay_test"
     },
     {
-      "name": "init_tests"
+      "name": "libbase_test"
     },
     {
-      "name": "libbase_test"
+      "name": "libpackagelistparser_test"
     },
     {
       "name": "libprocinfo_test"
diff --git a/adb/Android.bp b/adb/Android.bp
index 06cfcbf..57872b0 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -142,6 +142,10 @@
     "sysdeps/posix/network.cpp",
 ]
 
+libadb_linux_srcs = [
+    "fdevent/fdevent_epoll.cpp",
+]
+
 libadb_test_srcs = [
     "adb_io_test.cpp",
     "adb_listeners_test.cpp",
@@ -170,12 +174,11 @@
 
     target: {
         linux: {
-            srcs: ["client/usb_linux.cpp"],
+            srcs: ["client/usb_linux.cpp"] + libadb_linux_srcs,
         },
         darwin: {
             srcs: ["client/usb_osx.cpp"],
         },
-
         not_windows: {
             srcs: libadb_posix_srcs,
         },
@@ -342,7 +345,7 @@
     // libminadbd wants both, as it's used to build native tests.
     compile_multilib: "both",
 
-    srcs: libadb_srcs + libadb_posix_srcs + [
+    srcs: libadb_srcs + libadb_linux_srcs + libadb_posix_srcs + [
         "daemon/auth.cpp",
         "daemon/jdwp_service.cpp",
     ],
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 24d4292..7dff1b8 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -1243,11 +1243,7 @@
     // TODO: Switch handle_forward_request to string_view.
     std::string service_str(service);
     if (handle_forward_request(
-                service_str.c_str(),
-                [=](std::string* error) {
-                    return acquire_one_transport(type, serial, transport_id, nullptr, error);
-                },
-                reply_fd)) {
+                service_str.c_str(), [=](std::string* error) { return s->transport; }, reply_fd)) {
         return HostRequestResult::Handled;
     }
 
diff --git a/adb/adb_utils.h b/adb/adb_utils.h
index faad03d..66cba12 100644
--- a/adb/adb_utils.h
+++ b/adb/adb_utils.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <charconv>
 #include <condition_variable>
 #include <mutex>
 #include <string>
@@ -112,33 +113,17 @@
 // Base-10 stroll on a string_view.
 template <typename T>
 inline bool ParseUint(T* result, std::string_view str, std::string_view* remaining = nullptr) {
-    if (str.empty() || !isdigit(str[0])) {
+    T value;
+    const auto res = std::from_chars(str.begin(), str.end(), value);
+    if (res.ec != std::errc{}) {
         return false;
     }
-
-    T value = 0;
-    std::string_view::iterator it;
-    constexpr T max = std::numeric_limits<T>::max();
-    for (it = str.begin(); it != str.end() && isdigit(*it); ++it) {
-        if (value > max / 10) {
-            return false;
-        }
-
-        value *= 10;
-
-        T digit = *it - '0';
-        if (value > max - digit) {
-            return false;
-        }
-
-        value += digit;
+    if (res.ptr != str.end() && !remaining) {
+        return false;
+    }
+    if (remaining) {
+        *remaining = std::string_view(res.ptr, str.end() - res.ptr);
     }
     *result = value;
-    if (remaining) {
-        *remaining = str.substr(it - str.begin());
-    } else {
-      return it == str.end();
-    }
-
     return true;
 }
diff --git a/adb/client/adb_client.cpp b/adb/client/adb_client.cpp
index 7e408a8..d91ae35 100644
--- a/adb/client/adb_client.cpp
+++ b/adb/client/adb_client.cpp
@@ -149,7 +149,8 @@
     return false;
 }
 
-static int _adb_connect(std::string_view service, TransportId* transport, std::string* error) {
+static int _adb_connect(std::string_view service, TransportId* transport, std::string* error,
+                        bool force_switch = false) {
     LOG(DEBUG) << "_adb_connect: " << service;
     if (service.empty() || service.size() > MAX_PAYLOAD) {
         *error = android::base::StringPrintf("bad service name length (%zd)", service.size());
@@ -164,7 +165,7 @@
         return -2;
     }
 
-    if (!service.starts_with("host")) {
+    if (!service.starts_with("host") || force_switch) {
         std::optional<TransportId> transport_result = switch_socket_transport(fd.get(), error);
         if (!transport_result) {
             return -1;
@@ -323,7 +324,8 @@
     return result;
 }
 
-int adb_connect(TransportId* transport, std::string_view service, std::string* error) {
+int adb_connect(TransportId* transport, std::string_view service, std::string* error,
+                bool force_switch_device) {
     LOG(DEBUG) << "adb_connect: service: " << service;
 
     // Query the adb server's version.
@@ -336,7 +338,7 @@
         return 0;
     }
 
-    unique_fd fd(_adb_connect(service, transport, error));
+    unique_fd fd(_adb_connect(service, transport, error, force_switch_device));
     if (fd == -1) {
         D("_adb_connect error: %s", error->c_str());
     } else if(fd == -2) {
@@ -398,9 +400,15 @@
 }
 
 bool adb_get_feature_set(FeatureSet* feature_set, std::string* error) {
-    std::string result;
-    if (adb_query(format_host_command("features"), &result, error)) {
-        *feature_set = StringToFeatureSet(result);
+    static FeatureSet* features = nullptr;
+    if (!features) {
+        std::string result;
+        if (adb_query(format_host_command("features"), &result, error)) {
+            features = new FeatureSet(StringToFeatureSet(result));
+        }
+    }
+    if (features) {
+        *feature_set = *features;
         return true;
     }
     feature_set->clear();
diff --git a/adb/client/adb_client.h b/adb/client/adb_client.h
index fe1e584..ba53041 100644
--- a/adb/client/adb_client.h
+++ b/adb/client/adb_client.h
@@ -34,7 +34,11 @@
 int adb_connect(std::string_view service, std::string* _Nonnull error);
 
 // Same as above, except returning the TransportId for the service that we've connected to.
-int adb_connect(TransportId* _Nullable id, std::string_view service, std::string* _Nonnull error);
+// force_switch_device forces the function to attempt to select a device, even if the service
+// string appears to be a host: service (for use with host services that are device specific, like
+// forward).
+int adb_connect(TransportId* _Nullable id, std::string_view service, std::string* _Nonnull error,
+                bool force_switch_device = false);
 
 // Kill the currently running adb server, if it exists.
 bool adb_kill_server();
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
index 16fa215..a6e8998 100644
--- a/adb/client/adb_install.cpp
+++ b/adb/client/adb_install.cpp
@@ -16,6 +16,7 @@
 
 #include "adb_install.h"
 
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -40,18 +41,31 @@
 static constexpr int kFastDeployMinApi = 24;
 #endif
 
+namespace {
+
+enum InstallMode {
+    INSTALL_DEFAULT,
+    INSTALL_PUSH,
+    INSTALL_STREAM,
+};
+
+}
+
 static bool can_use_feature(const char* feature) {
     FeatureSet features;
     std::string error;
     if (!adb_get_feature_set(&features, &error)) {
         fprintf(stderr, "error: %s\n", error.c_str());
-        return true;
+        return false;
     }
     return CanUseFeature(features, feature);
 }
 
-static bool use_legacy_install() {
-    return !can_use_feature(kFeatureCmd);
+static InstallMode best_install_mode() {
+    if (can_use_feature(kFeatureCmd)) {
+        return INSTALL_STREAM;
+    }
+    return INSTALL_PUSH;
 }
 
 static bool is_apex_supported() {
@@ -112,7 +126,7 @@
 }
 
 int uninstall_app(int argc, const char** argv) {
-    if (use_legacy_install()) {
+    if (best_install_mode() == INSTALL_PUSH) {
         return uninstall_app_legacy(argc, argv);
     }
     return uninstall_app_streamed(argc, argv);
@@ -200,32 +214,49 @@
             return 1;
         }
 
+#ifdef __linux__
+        posix_fadvise(local_fd.get(), 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+#endif
+
+        const bool use_abb = can_use_feature(kFeatureAbb);
         std::string error;
-        std::string cmd = "exec:cmd package";
+        std::vector<std::string> cmd_args = {use_abb ? "package" : "exec:cmd package"};
+        cmd_args.reserve(argc + 3);
 
         // don't copy the APK name, but, copy the rest of the arguments as-is
         while (argc-- > 1) {
-            cmd += " " + escape_arg(std::string(*argv++));
+            if (use_abb) {
+                cmd_args.push_back(*argv++);
+            } else {
+                cmd_args.push_back(escape_arg(*argv++));
+            }
         }
 
         // add size parameter [required for streaming installs]
         // do last to override any user specified value
-        cmd += " " + android::base::StringPrintf("-S %" PRIu64, static_cast<uint64_t>(sb.st_size));
+        cmd_args.push_back("-S");
+        cmd_args.push_back(
+                android::base::StringPrintf("%" PRIu64, static_cast<uint64_t>(sb.st_size)));
 
         if (is_apex) {
-            cmd += " --apex";
+            cmd_args.push_back("--apex");
         }
 
-        unique_fd remote_fd(adb_connect(cmd, &error));
+        unique_fd remote_fd;
+        if (use_abb) {
+            remote_fd = send_abb_exec_command(cmd_args, &error);
+        } else {
+            remote_fd.reset(adb_connect(android::base::Join(cmd_args, " "), &error));
+        }
         if (remote_fd < 0) {
             fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
             return 1;
         }
 
-        char buf[BUFSIZ];
         copy_to_file(local_fd.get(), remote_fd.get());
-        read_status_line(remote_fd.get(), buf, sizeof(buf));
 
+        char buf[BUFSIZ];
+        read_status_line(remote_fd.get(), buf, sizeof(buf));
         if (!strncmp("Success", buf, 7)) {
             fputs(buf, stdout);
             return 0;
@@ -256,8 +287,7 @@
 
     int result = -1;
     std::vector<const char*> apk_file = {argv[last_apk]};
-    std::string apk_dest =
-            "/data/local/tmp/" + android::base::Basename(argv[last_apk]);
+    std::string apk_dest = "/data/local/tmp/" + android::base::Basename(argv[last_apk]);
 
     if (use_fastdeploy == true) {
 #if defined(ENABLE_FASTDEPLOY)
@@ -292,11 +322,7 @@
 
 int install_app(int argc, const char** argv) {
     std::vector<int> processedArgIndicies;
-    enum installMode {
-        INSTALL_DEFAULT,
-        INSTALL_PUSH,
-        INSTALL_STREAM
-    } installMode = INSTALL_DEFAULT;
+    InstallMode installMode = INSTALL_DEFAULT;
     bool use_fastdeploy = false;
     bool is_reinstall = false;
     bool use_localagent = false;
@@ -337,14 +363,10 @@
     }
 
     if (installMode == INSTALL_DEFAULT) {
-        if (use_legacy_install()) {
-            installMode = INSTALL_PUSH;
-        } else {
-            installMode = INSTALL_STREAM;
-        }
+        installMode = best_install_mode();
     }
 
-    if (installMode == INSTALL_STREAM && use_legacy_install() == true) {
+    if (installMode == INSTALL_STREAM && best_install_mode() == INSTALL_PUSH) {
         error_exit("Attempting to use streaming install on unsupported device");
     }
 
@@ -409,7 +431,8 @@
             android::base::EndsWithIgnoreCase(file, ".dm") ||
             android::base::EndsWithIgnoreCase(file, ".fsv_sig")) {
             struct stat sb;
-            if (stat(file, &sb) != -1) total_size += sb.st_size;
+            if (stat(file, &sb) == -1) perror_exit("failed to stat \"%s\"", file);
+            total_size += sb.st_size;
             first_apk = i;
         } else {
             break;
@@ -419,7 +442,7 @@
     if (first_apk == -1) error_exit("need APK file on command line");
 
     std::string install_cmd;
-    if (use_legacy_install()) {
+    if (best_install_mode() == INSTALL_PUSH) {
         install_cmd = "exec:pm";
     } else {
         install_cmd = "exec:cmd package";
@@ -459,13 +482,13 @@
     }
 
     // Valid session, now stream the APKs
-    int success = 1;
+    bool success = true;
     for (int i = first_apk; i < argc; i++) {
         const char* file = argv[i];
         struct stat sb;
         if (stat(file, &sb) == -1) {
-            fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
-            success = 0;
+            fprintf(stderr, "adb: failed to stat \"%s\": %s\n", file, strerror(errno));
+            success = false;
             goto finalize_session;
         }
 
@@ -476,8 +499,8 @@
 
         unique_fd local_fd(adb_open(file, O_RDONLY | O_CLOEXEC));
         if (local_fd < 0) {
-            fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
-            success = 0;
+            fprintf(stderr, "adb: failed to open \"%s\": %s\n", file, strerror(errno));
+            success = false;
             goto finalize_session;
         }
 
@@ -485,7 +508,7 @@
         unique_fd remote_fd(adb_connect(cmd, &error));
         if (remote_fd < 0) {
             fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
-            success = 0;
+            success = false;
             goto finalize_session;
         }
 
@@ -493,15 +516,15 @@
         read_status_line(remote_fd.get(), buf, sizeof(buf));
 
         if (strncmp("Success", buf, 7)) {
-            fprintf(stderr, "adb: failed to write %s\n", file);
+            fprintf(stderr, "adb: failed to write \"%s\"\n", file);
             fputs(buf, stderr);
-            success = 0;
+            success = false;
             goto finalize_session;
         }
     }
 
 finalize_session:
-    // Commit session if we streamed everything okay; otherwise abandon
+    // Commit session if we streamed everything okay; otherwise abandon.
     std::string service = android::base::StringPrintf("%s install-%s %d", install_cmd.c_str(),
                                                       success ? "commit" : "abandon", session_id);
     {
@@ -512,14 +535,16 @@
         }
         read_status_line(fd.get(), buf, sizeof(buf));
     }
+    if (!success) return EXIT_FAILURE;
 
-    if (!strncmp("Success", buf, 7)) {
-        fputs(buf, stdout);
-        return 0;
+    if (strncmp("Success", buf, 7)) {
+        fprintf(stderr, "adb: failed to finalize session\n");
+        fputs(buf, stderr);
+        return EXIT_FAILURE;
     }
-    fprintf(stderr, "adb: failed to finalize session\n");
-    fputs(buf, stderr);
-    return EXIT_FAILURE;
+
+    fputs(buf, stdout);
+    return EXIT_SUCCESS;
 }
 
 int install_multi_package(int argc, const char** argv) {
@@ -542,7 +567,7 @@
 
     if (first_package == -1) error_exit("need APK or APEX files on command line");
 
-    if (use_legacy_install()) {
+    if (best_install_mode() == INSTALL_PUSH) {
         fprintf(stderr, "adb: multi-package install is not supported on this device\n");
         return EXIT_FAILURE;
     }
@@ -739,6 +764,20 @@
 }
 
 int delete_device_file(const std::string& filename) {
-    std::string cmd = "rm -f " + escape_arg(filename);
-    return send_shell_command(cmd);
+    // http://b/17339227 "Sideloading a Readonly File Results in a Prompt to
+    // Delete" caused us to add `-f` here, to avoid the equivalent of the `-i`
+    // prompt that you get from BSD rm (used in Android 5) if you have a
+    // non-writable file and stdin is a tty (which is true for old versions of
+    // adbd).
+    //
+    // Unfortunately, `rm -f` requires Android 4.3, so that workaround broke
+    // earlier Android releases. This was reported as http://b/37704384 "adb
+    // install -r passes invalid argument to rm on Android 4.1" and
+    // http://b/37035817 "ADB Fails: rm failed for -f, No such file or
+    // directory".
+    //
+    // Testing on a variety of devices and emulators shows that redirecting
+    // stdin is sufficient to avoid the pseudo-`-i`, and works on toolbox,
+    // BSD, and toybox versions of rm.
+    return send_shell_command("rm " + escape_arg(filename) + " </dev/null");
 }
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 3c03eb2..fe2bdfe 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -153,6 +153,7 @@
         "     -d: allow version code downgrade (debuggable packages only)\n"
         "     -p: partial application install (install-multiple only)\n"
         "     -g: grant all runtime permissions\n"
+        "     --abi ABI: override platform's default ABI\n"
         "     --instant: cause the app to be installed as an ephemeral install app\n"
         "     --no-streaming: always push APK to device and invoke Package Manager as separate steps\n"
         "     --streaming: force streaming APK directly into Package Manager\n"
@@ -164,6 +165,7 @@
 #ifndef _WIN32
         "     --local-agent: locate agent files from local source build (instead of SDK location)\n"
 #endif
+        "     (See also `adb shell pm help` for more options.)\n"
         //TODO--installlog <filename>
         " uninstall [-k] PACKAGE\n"
         "     remove this app package from the device\n"
@@ -355,7 +357,7 @@
 }
 
 void copy_to_file(int inFd, int outFd) {
-    std::vector<char> buf(32 * 1024);
+    std::vector<char> buf(64 * 1024);
     int len;
     long total = 0;
     int old_stdin_mode = -1;
@@ -1702,10 +1704,23 @@
             error_exit("tcpip: invalid port: %s", argv[1]);
         }
         return adb_connect_command(android::base::StringPrintf("tcpip:%d", port));
+    } else if (!strcmp(argv[0], "remount")) {
+        FeatureSet features;
+        std::string error;
+        if (!adb_get_feature_set(&features, &error)) {
+            fprintf(stderr, "error: %s\n", error.c_str());
+            return 1;
+        }
+
+        if (CanUseFeature(features, kFeatureRemountShell)) {
+            const char* arg[2] = {"shell", "remount"};
+            return adb_shell(2, arg);
+        } else {
+            return adb_connect_command("remount:");
+        }
     }
     // clang-format off
-    else if (!strcmp(argv[0], "remount") ||
-             !strcmp(argv[0], "reboot") ||
+    else if (!strcmp(argv[0], "reboot") ||
              !strcmp(argv[0], "reboot-bootloader") ||
              !strcmp(argv[0], "reboot-fastboot") ||
              !strcmp(argv[0], "usb") ||
@@ -1737,41 +1752,33 @@
         // Determine the <host-prefix> for this command.
         std::string host_prefix;
         if (reverse) {
-            host_prefix = "reverse";
+            host_prefix = "reverse:";
         } else {
-            if (serial) {
-                host_prefix = android::base::StringPrintf("host-serial:%s", serial);
-            } else if (transport_type == kTransportUsb) {
-                host_prefix = "host-usb";
-            } else if (transport_type == kTransportLocal) {
-                host_prefix = "host-local";
-            } else {
-                host_prefix = "host";
-            }
+            host_prefix = "host:";
         }
 
         std::string cmd, error_message;
         if (strcmp(argv[0], "--list") == 0) {
             if (argc != 1) error_exit("--list doesn't take any arguments");
-            return adb_query_command(host_prefix + ":list-forward");
+            return adb_query_command(host_prefix + "list-forward");
         } else if (strcmp(argv[0], "--remove-all") == 0) {
             if (argc != 1) error_exit("--remove-all doesn't take any arguments");
-            cmd = host_prefix + ":killforward-all";
+            cmd = "killforward-all";
         } else if (strcmp(argv[0], "--remove") == 0) {
             // forward --remove <local>
             if (argc != 2) error_exit("--remove requires an argument");
-            cmd = host_prefix + ":killforward:" + argv[1];
+            cmd = std::string("killforward:") + argv[1];
         } else if (strcmp(argv[0], "--no-rebind") == 0) {
             // forward --no-rebind <local> <remote>
             if (argc != 3) error_exit("--no-rebind takes two arguments");
             if (forward_targets_are_valid(argv[1], argv[2], &error_message)) {
-                cmd = host_prefix + ":forward:norebind:" + argv[1] + ";" + argv[2];
+                cmd = std::string("forward:norebind:") + argv[1] + ";" + argv[2];
             }
         } else {
             // forward <local> <remote>
             if (argc != 2) error_exit("forward takes two arguments");
             if (forward_targets_are_valid(argv[0], argv[1], &error_message)) {
-                cmd = host_prefix + ":forward:" + argv[0] + ";" + argv[1];
+                cmd = std::string("forward:") + argv[0] + ";" + argv[1];
             }
         }
 
@@ -1779,7 +1786,7 @@
             error_exit("error: %s", error_message.c_str());
         }
 
-        unique_fd fd(adb_connect(cmd, &error_message));
+        unique_fd fd(adb_connect(nullptr, host_prefix + cmd, &error_message, true));
         if (fd < 0 || !adb_status(fd.get(), &error_message)) {
             error_exit("error: %s", error_message.c_str());
         }
diff --git a/adb/client/commandline.h b/adb/client/commandline.h
index 6cfd4f7..cd5933a 100644
--- a/adb/client/commandline.h
+++ b/adb/client/commandline.h
@@ -17,7 +17,11 @@
 #ifndef COMMANDLINE_H
 #define COMMANDLINE_H
 
+#include <android-base/strings.h>
+
 #include "adb.h"
+#include "adb_client.h"
+#include "adb_unique_fd.h"
 
 // Callback used to handle the standard streams (stdout and stderr) sent by the
 // device's upon receiving a command.
@@ -105,4 +109,17 @@
         const std::string& command, bool disable_shell_protocol = false,
         StandardStreamsCallbackInterface* callback = &DEFAULT_STANDARD_STREAMS_CALLBACK);
 
+// Connects to the device "abb" service with |command| and returns the fd.
+template <typename ContainerT>
+unique_fd send_abb_exec_command(const ContainerT& command_args, std::string* error) {
+    std::string service_string = "abb_exec:" + android::base::Join(command_args, ABB_ARG_DELIMETER);
+
+    unique_fd fd(adb_connect(service_string, error));
+    if (fd < 0) {
+        fprintf(stderr, "adb: failed to run abb_exec. Error: %s\n", error->c_str());
+        return unique_fd{};
+    }
+    return fd;
+}
+
 #endif  // COMMANDLINE_H
diff --git a/adb/daemon/abb.cpp b/adb/daemon/abb.cpp
index 425438e..17c25e8 100644
--- a/adb/daemon/abb.cpp
+++ b/adb/daemon/abb.cpp
@@ -24,6 +24,7 @@
 #include "adb_io.h"
 #include "adb_utils.h"
 #include "shell_service.h"
+#include "sysdeps.h"
 
 namespace {
 
@@ -69,6 +70,11 @@
 }  // namespace
 
 static int execCmd(std::string_view args, borrowed_fd in, borrowed_fd out, borrowed_fd err) {
+    int max_buf = LINUX_MAX_SOCKET_SIZE;
+    adb_setsockopt(in, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(max_buf));
+    adb_setsockopt(out, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(max_buf));
+    adb_setsockopt(err, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(max_buf));
+
     AdbFdTextOutput oin(out);
     AdbFdTextOutput oerr(err);
     return cmdMain(parseCmdArgs(args), oin, oerr, in.get(), out.get(), err.get(),
@@ -98,6 +104,8 @@
         }
 
         unique_fd result = StartCommandInProcess(std::string(name), &execCmd, protocol);
+        int max_buf = LINUX_MAX_SOCKET_SIZE;
+        adb_setsockopt(result, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(max_buf));
         if (android::base::SendFileDescriptors(fd, "", 1, result.get()) != 1) {
             PLOG(ERROR) << "Failed to send an inprocess fd for command: " << data;
             break;
diff --git a/adb/daemon/abb_service.cpp b/adb/daemon/abb_service.cpp
index a435279..e1df4a5 100644
--- a/adb/daemon/abb_service.cpp
+++ b/adb/daemon/abb_service.cpp
@@ -53,14 +53,13 @@
             return error_fd;
         }
 
-        if (!SendProtocolString(socket_fd_, std::string(command))) {
+        if (!SendProtocolString(socket_fd_, command)) {
             PLOG(ERROR) << "failed to send command to abb";
             socket_fd_.reset();
             continue;
         }
 
         unique_fd fd;
-        std::string error;
         char buf;
         if (android::base::ReceiveFileDescriptors(socket_fd_, &buf, 1, &fd) != 1) {
             PLOG(ERROR) << "failed to receive FD from abb";
diff --git a/adb/daemon/remount_service.cpp b/adb/daemon/remount_service.cpp
index ce494ee..6bd7855 100644
--- a/adb/daemon/remount_service.cpp
+++ b/adb/daemon/remount_service.cpp
@@ -48,6 +48,8 @@
         dup2(fd, STDERR_FILENO);
 
         execl(kRemountCmd, kRemountCmd, cmd.empty() ? nullptr : cmd.c_str(), nullptr);
+        const char* msg = "failed to exec remount\n";
+        write(STDERR_FILENO, msg, strlen(msg));
         _exit(errno);
     }
 
@@ -83,6 +85,6 @@
 }
 
 void remount_service(unique_fd fd, const std::string& cmd) {
-    const char* success = do_remount(fd.get(), cmd) ? "succeeded" : "failed";
-    WriteFdFmt(fd.get(), "remount %s\n", success);
+    do_remount(fd.get(), cmd);
+    // The remount command will print success or failure for us.
 }
diff --git a/adb/daemon/usb_ffs.cpp b/adb/daemon/usb_ffs.cpp
index a64ce40..338d776 100644
--- a/adb/daemon/usb_ffs.cpp
+++ b/adb/daemon/usb_ffs.cpp
@@ -299,7 +299,6 @@
         }
         // Signal only when writing the descriptors to ffs
         android::base::SetProperty("sys.usb.ffs.ready", "1");
-        *out_control = std::move(control);
     }
 
     bulk_out.reset(adb_open(USB_FFS_ADB_OUT, O_RDONLY));
@@ -314,6 +313,7 @@
         return false;
     }
 
+    *out_control = std::move(control);
     *out_bulk_in = std::move(bulk_in);
     *out_bulk_out = std::move(bulk_out);
     return true;
diff --git a/adb/fdevent/fdevent.cpp b/adb/fdevent/fdevent.cpp
index 28b8f37..562f587 100644
--- a/adb/fdevent/fdevent.cpp
+++ b/adb/fdevent/fdevent.cpp
@@ -26,16 +26,24 @@
 
 #include "adb_utils.h"
 #include "fdevent.h"
+#include "fdevent_epoll.h"
 #include "fdevent_poll.h"
 
+using namespace std::chrono_literals;
+using std::chrono::duration_cast;
+
+void invoke_fde(struct fdevent* fde, unsigned events) {
+    if (auto f = std::get_if<fd_func>(&fde->func)) {
+        (*f)(fde->fd.get(), events, fde->arg);
+    } else if (auto f = std::get_if<fd_func2>(&fde->func)) {
+        (*f)(fde, events, fde->arg);
+    } else {
+        __builtin_unreachable();
+    }
+}
+
 std::string dump_fde(const fdevent* fde) {
     std::string state;
-    if (fde->state & FDE_ACTIVE) {
-        state += "A";
-    }
-    if (fde->state & FDE_PENDING) {
-        state += "P";
-    }
     if (fde->state & FDE_READ) {
         state += "R";
     }
@@ -53,9 +61,11 @@
     CheckMainThread();
     CHECK_GE(fd.get(), 0);
 
+    int fd_num = fd.get();
+
     fdevent* fde = new fdevent();
     fde->id = fdevent_id_++;
-    fde->state = FDE_ACTIVE;
+    fde->state = 0;
     fde->fd = std::move(fd);
     fde->func = func;
     fde->arg = arg;
@@ -66,6 +76,10 @@
         LOG(ERROR) << "failed to set non-blocking mode for fd " << fde->fd.get();
     }
 
+    auto [it, inserted] = this->installed_fdevents_.emplace(fd_num, fde);
+    CHECK(inserted);
+    UNUSED(it);
+
     this->Register(fde);
     return fde;
 }
@@ -78,18 +92,22 @@
 
     this->Unregister(fde);
 
+    auto erased = this->installed_fdevents_.erase(fde->fd.get());
+    CHECK_EQ(1UL, erased);
+
     unique_fd result = std::move(fde->fd);
     delete fde;
     return result;
 }
 
 void fdevent_context::Add(fdevent* fde, unsigned events) {
-    Set(fde, (fde->state & FDE_EVENTMASK) | events);
+    CHECK(!(events & FDE_TIMEOUT));
+    Set(fde, fde->state | events);
 }
 
 void fdevent_context::Del(fdevent* fde, unsigned events) {
     CHECK(!(events & FDE_TIMEOUT));
-    Set(fde, (fde->state & FDE_EVENTMASK) & ~events);
+    Set(fde, fde->state & ~events);
 }
 
 void fdevent_context::SetTimeout(fdevent* fde, std::optional<std::chrono::milliseconds> timeout) {
@@ -98,6 +116,56 @@
     fde->last_active = std::chrono::steady_clock::now();
 }
 
+std::optional<std::chrono::milliseconds> fdevent_context::CalculatePollDuration() {
+    std::optional<std::chrono::milliseconds> result = std::nullopt;
+    auto now = std::chrono::steady_clock::now();
+    CheckMainThread();
+
+    for (const auto& [fd, fde] : this->installed_fdevents_) {
+        UNUSED(fd);
+        auto timeout_opt = fde->timeout;
+        if (timeout_opt) {
+            auto deadline = fde->last_active + *timeout_opt;
+            auto time_left = duration_cast<std::chrono::milliseconds>(deadline - now);
+            if (time_left < 0ms) {
+                time_left = 0ms;
+            }
+
+            if (!result) {
+                result = time_left;
+            } else {
+                result = std::min(*result, time_left);
+            }
+        }
+    }
+
+    return result;
+}
+
+void fdevent_context::HandleEvents(const std::vector<fdevent_event>& events) {
+    for (const auto& event : events) {
+        invoke_fde(event.fde, event.events);
+    }
+    FlushRunQueue();
+}
+
+void fdevent_context::FlushRunQueue() {
+    // We need to be careful around reentrancy here, since a function we call can queue up another
+    // function.
+    while (true) {
+        std::function<void()> fn;
+        {
+            std::lock_guard<std::mutex> lock(this->run_queue_mutex_);
+            if (this->run_queue_.empty()) {
+                break;
+            }
+            fn = std::move(this->run_queue_.front());
+            this->run_queue_.pop_front();
+        }
+        fn();
+    }
+}
+
 void fdevent_context::CheckMainThread() {
     if (main_thread_id_) {
         CHECK_EQ(*main_thread_id_, android::base::GetThreadId());
@@ -118,25 +186,16 @@
     Interrupt();
 }
 
-void fdevent_context::FlushRunQueue() {
-    // We need to be careful around reentrancy here, since a function we call can queue up another
-    // function.
-    while (true) {
-        std::function<void()> fn;
-        {
-            std::lock_guard<std::mutex> lock(this->run_queue_mutex_);
-            if (this->run_queue_.empty()) {
-                break;
-            }
-            fn = this->run_queue_.front();
-            this->run_queue_.pop_front();
-        }
-        fn();
-    }
+static std::unique_ptr<fdevent_context> fdevent_create_context() {
+#if defined(__linux__)
+    return std::make_unique<fdevent_context_epoll>();
+#else
+    return std::make_unique<fdevent_context_poll>();
+#endif
 }
 
 static auto& g_ambient_fdevent_context =
-        *new std::unique_ptr<fdevent_context>(new fdevent_context_poll());
+        *new std::unique_ptr<fdevent_context>(fdevent_create_context());
 
 static fdevent_context* fdevent_get_ambient() {
     return g_ambient_fdevent_context.get();
@@ -197,5 +256,5 @@
 }
 
 void fdevent_reset() {
-    g_ambient_fdevent_context.reset(new fdevent_context_poll());
+    g_ambient_fdevent_context = fdevent_create_context();
 }
diff --git a/adb/fdevent/fdevent.h b/adb/fdevent/fdevent.h
index ccb0c92..86814d7 100644
--- a/adb/fdevent/fdevent.h
+++ b/adb/fdevent/fdevent.h
@@ -20,11 +20,13 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <atomic>
 #include <chrono>
 #include <deque>
 #include <functional>
 #include <mutex>
 #include <optional>
+#include <unordered_map>
 #include <variant>
 
 #include <android-base/thread_annotations.h>
@@ -37,19 +39,19 @@
 #define FDE_ERROR 0x0004
 #define FDE_TIMEOUT 0x0008
 
-// Internal states.
-#define FDE_EVENTMASK  0x00ff
-#define FDE_STATEMASK  0xff00
-
-#define FDE_ACTIVE     0x0100
-#define FDE_PENDING    0x0200
+struct fdevent;
 
 typedef void (*fd_func)(int fd, unsigned events, void *userdata);
 typedef void (*fd_func2)(struct fdevent* fde, unsigned events, void* userdata);
 
-struct fdevent;
+void invoke_fde(struct fdevent* fde, unsigned events);
 std::string dump_fde(const fdevent* fde);
 
+struct fdevent_event {
+    fdevent* fde;
+    unsigned events;
+};
+
 struct fdevent_context {
   public:
     virtual ~fdevent_context() = default;
@@ -58,14 +60,13 @@
     fdevent* Create(unique_fd fd, std::variant<fd_func, fd_func2> func, void* arg);
 
     // Deallocate an fdevent object, returning the file descriptor that was owned by it.
+    // Note that this calls Set, which is a virtual method, so destructors that call this must be
+    // final.
     unique_fd Destroy(fdevent* fde);
 
   protected:
-    // Register an fdevent that is being created by Create with the fdevent_context.
-    virtual void Register(fdevent* fde) = 0;
-
-    // Unregister an fdevent that is being destroyed by Destroy with the fdevent_context.
-    virtual void Unregister(fdevent* fde) = 0;
+    virtual void Register(fdevent*) {}
+    virtual void Unregister(fdevent*) {}
 
   public:
     // Change which events should cause notifications.
@@ -79,6 +80,15 @@
     // trigger repeatedly every |timeout| ms.
     void SetTimeout(fdevent* fde, std::optional<std::chrono::milliseconds> timeout);
 
+  protected:
+    std::optional<std::chrono::milliseconds> CalculatePollDuration();
+    void HandleEvents(const std::vector<fdevent_event>& events);
+
+  private:
+    // Run all pending functions enqueued via Run().
+    void FlushRunQueue() EXCLUDES(run_queue_mutex_);
+
+  public:
     // Loop until TerminateLoop is called, handling events.
     // Implementations should call FlushRunQueue on every iteration, and check the value of
     // terminate_loop_ to determine whether to stop.
@@ -99,12 +109,12 @@
     // Interrupt the run loop.
     virtual void Interrupt() = 0;
 
-    // Run all pending functions enqueued via Run().
-    void FlushRunQueue() EXCLUDES(run_queue_mutex_);
-
     std::optional<uint64_t> main_thread_id_ = std::nullopt;
     std::atomic<bool> terminate_loop_ = false;
 
+  protected:
+    std::unordered_map<int, fdevent*> installed_fdevents_;
+
   private:
     uint64_t fdevent_id_ = 0;
     std::mutex run_queue_mutex_;
@@ -118,7 +128,6 @@
     int force_eof = 0;
 
     uint16_t state = 0;
-    uint16_t events = 0;
     std::optional<std::chrono::milliseconds> timeout;
     std::chrono::steady_clock::time_point last_active;
 
diff --git a/adb/fdevent/fdevent_epoll.cpp b/adb/fdevent/fdevent_epoll.cpp
new file mode 100644
index 0000000..e3d1674
--- /dev/null
+++ b/adb/fdevent/fdevent_epoll.cpp
@@ -0,0 +1,200 @@
+/*
+ * 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 "fdevent_epoll.h"
+
+#if defined(__linux__)
+
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+
+#include <android-base/logging.h>
+#include <android-base/threads.h>
+
+#include "adb_unique_fd.h"
+#include "fdevent.h"
+
+static void fdevent_interrupt(int fd, unsigned, void*) {
+    uint64_t buf;
+    ssize_t rc = TEMP_FAILURE_RETRY(adb_read(fd, &buf, sizeof(buf)));
+    if (rc == -1) {
+        PLOG(FATAL) << "failed to read from fdevent interrupt fd";
+    }
+}
+
+fdevent_context_epoll::fdevent_context_epoll() {
+    epoll_fd_.reset(epoll_create1(EPOLL_CLOEXEC));
+
+    unique_fd interrupt_fd(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
+    if (interrupt_fd == -1) {
+        PLOG(FATAL) << "failed to create fdevent interrupt eventfd";
+    }
+
+    unique_fd interrupt_fd_dup(fcntl(interrupt_fd.get(), F_DUPFD_CLOEXEC, 3));
+    if (interrupt_fd_dup == -1) {
+        PLOG(FATAL) << "failed to dup fdevent interrupt eventfd";
+    }
+
+    this->interrupt_fd_ = std::move(interrupt_fd_dup);
+    fdevent* fde = this->Create(std::move(interrupt_fd), fdevent_interrupt, nullptr);
+    CHECK(fde != nullptr);
+    this->Add(fde, FDE_READ);
+}
+
+fdevent_context_epoll::~fdevent_context_epoll() {
+    // Destroy calls virtual methods, but this class is final, so that's okay.
+    this->Destroy(this->interrupt_fde_);
+}
+
+static epoll_event calculate_epoll_event(fdevent* fde) {
+    epoll_event result;
+    result.events = 0;
+    if (fde->state & FDE_READ) {
+        result.events |= EPOLLIN;
+    }
+    if (fde->state & FDE_WRITE) {
+        result.events |= EPOLLOUT;
+    }
+    if (fde->state & FDE_ERROR) {
+        result.events |= EPOLLERR;
+    }
+    result.events |= EPOLLRDHUP;
+    result.data.ptr = fde;
+    return result;
+}
+
+void fdevent_context_epoll::Register(fdevent* fde) {
+    epoll_event ev = calculate_epoll_event(fde);
+    if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_ADD, fde->fd.get(), &ev) != 0) {
+        PLOG(FATAL) << "failed to register fd " << fde->fd.get() << " with epoll";
+    }
+}
+
+void fdevent_context_epoll::Unregister(fdevent* fde) {
+    if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_DEL, fde->fd.get(), nullptr) != 0) {
+        PLOG(FATAL) << "failed to unregister fd " << fde->fd.get() << " with epoll";
+    }
+}
+
+void fdevent_context_epoll::Set(fdevent* fde, unsigned events) {
+    unsigned previous_state = fde->state;
+    fde->state = events;
+
+    // If the state is the same, or only differed by FDE_TIMEOUT, we don't need to modify epoll.
+    if ((previous_state & ~FDE_TIMEOUT) == (events & ~FDE_TIMEOUT)) {
+        return;
+    }
+
+    epoll_event ev = calculate_epoll_event(fde);
+    if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_MOD, fde->fd.get(), &ev) != 0) {
+        PLOG(FATAL) << "failed to modify fd " << fde->fd.get() << " with epoll";
+    }
+}
+
+void fdevent_context_epoll::Loop() {
+    main_thread_id_ = android::base::GetThreadId();
+
+    std::vector<fdevent_event> fde_events;
+    std::vector<epoll_event> epoll_events;
+    epoll_events.resize(this->installed_fdevents_.size());
+
+    while (true) {
+        if (terminate_loop_) {
+            break;
+        }
+
+        int rc = -1;
+        while (rc == -1) {
+            std::optional<std::chrono::milliseconds> timeout = CalculatePollDuration();
+            int timeout_ms;
+            if (!timeout) {
+                timeout_ms = -1;
+            } else {
+                timeout_ms = timeout->count();
+            }
+
+            rc = epoll_wait(epoll_fd_.get(), epoll_events.data(), epoll_events.size(), timeout_ms);
+            if (rc == -1 && errno != EINTR) {
+                PLOG(FATAL) << "epoll_wait failed";
+            }
+        }
+
+        auto post_poll = std::chrono::steady_clock::now();
+        std::unordered_map<fdevent*, unsigned> event_map;
+        for (int i = 0; i < rc; ++i) {
+            fdevent* fde = static_cast<fdevent*>(epoll_events[i].data.ptr);
+
+            unsigned events = 0;
+            if (epoll_events[i].events & EPOLLIN) {
+                CHECK(fde->state & FDE_READ);
+                events |= FDE_READ;
+            }
+            if (epoll_events[i].events & EPOLLOUT) {
+                CHECK(fde->state & FDE_WRITE);
+                events |= FDE_WRITE;
+            }
+            if (epoll_events[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) {
+                // We fake a read, as the rest of the code assumes that errors will
+                // be detected at that point.
+                events |= FDE_READ | FDE_ERROR;
+            }
+
+            event_map[fde] = events;
+        }
+
+        for (const auto& [fd, fde] : installed_fdevents_) {
+            unsigned events = 0;
+            if (auto it = event_map.find(fde); it != event_map.end()) {
+                events = it->second;
+            }
+
+            if (events == 0) {
+                if (fde->timeout) {
+                    auto deadline = fde->last_active + *fde->timeout;
+                    if (deadline < post_poll) {
+                        events |= FDE_TIMEOUT;
+                    }
+                }
+            }
+
+            if (events != 0) {
+                LOG(DEBUG) << dump_fde(fde) << " got events " << std::hex << std::showbase
+                           << events;
+                fde_events.push_back({fde, events});
+                fde->last_active = post_poll;
+            }
+        }
+        this->HandleEvents(std::move(fde_events));
+        fde_events.clear();
+    }
+
+    main_thread_id_.reset();
+}
+
+size_t fdevent_context_epoll::InstalledCount() {
+    // We always have an installed fde for interrupt.
+    return this->installed_fdevents_.size() - 1;
+}
+
+void fdevent_context_epoll::Interrupt() {
+    uint64_t i = 1;
+    ssize_t rc = TEMP_FAILURE_RETRY(adb_write(this->interrupt_fd_, &i, sizeof(i)));
+    if (rc != sizeof(i)) {
+        PLOG(FATAL) << "failed to write to fdevent interrupt eventfd";
+    }
+}
+
+#endif  // defined(__linux__)
diff --git a/adb/fdevent/fdevent_epoll.h b/adb/fdevent/fdevent_epoll.h
new file mode 100644
index 0000000..684fa32
--- /dev/null
+++ b/adb/fdevent/fdevent_epoll.h
@@ -0,0 +1,61 @@
+#pragma once
+
+/*
+ * 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(__linux__)
+
+#include "sysdeps.h"
+
+#include <sys/epoll.h>
+
+#include <deque>
+#include <list>
+#include <mutex>
+#include <unordered_map>
+
+#include <android-base/thread_annotations.h>
+
+#include "adb_unique_fd.h"
+#include "fdevent.h"
+
+struct fdevent_context_epoll final : public fdevent_context {
+    fdevent_context_epoll();
+    virtual ~fdevent_context_epoll();
+
+    virtual void Register(fdevent* fde) final;
+    virtual void Unregister(fdevent* fde) final;
+
+    virtual void Set(fdevent* fde, unsigned events) final;
+
+    virtual void Loop() final;
+    size_t InstalledCount() final;
+
+  protected:
+    virtual void Interrupt() final;
+
+  public:
+    // All operations to fdevent should happen only in the main thread.
+    // That's why we don't need a lock for fdevent.
+    std::unordered_map<int, fdevent*> epoll_node_map_;
+    std::list<fdevent*> pending_list_;
+
+    unique_fd epoll_fd_;
+    unique_fd interrupt_fd_;
+    fdevent* interrupt_fde_ = nullptr;
+};
+
+#endif  // defined(__linux__)
diff --git a/adb/fdevent/fdevent_poll.cpp b/adb/fdevent/fdevent_poll.cpp
index 75ea081..cc4a7a1 100644
--- a/adb/fdevent/fdevent_poll.cpp
+++ b/adb/fdevent/fdevent_poll.cpp
@@ -75,60 +75,14 @@
 }
 
 fdevent_context_poll::~fdevent_context_poll() {
+    // Destroy calls virtual methods, but this class is final, so that's okay.
     this->Destroy(this->interrupt_fde_);
 }
 
-void fdevent_context_poll::Register(fdevent* fde) {
-    auto pair = poll_node_map_.emplace(fde->fd.get(), PollNode(fde));
-    CHECK(pair.second) << "install existing fd " << fde->fd.get();
-}
-
-void fdevent_context_poll::Unregister(fdevent* fde) {
-    if (fde->state & FDE_ACTIVE) {
-        poll_node_map_.erase(fde->fd.get());
-
-        if (fde->state & FDE_PENDING) {
-            pending_list_.remove(fde);
-        }
-        fde->state = 0;
-        fde->events = 0;
-    }
-}
-
 void fdevent_context_poll::Set(fdevent* fde, unsigned events) {
     CheckMainThread();
-    events &= FDE_EVENTMASK;
-    if ((fde->state & FDE_EVENTMASK) == events) {
-        return;
-    }
-    CHECK(fde->state & FDE_ACTIVE);
-
-    auto it = poll_node_map_.find(fde->fd.get());
-    CHECK(it != poll_node_map_.end());
-    PollNode& node = it->second;
-    if (events & FDE_READ) {
-        node.pollfd.events |= POLLIN;
-    } else {
-        node.pollfd.events &= ~POLLIN;
-    }
-
-    if (events & FDE_WRITE) {
-        node.pollfd.events |= POLLOUT;
-    } else {
-        node.pollfd.events &= ~POLLOUT;
-    }
-    fde->state = (fde->state & FDE_STATEMASK) | events;
-
+    fde->state = events;
     D("fdevent_set: %s, events = %u", dump_fde(fde).c_str(), events);
-
-    if (fde->state & FDE_PENDING) {
-        // If we are pending, make sure we don't signal an event that is no longer wanted.
-        fde->events &= events;
-        if (fde->events == 0) {
-            pending_list_.remove(fde);
-            fde->state &= ~FDE_PENDING;
-        }
-    }
 }
 
 static std::string dump_pollfds(const std::vector<adb_pollfd>& pollfds) {
@@ -146,204 +100,94 @@
     return result;
 }
 
-static std::optional<std::chrono::milliseconds> calculate_timeout(fdevent_context_poll* ctx) {
-    std::optional<std::chrono::milliseconds> result = std::nullopt;
-    auto now = std::chrono::steady_clock::now();
-    ctx->CheckMainThread();
-
-    for (const auto& [fd, pollnode] : ctx->poll_node_map_) {
-        UNUSED(fd);
-        auto timeout_opt = pollnode.fde->timeout;
-        if (timeout_opt) {
-            auto deadline = pollnode.fde->last_active + *timeout_opt;
-            auto time_left = std::chrono::duration_cast<std::chrono::milliseconds>(deadline - now);
-            if (time_left < std::chrono::milliseconds::zero()) {
-                time_left = std::chrono::milliseconds::zero();
-            }
-
-            if (!result) {
-                result = time_left;
-            } else {
-                result = std::min(*result, time_left);
-            }
-        }
-    }
-
-    return result;
-}
-
-static void fdevent_process(fdevent_context_poll* ctx) {
-    std::vector<adb_pollfd> pollfds;
-    for (const auto& pair : ctx->poll_node_map_) {
-        pollfds.push_back(pair.second.pollfd);
-    }
-    CHECK_GT(pollfds.size(), 0u);
-    D("poll(), pollfds = %s", dump_pollfds(pollfds).c_str());
-
-    auto timeout = calculate_timeout(ctx);
-    int timeout_ms;
-    if (!timeout) {
-        timeout_ms = -1;
-    } else {
-        timeout_ms = timeout->count();
-    }
-
-    int ret = adb_poll(&pollfds[0], pollfds.size(), timeout_ms);
-    if (ret == -1) {
-        PLOG(ERROR) << "poll(), ret = " << ret;
-        return;
-    }
-
-    auto post_poll = std::chrono::steady_clock::now();
-
-    for (const auto& pollfd : pollfds) {
-        if (pollfd.revents != 0) {
-            D("for fd %d, revents = %x", pollfd.fd, pollfd.revents);
-        }
-        unsigned events = 0;
-        if (pollfd.revents & POLLIN) {
-            events |= FDE_READ;
-        }
-        if (pollfd.revents & POLLOUT) {
-            events |= FDE_WRITE;
-        }
-        if (pollfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
-            // We fake a read, as the rest of the code assumes that errors will
-            // be detected at that point.
-            events |= FDE_READ | FDE_ERROR;
-        }
-#if defined(__linux__)
-        if (pollfd.revents & POLLRDHUP) {
-            events |= FDE_READ | FDE_ERROR;
-        }
-#endif
-        auto it = ctx->poll_node_map_.find(pollfd.fd);
-        CHECK(it != ctx->poll_node_map_.end());
-        fdevent* fde = it->second.fde;
-
-        if (events == 0) {
-            // Check for timeout.
-            if (fde->timeout) {
-                auto deadline = fde->last_active + *fde->timeout;
-                if (deadline < post_poll) {
-                    events |= FDE_TIMEOUT;
-                }
-            }
-        }
-
-        if (events != 0) {
-            CHECK_EQ(fde->fd.get(), pollfd.fd);
-            fde->events |= events;
-            fde->last_active = post_poll;
-            D("%s got events %x", dump_fde(fde).c_str(), events);
-            fde->state |= FDE_PENDING;
-            ctx->pending_list_.push_back(fde);
-        }
-    }
-}
-
-template <class T>
-struct always_false : std::false_type {};
-
-static void fdevent_call_fdfunc(fdevent* fde) {
-    unsigned events = fde->events;
-    fde->events = 0;
-    CHECK(fde->state & FDE_PENDING);
-    fde->state &= (~FDE_PENDING);
-    D("fdevent_call_fdfunc %s", dump_fde(fde).c_str());
-    std::visit(
-            [&](auto&& f) {
-                using F = std::decay_t<decltype(f)>;
-                if constexpr (std::is_same_v<fd_func, F>) {
-                    f(fde->fd.get(), events, fde->arg);
-                } else if constexpr (std::is_same_v<fd_func2, F>) {
-                    f(fde, events, fde->arg);
-                } else {
-                    static_assert(always_false<F>::value, "non-exhaustive visitor");
-                }
-            },
-            fde->func);
-}
-
-static void fdevent_check_spin(fdevent_context_poll* ctx, uint64_t cycle) {
-    // Check to see if we're spinning because we forgot about an fdevent
-    // by keeping track of how long fdevents have been continuously pending.
-    struct SpinCheck {
-        fdevent* fde;
-        android::base::boot_clock::time_point timestamp;
-        uint64_t cycle;
-    };
-
-    // TODO: Move this into the base fdevent_context.
-    static auto& g_continuously_pending = *new std::unordered_map<uint64_t, SpinCheck>();
-    static auto last_cycle = android::base::boot_clock::now();
-
-    auto now = android::base::boot_clock::now();
-    if (now - last_cycle > 10ms) {
-        // We're not spinning.
-        g_continuously_pending.clear();
-        last_cycle = now;
-        return;
-    }
-    last_cycle = now;
-
-    for (auto* fde : ctx->pending_list_) {
-        auto it = g_continuously_pending.find(fde->id);
-        if (it == g_continuously_pending.end()) {
-            g_continuously_pending[fde->id] =
-                    SpinCheck{.fde = fde, .timestamp = now, .cycle = cycle};
-        } else {
-            it->second.cycle = cycle;
-        }
-    }
-
-    for (auto it = g_continuously_pending.begin(); it != g_continuously_pending.end();) {
-        if (it->second.cycle != cycle) {
-            it = g_continuously_pending.erase(it);
-        } else {
-            // Use an absurdly long window, since all we really care about is
-            // getting a bugreport eventually.
-            if (now - it->second.timestamp > 300s) {
-                LOG(FATAL_WITHOUT_ABORT)
-                        << "detected spin in fdevent: " << dump_fde(it->second.fde);
-#if defined(__linux__)
-                int fd = it->second.fde->fd.get();
-                std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
-                std::string path;
-                if (!android::base::Readlink(fd_path, &path)) {
-                    PLOG(FATAL_WITHOUT_ABORT) << "readlink of fd " << fd << " failed";
-                }
-                LOG(FATAL_WITHOUT_ABORT) << "fd " << fd << " = " << path;
-#endif
-                abort();
-            }
-            ++it;
-        }
-    }
-}
-
 void fdevent_context_poll::Loop() {
     main_thread_id_ = android::base::GetThreadId();
 
-    uint64_t cycle = 0;
     while (true) {
         if (terminate_loop_) {
             break;
         }
 
         D("--- --- waiting for events");
+        std::vector<adb_pollfd> pollfds;
+        for (const auto& [fd, fde] : this->installed_fdevents_) {
+            adb_pollfd pfd;
+            pfd.fd = fd;
+            pfd.events = 0;
+            if (fde->state & FDE_READ) {
+                pfd.events |= POLLIN;
+            }
+            if (fde->state & FDE_WRITE) {
+                pfd.events |= POLLOUT;
+            }
+            if (fde->state & FDE_ERROR) {
+                pfd.events |= POLLERR;
+            }
+#if defined(__linux__)
+            pfd.events |= POLLRDHUP;
+#endif
+            pfd.revents = 0;
+            pollfds.push_back(pfd);
+        }
+        CHECK_GT(pollfds.size(), 0u);
+        D("poll(), pollfds = %s", dump_pollfds(pollfds).c_str());
 
-        fdevent_process(this);
-
-        fdevent_check_spin(this, cycle++);
-
-        while (!pending_list_.empty()) {
-            fdevent* fde = pending_list_.front();
-            pending_list_.pop_front();
-            fdevent_call_fdfunc(fde);
+        std::optional<std::chrono::milliseconds> timeout = CalculatePollDuration();
+        int timeout_ms;
+        if (!timeout) {
+            timeout_ms = -1;
+        } else {
+            timeout_ms = timeout->count();
         }
 
-        this->FlushRunQueue();
+        int ret = adb_poll(pollfds.data(), pollfds.size(), timeout_ms);
+        if (ret == -1) {
+            PLOG(ERROR) << "poll(), ret = " << ret;
+            return;
+        }
+
+        auto post_poll = std::chrono::steady_clock::now();
+        std::vector<fdevent_event> poll_events;
+
+        for (const auto& pollfd : pollfds) {
+            unsigned events = 0;
+            if (pollfd.revents & POLLIN) {
+                events |= FDE_READ;
+            }
+            if (pollfd.revents & POLLOUT) {
+                events |= FDE_WRITE;
+            }
+            if (pollfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
+                // We fake a read, as the rest of the code assumes that errors will
+                // be detected at that point.
+                events |= FDE_READ | FDE_ERROR;
+            }
+#if defined(__linux__)
+            if (pollfd.revents & POLLRDHUP) {
+                events |= FDE_READ | FDE_ERROR;
+            }
+#endif
+
+            auto it = this->installed_fdevents_.find(pollfd.fd);
+            CHECK(it != this->installed_fdevents_.end());
+            fdevent* fde = it->second;
+
+            if (events == 0) {
+                if (fde->timeout) {
+                    auto deadline = fde->last_active + *fde->timeout;
+                    if (deadline < post_poll) {
+                        events |= FDE_TIMEOUT;
+                    }
+                }
+            }
+
+            if (events != 0) {
+                D("%s got events %x", dump_fde(fde).c_str(), events);
+                poll_events.push_back({fde, events});
+                fde->last_active = post_poll;
+            }
+        }
+        this->HandleEvents(std::move(poll_events));
     }
 
     main_thread_id_.reset();
@@ -351,7 +195,7 @@
 
 size_t fdevent_context_poll::InstalledCount() {
     // We always have an installed fde for interrupt.
-    return poll_node_map_.size() - 1;
+    return this->installed_fdevents_.size() - 1;
 }
 
 void fdevent_context_poll::Interrupt() {
diff --git a/adb/fdevent/fdevent_poll.h b/adb/fdevent/fdevent_poll.h
index db08301..98abab2 100644
--- a/adb/fdevent/fdevent_poll.h
+++ b/adb/fdevent/fdevent_poll.h
@@ -44,13 +44,10 @@
   }
 };
 
-struct fdevent_context_poll : public fdevent_context {
+struct fdevent_context_poll final : public fdevent_context {
     fdevent_context_poll();
     virtual ~fdevent_context_poll();
 
-    virtual void Register(fdevent* fde) final;
-    virtual void Unregister(fdevent* fde) final;
-
     virtual void Set(fdevent* fde, unsigned events) final;
 
     virtual void Loop() final;
@@ -61,11 +58,6 @@
     virtual void Interrupt() final;
 
   public:
-    // All operations to fdevent should happen only in the main thread.
-    // That's why we don't need a lock for fdevent.
-    std::unordered_map<int, PollNode> poll_node_map_;
-    std::list<fdevent*> pending_list_;
-
     unique_fd interrupt_fd_;
     fdevent* interrupt_fde_ = nullptr;
 };
diff --git a/adb/fdevent/fdevent_test.cpp b/adb/fdevent/fdevent_test.cpp
index 682f061..e06b3b3 100644
--- a/adb/fdevent/fdevent_test.cpp
+++ b/adb/fdevent/fdevent_test.cpp
@@ -118,8 +118,8 @@
 TEST_F(FdeventTest, smoke) {
     for (bool use_new_callback : {true, false}) {
         fdevent_reset();
-        const size_t PIPE_COUNT = 10;
-        const size_t MESSAGE_LOOP_COUNT = 100;
+        const size_t PIPE_COUNT = 512;
+        const size_t MESSAGE_LOOP_COUNT = 10;
         const std::string MESSAGE = "fdevent_test";
         int fd_pair1[2];
         int fd_pair2[2];
@@ -172,44 +172,6 @@
     }
 }
 
-struct InvalidFdArg {
-    fdevent* fde;
-    unsigned expected_events;
-    size_t* happened_event_count;
-};
-
-static void InvalidFdEventCallback(int, unsigned events, void* userdata) {
-    InvalidFdArg* arg = reinterpret_cast<InvalidFdArg*>(userdata);
-    ASSERT_EQ(arg->expected_events, events);
-    fdevent_destroy(arg->fde);
-    if (++*(arg->happened_event_count) == 2) {
-        fdevent_terminate_loop();
-    }
-}
-
-static void InvalidFdThreadFunc() {
-    const int INVALID_READ_FD = std::numeric_limits<int>::max() - 1;
-    size_t happened_event_count = 0;
-    InvalidFdArg read_arg;
-    read_arg.expected_events = FDE_READ | FDE_ERROR;
-    read_arg.happened_event_count = &happened_event_count;
-    read_arg.fde = fdevent_create(INVALID_READ_FD, InvalidFdEventCallback, &read_arg);
-    fdevent_add(read_arg.fde, FDE_READ);
-
-    const int INVALID_WRITE_FD = std::numeric_limits<int>::max();
-    InvalidFdArg write_arg;
-    write_arg.expected_events = FDE_READ | FDE_ERROR;
-    write_arg.happened_event_count = &happened_event_count;
-    write_arg.fde = fdevent_create(INVALID_WRITE_FD, InvalidFdEventCallback, &write_arg);
-    fdevent_add(write_arg.fde, FDE_WRITE);
-    fdevent_loop();
-}
-
-TEST_F(FdeventTest, invalid_fd) {
-    std::thread thread(InvalidFdThreadFunc);
-    thread.join();
-}
-
 TEST_F(FdeventTest, run_on_main_thread) {
     std::vector<int> vec;
 
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index b0e7fa0..987f994 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -33,6 +33,7 @@
 // Include this before open/close/unlink are defined as macros below.
 #include <android-base/errors.h>
 #include <android-base/macros.h>
+#include <android-base/off64_t.h>
 #include <android-base/unique_fd.h>
 #include <android-base/utf8.h>
 
@@ -91,11 +92,14 @@
 extern int adb_open(const char* path, int options);
 extern int adb_creat(const char* path, int mode);
 extern int adb_read(borrowed_fd fd, void* buf, int len);
+extern int adb_pread(borrowed_fd fd, void* buf, int len, off64_t offset);
 extern int adb_write(borrowed_fd fd, const void* buf, int len);
+extern int adb_pwrite(borrowed_fd fd, const void* buf, int len, off64_t offset);
 extern int64_t adb_lseek(borrowed_fd fd, int64_t pos, int where);
 extern int adb_shutdown(borrowed_fd fd, int direction = SHUT_RDWR);
 extern int adb_close(int fd);
 extern int adb_register_socket(SOCKET s);
+extern HANDLE adb_get_os_handle(borrowed_fd fd);
 
 // See the comments for the !defined(_WIN32) version of unix_close().
 static __inline__ int unix_close(int fd) {
@@ -115,6 +119,9 @@
 #undef   read
 #define  read  ___xxx_read
 
+#undef pread
+#define pread ___xxx_pread
+
 // See the comments for the !defined(_WIN32) version of unix_write().
 static __inline__ int unix_write(borrowed_fd fd, const void* buf, size_t len) {
     return write(fd.get(), buf, len);
@@ -122,6 +129,9 @@
 #undef   write
 #define  write  ___xxx_write
 
+#undef pwrite
+#define pwrite ___xxx_pwrite
+
 // See the comments for the !defined(_WIN32) version of unix_lseek().
 static __inline__ int unix_lseek(borrowed_fd fd, int pos, int where) {
     return lseek(fd.get(), pos, where);
@@ -415,6 +425,14 @@
     return TEMP_FAILURE_RETRY(read(fd.get(), buf, len));
 }
 
+static __inline__ int adb_pread(int fd, void* buf, size_t len, off64_t offset) {
+#if defined(__APPLE__)
+    return TEMP_FAILURE_RETRY(pread(fd, buf, len, offset));
+#else
+    return TEMP_FAILURE_RETRY(pread64(fd, buf, len, offset));
+#endif
+}
+
 // Like unix_read(), but does not handle EINTR.
 static __inline__ int unix_read_interruptible(borrowed_fd fd, void* buf, size_t len) {
     return read(fd.get(), buf, len);
@@ -422,12 +440,25 @@
 
 #undef read
 #define read ___xxx_read
+#undef pread
+#define pread ___xxx_pread
 
 static __inline__ int adb_write(borrowed_fd fd, const void* buf, size_t len) {
     return TEMP_FAILURE_RETRY(write(fd.get(), buf, len));
 }
+
+static __inline__ int adb_pwrite(int fd, const void* buf, size_t len, off64_t offset) {
+#if defined(__APPLE__)
+    return TEMP_FAILURE_RETRY(pwrite(fd, buf, len, offset));
+#else
+    return TEMP_FAILURE_RETRY(pwrite64(fd, buf, len, offset));
+#endif
+}
+
 #undef   write
 #define  write  ___xxx_write
+#undef pwrite
+#define pwrite ___xxx_pwrite
 
 static __inline__ int64_t adb_lseek(borrowed_fd fd, int64_t pos, int where) {
 #if defined(__APPLE__)
diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp
index 6372b3d..4d6cf3d 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -60,6 +60,7 @@
     int (*_fh_read)(FH, void*, int);
     int (*_fh_write)(FH, const void*, int);
     int (*_fh_writev)(FH, const adb_iovec*, int);
+    intptr_t (*_fh_get_os_handle)(FH);
 } FHClassRec;
 
 static void _fh_file_init(FH);
@@ -68,14 +69,11 @@
 static int _fh_file_read(FH, void*, int);
 static int _fh_file_write(FH, const void*, int);
 static int _fh_file_writev(FH, const adb_iovec*, int);
+static intptr_t _fh_file_get_os_handle(FH f);
 
 static const FHClassRec _fh_file_class = {
-    _fh_file_init,
-    _fh_file_close,
-    _fh_file_lseek,
-    _fh_file_read,
-    _fh_file_write,
-    _fh_file_writev,
+        _fh_file_init,  _fh_file_close,  _fh_file_lseek,         _fh_file_read,
+        _fh_file_write, _fh_file_writev, _fh_file_get_os_handle,
 };
 
 static void _fh_socket_init(FH);
@@ -84,14 +82,11 @@
 static int _fh_socket_read(FH, void*, int);
 static int _fh_socket_write(FH, const void*, int);
 static int _fh_socket_writev(FH, const adb_iovec*, int);
+static intptr_t _fh_socket_get_os_handle(FH f);
 
 static const FHClassRec _fh_socket_class = {
-    _fh_socket_init,
-    _fh_socket_close,
-    _fh_socket_lseek,
-    _fh_socket_read,
-    _fh_socket_write,
-    _fh_socket_writev,
+        _fh_socket_init,  _fh_socket_close,  _fh_socket_lseek,         _fh_socket_read,
+        _fh_socket_write, _fh_socket_writev, _fh_socket_get_os_handle,
 };
 
 #if defined(assert)
@@ -331,6 +326,10 @@
     return li.QuadPart;
 }
 
+static intptr_t _fh_file_get_os_handle(FH f) {
+    return reinterpret_cast<intptr_t>(f->u.handle);
+}
+
 /**************************************************************************/
 /**************************************************************************/
 /*****                                                                *****/
@@ -456,6 +455,26 @@
     return f->clazz->_fh_read(f, buf, len);
 }
 
+int adb_pread(borrowed_fd fd, void* buf, int len, off64_t offset) {
+    OVERLAPPED overlapped = {};
+    overlapped.Offset = static_cast<DWORD>(offset);
+    overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
+    DWORD bytes_read;
+    if (!::ReadFile(adb_get_os_handle(fd), buf, static_cast<DWORD>(len), &bytes_read,
+                    &overlapped)) {
+        D("adb_pread: could not read %d bytes from FD %d", len, fd.get());
+        switch (::GetLastError()) {
+            case ERROR_IO_PENDING:
+                errno = EAGAIN;
+                return -1;
+            default:
+                errno = EINVAL;
+                return -1;
+        }
+    }
+    return static_cast<int>(bytes_read);
+}
+
 int adb_write(borrowed_fd fd, const void* buf, int len) {
     FH f = _fh_from_int(fd, __func__);
 
@@ -478,6 +497,25 @@
     return f->clazz->_fh_writev(f, iov, iovcnt);
 }
 
+int adb_pwrite(borrowed_fd fd, const void* buf, int len, off64_t offset) {
+    OVERLAPPED params = {};
+    params.Offset = static_cast<DWORD>(offset);
+    params.OffsetHigh = static_cast<DWORD>(offset >> 32);
+    DWORD bytes_written = 0;
+    if (!::WriteFile(adb_get_os_handle(fd), buf, len, &bytes_written, &params)) {
+        D("adb_pwrite: could not write %d bytes to FD %d", len, fd.get());
+        switch (::GetLastError()) {
+            case ERROR_IO_PENDING:
+                errno = EAGAIN;
+                return -1;
+            default:
+                errno = EINVAL;
+                return -1;
+        }
+    }
+    return static_cast<int>(bytes_written);
+}
+
 int64_t adb_lseek(borrowed_fd fd, int64_t pos, int where) {
     FH f = _fh_from_int(fd, __func__);
     if (!f) {
@@ -500,6 +538,20 @@
     return 0;
 }
 
+HANDLE adb_get_os_handle(borrowed_fd fd) {
+    FH f = _fh_from_int(fd, __func__);
+
+    if (!f) {
+        errno = EBADF;
+        return nullptr;
+    }
+
+    D("adb_get_os_handle: %s", f->name);
+    const intptr_t intptr_handle = f->clazz->_fh_get_os_handle(f);
+    const HANDLE handle = reinterpret_cast<const HANDLE>(intptr_handle);
+    return handle;
+}
+
 /**************************************************************************/
 /**************************************************************************/
 /*****                                                                *****/
@@ -688,12 +740,16 @@
               android::base::SystemErrorCodeToString(err).c_str());
         }
         _socket_set_errno(err);
-        result = -1;
+        return -1;
     }
     CHECK_GE(static_cast<DWORD>(std::numeric_limits<int>::max()), bytes_written);
     return static_cast<int>(bytes_written);
 }
 
+static intptr_t _fh_socket_get_os_handle(FH f) {
+    return f->u.socket;
+}
+
 /**************************************************************************/
 /**************************************************************************/
 /*****                                                                *****/
diff --git a/adb/transport.cpp b/adb/transport.cpp
index 3d1d620..d9749ac 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -73,6 +73,7 @@
 const char* const kFeatureAbb = "abb";
 const char* const kFeatureFixedPushSymlinkTimestamp = "fixed_push_symlink_timestamp";
 const char* const kFeatureAbbExec = "abb_exec";
+const char* const kFeatureRemountShell = "remount_shell";
 
 namespace {
 
@@ -1049,6 +1050,7 @@
             kFeatureAbb,
             kFeatureFixedPushSymlinkTimestamp,
             kFeatureAbbExec,
+            kFeatureRemountShell,
             // Increment ADB_SERVER_VERSION when adding a feature that adbd needs
             // to know about. Otherwise, the client can be stuck running an old
             // version of the server even after upgrading their copy of adb.
diff --git a/adb/transport.h b/adb/transport.h
index f4490ed..245037e 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -69,6 +69,7 @@
 extern const char* const kFeatureAbb;
 // adbd properly updates symlink timestamps on push.
 extern const char* const kFeatureFixedPushSymlinkTimestamp;
+extern const char* const kFeatureRemountShell;
 
 TransportId NextTransportId();
 
diff --git a/adb/types.h b/adb/types.h
index cd1366d..8bd66be 100644
--- a/adb/types.h
+++ b/adb/types.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <string.h>
+
 #include <algorithm>
 #include <deque>
 #include <memory>
diff --git a/base/errors_unix.cpp b/base/errors_unix.cpp
index 296995e..48269b6 100644
--- a/base/errors_unix.cpp
+++ b/base/errors_unix.cpp
@@ -17,6 +17,7 @@
 #include "android-base/errors.h"
 
 #include <errno.h>
+#include <string.h>
 
 namespace android {
 namespace base {
diff --git a/base/include/android-base/endian.h b/base/include/android-base/endian.h
index cbbd8c9..2d0f614 100644
--- a/base/include/android-base/endian.h
+++ b/base/include/android-base/endian.h
@@ -18,6 +18,9 @@
 
 /* A cross-platform equivalent of bionic's <sys/endian.h>. */
 
+/* For __BIONIC__ and __GLIBC__ */
+#include <sys/cdefs.h>
+
 #if defined(__BIONIC__)
 
 #include <sys/endian.h>
@@ -38,26 +41,32 @@
 #define betoh16(x) be16toh(x)
 #define betoh32(x) be32toh(x)
 #define betoh64(x) be64toh(x)
+#define letoh16(x) le16toh(x)
+#define letoh32(x) le32toh(x)
+#define letoh64(x) le64toh(x)
 
 #else
 
-/* Mac OS and Windows have nothing. */
-
-#define __LITTLE_ENDIAN 1234
-#define LITTLE_ENDIAN __LITTLE_ENDIAN
-
-#define __BIG_ENDIAN 4321
-#define BIG_ENDIAN __BIG_ENDIAN
-
-#define __BYTE_ORDER __LITTLE_ENDIAN
-#define BYTE_ORDER __BYTE_ORDER
-
+#if defined(__APPLE__)
+/* macOS has some of the basics. */
+#include <sys/_endian.h>
+#else
+/* Windows has even less. */
+#include <sys/param.h>
 #define htons(x) __builtin_bswap16(x)
 #define htonl(x) __builtin_bswap32(x)
-#define htonq(x) __builtin_bswap64(x)
-
 #define ntohs(x) __builtin_bswap16(x)
 #define ntohl(x) __builtin_bswap32(x)
+#endif
+
+/* Neither macOS nor Windows have the rest. */
+
+#define __LITTLE_ENDIAN 1234
+#define __BIG_ENDIAN 4321
+#define __BYTE_ORDER __LITTLE_ENDIAN
+
+#define htonq(x) __builtin_bswap64(x)
+
 #define ntohq(x) __builtin_bswap64(x)
 
 #define htobe16(x) __builtin_bswap16(x)
diff --git a/base/include/android-base/format.h b/base/include/android-base/format.h
index 6799c1f..330040d 100644
--- a/base/include/android-base/format.h
+++ b/base/include/android-base/format.h
@@ -18,8 +18,11 @@
 
 // We include fmtlib here as an alias, since libbase will have fmtlib statically linked already.
 // It is accessed through its normal fmt:: namespace.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wshadow"
+#include <fmt/chrono.h>
+#pragma clang diagnostic pop
 #include <fmt/core.h>
 #include <fmt/format.h>
 #include <fmt/ostream.h>
 #include <fmt/printf.h>
-#include <fmt/time.h>
diff --git a/base/include/android-base/mapped_file.h b/base/include/android-base/mapped_file.h
index 2ab49ab..6a19f1b 100644
--- a/base/include/android-base/mapped_file.h
+++ b/base/include/android-base/mapped_file.h
@@ -28,15 +28,17 @@
 #include <windows.h>
 #define PROT_READ 1
 #define PROT_WRITE 2
+using os_handle = HANDLE;
 #else
 #include <sys/mman.h>
+using os_handle = int;
 #endif
 
 namespace android {
 namespace base {
 
 /**
- * A region of a file mapped into memory, also known as MmapFile.
+ * A region of a file mapped into memory (for grepping: also known as MmapFile or file mapping).
  */
 class MappedFile {
  public:
@@ -49,16 +51,33 @@
                                             int prot);
 
   /**
+   * 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);
+
+  /**
    * Removes the mapping.
    */
   ~MappedFile();
 
-  char* data() { return base_ + offset_; }
-  size_t size() { return size_; }
+  /**
+   * Not copyable but movable.
+   */
+  MappedFile(MappedFile&& other);
+  MappedFile& operator=(MappedFile&& other);
+
+  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);
 
+  void Close();
+
   char* base_;
   size_t size_;
 
diff --git a/base/mapped_file.cpp b/base/mapped_file.cpp
index f60de56..862b73b 100644
--- a/base/mapped_file.cpp
+++ b/base/mapped_file.cpp
@@ -16,13 +16,15 @@
 
 #include "android-base/mapped_file.h"
 
-#include <errno.h>
+#include <utility>
 
-#include "android-base/unique_fd.h"
+#include <errno.h>
 
 namespace android {
 namespace base {
 
+static constexpr char kEmptyBuffer[] = {'0'};
+
 static off64_t InitPageSize() {
 #if defined(_WIN32)
   SYSTEM_INFO si;
@@ -35,51 +37,86 @@
 
 std::unique_ptr<MappedFile> MappedFile::FromFd(borrowed_fd fd, off64_t offset, size_t length,
                                                int prot) {
-  static off64_t page_size = InitPageSize();
+#if defined(_WIN32)
+  auto file =
+      FromOsHandle(reinterpret_cast<HANDLE>(_get_osfhandle(fd.get())), offset, length, prot);
+#else
+  auto file = 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) {
+  static const off64_t page_size = InitPageSize();
   size_t slop = offset % page_size;
   off64_t file_offset = offset - slop;
   off64_t file_length = length + slop;
 
 #if defined(_WIN32)
-  HANDLE handle =
-      CreateFileMapping(reinterpret_cast<HANDLE>(_get_osfhandle(fd.get())), nullptr,
-                        (prot & PROT_WRITE) ? PAGE_READWRITE : PAGE_READONLY, 0, 0, nullptr);
+  HANDLE handle = CreateFileMappingW(
+      h, nullptr, (prot & PROT_WRITE) ? PAGE_READWRITE : PAGE_READONLY, 0, 0, nullptr);
   if (handle == nullptr) {
     // 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) {
-      return std::unique_ptr<MappedFile>(new MappedFile{nullptr, 0, 0, nullptr});
+    if (length == 0 && ::GetLastError() == ERROR_FILE_INVALID) {
+      return MappedFile{const_cast<char*>(kEmptyBuffer), 0, 0, nullptr};
     }
-    return nullptr;
+    return MappedFile(nullptr, 0, 0, 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 nullptr;
+    return MappedFile(nullptr, 0, 0, nullptr);
   }
-  return std::unique_ptr<MappedFile>(
-      new MappedFile{static_cast<char*>(base), length, slop, handle});
+  return MappedFile{static_cast<char*>(base), length, slop, handle};
 #else
-  void* base = mmap(nullptr, file_length, prot, MAP_SHARED, fd.get(), file_offset);
+  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 std::unique_ptr<MappedFile>(new MappedFile{nullptr, 0, 0});
+      return MappedFile{const_cast<char*>(kEmptyBuffer), 0, 0};
     }
-    return nullptr;
+    return MappedFile(nullptr, 0, 0);
   }
-  return std::unique_ptr<MappedFile>(new MappedFile{static_cast<char*>(base), length, slop});
+  return MappedFile{static_cast<char*>(base), length, slop};
 #endif
 }
 
+MappedFile::MappedFile(MappedFile&& other)
+    : base_(std::exchange(other.base_, nullptr)),
+      size_(std::exchange(other.size_, 0)),
+      offset_(std::exchange(other.offset_, 0))
+#ifdef _WIN32
+      ,
+      handle_(std::exchange(other.handle_, nullptr))
+#endif
+{
+}
+
+MappedFile& MappedFile::operator=(MappedFile&& other) {
+  Close();
+  base_ = std::exchange(other.base_, nullptr);
+  size_ = std::exchange(other.size_, 0);
+  offset_ = std::exchange(other.offset_, 0);
+#ifdef _WIN32
+  handle_ = std::exchange(other.handle_, nullptr);
+#endif
+  return *this;
+}
+
 MappedFile::~MappedFile() {
+  Close();
+}
+
+void MappedFile::Close() {
 #if defined(_WIN32)
-  if (base_ != nullptr) UnmapViewOfFile(base_);
+  if (base_ != nullptr && size_ != 0) UnmapViewOfFile(base_);
   if (handle_ != nullptr) CloseHandle(handle_);
+  handle_ = nullptr;
 #else
-  if (base_ != nullptr) munmap(base_, size_ + offset_);
+  if (base_ != nullptr && size_ != 0) munmap(base_, size_ + offset_);
 #endif
 
   base_ = nullptr;
diff --git a/base/mapped_file_test.cpp b/base/mapped_file_test.cpp
index cfde73c..3629108 100644
--- a/base/mapped_file_test.cpp
+++ b/base/mapped_file_test.cpp
@@ -44,5 +44,8 @@
   ASSERT_TRUE(tf.fd != -1);
 
   auto m = android::base::MappedFile::FromFd(tf.fd, 4096, 0, PROT_READ);
-  ASSERT_EQ(0u, m->size());
+  ASSERT_NE(nullptr, m);
+  EXPECT_TRUE((bool)*m);
+  EXPECT_EQ(0u, m->size());
+  EXPECT_NE(nullptr, m->data());
 }
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 2e226da..0602e0a 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -194,6 +194,7 @@
 cc_test {
     name: "debuggerd_test",
     defaults: ["debuggerd_defaults"],
+    require_root: true,
 
     cflags: ["-Wno-missing-field-initializers"],
     srcs: [
diff --git a/debuggerd/tombstoned/tombstoned.rc b/debuggerd/tombstoned/tombstoned.rc
index 53ef01c..b4a1e71 100644
--- a/debuggerd/tombstoned/tombstoned.rc
+++ b/debuggerd/tombstoned/tombstoned.rc
@@ -2,10 +2,10 @@
     user tombstoned
     group system
 
-    # Don't start tombstoned until after the real /data is mounted.
-    class late_start
-
     socket tombstoned_crash seqpacket 0666 system system
     socket tombstoned_intercept seqpacket 0666 system system
     socket tombstoned_java_trace seqpacket 0666 system system
     writepid /dev/cpuset/system-background/tasks
+
+on post-fs-data
+    start tombstoned
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index 409ef70..4c77c75 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -194,23 +194,6 @@
     return device->WriteStatus(FastbootResult::FAIL, "Couldn't download data");
 }
 
-bool FlashHandler(FastbootDevice* device, const std::vector<std::string>& args) {
-    if (args.size() < 2) {
-        return device->WriteStatus(FastbootResult::FAIL, "Invalid arguments");
-    }
-
-    if (GetDeviceLockStatus()) {
-        return device->WriteStatus(FastbootResult::FAIL,
-                                   "Flashing is not allowed on locked devices");
-    }
-
-    int ret = Flash(device, args[1]);
-    if (ret < 0) {
-        return device->WriteStatus(FastbootResult::FAIL, strerror(-ret));
-    }
-    return device->WriteStatus(FastbootResult::OKAY, "Flashing succeeded");
-}
-
 bool SetActiveHandler(FastbootDevice* device, const std::vector<std::string>& args) {
     if (args.size() < 2) {
         return device->WriteStatus(FastbootResult::FAIL, "Missing slot argument");
@@ -440,6 +423,11 @@
     if (!partition) {
         return device->WriteFail("Partition does not exist");
     }
+
+    // Remove the updated flag to cancel any snapshots.
+    uint32_t attrs = partition->attributes();
+    partition->set_attributes(attrs & ~LP_PARTITION_ATTR_UPDATED);
+
     if (!builder->ResizePartition(partition, partition_size)) {
         return device->WriteFail("Not enough space to resize partition");
     }
@@ -449,6 +437,42 @@
     return device->WriteOkay("Partition resized");
 }
 
+void CancelPartitionSnapshot(FastbootDevice* device, const std::string& partition_name) {
+    PartitionBuilder builder(device, partition_name);
+    if (!builder.Valid()) return;
+
+    auto partition = builder->FindPartition(partition_name);
+    if (!partition) return;
+
+    // Remove the updated flag to cancel any snapshots.
+    uint32_t attrs = partition->attributes();
+    partition->set_attributes(attrs & ~LP_PARTITION_ATTR_UPDATED);
+
+    builder.Write();
+}
+
+bool FlashHandler(FastbootDevice* device, const std::vector<std::string>& args) {
+    if (args.size() < 2) {
+        return device->WriteStatus(FastbootResult::FAIL, "Invalid arguments");
+    }
+
+    if (GetDeviceLockStatus()) {
+        return device->WriteStatus(FastbootResult::FAIL,
+                                   "Flashing is not allowed on locked devices");
+    }
+
+    const auto& partition_name = args[1];
+    if (LogicalPartitionExists(device, partition_name)) {
+        CancelPartitionSnapshot(device, partition_name);
+    }
+
+    int ret = Flash(device, partition_name);
+    if (ret < 0) {
+        return device->WriteStatus(FastbootResult::FAIL, strerror(-ret));
+    }
+    return device->WriteStatus(FastbootResult::OKAY, "Flashing succeeded");
+}
+
 bool UpdateSuperHandler(FastbootDevice* device, const std::vector<std::string>& args) {
     if (args.size() < 2) {
         return device->WriteFail("Invalid arguments");
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index e01e39b..b3f2d5f 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -56,8 +56,16 @@
     if (!path) {
         return false;
     }
+
+    CreateLogicalPartitionParams params = {
+            .block_device = *path,
+            .metadata_slot = slot_number,
+            .partition_name = partition_name,
+            .force_writable = true,
+            .timeout_ms = 5s,
+    };
     std::string dm_path;
-    if (!CreateLogicalPartition(path->c_str(), slot_number, partition_name, true, 5s, &dm_path)) {
+    if (!CreateLogicalPartition(params, &dm_path)) {
         LOG(ERROR) << "Could not map partition: " << partition_name;
         return false;
     }
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 8923f40..4737ae4 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -160,6 +160,10 @@
                                                       "vbmeta_system",
                                                                   true,  ImageType::BootCritical },
     { "vendor",   "vendor.img",       "vendor.sig",   "vendor",   true,  ImageType::Normal },
+    { "vendor_boot",
+                  "vendor_boot.img",  "vendor_boot.sig",
+                                                      "vendor_boot",
+                                                                  true,  ImageType::BootCritical },
     { nullptr,    "vendor_other.img", "vendor.sig",   "vendor",   true,  ImageType::Normal },
         // clang-format on
 };
@@ -2054,10 +2058,10 @@
             if (partition == "userdata" && set_fbe_marker) {
                 fprintf(stderr, "setting FBE marker on initial userdata...\n");
                 std::string initial_userdata_dir = create_fbemarker_tmpdir();
-                fb_perform_format(partition, 1, "", "", initial_userdata_dir);
+                fb_perform_format(partition, 1, partition_type, "", initial_userdata_dir);
                 delete_fbemarker_tmpdir(initial_userdata_dir);
             } else {
-                fb_perform_format(partition, 1, "", "", "");
+                fb_perform_format(partition, 1, partition_type, "", "");
             }
         }
     }
diff --git a/fastboot/fastboot_driver.cpp b/fastboot/fastboot_driver.cpp
index fea0a77..b897182 100644
--- a/fastboot/fastboot_driver.cpp
+++ b/fastboot/fastboot_driver.cpp
@@ -291,7 +291,7 @@
 RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response,
                                     std::vector<std::string>* info) {
     RetCode ret;
-    int dsize;
+    int dsize = 0;
     if ((ret = RawCommand(FB_CMD_UPLOAD, response, info, &dsize))) {
         error_ = "Upload request failed: " + error_;
         return ret;
diff --git a/fs_mgr/fs_mgr_dm_linear.cpp b/fs_mgr/fs_mgr_dm_linear.cpp
index 04ba0bf..ea799ce 100644
--- a/fs_mgr/fs_mgr_dm_linear.cpp
+++ b/fs_mgr/fs_mgr_dm_linear.cpp
@@ -52,29 +52,36 @@
 using DmTargetZero = android::dm::DmTargetZero;
 using DmTargetLinear = android::dm::DmTargetLinear;
 
-static bool GetPhysicalPartitionDevicePath(const LpMetadata& metadata,
+static bool GetPhysicalPartitionDevicePath(const IPartitionOpener& opener,
+                                           const LpMetadata& metadata,
                                            const LpMetadataBlockDevice& block_device,
-                                           const std::string& super_device,
-                                           std::string* result) {
-    // Note: device-mapper will not accept symlinks, so we must use realpath
-    // here.
-    std::string name = GetBlockDevicePartitionName(block_device);
-    std::string path = "/dev/block/by-name/" + name;
+                                           const std::string& super_device, std::string* result) {
     // If the super device is the source of this block device's metadata,
     // make sure we use the correct super device (and not just "super",
     // which might not exist.)
+    std::string name = GetBlockDevicePartitionName(block_device);
+    std::string dev_string = opener.GetDeviceString(name);
     if (GetMetadataSuperBlockDevice(metadata) == &block_device) {
-        path = super_device;
+        dev_string = opener.GetDeviceString(super_device);
     }
-    if (!android::base::Realpath(path, result)) {
-        PERROR << "realpath: " << path;
-        return false;
+
+    // Note: device-mapper will not accept symlinks, so we must use realpath
+    // here. If the device string is a major:minor sequence, we don't need to
+    // to call Realpath (it would not work anyway).
+    if (android::base::StartsWith(dev_string, "/")) {
+        if (!android::base::Realpath(dev_string, result)) {
+            PERROR << "realpath: " << dev_string;
+            return false;
+        }
+    } else {
+        *result = dev_string;
     }
     return true;
 }
 
-static bool CreateDmTable(const LpMetadata& metadata, const LpMetadataPartition& partition,
-                          const std::string& super_device, DmTable* table) {
+bool CreateDmTable(const IPartitionOpener& opener, const LpMetadata& metadata,
+                   const LpMetadataPartition& partition, const std::string& super_device,
+                   DmTable* table) {
     uint64_t sector = 0;
     for (size_t i = 0; i < partition.num_extents; i++) {
         const auto& extent = metadata.extents[partition.first_extent_index + i];
@@ -85,12 +92,13 @@
                 break;
             case LP_TARGET_TYPE_LINEAR: {
                 const auto& block_device = metadata.block_devices[extent.target_source];
-                std::string path;
-                if (!GetPhysicalPartitionDevicePath(metadata, block_device, super_device, &path)) {
+                std::string dev_string;
+                if (!GetPhysicalPartitionDevicePath(opener, metadata, block_device, super_device,
+                                                    &dev_string)) {
                     LOG(ERROR) << "Unable to complete device-mapper table, unknown block device";
                     return false;
                 }
-                target = std::make_unique<DmTargetLinear>(sector, extent.num_sectors, path,
+                target = std::make_unique<DmTargetLinear>(sector, extent.num_sectors, dev_string,
                                                           extent.target_data);
                 break;
             }
@@ -109,26 +117,6 @@
     return true;
 }
 
-static bool CreateLogicalPartition(const LpMetadata& metadata, const LpMetadataPartition& partition,
-                                   bool force_writable, const std::chrono::milliseconds& timeout_ms,
-                                   const std::string& super_device, std::string* path) {
-    DeviceMapper& dm = DeviceMapper::Instance();
-
-    DmTable table;
-    if (!CreateDmTable(metadata, partition, super_device, &table)) {
-        return false;
-    }
-    if (force_writable) {
-        table.set_readonly(false);
-    }
-    std::string name = GetPartitionName(partition);
-    if (!dm.CreateDevice(name, table, path, timeout_ms)) {
-        return false;
-    }
-    LINFO << "Created logical partition " << name << " on device " << *path;
-    return true;
-}
-
 bool CreateLogicalPartitions(const std::string& block_device) {
     uint32_t slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix());
     auto metadata = ReadMetadata(block_device.c_str(), slot);
@@ -145,13 +133,20 @@
 }
 
 bool CreateLogicalPartitions(const LpMetadata& metadata, const std::string& super_device) {
+    CreateLogicalPartitionParams params = {
+            .block_device = super_device,
+            .metadata = &metadata,
+    };
     for (const auto& partition : metadata.partitions) {
         if (!partition.num_extents) {
             LINFO << "Skipping zero-length logical partition: " << GetPartitionName(partition);
             continue;
         }
-        std::string path;
-        if (!CreateLogicalPartition(metadata, partition, false, {}, super_device, &path)) {
+
+        params.partition = &partition;
+
+        std::string ignore_path;
+        if (!CreateLogicalPartition(params, &ignore_path)) {
             LERROR << "Could not create logical partition: " << GetPartitionName(partition);
             return false;
         }
@@ -159,29 +154,89 @@
     return true;
 }
 
-bool CreateLogicalPartition(const std::string& block_device, const LpMetadata& metadata,
-                            const std::string& partition_name, bool force_writable,
-                            const std::chrono::milliseconds& timeout_ms, std::string* path) {
-    for (const auto& partition : metadata.partitions) {
-        if (GetPartitionName(partition) == partition_name) {
-            return CreateLogicalPartition(metadata, partition, force_writable, timeout_ms,
-                                          block_device, path);
+bool CreateLogicalPartitionParams::InitDefaults(CreateLogicalPartitionParams::OwnedData* owned) {
+    if (block_device.empty()) {
+        LOG(ERROR) << "block_device is required for CreateLogicalPartition";
+        return false;
+    }
+
+    // Read metadata if needed.
+    if (!metadata) {
+        if (!metadata_slot) {
+            LOG(ERROR) << "Either metadata or a metadata slot must be specified.";
+            return false;
+        }
+        auto slot = *metadata_slot;
+        if (owned->metadata = ReadMetadata(block_device, slot); !owned->metadata) {
+            LOG(ERROR) << "Could not read partition table for: " << block_device;
+            return false;
+        }
+        metadata = owned->metadata.get();
+    }
+
+    // Find the partition by name if needed.
+    if (!partition) {
+        for (const auto& metadata_partition : metadata->partitions) {
+            if (android::fs_mgr::GetPartitionName(metadata_partition) == partition_name) {
+                partition = &metadata_partition;
+                break;
+            }
         }
     }
-    LERROR << "Could not find any partition with name: " << partition_name;
-    return false;
+    if (!partition) {
+        LERROR << "Could not find any partition with name: " << partition_name;
+        return false;
+    }
+    if (partition_name.empty()) {
+        partition_name = android::fs_mgr::GetPartitionName(*partition);
+    } else if (partition_name != android::fs_mgr::GetPartitionName(*partition)) {
+        LERROR << "Inconsistent partition_name " << partition_name << " with partition "
+               << android::fs_mgr::GetPartitionName(*partition);
+        return false;
+    }
+
+    if (!partition_opener) {
+        owned->partition_opener = std::make_unique<PartitionOpener>();
+        partition_opener = owned->partition_opener.get();
+    }
+
+    if (device_name.empty()) {
+        device_name = partition_name;
+    }
+
+    return true;
 }
 
-bool CreateLogicalPartition(const std::string& block_device, uint32_t metadata_slot,
-                            const std::string& partition_name, bool force_writable,
-                            const std::chrono::milliseconds& timeout_ms, std::string* path) {
-    auto metadata = ReadMetadata(block_device.c_str(), metadata_slot);
-    if (!metadata) {
-        LOG(ERROR) << "Could not read partition table.";
-        return true;
+bool CreateLogicalPartition(CreateLogicalPartitionParams params, std::string* path) {
+    CreateLogicalPartitionParams::OwnedData owned_data;
+    if (!params.InitDefaults(&owned_data)) return false;
+
+    DmTable table;
+    if (!CreateDmTable(*params.partition_opener, *params.metadata, *params.partition,
+                       params.block_device, &table)) {
+        return false;
     }
-    return CreateLogicalPartition(block_device, *metadata.get(), partition_name, force_writable,
-                                  timeout_ms, path);
+    if (params.force_writable) {
+        table.set_readonly(false);
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    if (!dm.CreateDevice(params.device_name, table, path, params.timeout_ms)) {
+        return false;
+    }
+    LINFO << "Created logical partition " << params.device_name << " on device " << *path;
+    return true;
+}
+
+std::string CreateLogicalPartitionParams::GetDeviceName() const {
+    if (!device_name.empty()) return device_name;
+    return GetPartitionName();
+}
+
+std::string CreateLogicalPartitionParams::GetPartitionName() const {
+    if (!partition_name.empty()) return partition_name;
+    if (partition) return android::fs_mgr::GetPartitionName(*partition);
+    return "<unknown partition>";
 }
 
 bool UnmapDevice(const std::string& name) {
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 9a0f4fe..bc197cd 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -46,7 +46,7 @@
 namespace fs_mgr {
 namespace {
 
-const std::string kDefaultAndroidDtDir("/proc/device-tree/firmware/android");
+constexpr char kDefaultAndroidDtDir[] = "/proc/device-tree/firmware/android";
 
 struct FlagList {
     const char *name;
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index ac15ce4..358c980 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -133,7 +133,7 @@
     return ret | !rmdir(test_directory.c_str());
 }
 
-// At less than 1% free space return value of false,
+// At less than 1% or 8MB of free space return value of false,
 // means we will try to wrap with overlayfs.
 bool fs_mgr_filesystem_has_space(const std::string& mount_point) {
     // If we have access issues to find out space remaining, return true
@@ -145,9 +145,36 @@
         return true;
     }
 
-    static constexpr int kPercentThreshold = 1;  // 1%
+    static constexpr int kPercentThreshold = 1;                       // 1%
+    static constexpr unsigned long kSizeThreshold = 8 * 1024 * 1024;  // 8MB
 
-    return (vst.f_bfree >= (vst.f_blocks * kPercentThreshold / 100));
+    return (vst.f_bfree >= (vst.f_blocks * kPercentThreshold / 100)) &&
+           (vst.f_bfree * vst.f_bsize) >= kSizeThreshold;
+}
+
+const auto kPhysicalDevice = "/dev/block/by-name/"s;
+
+bool fs_mgr_update_blk_device(FstabEntry* entry) {
+    if (entry->fs_mgr_flags.logical) {
+        fs_mgr_update_logical_partition(entry);
+    }
+    if (fs_mgr_access(entry->blk_device)) {
+        return true;
+    }
+    if (entry->blk_device != "/dev/root") {
+        return false;
+    }
+
+    // special case for system-as-root (taimen and others)
+    auto blk_device = kPhysicalDevice + "system";
+    if (!fs_mgr_access(blk_device)) {
+        blk_device += fs_mgr_get_slot_suffix();
+        if (!fs_mgr_access(blk_device)) {
+            return false;
+        }
+    }
+    entry->blk_device = blk_device;
+    return true;
 }
 
 bool fs_mgr_overlayfs_enabled(FstabEntry* entry) {
@@ -157,19 +184,19 @@
     if (!fs_mgr_filesystem_has_space(entry->mount_point)) {
         return true;
     }
-    if (entry->fs_mgr_flags.logical) {
-        fs_mgr_update_logical_partition(entry);
+
+    // blk_device needs to be setup so we can check superblock.
+    // If we fail here, because during init first stage and have doubts.
+    if (!fs_mgr_update_blk_device(entry)) {
+        return true;
     }
+
+    // check if ext4 de-dupe
     auto save_errno = errno;
-    errno = 0;
     auto has_shared_blocks = fs_mgr_has_shared_blocks(entry->mount_point, entry->blk_device);
     if (!has_shared_blocks && (entry->mount_point == "/system")) {
         has_shared_blocks = fs_mgr_has_shared_blocks("/", entry->blk_device);
     }
-    // special case for first stage init for system as root (taimen)
-    if (!has_shared_blocks && (errno == ENOENT) && (entry->blk_device == "/dev/root")) {
-        has_shared_blocks = true;
-    }
     errno = save_errno;
     return has_shared_blocks;
 }
@@ -388,8 +415,6 @@
     return SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix());
 }
 
-const auto kPhysicalDevice = "/dev/block/by-name/"s;
-
 std::string fs_mgr_overlayfs_super_device(uint32_t slot_number) {
     return kPhysicalDevice + fs_mgr_get_super_partition_name(slot_number);
 }
@@ -688,7 +713,7 @@
     }
     report = report + ")=";
 
-    auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_RELATIME,
+    auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
                      options.c_str());
     if (ret) {
         retval = false;
@@ -751,7 +776,7 @@
     entry.fs_type = mnt_type;
     if ((mnt_type == "f2fs") && !f2fs) entry.fs_type = "ext4";
     if ((mnt_type == "ext4") && !ext4) entry.fs_type = "f2fs";
-    entry.flags = MS_RELATIME;
+    entry.flags = MS_NOATIME;
     if (readonly) {
         entry.flags |= MS_RDONLY;
     } else {
@@ -936,9 +961,16 @@
     }
 
     if (changed || partition_create) {
-        if (!CreateLogicalPartition(super_device, slot_number, partition_name, true, 10s,
-                                    scratch_device))
+        CreateLogicalPartitionParams params = {
+                .block_device = super_device,
+                .metadata_slot = slot_number,
+                .partition_name = partition_name,
+                .force_writable = true,
+                .timeout_ms = 10s,
+        };
+        if (!CreateLogicalPartition(params, scratch_device)) {
             return false;
+        }
 
         if (change) *change = true;
     }
@@ -1159,11 +1191,15 @@
     if ((mount_point != nullptr) && !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
         auto scratch_device = fs_mgr_overlayfs_scratch_device();
         if (scratch_device.empty()) {
-            auto slot_number = fs_mgr_overlayfs_slot_number();
-            auto super_device = fs_mgr_overlayfs_super_device(slot_number);
-            const auto partition_name = android::base::Basename(kScratchMountPoint);
-            CreateLogicalPartition(super_device, slot_number, partition_name, true, 10s,
-                                   &scratch_device);
+            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);
         }
         mount_scratch = fs_mgr_overlayfs_mount_scratch(scratch_device,
                                                        fs_mgr_overlayfs_scratch_mount_type());
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index 149bee3..93bba68 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -82,13 +82,7 @@
 
 void MyLogger(android::base::LogId id, android::base::LogSeverity severity, const char* tag,
               const char* file, unsigned int line, const char* message) {
-    static const char log_characters[] = "VD\0WEFF";
-    if (severity < sizeof(log_characters)) {
-        auto severity_char = log_characters[severity];
-        if (severity_char) fprintf(stderr, "%c ", severity_char);
-    }
     fprintf(stderr, "%s\n", message);
-
     static auto logd = android::base::LogdLogger();
     logd(id, severity, tag, file, line, message);
 }
@@ -107,11 +101,9 @@
 
 }  // namespace
 
-int main(int argc, char* argv[]) {
-    android::base::InitLogging(argv, MyLogger);
-
+static int do_remount(int argc, char* argv[]) {
     enum {
-        SUCCESS,
+        SUCCESS = 0,
         NOT_USERDEBUG,
         BADARG,
         NOT_ROOT,
@@ -165,7 +157,7 @@
 
     // Make sure we are root.
     if (::getuid() != 0) {
-        LOG(ERROR) << "must be run as root";
+        LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
         return NOT_ROOT;
     }
 
@@ -390,3 +382,10 @@
 
     return retval;
 }
+
+int main(int argc, char* argv[]) {
+    android::base::InitLogging(argv, MyLogger);
+    int result = do_remount(argc, argv);
+    printf("remount %s\n", result ? "failed" : "succeeded");
+    return result;
+}
diff --git a/fs_mgr/include/fs_mgr_dm_linear.h b/fs_mgr/include/fs_mgr_dm_linear.h
index a1dc2dc..a912208 100644
--- a/fs_mgr/include/fs_mgr_dm_linear.h
+++ b/fs_mgr/include/fs_mgr_dm_linear.h
@@ -29,6 +29,7 @@
 
 #include <chrono>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -49,27 +50,65 @@
 // method for ReadMetadata and CreateLogicalPartitions.
 bool CreateLogicalPartitions(const std::string& block_device);
 
-// Create a block device for a single logical partition, given metadata and
-// the partition name. On success, a path to the partition's block device is
-// returned. If |force_writable| is true, the "readonly" flag will be ignored
-// so the partition can be flashed.
-//
-// If |timeout_ms| is non-zero, then CreateLogicalPartition will block for the
-// given amount of time until the path returned in |path| is available.
-bool CreateLogicalPartition(const std::string& block_device, uint32_t metadata_slot,
-                            const std::string& partition_name, bool force_writable,
-                            const std::chrono::milliseconds& timeout_ms, std::string* path);
+struct CreateLogicalPartitionParams {
+    // Block device of the super partition.
+    std::string block_device;
 
-// Same as above, but with a given metadata object. Care should be taken that
-// the metadata represents a valid partition layout.
-bool CreateLogicalPartition(const std::string& block_device, const LpMetadata& metadata,
-                            const std::string& partition_name, bool force_writable,
-                            const std::chrono::milliseconds& timeout_ms, std::string* path);
+    // If |metadata| is null, the slot will be read using |metadata_slot|.
+    const LpMetadata* metadata = nullptr;
+    std::optional<uint32_t> metadata_slot;
+
+    // If |partition| is not set, it will be found via |partition_name|.
+    const LpMetadataPartition* partition = nullptr;
+    std::string partition_name;
+
+    // Force the device to be read-write even if it was specified as readonly
+    // in the metadata.
+    bool force_writable = false;
+
+    // If |timeout_ms| is non-zero, then CreateLogicalPartition will block for
+    // the given amount of time until the path returned in |path| is available.
+    std::chrono::milliseconds timeout_ms = {};
+
+    // If this is non-empty, it will override the device mapper name (by
+    // default the partition name will be used).
+    std::string device_name;
+
+    // If non-null, this will use the specified IPartitionOpener rather than
+    // the default one.
+    const IPartitionOpener* partition_opener = nullptr;
+
+    // Helpers for determining the effective partition and device name.
+    std::string GetPartitionName() const;
+    std::string GetDeviceName() const;
+
+    // Specify ownership of fields. The ownership of these fields are managed
+    // by the caller of InitDefaults().
+    // These are not declared in CreateLogicalPartitionParams so that the
+    // copy constructor is not deleted.
+    struct OwnedData {
+        std::unique_ptr<LpMetadata> metadata;
+        std::unique_ptr<IPartitionOpener> partition_opener;
+    };
+
+    // Fill in default values for |params| that CreateLogicalPartition assumes. Caller does
+    // not need to call this before calling CreateLogicalPartition; CreateLogicalPartition sets
+    // values when they are missing.
+    // Caller is responsible for destroying owned_data when |this| is not used.
+    bool InitDefaults(OwnedData* owned);
+};
+
+bool CreateLogicalPartition(CreateLogicalPartitionParams params, std::string* path);
 
 // Destroy the block device for a logical partition, by name. If |timeout_ms|
 // is non-zero, then this will block until the device path has been unlinked.
 bool DestroyLogicalPartition(const std::string& name);
 
+// Helper for populating a DmTable for a logical partition.
+bool CreateDmTable(const IPartitionOpener& opener, const LpMetadata& metadata,
+                   const LpMetadataPartition& partition, const std::string& super_device,
+                   android::dm::DmTable* table);
+
 }  // namespace fs_mgr
 }  // namespace android
 
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index a4e0d76..e7a3ff2 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -79,6 +79,13 @@
     return true;
 }
 
+bool DeviceMapper::DeleteDeviceIfExists(const std::string& name) {
+    if (GetState(name) == DmDeviceState::INVALID) {
+        return true;
+    }
+    return DeleteDevice(name);
+}
+
 bool DeviceMapper::DeleteDevice(const std::string& name) {
     struct dm_ioctl io;
     InitIo(&io, name);
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index 7c9804c..65f6e12 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -244,7 +244,8 @@
 }
 
 std::string DmTargetDefaultKey::GetParameterString() const {
-    return cipher_ + " " + key_ + " " + blockdev_ + " " + std::to_string(start_sector_);
+    return cipher_ + " " + key_ + " " + blockdev_ + " " + std::to_string(start_sector_) +
+           (set_dun_ ? " 1 set_dun" : "");
 }
 
 }  // namespace dm
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index f5783cb..e25ce7f 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -89,6 +89,7 @@
     // Removes a device mapper device with the given name.
     // Returns 'true' on success, false otherwise.
     bool DeleteDevice(const std::string& name);
+    bool DeleteDeviceIfExists(const std::string& name);
 
     // Fetches and returns the complete state of the underlying device mapper
     // device with given name.
@@ -197,6 +198,7 @@
     struct TargetInfo {
         struct dm_target_spec spec;
         std::string data;
+        TargetInfo() {}
         TargetInfo(const struct dm_target_spec& spec, const std::string& data)
             : spec(spec), data(data) {}
     };
diff --git a/fs_mgr/libdm/include/libdm/dm_target.h b/fs_mgr/libdm/include/libdm/dm_target.h
index a66ab7a..a78bc71 100644
--- a/fs_mgr/libdm/include/libdm/dm_target.h
+++ b/fs_mgr/libdm/include/libdm/dm_target.h
@@ -280,12 +280,14 @@
 class DmTargetDefaultKey final : public DmTarget {
   public:
     DmTargetDefaultKey(uint64_t start, uint64_t length, const std::string& cipher,
-                       const std::string& key, const std::string& blockdev, uint64_t start_sector)
+                       const std::string& key, const std::string& blockdev, uint64_t start_sector,
+                       bool set_dun = false)
         : DmTarget(start, length),
           cipher_(cipher),
           key_(key),
           blockdev_(blockdev),
-          start_sector_(start_sector) {}
+          start_sector_(start_sector),
+          set_dun_(set_dun) {}
 
     std::string name() const override { return "default-key"; }
     bool Valid() const override { return true; }
@@ -296,6 +298,7 @@
     std::string key_;
     std::string blockdev_;
     uint64_t start_sector_;
+    bool set_dun_;
 };
 
 }  // namespace dm
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index b504161..f0142bb 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -36,6 +36,7 @@
         "builder.cpp",
         "images.cpp",
         "partition_opener.cpp",
+        "property_fetcher.cpp",
         "reader.cpp",
         "utility.cpp",
         "writer.cpp",
@@ -54,13 +55,14 @@
     export_include_dirs: ["include"],
 }
 
-cc_test {
-    name: "liblp_test_static",
+cc_defaults {
+    name: "liblp_test_defaults",
     defaults: ["fs_mgr_defaults"],
     cppflags: [
         "-Wno-unused-parameter",
     ],
     static_libs: [
+        "libcutils",
         "libgmock",
         "libfs_mgr",
         "liblp",
@@ -68,15 +70,25 @@
     stl: "libc++_static",
     srcs: [
         "builder_test.cpp",
+        "device_test.cpp",
         "io_test.cpp",
         "test_partition_opener.cpp",
         "utility_test.cpp",
     ],
-    target: {
-        android: {
-            static_libs: [
-                "libcutils",
-            ],
-        },
-    },
 }
+
+cc_test {
+    name: "liblp_test",
+    defaults: ["liblp_test_defaults"],
+    test_config: "liblp_test.xml",
+    test_suites: [
+        "device-tests",
+        "vts-core",
+    ],
+}
+
+cc_test {
+    name: "vts_kernel_liblp_test",
+    defaults: ["liblp_test_defaults"],
+}
+
diff --git a/fs_mgr/liblp/AndroidTest.xml b/fs_mgr/liblp/AndroidTest.xml
index fe1002c..2eb0ad1 100644
--- a/fs_mgr/liblp/AndroidTest.xml
+++ b/fs_mgr/liblp/AndroidTest.xml
@@ -21,8 +21,8 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
       <option name="test-module-name" value="VtsKernelLiblpTest"/>
-        <option name="binary-test-source" value="_32bit::DATA/nativetest/liblp_test_static/liblp_test_static" />
-        <option name="binary-test-source" value="_64bit::DATA/nativetest64/liblp_test_static/liblp_test_static" />
+        <option name="binary-test-source" value="_32bit::DATA/nativetest/vts_kernel_liblp_test/vts_kernel_liblp_test" />
+        <option name="binary-test-source" value="_64bit::DATA/nativetest64/vts_kernel_liblp_test/vts_kernel_liblp_test" />
         <option name="binary-test-type" value="gtest"/>
         <option name="test-timeout" value="1m"/>
         <option name="precondition-first-api-level" value="29" />
diff --git a/fs_mgr/liblp/TEST_MAPPING b/fs_mgr/liblp/TEST_MAPPING
new file mode 100644
index 0000000..04bcbda
--- /dev/null
+++ b/fs_mgr/liblp/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "liblp_test"
+    }
+  ]
+}
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 8797ea9..c91fbe4 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -20,21 +20,16 @@
 
 #include <algorithm>
 
-#include <android-base/properties.h>
 #include <android-base/unique_fd.h>
 
 #include "liblp/liblp.h"
+#include "liblp/property_fetcher.h"
 #include "reader.h"
 #include "utility.h"
 
 namespace android {
 namespace fs_mgr {
 
-std::optional<bool> MetadataBuilder::sABOverride;
-std::optional<bool> MetadataBuilder::sRetrofitDap;
-
-static const std::string kDefaultGroup = "default";
-
 bool LinearExtent::AddTo(LpMetadata* out) const {
     if (device_index_ >= out->block_devices.size()) {
         LERROR << "Extent references unknown block device.";
@@ -50,7 +45,7 @@
     return true;
 }
 
-Partition::Partition(const std::string& name, const std::string& group_name, uint32_t attributes)
+Partition::Partition(std::string_view name, std::string_view group_name, uint32_t attributes)
     : name_(name), group_name_(group_name), attributes_(attributes), size_(0) {}
 
 void Partition::AddExtent(std::unique_ptr<Extent>&& extent) {
@@ -145,7 +140,7 @@
     }
     if (opener) {
         for (size_t i = 0; i < builder->block_devices_.size(); i++) {
-            std::string partition_name = GetBlockDevicePartitionName(builder->block_devices_[i]);
+            std::string partition_name = builder->GetBlockDevicePartitionName(i);
             BlockDeviceInfo device_info;
             if (opener->GetInfo(partition_name, &device_info)) {
                 builder->UpdateBlockDeviceInfo(i, device_info);
@@ -164,16 +159,34 @@
         return nullptr;
     }
 
-    // On non-retrofit devices there is only one location for metadata: the
-    // super partition. update_engine will remove and resize partitions as
-    // needed. On the other hand, for retrofit devices, we'll need to
-    // translate block device and group names to update their slot suffixes.
+    // On retrofit DAP devices, modify the metadata so that it is suitable for being written
+    // to the target slot later. We detect retrofit DAP devices by checking the super partition
+    // name and system properties.
+    // See comments for UpdateMetadataForOtherSuper.
     auto super_device = GetMetadataSuperBlockDevice(*metadata.get());
-    if (GetBlockDevicePartitionName(*super_device) == "super" ||
-        !IsRetrofitDynamicPartitionsDevice()) {
-        return New(*metadata.get(), &opener);
+    if (android::fs_mgr::GetBlockDevicePartitionName(*super_device) != "super" &&
+        IsRetrofitDynamicPartitionsDevice()) {
+        if (!UpdateMetadataForOtherSuper(metadata.get(), source_slot_number, target_slot_number)) {
+            return nullptr;
+        }
     }
 
+    if (IPropertyFetcher::GetInstance()->GetBoolProperty("ro.virtual_ab.enabled", false)) {
+        if (!UpdateMetadataForInPlaceSnapshot(metadata.get(), source_slot_number,
+                                              target_slot_number)) {
+            return nullptr;
+        }
+    }
+
+    return New(*metadata.get(), &opener);
+}
+
+// For retrofit DAP devices, there are (conceptually) two super partitions. We'll need to translate
+// block device and group names to update their slot suffixes.
+// (On the other hand, On non-retrofit DAP devices there is only one location for metadata: the
+// super partition. update_engine will remove and resize partitions as needed.)
+bool MetadataBuilder::UpdateMetadataForOtherSuper(LpMetadata* metadata, uint32_t source_slot_number,
+                                                  uint32_t target_slot_number) {
     // Clear partitions and extents, since they have no meaning on the target
     // slot. We also clear groups since they are re-added during OTA.
     metadata->partitions.clear();
@@ -186,14 +199,15 @@
     // Translate block devices.
     auto source_block_devices = std::move(metadata->block_devices);
     for (const auto& source_block_device : source_block_devices) {
-        std::string partition_name = GetBlockDevicePartitionName(source_block_device);
+        std::string partition_name =
+                android::fs_mgr::GetBlockDevicePartitionName(source_block_device);
         std::string slot_suffix = GetPartitionSlotSuffix(partition_name);
         if (slot_suffix.empty() || slot_suffix != source_slot_suffix) {
             // This should never happen. It means that the source metadata
             // refers to a target or unknown block device.
             LERROR << "Invalid block device for slot " << source_slot_suffix << ": "
                    << partition_name;
-            return nullptr;
+            return false;
         }
         std::string new_name =
                 partition_name.substr(0, partition_name.size() - slot_suffix.size()) +
@@ -202,20 +216,12 @@
         auto new_device = source_block_device;
         if (!UpdateBlockDevicePartitionName(&new_device, new_name)) {
             LERROR << "Partition name too long: " << new_name;
-            return nullptr;
+            return false;
         }
         metadata->block_devices.emplace_back(new_device);
     }
 
-    return New(*metadata.get(), &opener);
-}
-
-void MetadataBuilder::OverrideABForTesting(bool ab_device) {
-    sABOverride = ab_device;
-}
-
-void MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(bool retrofit) {
-    sRetrofitDap = retrofit;
+    return true;
 }
 
 MetadataBuilder::MetadataBuilder() : auto_slot_suffixing_(false) {
@@ -226,7 +232,7 @@
     memset(&header_, 0, sizeof(header_));
     header_.magic = LP_METADATA_HEADER_MAGIC;
     header_.major_version = LP_METADATA_MAJOR_VERSION;
-    header_.minor_version = LP_METADATA_MINOR_VERSION;
+    header_.minor_version = LP_METADATA_MINOR_VERSION_MIN;
     header_.header_size = sizeof(header_);
     header_.partitions.entry_size = sizeof(LpMetadataPartition);
     header_.extents.entry_size = sizeof(LpMetadataExtent);
@@ -377,7 +383,7 @@
             block_devices_.emplace_back(out);
         }
     }
-    if (GetBlockDevicePartitionName(block_devices_[0]) != super_partition) {
+    if (GetBlockDevicePartitionName(0) != super_partition) {
         LERROR << "No super partition was specified.";
         return false;
     }
@@ -414,7 +420,7 @@
     geometry_.metadata_slot_count = metadata_slot_count;
     geometry_.logical_block_size = logical_block_size;
 
-    if (!AddGroup(kDefaultGroup, 0)) {
+    if (!AddGroup(std::string(kDefaultGroup), 0)) {
         return false;
     }
     return true;
@@ -433,7 +439,7 @@
     return AddPartition(name, kDefaultGroup, attributes);
 }
 
-Partition* MetadataBuilder::AddPartition(const std::string& name, const std::string& group_name,
+Partition* MetadataBuilder::AddPartition(std::string_view name, std::string_view group_name,
                                          uint32_t attributes) {
     if (name.empty()) {
         LERROR << "Partition must have a non-empty name.";
@@ -451,7 +457,7 @@
     return partitions_.back().get();
 }
 
-Partition* MetadataBuilder::FindPartition(const std::string& name) {
+Partition* MetadataBuilder::FindPartition(std::string_view name) {
     for (const auto& partition : partitions_) {
         if (partition->name() == name) {
             return partition.get();
@@ -460,7 +466,7 @@
     return nullptr;
 }
 
-PartitionGroup* MetadataBuilder::FindGroup(const std::string& group_name) {
+PartitionGroup* MetadataBuilder::FindGroup(std::string_view group_name) {
     for (const auto& group : groups_) {
         if (group->name() == group_name) {
             return group.get();
@@ -584,8 +590,7 @@
     CHECK_NE(sectors_per_block, 0);
     CHECK(sectors_needed % sectors_per_block == 0);
 
-    if (IsABDevice() && !IsRetrofitMetadata() &&
-        GetPartitionSlotSuffix(partition->name()) == "_b") {
+    if (IsABDevice() && ShouldHalveSuper() && GetPartitionSlotSuffix(partition->name()) == "_b") {
         // Allocate "a" partitions top-down and "b" partitions bottom-up, to
         // minimize fragmentation during OTA.
         free_regions = PrioritizeSecondHalfOfSuper(free_regions);
@@ -791,6 +796,11 @@
             return nullptr;
         }
 
+        if (partition->attributes() & LP_PARTITION_ATTR_UPDATED) {
+            static const uint16_t kMinVersion = LP_METADATA_VERSION_FOR_UPDATED_ATTR;
+            metadata->header.minor_version = std::max(metadata->header.minor_version, kMinVersion);
+        }
+
         strncpy(part.name, partition->name().c_str(), sizeof(part.name));
         part.first_extent_index = static_cast<uint32_t>(metadata->extents.size());
         part.num_extents = static_cast<uint32_t>(partition->extents().size());
@@ -851,7 +861,7 @@
 bool MetadataBuilder::FindBlockDeviceByName(const std::string& partition_name,
                                             uint32_t* index) const {
     for (size_t i = 0; i < block_devices_.size(); i++) {
-        if (GetBlockDevicePartitionName(block_devices_[i]) == partition_name) {
+        if (GetBlockDevicePartitionName(i) == partition_name) {
             *index = i;
             return true;
         }
@@ -948,7 +958,7 @@
     return names;
 }
 
-void MetadataBuilder::RemoveGroupAndPartitions(const std::string& group_name) {
+void MetadataBuilder::RemoveGroupAndPartitions(std::string_view group_name) {
     if (group_name == kDefaultGroup) {
         // Cannot remove the default group.
         return;
@@ -976,7 +986,8 @@
     // Note: we don't compare alignment, since it's a performance thing and
     // won't affect whether old extents continue to work.
     return first.first_logical_sector == second.first_logical_sector && first.size == second.size &&
-           GetBlockDevicePartitionName(first) == GetBlockDevicePartitionName(second);
+           android::fs_mgr::GetBlockDevicePartitionName(first) ==
+                   android::fs_mgr::GetBlockDevicePartitionName(second);
 }
 
 bool MetadataBuilder::ImportPartitions(const LpMetadata& metadata,
@@ -1050,21 +1061,17 @@
 }
 
 bool MetadataBuilder::IsABDevice() {
-    if (sABOverride.has_value()) {
-        return *sABOverride;
-    }
-    return !android::base::GetProperty("ro.boot.slot_suffix", "").empty();
+    return !IPropertyFetcher::GetInstance()->GetProperty("ro.boot.slot_suffix", "").empty();
 }
 
 bool MetadataBuilder::IsRetrofitDynamicPartitionsDevice() {
-    if (sRetrofitDap.has_value()) {
-        return *sRetrofitDap;
-    }
-    return android::base::GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false);
+    return IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions_retrofit",
+                                                            false);
 }
 
-bool MetadataBuilder::IsRetrofitMetadata() const {
-    return GetBlockDevicePartitionName(block_devices_[0]) != LP_METADATA_DEFAULT_PARTITION_NAME;
+bool MetadataBuilder::ShouldHalveSuper() const {
+    return GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
+           !IPropertyFetcher::GetInstance()->GetBoolProperty("ro.virtual_ab.enabled", false);
 }
 
 bool MetadataBuilder::AddLinearExtent(Partition* partition, const std::string& block_device,
@@ -1090,7 +1097,7 @@
     return partitions;
 }
 
-bool MetadataBuilder::ChangePartitionGroup(Partition* partition, const std::string& group_name) {
+bool MetadataBuilder::ChangePartitionGroup(Partition* partition, std::string_view group_name) {
     if (!FindGroup(group_name)) {
         LERROR << "Partition cannot change to unknown group: " << group_name;
         return false;
@@ -1128,5 +1135,11 @@
     return true;
 }
 
+std::string MetadataBuilder::GetBlockDevicePartitionName(uint64_t index) const {
+    return index < block_devices_.size()
+                   ? android::fs_mgr::GetBlockDevicePartitionName(block_devices_[index])
+                   : "";
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index 377ec68..c5b4047 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -14,42 +14,33 @@
  * limitations under the License.
  */
 
-#include <fs_mgr.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <liblp/builder.h>
 
+#include "liblp_test.h"
 #include "utility.h"
 
 using namespace std;
 using namespace android::fs_mgr;
+using namespace android::fs_mgr::testing;
+using ::testing::_;
+using ::testing::AnyNumber;
 using ::testing::ElementsAre;
+using ::testing::NiceMock;
+using ::testing::Return;
 
 class Environment : public ::testing::Environment {
   public:
-    void SetUp() override {
-        MetadataBuilder::OverrideABForTesting(false);
-        MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
-    }
+    void SetUp() override { ResetMockPropertyFetcher(); }
 };
 
 int main(int argc, char** argv) {
-    ::testing::AddGlobalTestEnvironment(new Environment);
     ::testing::InitGoogleTest(&argc, argv);
     return RUN_ALL_TESTS();
 }
 
-class BuilderTest : public ::testing::Test {
-  public:
-    void SetUp() override {
-        MetadataBuilder::OverrideABForTesting(false);
-        MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
-    }
-    void TearDown() override {
-        MetadataBuilder::OverrideABForTesting(false);
-        MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
-    }
-};
+class BuilderTest : public LiblpTest {};
 
 TEST_F(BuilderTest, BuildBasic) {
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
@@ -358,7 +349,7 @@
     const LpMetadataHeader& header = exported->header;
     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);
+    EXPECT_EQ(header.minor_version, LP_METADATA_MINOR_VERSION_MIN);
 
     ASSERT_EQ(exported->partitions.size(), 2);
     ASSERT_EQ(exported->extents.size(), 3);
@@ -456,23 +447,6 @@
     EXPECT_EQ(builder, nullptr);
 }
 
-TEST_F(BuilderTest, block_device_info) {
-    PartitionOpener opener;
-
-    BlockDeviceInfo device_info;
-    ASSERT_TRUE(opener.GetInfo(fs_mgr_get_super_partition_name(), &device_info));
-
-    // Sanity check that the device doesn't give us some weird inefficient
-    // alignment.
-    ASSERT_EQ(device_info.alignment % LP_SECTOR_SIZE, 0);
-    ASSERT_EQ(device_info.alignment_offset % LP_SECTOR_SIZE, 0);
-    ASSERT_LE(device_info.alignment_offset, INT_MAX);
-    ASSERT_EQ(device_info.logical_block_size % LP_SECTOR_SIZE, 0);
-
-    // Having an alignment offset > alignment doesn't really make sense.
-    ASSERT_LT(device_info.alignment_offset, device_info.alignment);
-}
-
 TEST_F(BuilderTest, UpdateBlockDeviceInfo) {
     BlockDeviceInfo device_info("super", 1024 * 1024, 4096, 1024, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
@@ -785,7 +759,9 @@
 
     // A and B slots should be allocated from separate halves of the partition,
     // to mitigate allocating too many extents. (b/120433288)
-    MetadataBuilder::OverrideABForTesting(true);
+    ON_CALL(*GetMockedPropertyFetcher(), GetProperty("ro.boot.slot_suffix", _))
+            .WillByDefault(Return("_a"));
+
     auto builder = MetadataBuilder::New(device_info, 65536, 2);
     ASSERT_NE(builder, nullptr);
     Partition* system_a = builder->AddPartition("system_a", 0);
diff --git a/fs_mgr/liblp/device_test.cpp b/fs_mgr/liblp/device_test.cpp
new file mode 100644
index 0000000..382d53d
--- /dev/null
+++ b/fs_mgr/liblp/device_test.cpp
@@ -0,0 +1,84 @@
+/*
+ * 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 <android-base/properties.h>
+#include <fs_mgr.h>
+#include <fstab/fstab.h>
+#include <gtest/gtest.h>
+#include <liblp/liblp.h>
+#include <liblp/metadata_format.h>
+#include <liblp/partition_opener.h>
+#include <liblp/property_fetcher.h>
+
+#include "liblp_test.h"
+
+using namespace android::fs_mgr;
+using namespace android::fs_mgr::testing;
+using ::testing::Return;
+
+// Compliance test on the actual device with dynamic partitions.
+class DeviceTest : public LiblpTest {
+  public:
+    void SetUp() override {
+        // Read real properties.
+        IPropertyFetcher::OverrideForTesting(std::make_unique<PropertyFetcher>());
+        if (!IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions",
+                                                              false)) {
+            GTEST_SKIP() << "Device doesn't have dynamic partitions enabled, skipping";
+        }
+    }
+};
+
+TEST_F(DeviceTest, BlockDeviceInfo) {
+    PartitionOpener opener;
+    BlockDeviceInfo device_info;
+    ASSERT_TRUE(opener.GetInfo(fs_mgr_get_super_partition_name(), &device_info));
+
+    // Sanity check that the device doesn't give us some weird inefficient
+    // alignment.
+    EXPECT_EQ(device_info.alignment % LP_SECTOR_SIZE, 0);
+    EXPECT_EQ(device_info.alignment_offset % LP_SECTOR_SIZE, 0);
+    EXPECT_LE(device_info.alignment_offset, INT_MAX);
+    EXPECT_EQ(device_info.logical_block_size % LP_SECTOR_SIZE, 0);
+
+    // Having an alignment offset > alignment doesn't really make sense.
+    EXPECT_LT(device_info.alignment_offset, device_info.alignment);
+}
+
+TEST_F(DeviceTest, ReadSuperPartitionCurrentSlot) {
+    auto slot_suffix = fs_mgr_get_slot_suffix();
+    auto slot_number = SlotNumberForSlotSuffix(slot_suffix);
+    auto super_name = fs_mgr_get_super_partition_name(slot_number);
+    auto metadata = ReadMetadata(super_name, slot_number);
+    EXPECT_NE(metadata, nullptr);
+}
+
+TEST_F(DeviceTest, ReadSuperPartitionOtherSlot) {
+    auto other_slot_suffix = fs_mgr_get_other_slot_suffix();
+    if (other_slot_suffix.empty()) {
+        GTEST_SKIP() << "No other slot, skipping";
+    }
+    if (IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions_retrofit",
+                                                         false)) {
+        GTEST_SKIP() << "Device with retrofit dynamic partition may not have metadata at other "
+                     << "slot, skipping";
+    }
+
+    auto other_slot_number = SlotNumberForSlotSuffix(other_slot_suffix);
+    auto other_super_name = fs_mgr_get_super_partition_name(other_slot_number);
+    auto other_metadata = ReadMetadata(other_super_name, other_slot_number);
+    EXPECT_NE(other_metadata, nullptr);
+}
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index a2221ef..e3b591a 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -24,6 +24,7 @@
 #include <memory>
 #include <optional>
 #include <set>
+#include <string_view>
 
 #include "liblp.h"
 #include "partition_opener.h"
@@ -37,6 +38,9 @@
 static const uint32_t kDefaultPartitionAlignment = 1024 * 1024;
 static const uint32_t kDefaultBlockSize = 4096;
 
+// Name of the default group in a metadata.
+static constexpr std::string_view kDefaultGroup = "default";
+
 // Abstraction around dm-targets that can be encoded into logical partition tables.
 class Extent {
   public:
@@ -104,7 +108,7 @@
     friend class MetadataBuilder;
 
   public:
-    Partition(const std::string& name, const std::string& group_name, uint32_t attributes);
+    Partition(std::string_view name, std::string_view group_name, uint32_t attributes);
 
     // Add a raw extent.
     void AddExtent(std::unique_ptr<Extent>&& extent);
@@ -119,12 +123,13 @@
     const std::string& name() const { return name_; }
     const std::string& group_name() const { return group_name_; }
     uint32_t attributes() const { return attributes_; }
+    void set_attributes(uint32_t attributes) { attributes_ = attributes; }
     const std::vector<std::unique_ptr<Extent>>& extents() const { return extents_; }
     uint64_t size() const { return size_; }
 
   private:
     void ShrinkTo(uint64_t aligned_size);
-    void set_group_name(const std::string& group_name) { group_name_ = group_name; }
+    void set_group_name(std::string_view group_name) { group_name_ = group_name; }
 
     std::string name_;
     std::string group_name_;
@@ -196,12 +201,6 @@
         return New(device_info, metadata_max_size, metadata_slot_count);
     }
 
-    // Used by the test harness to override whether the device is "A/B".
-    static void OverrideABForTesting(bool ab_device);
-
-    // Used by the test harness to override whether the device is "retrofitting dynamic partitions".
-    static void OverrideRetrofitDynamicParititonsForTesting(bool retrofit);
-
     // Define a new partition group. By default there is one group called
     // "default", with an unrestricted size. A non-zero size will restrict the
     // total space used by all partitions in the group.
@@ -215,7 +214,7 @@
 
     // Add a partition, returning a handle so it can be sized as needed. If a
     // partition with the given name already exists, nullptr is returned.
-    Partition* AddPartition(const std::string& name, const std::string& group_name,
+    Partition* AddPartition(std::string_view name, std::string_view group_name,
                             uint32_t attributes);
 
     // Same as AddPartition above, but uses the default partition group which
@@ -226,10 +225,10 @@
     void RemovePartition(const std::string& name);
 
     // Find a partition by name. If no partition is found, nullptr is returned.
-    Partition* FindPartition(const std::string& name);
+    Partition* FindPartition(std::string_view name);
 
     // Find a group by name. If no group is found, nullptr is returned.
-    PartitionGroup* FindGroup(const std::string& name);
+    PartitionGroup* FindGroup(std::string_view name);
 
     // Add a predetermined extent to a partition.
     bool AddLinearExtent(Partition* partition, const std::string& block_device,
@@ -254,7 +253,7 @@
     // the metadata is exported, to avoid errors during potential group and
     // size shuffling operations. This will return false if the new group does
     // not exist.
-    bool ChangePartitionGroup(Partition* partition, const std::string& group_name);
+    bool ChangePartitionGroup(Partition* partition, std::string_view group_name);
 
     // Changes the size of a partition group. Size constraints will not be
     // checked until metadata is exported, to avoid errors during group
@@ -270,7 +269,7 @@
     std::vector<std::string> ListGroups() const;
 
     // Remove all partitions belonging to a group, then remove the group.
-    void RemoveGroupAndPartitions(const std::string& group_name);
+    void RemoveGroupAndPartitions(std::string_view group_name);
 
     // Set the LP_METADATA_AUTO_SLOT_SUFFIXING flag.
     void SetAutoSlotSuffixing();
@@ -289,6 +288,9 @@
     // Return true if a block device is found, else false.
     bool HasBlockDevice(const std::string& partition_name) const;
 
+    // Return the name of the block device at |index|.
+    std::string GetBlockDevicePartitionName(uint64_t index) const;
+
   private:
     MetadataBuilder();
     MetadataBuilder(const MetadataBuilder&) = delete;
@@ -316,8 +318,8 @@
     // Return true if the device is retrofitting dynamic partitions.
     static bool IsRetrofitDynamicPartitionsDevice();
 
-    // Return true if "this" metadata represents a metadata on a retrofit device.
-    bool IsRetrofitMetadata() const;
+    // Return true if _b partitions should be prioritized at the second half of the device.
+    bool ShouldHalveSuper() const;
 
     bool ValidatePartitionGroups() const;
 
@@ -347,8 +349,8 @@
                                                     const std::vector<Interval>& free_list,
                                                     uint64_t sectors_needed) const;
 
-    static std::optional<bool> sABOverride;
-    static std::optional<bool> sRetrofitDap;
+    static bool UpdateMetadataForOtherSuper(LpMetadata* metadata, uint32_t source_slot_number,
+                                            uint32_t target_slot_number);
 
     LpMetadataGeometry geometry_;
     LpMetadataHeader header_;
diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h
index 8934aaf..6e928b4 100644
--- a/fs_mgr/liblp/include/liblp/metadata_format.h
+++ b/fs_mgr/liblp/include/liblp/metadata_format.h
@@ -39,7 +39,11 @@
 
 /* Current metadata version. */
 #define LP_METADATA_MAJOR_VERSION 10
-#define LP_METADATA_MINOR_VERSION 0
+#define LP_METADATA_MINOR_VERSION_MIN 0
+#define LP_METADATA_MINOR_VERSION_MAX 1
+
+/* Metadata version needed to use the UPDATED partition attribute. */
+#define LP_METADATA_VERSION_FOR_UPDATED_ATTR 1
 
 /* Attributes for the LpMetadataPartition::attributes field.
  *
@@ -58,8 +62,20 @@
  */
 #define LP_PARTITION_ATTR_SLOT_SUFFIXED (1 << 1)
 
-/* Mask that defines all valid attributes. */
-#define LP_PARTITION_ATTRIBUTE_MASK (LP_PARTITION_ATTR_READONLY | LP_PARTITION_ATTR_SLOT_SUFFIXED)
+/* This flag is applied automatically when using MetadataBuilder::NewForUpdate.
+ * It signals that the partition was created (or modified) for a snapshot-based
+ * update. If this flag is not present, the partition was likely flashed via
+ * fastboot.
+ */
+#define LP_PARTITION_ATTR_UPDATED (1 << 2)
+
+/* Mask that defines all valid attributes. When changing this, make sure to
+ * update ParseMetadata().
+ */
+#define LP_PARTITION_ATTRIBUTE_MASK_V0 \
+    (LP_PARTITION_ATTR_READONLY | LP_PARTITION_ATTR_SLOT_SUFFIXED)
+#define LP_PARTITION_ATTRIBUTE_MASK_V1 (LP_PARTITION_ATTRIBUTE_MASK_V0 | LP_PARTITION_ATTR_UPDATED)
+#define LP_PARTITION_ATTRIBUTE_MASK LP_PARTITION_ATTRIBUTE_MASK_V1
 
 /* Default name of the physical partition that holds logical partition entries.
  * The layout of this partition will look like:
diff --git a/fs_mgr/liblp/include/liblp/mock_property_fetcher.h b/fs_mgr/liblp/include/liblp/mock_property_fetcher.h
new file mode 100644
index 0000000..f15611b
--- /dev/null
+++ b/fs_mgr/liblp/include/liblp/mock_property_fetcher.h
@@ -0,0 +1,56 @@
+/*
+ * 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 <gmock/gmock.h>
+
+#include <liblp/property_fetcher.h>
+
+namespace android {
+namespace fs_mgr {
+namespace testing {
+
+class MockPropertyFetcher : public IPropertyFetcher {
+  public:
+    MOCK_METHOD2(GetProperty, std::string(const std::string&, const std::string&));
+    MOCK_METHOD2(GetBoolProperty, bool(const std::string&, bool));
+
+    // By default, return default_value for all functions.
+    MockPropertyFetcher() {
+        using ::testing::_;
+        using ::testing::Invoke;
+        ON_CALL(*this, GetProperty(_, _)).WillByDefault(Invoke([](const auto&, const auto& def) {
+            return def;
+        }));
+        ON_CALL(*this, GetBoolProperty(_, _)).WillByDefault(Invoke([](const auto&, auto def) {
+            return def;
+        }));
+    }
+};
+
+static inline void ResetMockPropertyFetcher() {
+    IPropertyFetcher::OverrideForTesting(
+            std::make_unique<::testing::NiceMock<MockPropertyFetcher>>());
+}
+
+static inline MockPropertyFetcher* GetMockedPropertyFetcher() {
+    return static_cast<MockPropertyFetcher*>(IPropertyFetcher::GetInstance());
+}
+
+}  // namespace testing
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/fs_mgr/liblp/include/liblp/partition_opener.h b/fs_mgr/liblp/include/liblp/partition_opener.h
index e506bd5..7c9100b 100644
--- a/fs_mgr/liblp/include/liblp/partition_opener.h
+++ b/fs_mgr/liblp/include/liblp/partition_opener.h
@@ -62,6 +62,11 @@
     // Return block device information about the given named physical partition.
     // The name can be an absolute path if the full path is already known.
     virtual bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const = 0;
+
+    // Return a path that can be used to pass the block device to device-mapper.
+    // This must either result in an absolute path, or a major:minor device
+    // sequence.
+    virtual std::string GetDeviceString(const std::string& partition_name) const = 0;
 };
 
 // Helper class to implement IPartitionOpener. If |partition_name| is not an
@@ -71,6 +76,7 @@
     virtual android::base::unique_fd Open(const std::string& partition_name,
                                           int flags) const override;
     virtual bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const override;
+    virtual std::string GetDeviceString(const std::string& partition_name) const override;
 };
 
 }  // namespace fs_mgr
diff --git a/fs_mgr/liblp/include/liblp/property_fetcher.h b/fs_mgr/liblp/include/liblp/property_fetcher.h
new file mode 100644
index 0000000..e73a1f5
--- /dev/null
+++ b/fs_mgr/liblp/include/liblp/property_fetcher.h
@@ -0,0 +1,42 @@
+//
+// 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 <memory>
+
+namespace android {
+namespace fs_mgr {
+
+class IPropertyFetcher {
+  public:
+    virtual ~IPropertyFetcher() = default;
+    virtual std::string GetProperty(const std::string& key, const std::string& defaultValue) = 0;
+    virtual bool GetBoolProperty(const std::string& key, bool defaultValue) = 0;
+
+    static IPropertyFetcher* GetInstance();
+    static void OverrideForTesting(std::unique_ptr<IPropertyFetcher>&&);
+};
+
+class PropertyFetcher : public IPropertyFetcher {
+  public:
+    ~PropertyFetcher() = default;
+    std::string GetProperty(const std::string& key, const std::string& defaultValue) override;
+    bool GetBoolProperty(const std::string& key, bool defaultValue) override;
+};
+
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index 70dd85f..22f6746 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -21,12 +21,12 @@
 
 #include <android-base/file.h>
 #include <android-base/unique_fd.h>
-#include <fs_mgr.h>
-#include <fstab/fstab.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <liblp/builder.h>
 
 #include "images.h"
+#include "liblp_test.h"
 #include "reader.h"
 #include "test_partition_opener.h"
 #include "utility.h"
@@ -34,6 +34,9 @@
 
 using namespace std;
 using namespace android::fs_mgr;
+using namespace android::fs_mgr::testing;
+using ::testing::_;
+using ::testing::Return;
 using unique_fd = android::base::unique_fd;
 
 // Our tests assume a 128KiB disk with two 512 byte metadata slots.
@@ -120,7 +123,7 @@
 }
 
 // Test that our CreateFakeDisk() function works.
-TEST(liblp, CreateFakeDisk) {
+TEST_F(LiblpTest, CreateFakeDisk) {
     unique_fd fd = CreateFakeDisk();
     ASSERT_GE(fd, 0);
 
@@ -136,7 +139,7 @@
 
 // Flashing metadata should not work if the metadata was created for a larger
 // disk than the destination disk.
-TEST(liblp, ExportDiskTooSmall) {
+TEST_F(LiblpTest, ExportDiskTooSmall) {
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize + 4096, 512, 2);
     ASSERT_NE(builder, nullptr);
     unique_ptr<LpMetadata> exported = builder->Export();
@@ -153,7 +156,7 @@
 }
 
 // Test the basics of flashing a partition and reading it back.
-TEST(liblp, FlashAndReadback) {
+TEST_F(LiblpTest, FlashAndReadback) {
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(AddDefaultPartitions(builder.get()));
@@ -203,7 +206,7 @@
 }
 
 // Test that we can update metadata slots without disturbing others.
-TEST(liblp, UpdateAnyMetadataSlot) {
+TEST_F(LiblpTest, UpdateAnyMetadataSlot) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -248,7 +251,7 @@
     }
 }
 
-TEST(liblp, InvalidMetadataSlot) {
+TEST_F(LiblpTest, InvalidMetadataSlot) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -267,7 +270,7 @@
 
 // Test that updating a metadata slot does not allow it to be computed based
 // on mismatching geometry.
-TEST(liblp, NoChangingGeometry) {
+TEST_F(LiblpTest, NoChangingGeometry) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -296,7 +299,7 @@
 }
 
 // Test that changing one bit of metadata is enough to break the checksum.
-TEST(liblp, BitFlipGeometry) {
+TEST_F(LiblpTest, BitFlipGeometry) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -315,7 +318,7 @@
     EXPECT_EQ(metadata->geometry.metadata_slot_count, 2);
 }
 
-TEST(liblp, ReadBackupGeometry) {
+TEST_F(LiblpTest, ReadBackupGeometry) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -335,7 +338,7 @@
     EXPECT_EQ(ReadMetadata(opener, "super", 0), nullptr);
 }
 
-TEST(liblp, ReadBackupMetadata) {
+TEST_F(LiblpTest, ReadBackupMetadata) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -362,7 +365,7 @@
 
 // Test that we don't attempt to write metadata if it would overflow its
 // reserved space.
-TEST(liblp, TooManyPartitions) {
+TEST_F(LiblpTest, TooManyPartitions) {
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
 
@@ -416,7 +419,7 @@
 }
 
 // Test that we can read and write image files.
-TEST(liblp, ImageFiles) {
+TEST_F(LiblpTest, ImageFiles) {
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(AddDefaultPartitions(builder.get()));
@@ -432,7 +435,7 @@
 }
 
 // Test that we can read images from buffers.
-TEST(liblp, ImageFilesInMemory) {
+TEST_F(LiblpTest, ImageFilesInMemory) {
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(AddDefaultPartitions(builder.get()));
@@ -492,7 +495,7 @@
 
 // Test that an interrupted flash operation on the "primary" copy of metadata
 // is not fatal.
-TEST(liblp, UpdatePrimaryMetadataFailure) {
+TEST_F(LiblpTest, UpdatePrimaryMetadataFailure) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -520,7 +523,7 @@
 
 // Test that an interrupted flash operation on the "backup" copy of metadata
 // is not fatal.
-TEST(liblp, UpdateBackupMetadataFailure) {
+TEST_F(LiblpTest, UpdateBackupMetadataFailure) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -549,7 +552,7 @@
 // Test that an interrupted write *in between* writing metadata will read
 // the correct metadata copy. The primary is always considered newer than
 // the backup.
-TEST(liblp, UpdateMetadataCleanFailure) {
+TEST_F(LiblpTest, UpdateMetadataCleanFailure) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -586,7 +589,7 @@
 }
 
 // Test that writing a sparse image can be read back.
-TEST(liblp, FlashSparseImage) {
+TEST_F(LiblpTest, FlashSparseImage) {
     unique_fd fd = CreateFakeDisk();
     ASSERT_GE(fd, 0);
 
@@ -620,7 +623,7 @@
     ASSERT_NE(ReadBackupMetadata(fd.get(), geometry, 0), nullptr);
 }
 
-TEST(liblp, AutoSlotSuffixing) {
+TEST_F(LiblpTest, AutoSlotSuffixing) {
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(AddDefaultPartitions(builder.get()));
@@ -663,8 +666,9 @@
     EXPECT_EQ(metadata->groups[1].flags, 0);
 }
 
-TEST(liblp, UpdateRetrofit) {
-    MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(true);
+TEST_F(LiblpTest, UpdateRetrofit) {
+    ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+            .WillByDefault(Return(true));
 
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
@@ -694,8 +698,9 @@
     ASSERT_TRUE(updated->extents.empty());
 }
 
-TEST(liblp, UpdateNonRetrofit) {
-    MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
+TEST_F(LiblpTest, UpdateNonRetrofit) {
+    ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+            .WillByDefault(Return(false));
 
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
@@ -709,18 +714,31 @@
     EXPECT_EQ(GetBlockDevicePartitionName(updated->block_devices[0]), "super");
 }
 
-TEST(liblp, ReadSuperPartition) {
-    auto slot_suffix = fs_mgr_get_slot_suffix();
-    auto slot_number = SlotNumberForSlotSuffix(slot_suffix);
-    auto super_name = fs_mgr_get_super_partition_name(slot_number);
-    auto metadata = ReadMetadata(super_name, slot_number);
-    ASSERT_NE(metadata, nullptr);
+TEST_F(LiblpTest, UpdateVirtualAB) {
+    ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
+            .WillByDefault(Return(true));
 
-    if (!slot_suffix.empty()) {
-        auto other_slot_suffix = fs_mgr_get_other_slot_suffix();
-        auto other_slot_number = SlotNumberForSlotSuffix(other_slot_suffix);
-        auto other_super_name = fs_mgr_get_super_partition_name(other_slot_number);
-        auto other_metadata = ReadMetadata(other_super_name, other_slot_number);
-        ASSERT_NE(other_metadata, nullptr);
-    }
+    unique_fd fd = CreateFlashedDisk();
+    ASSERT_GE(fd, 0);
+
+    DefaultPartitionOpener opener(fd);
+    auto builder = MetadataBuilder::NewForUpdate(opener, "super", 0, 1);
+    ASSERT_NE(builder, nullptr);
+    auto updated = builder->Export();
+    ASSERT_NE(updated, nullptr);
+    ASSERT_TRUE(UpdatePartitionTable(opener, "super", *updated.get(), 1));
+
+    // Validate old slot.
+    auto metadata = ReadMetadata(opener, "super", 0);
+    ASSERT_NE(metadata, nullptr);
+    ASSERT_EQ(metadata->header.minor_version, 0);
+    ASSERT_GE(metadata->partitions.size(), 1);
+    ASSERT_EQ(metadata->partitions[0].attributes & LP_PARTITION_ATTR_UPDATED, 0);
+
+    // Validate new slot.
+    metadata = ReadMetadata(opener, "super", 1);
+    ASSERT_NE(metadata, nullptr);
+    ASSERT_EQ(metadata->header.minor_version, 1);
+    ASSERT_GE(metadata->partitions.size(), 1);
+    ASSERT_NE(metadata->partitions[0].attributes & LP_PARTITION_ATTR_UPDATED, 0);
 }
diff --git a/fs_mgr/liblp/liblp_test.h b/fs_mgr/liblp/liblp_test.h
new file mode 100644
index 0000000..c61aaa5
--- /dev/null
+++ b/fs_mgr/liblp/liblp_test.h
@@ -0,0 +1,35 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include <liblp/mock_property_fetcher.h>
+
+namespace android {
+namespace fs_mgr {
+namespace testing {
+
+class LiblpTest : public ::testing::Test {
+  public:
+    void SetUp() override { ResetMockPropertyFetcher(); }
+    void TearDown() override { ResetMockPropertyFetcher(); }
+};
+
+}  // namespace testing
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/fs_mgr/liblp/liblp_test.xml b/fs_mgr/liblp/liblp_test.xml
new file mode 100644
index 0000000..d9ee12e
--- /dev/null
+++ b/fs_mgr/liblp/liblp_test.xml
@@ -0,0 +1,27 @@
+<?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 liblp_test">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="liblp_test->/data/local/tmp/liblp_test" />
+    </target_preparer>
+    <option name="test-suite-tag" value="vts-core" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="liblp_test" />
+    </test>
+</configuration>
diff --git a/fs_mgr/liblp/partition_opener.cpp b/fs_mgr/liblp/partition_opener.cpp
index cc4a882..1d4db85 100644
--- a/fs_mgr/liblp/partition_opener.cpp
+++ b/fs_mgr/liblp/partition_opener.cpp
@@ -41,7 +41,21 @@
     if (android::base::StartsWith(path, "/")) {
         return path;
     }
-    return "/dev/block/by-name/" + path;
+
+    auto by_name = "/dev/block/by-name/" + path;
+    if (access(by_name.c_str(), F_OK) != 0) {
+        // If the by-name symlink doesn't exist, as a special case we allow
+        // certain devices to be used as partition names. This can happen if a
+        // Dynamic System Update is installed to an sdcard, which won't be in
+        // the boot device list.
+        //
+        // We whitelist because most devices in /dev/block are not valid for
+        // storing fiemaps.
+        if (android::base::StartsWith(path, "mmcblk")) {
+            return "/dev/block/" + path;
+        }
+    }
+    return by_name;
 }
 
 bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device_info) {
@@ -100,5 +114,9 @@
     return GetBlockDeviceInfo(path, info);
 }
 
+std::string PartitionOpener::GetDeviceString(const std::string& partition_name) const {
+    return GetPartitionAbsolutePath(partition_name);
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/property_fetcher.cpp b/fs_mgr/liblp/property_fetcher.cpp
new file mode 100644
index 0000000..038ef4d
--- /dev/null
+++ b/fs_mgr/liblp/property_fetcher.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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 "liblp/property_fetcher.h"
+
+#include <memory>
+
+#include <android-base/properties.h>
+
+namespace android {
+namespace fs_mgr {
+
+std::string PropertyFetcher::GetProperty(const std::string& key, const std::string& default_value) {
+    return android::base::GetProperty(key, default_value);
+}
+
+bool PropertyFetcher::GetBoolProperty(const std::string& key, bool default_value) {
+    return android::base::GetBoolProperty(key, default_value);
+}
+
+static std::unique_ptr<IPropertyFetcher>* GetInstanceAllocation() {
+    static std::unique_ptr<IPropertyFetcher> instance = std::make_unique<PropertyFetcher>();
+    return &instance;
+}
+
+IPropertyFetcher* IPropertyFetcher::GetInstance() {
+    return GetInstanceAllocation()->get();
+}
+
+void IPropertyFetcher::OverrideForTesting(std::unique_ptr<IPropertyFetcher>&& fetcher) {
+    GetInstanceAllocation()->swap(fetcher);
+    fetcher.reset();
+}
+
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp
index dcee6d2..aecf685 100644
--- a/fs_mgr/liblp/reader.cpp
+++ b/fs_mgr/liblp/reader.cpp
@@ -18,6 +18,7 @@
 
 #include <stddef.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 
 #include <functional>
@@ -180,7 +181,7 @@
     }
     // Check that the version is compatible.
     if (header.major_version != LP_METADATA_MAJOR_VERSION ||
-        header.minor_version > LP_METADATA_MINOR_VERSION) {
+        header.minor_version > LP_METADATA_MINOR_VERSION_MAX) {
         LERROR << "Logical partition metadata has incompatible version.";
         return false;
     }
@@ -244,6 +245,13 @@
         return nullptr;
     }
 
+    uint32_t valid_attributes = 0;
+    if (metadata->header.minor_version >= LP_METADATA_VERSION_FOR_UPDATED_ATTR) {
+        valid_attributes = LP_PARTITION_ATTRIBUTE_MASK_V1;
+    } else {
+        valid_attributes = LP_PARTITION_ATTRIBUTE_MASK_V0;
+    }
+
     // ValidateTableSize ensured that |cursor| is valid for the number of
     // entries in the table.
     uint8_t* cursor = buffer.get() + header.partitions.offset;
@@ -252,7 +260,7 @@
         memcpy(&partition, cursor, sizeof(partition));
         cursor += header.partitions.entry_size;
 
-        if (partition.attributes & ~LP_PARTITION_ATTRIBUTE_MASK) {
+        if (partition.attributes & ~valid_attributes) {
             LERROR << "Logical partition has invalid attribute set.";
             return nullptr;
         }
diff --git a/fs_mgr/liblp/utility.cpp b/fs_mgr/liblp/utility.cpp
index 338b525..afcce8f 100644
--- a/fs_mgr/liblp/utility.cpp
+++ b/fs_mgr/liblp/utility.cpp
@@ -24,6 +24,10 @@
 #include <sys/ioctl.h>
 #endif
 
+#include <map>
+#include <string>
+#include <vector>
+
 #include <android-base/file.h>
 #include <ext4_utils/ext4_utils.h>
 #include <openssl/sha.h>
@@ -182,6 +186,14 @@
     return true;
 }
 
+bool UpdatePartitionName(LpMetadataPartition* partition, const std::string& name) {
+    if (name.size() > sizeof(partition->name)) {
+        return false;
+    }
+    strncpy(partition->name, name.c_str(), sizeof(partition->name));
+    return true;
+}
+
 bool SetBlockReadonly(int fd, bool readonly) {
 #if defined(__linux__)
     int val = readonly;
@@ -207,5 +219,71 @@
     return base::unique_fd(open(path, flags));
 }
 
+bool UpdateMetadataForInPlaceSnapshot(LpMetadata* metadata, uint32_t source_slot_number,
+                                      uint32_t target_slot_number) {
+    std::string source_slot_suffix = SlotSuffixForSlotNumber(source_slot_number);
+    std::string target_slot_suffix = SlotSuffixForSlotNumber(target_slot_number);
+
+    // There can be leftover groups with target suffix on retrofit devices.
+    // They are useless now, so delete.
+    std::vector<LpMetadataPartitionGroup*> new_group_ptrs;
+    for (auto& group : metadata->groups) {
+        std::string group_name = GetPartitionGroupName(group);
+        std::string slot_suffix = GetPartitionSlotSuffix(group_name);
+        // Don't add groups with target slot suffix.
+        if (slot_suffix == target_slot_suffix) continue;
+        // Replace source slot suffix with target slot suffix.
+        if (slot_suffix == source_slot_suffix) {
+            std::string new_name = group_name.substr(0, group_name.size() - slot_suffix.size()) +
+                                   target_slot_suffix;
+            if (!UpdatePartitionGroupName(&group, new_name)) {
+                LERROR << "Group name too long: " << new_name;
+                return false;
+            }
+        }
+        new_group_ptrs.push_back(&group);
+    }
+
+    std::vector<LpMetadataPartition*> new_partition_ptrs;
+    for (auto& partition : metadata->partitions) {
+        std::string partition_name = GetPartitionName(partition);
+        std::string slot_suffix = GetPartitionSlotSuffix(partition_name);
+        // Don't add partitions with target slot suffix.
+        if (slot_suffix == target_slot_suffix) continue;
+        // Replace source slot suffix with target slot suffix.
+        if (slot_suffix == source_slot_suffix) {
+            std::string new_name =
+                    partition_name.substr(0, partition_name.size() - slot_suffix.size()) +
+                    target_slot_suffix;
+            if (!UpdatePartitionName(&partition, new_name)) {
+                LERROR << "Partition name too long: " << new_name;
+                return false;
+            }
+        }
+        // Update group index.
+        auto it = std::find(new_group_ptrs.begin(), new_group_ptrs.end(),
+                            &metadata->groups[partition.group_index]);
+        if (it == new_group_ptrs.end()) {
+            LWARN << "Removing partition " << partition_name << " from group "
+                  << GetPartitionGroupName(metadata->groups[partition.group_index])
+                  << "; this partition should not belong to this group!";
+            continue;  // not adding to new_partition_ptrs
+        }
+        partition.attributes |= LP_PARTITION_ATTR_UPDATED;
+        partition.group_index = std::distance(new_group_ptrs.begin(), it);
+        new_partition_ptrs.push_back(&partition);
+    }
+
+    std::vector<LpMetadataPartition> new_partitions;
+    for (auto* p : new_partition_ptrs) new_partitions.emplace_back(std::move(*p));
+    metadata->partitions = std::move(new_partitions);
+
+    std::vector<LpMetadataPartitionGroup> new_groups;
+    for (auto* g : new_group_ptrs) new_groups.emplace_back(std::move(*g));
+    metadata->groups = std::move(new_groups);
+
+    return true;
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/utility.h b/fs_mgr/liblp/utility.h
index 96f1717..25ab66b 100644
--- a/fs_mgr/liblp/utility.h
+++ b/fs_mgr/liblp/utility.h
@@ -89,12 +89,17 @@
 // Update names from C++ strings.
 bool UpdateBlockDevicePartitionName(LpMetadataBlockDevice* device, const std::string& name);
 bool UpdatePartitionGroupName(LpMetadataPartitionGroup* group, const std::string& name);
+bool UpdatePartitionName(LpMetadataPartition* partition, const std::string& name);
 
 // Call BLKROSET ioctl on fd so that fd is readonly / read-writable.
 bool SetBlockReadonly(int fd, bool readonly);
 
 ::android::base::unique_fd GetControlFileOrOpen(const char* path, int flags);
 
+// For Virtual A/B updates, modify |metadata| so that it can be written to |target_slot_number|.
+bool UpdateMetadataForInPlaceSnapshot(LpMetadata* metadata, uint32_t source_slot_number,
+                                      uint32_t target_slot_number);
+
 }  // namespace fs_mgr
 }  // namespace android
 
diff --git a/fs_mgr/liblp/utility_test.cpp b/fs_mgr/liblp/utility_test.cpp
index 15f7fff..cac3989 100644
--- a/fs_mgr/liblp/utility_test.cpp
+++ b/fs_mgr/liblp/utility_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <gtest/gtest.h>
+#include <liblp/builder.h>
 #include <liblp/liblp.h>
 
 #include "utility.h"
@@ -75,3 +76,81 @@
     EXPECT_EQ(GetPartitionSlotSuffix("system_a"), "_a");
     EXPECT_EQ(GetPartitionSlotSuffix("system_b"), "_b");
 }
+
+namespace android {
+namespace fs_mgr {
+// Equality comparison for testing. In reality, equality of device_index doesn't
+// necessary mean equality of the block device.
+bool operator==(const LinearExtent& l, const LinearExtent& r) {
+    return l.device_index() == r.device_index() && l.physical_sector() == r.physical_sector() &&
+           l.end_sector() == r.end_sector();
+}
+}  // namespace fs_mgr
+}  // namespace android
+
+static std::vector<LinearExtent> GetPartitionExtents(Partition* p) {
+    std::vector<LinearExtent> extents;
+    for (auto&& extent : p->extents()) {
+        auto linear_extent = extent->AsLinearExtent();
+        if (!linear_extent) return {};
+        extents.push_back(*linear_extent);
+    }
+    return extents;
+}
+
+TEST(liblp, UpdateMetadataForInPlaceSnapshot) {
+    using std::unique_ptr;
+
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+    ASSERT_NE(builder, nullptr);
+
+    ASSERT_TRUE(builder->AddGroup("group_a", 256 * 1024));
+    Partition* system_a = builder->AddPartition("system_a", "group_a", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system_a, nullptr);
+    ASSERT_TRUE(builder->ResizePartition(system_a, 40 * 1024));
+    Partition* vendor_a = builder->AddPartition("vendor_a", "group_a", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(vendor_a, nullptr);
+    ASSERT_TRUE(builder->ResizePartition(vendor_a, 20 * 1024));
+
+    ASSERT_TRUE(builder->AddGroup("group_b", 258 * 1024));
+    Partition* system_b = builder->AddPartition("system_b", "group_b", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system_b, nullptr);
+    ASSERT_TRUE(builder->ResizePartition(system_b, 36 * 1024));
+    Partition* vendor_b = builder->AddPartition("vendor_b", "group_b", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(vendor_b, nullptr);
+    ASSERT_TRUE(builder->ResizePartition(vendor_b, 32 * 1024));
+
+    auto system_a_extents = GetPartitionExtents(system_a);
+    ASSERT_FALSE(system_a_extents.empty());
+
+    auto vendor_a_extents = GetPartitionExtents(vendor_a);
+    ASSERT_FALSE(vendor_a_extents.empty());
+
+    auto metadata = builder->Export();
+    ASSERT_NE(nullptr, metadata);
+
+    ASSERT_TRUE(UpdateMetadataForInPlaceSnapshot(metadata.get(), 0, 1));
+
+    auto new_builder = MetadataBuilder::New(*metadata);
+    ASSERT_NE(nullptr, new_builder);
+
+    EXPECT_EQ(nullptr, new_builder->FindGroup("group_a"));
+    EXPECT_EQ(nullptr, new_builder->FindPartition("system_a"));
+    EXPECT_EQ(nullptr, new_builder->FindPartition("vendor_a"));
+
+    auto group_b = new_builder->FindGroup("group_b");
+    ASSERT_NE(nullptr, group_b);
+    ASSERT_EQ(256 * 1024, group_b->maximum_size());
+
+    auto new_system_b = new_builder->FindPartition("system_b");
+    ASSERT_NE(nullptr, new_system_b);
+    EXPECT_EQ(40 * 1024, new_system_b->size());
+    auto new_system_b_extents = GetPartitionExtents(new_system_b);
+    EXPECT_EQ(system_a_extents, new_system_b_extents);
+
+    auto new_vendor_b = new_builder->FindPartition("vendor_b");
+    ASSERT_NE(nullptr, new_vendor_b);
+    EXPECT_EQ(20 * 1024, new_vendor_b->size());
+    auto new_vendor_b_extents = GetPartitionExtents(new_vendor_b);
+    EXPECT_EQ(vendor_a_extents, new_vendor_b_extents);
+}
diff --git a/fs_mgr/liblp/writer.cpp b/fs_mgr/liblp/writer.cpp
index bffcb7e..8a983ad 100644
--- a/fs_mgr/liblp/writer.cpp
+++ b/fs_mgr/liblp/writer.cpp
@@ -17,6 +17,7 @@
 #include "writer.h"
 
 #include <inttypes.h>
+#include <string.h>
 #include <unistd.h>
 
 #include <string>
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 52aad12..a54db58 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -28,11 +28,19 @@
     ],
     static_libs: [
         "libdm",
+        "libfs_mgr",
+        "liblp",
     ],
     whole_static_libs: [
         "libext2_uuid",
         "libext4_utils",
-        "libfiemap",
+        "libfstab",
+    ],
+    header_libs: [
+        "libfiemap_headers",
+    ],
+    export_header_lib_headers: [
+        "libfiemap_headers",
     ],
     export_include_dirs: ["include"],
 }
@@ -48,7 +56,7 @@
     name: "libsnapshot",
     defaults: ["libsnapshot_defaults"],
     srcs: [":libsnapshot_sources"],
-    static_libs: [
+    whole_static_libs: [
         "libfiemap_binder",
     ],
 }
@@ -58,6 +66,9 @@
     defaults: ["libsnapshot_defaults"],
     srcs: [":libsnapshot_sources"],
     recovery_available: true,
+    whole_static_libs: [
+        "libfiemap_passthrough",
+    ],
 }
 
 cc_test {
@@ -65,6 +76,7 @@
     defaults: ["libsnapshot_defaults"],
     srcs: [
         "snapshot_test.cpp",
+        "test_helpers.cpp",
     ],
     shared_libs: [
         "libbinder",
@@ -74,6 +86,7 @@
         "libcutils",
         "libcrypto",
         "libfs_mgr",
+        "libgmock",
         "liblp",
         "libsnapshot",
     ],
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 03e9eef..e3fc4f6 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -22,8 +22,9 @@
 #include <vector>
 
 #include <android-base/unique_fd.h>
-#include <libdm/dm_target.h>
+#include <libdm/dm.h>
 #include <libfiemap/image_manager.h>
+#include <liblp/liblp.h>
 
 #ifndef FRIEND_TEST
 #define FRIEND_TEST(test_set_name, individual_test) \
@@ -32,9 +33,19 @@
 #endif
 
 namespace android {
+
+namespace fiemap {
+class IImageManager;
+}  // namespace fiemap
+
+namespace fs_mgr {
+struct CreateLogicalPartitionParams;
+class IPartitionOpener;
+}  // namespace fs_mgr
+
 namespace snapshot {
 
-enum class UpdateState {
+enum class UpdateState : unsigned int {
     // No update or merge is in progress.
     None,
 
@@ -47,14 +58,26 @@
     // The kernel is merging in the background.
     Merging,
 
+    // Post-merge cleanup steps could not be completed due to a transient
+    // error, but the next reboot will finish any pending operations.
+    MergeNeedsReboot,
+
     // Merging is complete, and needs to be acknowledged.
     MergeCompleted,
 
     // Merging failed due to an unrecoverable error.
-    MergeFailed
+    MergeFailed,
+
+    // The update was implicitly cancelled, either by a rollback or a flash
+    // operation via fastboot. This state can only be returned by WaitForMerge.
+    Cancelled
 };
 
 class SnapshotManager final {
+    using CreateLogicalPartitionParams = android::fs_mgr::CreateLogicalPartitionParams;
+    using LpMetadata = android::fs_mgr::LpMetadata;
+    using IPartitionOpener = android::fs_mgr::IPartitionOpener;
+
   public:
     // Dependency injection for testing.
     class IDeviceInfo {
@@ -62,18 +85,26 @@
         virtual ~IDeviceInfo() {}
         virtual std::string GetGsidDir() const = 0;
         virtual std::string GetMetadataDir() const = 0;
-
-        // Return true if the device is currently running off snapshot devices,
-        // indicating that we have booted after applying (but not merging) an
-        // OTA.
-        virtual bool IsRunningSnapshot() const = 0;
+        virtual std::string GetSlotSuffix() const = 0;
+        virtual std::string GetSuperDevice(uint32_t slot) const = 0;
+        virtual const IPartitionOpener& GetPartitionOpener() const = 0;
     };
 
+    ~SnapshotManager();
+
     // Return a new SnapshotManager instance, or null on error. The device
     // pointer is owned for the lifetime of SnapshotManager. If null, a default
     // instance will be created.
     static std::unique_ptr<SnapshotManager> New(IDeviceInfo* device = nullptr);
 
+    // This is similar to New(), except designed specifically for first-stage
+    // init.
+    static std::unique_ptr<SnapshotManager> NewForFirstStageMount(IDeviceInfo* device = nullptr);
+
+    // Helper function for first-stage init to check whether a SnapshotManager
+    // might be needed to perform first-stage mounts.
+    static bool IsSnapshotManagerNeeded();
+
     // Begin an update. This must be called before creating any snapshots. It
     // will fail if GetUpdateState() != None.
     bool BeginUpdate();
@@ -82,14 +113,37 @@
     // state != Initiated or None.
     bool CancelUpdate();
 
+    // 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.
+    bool FinishedSnapshotWrites();
+
     // Initiate a merge on all snapshot devices. This should only be used after an
     // update has been marked successful after booting.
     bool InitiateMerge();
 
-    // Wait for the current merge to finish, then perform cleanup when it
-    // completes. It is necessary to call this after InitiateMerge(), or when
-    // a merge is detected for the first time after boot.
-    bool WaitForMerge();
+    // Perform any necessary post-boot actions. This should be run soon after
+    // /data is mounted.
+    //
+    // If a merge is in progress, this function will block until the merge is
+    // completed. If a merge or update was cancelled, this will clean up any
+    // update artifacts and return.
+    //
+    // Note that after calling this, GetUpdateState() may still return that a
+    // merge is in progress:
+    //   MergeFailed indicates that a fatal error occurred. WaitForMerge() may
+    //   called any number of times again to attempt to make more progress, but
+    //   we do not expect it to succeed if a catastrophic error occurred.
+    //
+    //   MergeNeedsReboot indicates that the merge has completed, but cleanup
+    //   failed. This can happen if for some reason resources were not closed
+    //   properly. In this case another reboot is needed before we can take
+    //   another OTA. However, WaitForMerge() can be called again without
+    //   rebooting, to attempt to finish cleanup anyway.
+    //
+    //   MergeCompleted indicates that the update has fully completed.
+    //   GetUpdateState will return None, and a new update can begin.
+    UpdateState ProcessUpdateState();
 
     // Find the status of the current update, if any.
     //
@@ -99,19 +153,40 @@
     //   Other: 0
     UpdateState GetUpdateState(double* progress = nullptr);
 
+    // If this returns true, first-stage mount must call
+    // CreateLogicalAndSnapshotPartitions rather than CreateLogicalPartitions.
+    bool NeedSnapshotsInFirstStageMount();
+
+    // Perform first-stage mapping of snapshot targets. This replaces init's
+    // call to CreateLogicalPartitions when snapshots are present.
+    bool CreateLogicalAndSnapshotPartitions(const std::string& super_device);
+
   private:
+    FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
     FRIEND_TEST(SnapshotTest, CreateSnapshot);
-    FRIEND_TEST(SnapshotTest, MapSnapshot);
+    FRIEND_TEST(SnapshotTest, FirstStageMountAfterRollback);
+    FRIEND_TEST(SnapshotTest, FirstStageMountAndMerge);
+    FRIEND_TEST(SnapshotTest, FlashSuperDuringMerge);
+    FRIEND_TEST(SnapshotTest, FlashSuperDuringUpdate);
     FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
+    FRIEND_TEST(SnapshotTest, MapSnapshot);
+    FRIEND_TEST(SnapshotTest, Merge);
+    FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
+    FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
     friend class SnapshotTest;
 
+    using DmTargetSnapshot = android::dm::DmTargetSnapshot;
     using IImageManager = android::fiemap::IImageManager;
+    using TargetInfo = android::dm::DeviceMapper::TargetInfo;
 
     explicit SnapshotManager(IDeviceInfo* info);
 
-    // This is created lazily since it connects via binder.
+    // This is created lazily since it can connect via binder.
     bool EnsureImageManager();
 
+    // Helper for first-stage init.
+    bool ForceLocalImageManager();
+
     // Helper function for tests.
     IImageManager* image_manager() const { return images_.get(); }
 
@@ -120,16 +195,18 @@
     // this. It also serves as a proof-of-lock for some functions.
     class LockedFile final {
       public:
-        LockedFile(const std::string& path, android::base::unique_fd&& fd)
-            : path_(path), fd_(std::move(fd)) {}
+        LockedFile(const std::string& path, android::base::unique_fd&& fd, int lock_mode)
+            : path_(path), fd_(std::move(fd)), lock_mode_(lock_mode) {}
         ~LockedFile();
 
         const std::string& path() const { return path_; }
         int fd() const { return fd_; }
+        int lock_mode() const { return lock_mode_; }
 
       private:
         std::string path_;
         android::base::unique_fd fd_;
+        int lock_mode_;
     };
     std::unique_ptr<LockedFile> OpenFile(const std::string& file, int open_flags, int lock_flags);
     bool Truncate(LockedFile* file);
@@ -164,9 +241,8 @@
     bool MapSnapshot(LockedFile* lock, const std::string& name, const std::string& base_device,
                      const std::chrono::milliseconds& timeout_ms, std::string* dev_path);
 
-    // Remove the backing copy-on-write image for the named snapshot. If the
-    // device is still mapped, this will attempt an Unmap, and fail if the
-    // unmap fails.
+    // Remove the backing copy-on-write image for the named snapshot. The
+    // caller is responsible for ensuring that the snapshot is unmapped.
     bool DeleteSnapshot(LockedFile* lock, const std::string& name);
 
     // Unmap a snapshot device previously mapped with MapSnapshotDevice().
@@ -178,16 +254,28 @@
     // List the known snapshot names.
     bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots);
 
+    // Check for a cancelled or rolled back merge, returning true if such a
+    // condition was detected and handled.
+    bool HandleCancelledUpdate(LockedFile* lock);
+
+    // Remove artifacts created by the update process, such as snapshots, and
+    // set the update state to None.
+    bool RemoveAllUpdateState(LockedFile* lock);
+
     // Interact with /metadata/ota/state.
     std::unique_ptr<LockedFile> OpenStateFile(int open_flags, int lock_flags);
     std::unique_ptr<LockedFile> LockShared();
     std::unique_ptr<LockedFile> LockExclusive();
     UpdateState ReadUpdateState(LockedFile* file);
     bool WriteUpdateState(LockedFile* file, UpdateState state);
+    std::string GetStateFilePath() const;
+
+    enum class SnapshotState : int { Created, Merging, MergeCompleted };
+    static std::string to_string(SnapshotState state);
 
     // This state is persisted per-snapshot in /metadata/ota/snapshots/.
     struct SnapshotStatus {
-        std::string state;
+        SnapshotState state;
         uint64_t device_size;
         uint64_t snapshot_size;
         // These are non-zero when merging.
@@ -195,11 +283,42 @@
         uint64_t metadata_sectors = 0;
     };
 
+    // Helpers for merging.
+    bool SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
+    bool RewriteSnapshotDeviceTable(const std::string& dm_name);
+    bool MarkSnapshotMergeCompleted(LockedFile* snapshot_lock, const std::string& snapshot_name);
+    void AcknowledgeMergeSuccess(LockedFile* lock);
+    void AcknowledgeMergeFailure();
+    bool IsCancelledSnapshot(const std::string& snapshot_name);
+
+    // Note that these require the name of the device containing the snapshot,
+    // which may be the "inner" device. Use GetsnapshotDeviecName().
+    bool QuerySnapshotStatus(const std::string& dm_name, std::string* target_type,
+                             DmTargetSnapshot::Status* status);
+    bool IsSnapshotDevice(const std::string& dm_name, TargetInfo* target = nullptr);
+
+    // Internal callback for when merging is complete.
+    bool OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
+                                 const SnapshotStatus& status);
+    bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
+
+    // Only the following UpdateStates are used here:
+    //   UpdateState::Merging
+    //   UpdateState::MergeCompleted
+    //   UpdateState::MergeFailed
+    //   UpdateState::MergeNeedsReboot
+    UpdateState CheckMergeState();
+    UpdateState CheckMergeState(LockedFile* lock);
+    UpdateState CheckTargetMergeState(LockedFile* lock, const std::string& name);
+
     // Interact with status files under /metadata/ota/snapshots.
-    std::unique_ptr<LockedFile> OpenSnapshotStatusFile(const std::string& name, int open_flags,
-                                                       int lock_flags);
-    bool WriteSnapshotStatus(LockedFile* file, const SnapshotStatus& status);
-    bool ReadSnapshotStatus(LockedFile* file, SnapshotStatus* status);
+    bool WriteSnapshotStatus(LockedFile* lock, const std::string& name,
+                             const SnapshotStatus& status);
+    bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status);
+    std::string GetSnapshotStatusFilePath(const std::string& name);
+
+    std::string GetSnapshotBootIndicatorPath();
+    void RemoveSnapshotBootIndicator();
 
     // Return the name of the device holding the "snapshot" or "snapshot-merge"
     // target. This may not be the final device presented via MapSnapshot(), if
@@ -211,6 +330,7 @@
     std::string metadata_dir_;
     std::unique_ptr<IDeviceInfo> device_;
     std::unique_ptr<IImageManager> images_;
+    bool has_local_image_manager_ = false;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 7d36b06..71457ee 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -19,13 +19,21 @@
 #include <sys/types.h>
 #include <sys/unistd.h>
 
+#include <thread>
+#include <unordered_set>
+
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <ext4_utils/ext4_utils.h>
+#include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
+#include <fstab/fstab.h>
 #include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+#include <liblp/liblp.h>
 
 namespace android {
 namespace snapshot {
@@ -39,23 +47,36 @@
 using android::dm::kSectorSize;
 using android::dm::SnapshotStorageMode;
 using android::fiemap::IImageManager;
+using android::fs_mgr::CreateDmTable;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fs_mgr::GetPartitionName;
+using android::fs_mgr::LpMetadata;
+using android::fs_mgr::SlotNumberForSlotSuffix;
 using namespace std::chrono_literals;
 using namespace std::string_literals;
 
 // Unit is sectors, this is a 4K chunk.
 static constexpr uint32_t kSnapshotChunkSize = 8;
+static constexpr char kBootIndicatorPath[] = "/metadata/ota/snapshot-boot";
 
 class DeviceInfo final : public SnapshotManager::IDeviceInfo {
   public:
     std::string GetGsidDir() const override { return "ota"s; }
-    std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
-    bool IsRunningSnapshot() const override;
+    std::string GetMetadataDir() const override { return "/metadata/ota"s; }
+    std::string GetSlotSuffix() const override { return fs_mgr_get_slot_suffix(); }
+    const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const { return opener_; }
+    std::string GetSuperDevice(uint32_t slot) const override {
+        return fs_mgr_get_super_partition_name(slot);
+    }
+
+  private:
+    android::fs_mgr::PartitionOpener opener_;
 };
 
-bool DeviceInfo::IsRunningSnapshot() const {
-    // :TODO: implement this check.
-    return true;
-}
+// Note: IImageManager is an incomplete type in the header, so the default
+// destructor doesn't work.
+SnapshotManager::~SnapshotManager() {}
 
 std::unique_ptr<SnapshotManager> SnapshotManager::New(IDeviceInfo* info) {
     if (!info) {
@@ -64,6 +85,14 @@
     return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
 }
 
+std::unique_ptr<SnapshotManager> SnapshotManager::NewForFirstStageMount(IDeviceInfo* info) {
+    auto sm = New(info);
+    if (!sm || !sm->ForceLocalImageManager()) {
+        return nullptr;
+    }
+    return sm;
+}
+
 SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
     gsid_dir_ = device_->GetGsidDir();
     metadata_dir_ = device_->GetMetadataDir();
@@ -73,6 +102,10 @@
     return snapshot_name + "-cow";
 }
 
+static std::string GetBaseDeviceName(const std::string& partition_name) {
+    return partition_name + "-base";
+}
+
 bool SnapshotManager::BeginUpdate() {
     auto file = LockExclusive();
     if (!file) return false;
@@ -95,17 +128,41 @@
         LOG(ERROR) << "Cannot cancel update after it has completed or started merging";
         return false;
     }
+    return RemoveAllUpdateState(file.get());
+}
 
-    if (!RemoveAllSnapshots(file.get())) {
+bool SnapshotManager::RemoveAllUpdateState(LockedFile* lock) {
+    if (!RemoveAllSnapshots(lock)) {
         LOG(ERROR) << "Could not remove all snapshots";
         return false;
     }
 
-    if (!WriteUpdateState(file.get(), UpdateState::None)) {
-        LOG(ERROR) << "Could not write new update state";
+    RemoveSnapshotBootIndicator();
+
+    // If this fails, we'll keep trying to remove the update state (as the
+    // device reboots or starts a new update) until it finally succeeds.
+    return WriteUpdateState(lock, UpdateState::None);
+}
+
+bool SnapshotManager::FinishedSnapshotWrites() {
+    auto lock = LockExclusive();
+    if (!lock) return false;
+
+    if (ReadUpdateState(lock.get()) != UpdateState::Initiated) {
+        LOG(ERROR) << "Can only transition to the Unverified state from the Initiated state.";
         return false;
     }
-    return true;
+
+    // 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.
+    auto contents = device_->GetSlotSuffix();
+    auto boot_file = GetSnapshotBootIndicatorPath();
+    if (!android::base::WriteStringToFile(contents, boot_file)) {
+        PLOG(ERROR) << "write failed: " << boot_file;
+        return false;
+    }
+    return WriteUpdateState(lock.get(), UpdateState::Unverified);
 }
 
 bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name,
@@ -135,18 +192,15 @@
 
     LOG(INFO) << "Snapshot " << name << " will have COW size " << cow_size;
 
-    auto status_file = OpenSnapshotStatusFile(name, O_RDWR | O_CREAT, LOCK_EX);
-    if (!status_file) return false;
-
     // Note, we leave the status file hanging around if we fail to create the
     // actual backing image. This is harmless, since it'll get removed when
     // CancelUpdate is called.
     SnapshotStatus status = {
-            .state = "created",
+            .state = SnapshotState::Created,
             .device_size = device_size,
             .snapshot_size = snapshot_size,
     };
-    if (!WriteSnapshotStatus(status_file.get(), status)) {
+    if (!WriteSnapshotStatus(lock, name, status)) {
         PLOG(ERROR) << "Could not write snapshot status: " << name;
         return false;
     }
@@ -163,17 +217,19 @@
     CHECK(lock);
     if (!EnsureImageManager()) return false;
 
-    auto status_file = OpenSnapshotStatusFile(name, O_RDWR, LOCK_EX);
-    if (!status_file) return false;
-
     SnapshotStatus status;
-    if (!ReadSnapshotStatus(status_file.get(), &status)) {
+    if (!ReadSnapshotStatus(lock, name, &status)) {
+        return false;
+    }
+    if (status.state == SnapshotState::MergeCompleted) {
+        LOG(ERROR) << "Should not create a snapshot device for " << name
+                   << " after merging has completed.";
         return false;
     }
 
     // Validate the block device size, as well as the requested snapshot size.
-    // During this we also compute the linear sector region if any.
-    {
+    // Note that during first-stage init, we don't have the device paths.
+    if (android::base::StartsWith(base_device, "/")) {
         unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC));
         if (fd < 0) {
             PLOG(ERROR) << "open failed: " << base_device;
@@ -203,21 +259,42 @@
 
     auto cow_name = GetCowName(name);
 
+    bool ok;
     std::string cow_dev;
-    if (!images_->MapImageDevice(cow_name, timeout_ms, &cow_dev)) {
+    if (has_local_image_manager_) {
+        // If we forced a local image manager, it means we don't have binder,
+        // which means first-stage init. We must use device-mapper.
+        const auto& opener = device_->GetPartitionOpener();
+        ok = images_->MapImageWithDeviceMapper(opener, cow_name, &cow_dev);
+    } else {
+        ok = images_->MapImageDevice(cow_name, timeout_ms, &cow_dev);
+    }
+    if (!ok) {
+        LOG(ERROR) << "Could not map image device: " << cow_name;
         return false;
     }
 
     auto& dm = DeviceMapper::Instance();
 
-    // Merging is a global state, not per-snapshot. We do however track the
-    // progress of individual snapshots' merges.
+    // Note that merging is a global state. We do track whether individual devices
+    // have completed merging, but the start of the merge process is considered
+    // atomic.
     SnapshotStorageMode mode;
-    UpdateState update_state = ReadUpdateState(lock);
-    if (update_state == UpdateState::Merging || update_state == UpdateState::MergeCompleted) {
-        mode = SnapshotStorageMode::Merge;
-    } else {
-        mode = SnapshotStorageMode::Persistent;
+    switch (ReadUpdateState(lock)) {
+        case UpdateState::MergeCompleted:
+        case UpdateState::MergeNeedsReboot:
+            LOG(ERROR) << "Should not create a snapshot device for " << name
+                       << " after global merging has completed.";
+            return false;
+        case UpdateState::Merging:
+        case UpdateState::MergeFailed:
+            // Note: MergeFailed indicates that a merge is in progress, but
+            // is possibly stalled. We still have to honor the merge.
+            mode = SnapshotStorageMode::Merge;
+            break;
+        default:
+            mode = SnapshotStorageMode::Persistent;
+            break;
     }
 
     // The kernel (tested on 4.19) crashes horribly if a device has both a snapshot
@@ -262,16 +339,13 @@
     CHECK(lock);
     if (!EnsureImageManager()) return false;
 
-    auto status_file = OpenSnapshotStatusFile(name, O_RDWR, LOCK_EX);
-    if (!status_file) return false;
-
     SnapshotStatus status;
-    if (!ReadSnapshotStatus(status_file.get(), &status)) {
+    if (!ReadSnapshotStatus(lock, name, &status)) {
         return false;
     }
 
     auto& dm = DeviceMapper::Instance();
-    if (dm.GetState(name) != DmDeviceState::INVALID && !dm.DeleteDevice(name)) {
+    if (!dm.DeleteDeviceIfExists(name)) {
         LOG(ERROR) << "Could not delete snapshot device: " << name;
         return false;
     }
@@ -279,7 +353,7 @@
     // There may be an extra device, since the kernel doesn't let us have a
     // snapshot and linear target in the same table.
     auto dm_name = GetSnapshotDeviceName(name, status);
-    if (name != dm_name && !dm.DeleteDevice(dm_name)) {
+    if (name != dm_name && !dm.DeleteDeviceIfExists(dm_name)) {
         LOG(ERROR) << "Could not delete inner snapshot device: " << dm_name;
         return false;
     }
@@ -295,36 +369,630 @@
     CHECK(lock);
     if (!EnsureImageManager()) return false;
 
-    if (!UnmapSnapshot(lock, name)) {
-        LOG(ERROR) << "Snapshot could not be unmapped for deletion: " << name;
-        return false;
-    }
-
-    // Take the snapshot's lock after Unmap, since it will also try to lock.
-    auto status_file = OpenSnapshotStatusFile(name, O_RDONLY, LOCK_EX);
-    if (!status_file) return false;
-
     auto cow_name = GetCowName(name);
-    if (!images_->BackingImageExists(cow_name)) {
-        return true;
-    }
-    if (!images_->DeleteBackingImage(cow_name)) {
-        return false;
+    if (images_->BackingImageExists(cow_name)) {
+        if (images_->IsImageMapped(cow_name) && !images_->UnmapImageDevice(cow_name)) {
+            return false;
+        }
+        if (!images_->DeleteBackingImage(cow_name)) {
+            return false;
+        }
     }
 
-    if (!android::base::RemoveFileIfExists(status_file->path())) {
-        LOG(ERROR) << "Failed to remove status file: " << status_file->path();
+    std::string error;
+    auto file_path = GetSnapshotStatusFilePath(name);
+    if (!android::base::RemoveFileIfExists(file_path, &error)) {
+        LOG(ERROR) << "Failed to remove status file " << file_path << ": " << error;
         return false;
     }
     return true;
 }
 
 bool SnapshotManager::InitiateMerge() {
-    return false;
+    auto lock = LockExclusive();
+    if (!lock) return false;
+
+    UpdateState state = ReadUpdateState(lock.get());
+    if (state != UpdateState::Unverified) {
+        LOG(ERROR) << "Cannot begin a merge if an update has not been verified";
+        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;
+        return false;
+    }
+
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock.get(), &snapshots)) {
+        LOG(ERROR) << "Could not list snapshots";
+        return false;
+    }
+
+    auto& dm = DeviceMapper::Instance();
+    for (const auto& snapshot : snapshots) {
+        // The device has to be mapped, since everything should be merged at
+        // the same time. This is a fairly serious error. We could forcefully
+        // map everything here, but it should have been mapped during first-
+        // stage init.
+        if (dm.GetState(snapshot) == DmDeviceState::INVALID) {
+            LOG(ERROR) << "Cannot begin merge; device " << snapshot << " is not mapped.";
+            return false;
+        }
+    }
+
+    // Point of no return - mark that we're starting a merge. From now on every
+    // snapshot must be a merge target.
+    if (!WriteUpdateState(lock.get(), UpdateState::Merging)) {
+        return false;
+    }
+
+    bool rewrote_all = true;
+    for (const auto& snapshot : snapshots) {
+        // If this fails, we have no choice but to continue. Everything must
+        // be merged. This is not an ideal state to be in, but it is safe,
+        // because we the next boot will try again.
+        if (!SwitchSnapshotToMerge(lock.get(), snapshot)) {
+            LOG(ERROR) << "Failed to switch snapshot to a merge target: " << snapshot;
+            rewrote_all = false;
+        }
+    }
+
+    // If we couldn't switch everything to a merge target, pre-emptively mark
+    // this merge as failed. It will get acknowledged when WaitForMerge() is
+    // called.
+    if (!rewrote_all) {
+        WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+    }
+
+    // Return true no matter what, because a merge was initiated.
+    return true;
 }
 
-bool SnapshotManager::WaitForMerge() {
-    return false;
+bool SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
+    SnapshotStatus status;
+    if (!ReadSnapshotStatus(lock, name, &status)) {
+        return false;
+    }
+    if (status.state != SnapshotState::Created) {
+        LOG(WARNING) << "Snapshot " << name << " has unexpected state: " << to_string(status.state);
+    }
+
+    // After this, we return true because we technically did switch to a merge
+    // target. Everything else we do here is just informational.
+    auto dm_name = GetSnapshotDeviceName(name, status);
+    if (!RewriteSnapshotDeviceTable(dm_name)) {
+        return false;
+    }
+
+    status.state = SnapshotState::Merging;
+
+    DmTargetSnapshot::Status dm_status;
+    if (!QuerySnapshotStatus(dm_name, nullptr, &dm_status)) {
+        LOG(ERROR) << "Could not query merge status for snapshot: " << dm_name;
+    }
+    status.sectors_allocated = dm_status.sectors_allocated;
+    status.metadata_sectors = dm_status.metadata_sectors;
+    if (!WriteSnapshotStatus(lock, name, status)) {
+        LOG(ERROR) << "Could not update status file for snapshot: " << name;
+    }
+    return true;
+}
+
+bool SnapshotManager::RewriteSnapshotDeviceTable(const std::string& dm_name) {
+    auto& dm = DeviceMapper::Instance();
+
+    std::vector<DeviceMapper::TargetInfo> old_targets;
+    if (!dm.GetTableInfo(dm_name, &old_targets)) {
+        LOG(ERROR) << "Could not read snapshot device table: " << dm_name;
+        return false;
+    }
+    if (old_targets.size() != 1 || DeviceMapper::GetTargetType(old_targets[0].spec) != "snapshot") {
+        LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << dm_name;
+        return false;
+    }
+
+    std::string base_device, cow_device;
+    if (!DmTargetSnapshot::GetDevicesFromParams(old_targets[0].data, &base_device, &cow_device)) {
+        LOG(ERROR) << "Could not derive underlying devices for snapshot: " << dm_name;
+        return false;
+    }
+
+    DmTable table;
+    table.Emplace<DmTargetSnapshot>(0, old_targets[0].spec.length, base_device, cow_device,
+                                    SnapshotStorageMode::Merge, kSnapshotChunkSize);
+    if (!dm.LoadTableAndActivate(dm_name, table)) {
+        LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << dm_name;
+        return false;
+    }
+    LOG(INFO) << "Successfully switched snapshot device to a merge target: " << dm_name;
+    return true;
+}
+
+enum class TableQuery {
+    Table,
+    Status,
+};
+
+static bool GetSingleTarget(const std::string& dm_name, TableQuery query,
+                            DeviceMapper::TargetInfo* target) {
+    auto& dm = DeviceMapper::Instance();
+    if (dm.GetState(dm_name) == DmDeviceState::INVALID) {
+        return false;
+    }
+
+    std::vector<DeviceMapper::TargetInfo> targets;
+    bool result;
+    if (query == TableQuery::Status) {
+        result = dm.GetTableStatus(dm_name, &targets);
+    } else {
+        result = dm.GetTableInfo(dm_name, &targets);
+    }
+    if (!result) {
+        LOG(ERROR) << "Could not query device: " << dm_name;
+        return false;
+    }
+    if (targets.size() != 1) {
+        return false;
+    }
+
+    *target = std::move(targets[0]);
+    return true;
+}
+
+bool SnapshotManager::IsSnapshotDevice(const std::string& dm_name, TargetInfo* target) {
+    DeviceMapper::TargetInfo snap_target;
+    if (!GetSingleTarget(dm_name, TableQuery::Status, &snap_target)) {
+        return false;
+    }
+    auto type = DeviceMapper::GetTargetType(snap_target.spec);
+    if (type != "snapshot" && type != "snapshot-merge") {
+        return false;
+    }
+    if (target) {
+        *target = std::move(snap_target);
+    }
+    return true;
+}
+
+bool SnapshotManager::QuerySnapshotStatus(const std::string& dm_name, std::string* target_type,
+                                          DmTargetSnapshot::Status* status) {
+    DeviceMapper::TargetInfo target;
+    if (!IsSnapshotDevice(dm_name, &target)) {
+        LOG(ERROR) << "Device " << dm_name << " is not a snapshot or snapshot-merge device";
+        return false;
+    }
+    if (!DmTargetSnapshot::ParseStatusText(target.data, status)) {
+        LOG(ERROR) << "Could not parse snapshot status text: " << dm_name;
+        return false;
+    }
+    if (target_type) {
+        *target_type = DeviceMapper::GetTargetType(target.spec);
+    }
+    return true;
+}
+
+// Note that when a merge fails, we will *always* try again to complete the
+// merge each time the device boots. There is no harm in doing so, and if
+// the problem was transient, we might manage to get a new outcome.
+UpdateState SnapshotManager::ProcessUpdateState() {
+    while (true) {
+        UpdateState state = CheckMergeState();
+        if (state == UpdateState::MergeFailed) {
+            AcknowledgeMergeFailure();
+        }
+        if (state != UpdateState::Merging) {
+            // Either there is no merge, or the merge was finished, so no need
+            // to keep waiting.
+            return state;
+        }
+
+        // This wait is not super time sensitive, so we have a relatively
+        // low polling frequency.
+        std::this_thread::sleep_for(2s);
+    }
+}
+
+UpdateState SnapshotManager::CheckMergeState() {
+    auto lock = LockExclusive();
+    if (!lock) {
+        return UpdateState::MergeFailed;
+    }
+
+    UpdateState state = CheckMergeState(lock.get());
+    if (state == UpdateState::MergeCompleted) {
+        // Do this inside the same lock. Failures get acknowledged without the
+        // lock, because flock() might have failed.
+        AcknowledgeMergeSuccess(lock.get());
+    } else if (state == UpdateState::Cancelled) {
+        RemoveAllUpdateState(lock.get());
+    }
+    return state;
+}
+
+UpdateState SnapshotManager::CheckMergeState(LockedFile* lock) {
+    UpdateState state = ReadUpdateState(lock);
+    switch (state) {
+        case UpdateState::None:
+        case UpdateState::MergeCompleted:
+            // Harmless races are allowed between two callers of WaitForMerge,
+            // so in both of these cases we just propagate the state.
+            return state;
+
+        case UpdateState::Merging:
+        case UpdateState::MergeNeedsReboot:
+        case UpdateState::MergeFailed:
+            // We'll poll each snapshot below. Note that for the NeedsReboot
+            // case, we always poll once to give cleanup another opportunity to
+            // run.
+            break;
+
+        case UpdateState::Unverified:
+            // This is an edge case. Normally cancelled updates are detected
+            // via the merge poll below, but if we never started a merge, we
+            // need to also check here.
+            if (HandleCancelledUpdate(lock)) {
+                return UpdateState::Cancelled;
+            }
+            return state;
+
+        default:
+            return state;
+    }
+
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock, &snapshots)) {
+        return UpdateState::MergeFailed;
+    }
+
+    bool cancelled = false;
+    bool failed = false;
+    bool merging = false;
+    bool needs_reboot = false;
+    for (const auto& snapshot : snapshots) {
+        UpdateState snapshot_state = CheckTargetMergeState(lock, snapshot);
+        switch (snapshot_state) {
+            case UpdateState::MergeFailed:
+                failed = true;
+                break;
+            case UpdateState::Merging:
+                merging = true;
+                break;
+            case UpdateState::MergeNeedsReboot:
+                needs_reboot = true;
+                break;
+            case UpdateState::MergeCompleted:
+                break;
+            case UpdateState::Cancelled:
+                cancelled = true;
+                break;
+            default:
+                LOG(ERROR) << "Unknown merge status: " << static_cast<uint32_t>(snapshot_state);
+                failed = true;
+                break;
+        }
+    }
+
+    if (merging) {
+        // Note that we handle "Merging" before we handle anything else. We
+        // want to poll until *nothing* is merging if we can, so everything has
+        // a chance to get marked as completed or failed.
+        return UpdateState::Merging;
+    }
+    if (failed) {
+        // Note: since there are many drop-out cases for failure, we acknowledge
+        // it in WaitForMerge rather than here and elsewhere.
+        return UpdateState::MergeFailed;
+    }
+    if (needs_reboot) {
+        WriteUpdateState(lock, UpdateState::MergeNeedsReboot);
+        return UpdateState::MergeNeedsReboot;
+    }
+    if (cancelled) {
+        // This is an edge case, that we handle as correctly as we sensibly can.
+        // The underlying partition has changed behind update_engine, and we've
+        // removed the snapshot as a result. The exact state of the update is
+        // undefined now, but this can only happen on an unlocked device where
+        // partitions can be flashed without wiping userdata.
+        return UpdateState::Cancelled;
+    }
+    return UpdateState::MergeCompleted;
+}
+
+UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name) {
+    SnapshotStatus snapshot_status;
+    if (!ReadSnapshotStatus(lock, name, &snapshot_status)) {
+        return UpdateState::MergeFailed;
+    }
+
+    std::string dm_name = GetSnapshotDeviceName(name, snapshot_status);
+
+    if (!IsSnapshotDevice(dm_name)) {
+        if (IsCancelledSnapshot(name)) {
+            DeleteSnapshot(lock, name);
+            return UpdateState::Cancelled;
+        }
+
+        // During a check, we decided the merge was complete, but we were unable to
+        // collapse the device-mapper stack and perform COW cleanup. If we haven't
+        // rebooted after this check, the device will still be a snapshot-merge
+        // target. If the have rebooted, the device will now be a linear target,
+        // and we can try cleanup again.
+        if (snapshot_status.state == SnapshotState::MergeCompleted) {
+            // NB: It's okay if this fails now, we gave cleanup our best effort.
+            OnSnapshotMergeComplete(lock, name, snapshot_status);
+            return UpdateState::MergeCompleted;
+        }
+
+        LOG(ERROR) << "Expected snapshot or snapshot-merge for device: " << dm_name;
+        return UpdateState::MergeFailed;
+    }
+
+    // This check is expensive so it is only enabled for debugging.
+    DCHECK(!IsCancelledSnapshot(name));
+
+    std::string target_type;
+    DmTargetSnapshot::Status status;
+    if (!QuerySnapshotStatus(dm_name, &target_type, &status)) {
+        return UpdateState::MergeFailed;
+    }
+    if (target_type != "snapshot-merge") {
+        // We can get here if we failed to rewrite the target type in
+        // InitiateMerge(). If we failed to create the target in first-stage
+        // init, boot would not succeed.
+        LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
+        return UpdateState::MergeFailed;
+    }
+
+    // These two values are equal when merging is complete.
+    if (status.sectors_allocated != status.metadata_sectors) {
+        if (snapshot_status.state == SnapshotState::MergeCompleted) {
+            LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
+            return UpdateState::MergeFailed;
+        }
+        return UpdateState::Merging;
+    }
+
+    // Merging is done. First, update the status file to indicate the merge
+    // is complete. We do this before calling OnSnapshotMergeComplete, even
+    // though this means the write is potentially wasted work (since in the
+    // ideal case we'll immediately delete the file).
+    //
+    // This makes it simpler to reason about the next reboot: no matter what
+    // part of cleanup failed, first-stage init won't try to create another
+    // snapshot device for this partition.
+    snapshot_status.state = SnapshotState::MergeCompleted;
+    if (!WriteSnapshotStatus(lock, name, snapshot_status)) {
+        return UpdateState::MergeFailed;
+    }
+    if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) {
+        return UpdateState::MergeNeedsReboot;
+    }
+    return UpdateState::MergeCompleted;
+}
+
+std::string SnapshotManager::GetSnapshotBootIndicatorPath() {
+    return metadata_dir_ + "/" + android::base::Basename(kBootIndicatorPath);
+}
+
+void SnapshotManager::RemoveSnapshotBootIndicator() {
+    // It's okay if this fails - first-stage init performs a deeper check after
+    // reading the indicator file, so it's not a problem if it still exists
+    // after the update completes.
+    auto boot_file = GetSnapshotBootIndicatorPath();
+    if (unlink(boot_file.c_str()) == -1 && errno != ENOENT) {
+        PLOG(ERROR) << "unlink " << boot_file;
+    }
+}
+
+void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) {
+    RemoveAllUpdateState(lock);
+}
+
+void SnapshotManager::AcknowledgeMergeFailure() {
+    // Log first, so worst case, we always have a record of why the calls below
+    // were being made.
+    LOG(ERROR) << "Merge could not be completed and will be marked as failed.";
+
+    auto lock = LockExclusive();
+    if (!lock) return;
+
+    // Since we released the lock in between WaitForMerge and here, it's
+    // possible (1) the merge successfully completed or (2) was already
+    // marked as a failure. So make sure to check the state again, and
+    // only mark as a failure if appropriate.
+    UpdateState state = ReadUpdateState(lock.get());
+    if (state != UpdateState::Merging && state != UpdateState::MergeNeedsReboot) {
+        return;
+    }
+
+    WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+}
+
+bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
+                                              const SnapshotStatus& status) {
+    auto dm_name = GetSnapshotDeviceName(name, status);
+    if (IsSnapshotDevice(dm_name)) {
+        // We are extra-cautious here, to avoid deleting the wrong table.
+        std::string target_type;
+        DmTargetSnapshot::Status dm_status;
+        if (!QuerySnapshotStatus(dm_name, &target_type, &dm_status)) {
+            return false;
+        }
+        if (target_type != "snapshot-merge") {
+            LOG(ERROR) << "Unexpected target type " << target_type
+                       << " for snapshot device: " << dm_name;
+            return false;
+        }
+        if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
+            LOG(ERROR) << "Merge is unexpectedly incomplete for device " << dm_name;
+            return false;
+        }
+        if (!CollapseSnapshotDevice(name, status)) {
+            LOG(ERROR) << "Unable to collapse snapshot: " << name;
+            return false;
+        }
+        // Note that collapsing is implicitly an Unmap, so we don't need to
+        // unmap the snapshot.
+    }
+
+    if (!DeleteSnapshot(lock, name)) {
+        LOG(ERROR) << "Could not delete snapshot: " << name;
+        return false;
+    }
+    return true;
+}
+
+bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
+                                             const SnapshotStatus& status) {
+    auto& dm = DeviceMapper::Instance();
+    auto dm_name = GetSnapshotDeviceName(name, status);
+
+    // Verify we have a snapshot-merge device.
+    DeviceMapper::TargetInfo target;
+    if (!GetSingleTarget(dm_name, TableQuery::Table, &target)) {
+        return false;
+    }
+    if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
+        // This should be impossible, it was checked earlier.
+        LOG(ERROR) << "Snapshot device has invalid target type: " << dm_name;
+        return false;
+    }
+
+    std::string base_device, cow_device;
+    if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
+        LOG(ERROR) << "Could not parse snapshot device " << dm_name
+                   << " parameters: " << target.data;
+        return false;
+    }
+
+    uint64_t num_sectors = status.snapshot_size / kSectorSize;
+    if (num_sectors * kSectorSize != status.snapshot_size) {
+        LOG(ERROR) << "Snapshot " << name
+                   << " size is not sector aligned: " << status.snapshot_size;
+        return false;
+    }
+
+    if (dm_name != name) {
+        // We've derived the base device, but we actually need to replace the
+        // table of the outermost device. Do a quick verification that this
+        // device looks like we expect it to.
+        std::vector<DeviceMapper::TargetInfo> outer_table;
+        if (!dm.GetTableInfo(name, &outer_table)) {
+            LOG(ERROR) << "Could not validate outer snapshot table: " << name;
+            return false;
+        }
+        if (outer_table.size() != 2) {
+            LOG(ERROR) << "Expected 2 dm-linear targets for table " << name
+                       << ", got: " << outer_table.size();
+            return false;
+        }
+        for (const auto& target : outer_table) {
+            auto target_type = DeviceMapper::GetTargetType(target.spec);
+            if (target_type != "linear") {
+                LOG(ERROR) << "Outer snapshot table may only contain linear targets, but " << name
+                           << " has target: " << target_type;
+                return false;
+            }
+        }
+        uint64_t sectors = outer_table[0].spec.length + outer_table[1].spec.length;
+        if (sectors != num_sectors) {
+            LOG(ERROR) << "Outer snapshot " << name << " should have " << num_sectors
+                       << ", got: " << sectors;
+            return false;
+        }
+    }
+
+    // Grab the partition metadata for the snapshot.
+    uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
+    auto super_device = device_->GetSuperDevice(slot);
+    const auto& opener = device_->GetPartitionOpener();
+    auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
+    if (!metadata) {
+        LOG(ERROR) << "Could not read super partition metadata.";
+        return false;
+    }
+    auto partition = android::fs_mgr::FindPartition(*metadata.get(), name);
+    if (!partition) {
+        LOG(ERROR) << "Snapshot does not have a partition in super: " << name;
+        return false;
+    }
+
+    // Create a DmTable that is identical to the base device.
+    DmTable table;
+    if (!CreateDmTable(opener, *metadata.get(), *partition, super_device, &table)) {
+        LOG(ERROR) << "Could not create a DmTable for partition: " << name;
+        return false;
+    }
+
+    // Note: we are replacing the *outer* table here, so we do not use dm_name.
+    if (!dm.LoadTableAndActivate(name, table)) {
+        return false;
+    }
+
+    // Attempt to delete the snapshot device if one still exists. Nothing
+    // should be depending on the device, and device-mapper should have
+    // flushed remaining I/O. We could in theory replace with dm-zero (or
+    // re-use the table above), but for now it's better to know why this
+    // would fail.
+    if (!dm.DeleteDeviceIfExists(dm_name)) {
+        LOG(ERROR) << "Unable to delete snapshot device " << dm_name << ", COW cannot be "
+                   << "reclaimed until after reboot.";
+        return false;
+    }
+
+    // Cleanup the base device as well, since it is no longer used. This does
+    // not block cleanup.
+    auto base_name = GetBaseDeviceName(name);
+    if (!dm.DeleteDeviceIfExists(base_name)) {
+        LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
+    }
+    return true;
+}
+
+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;
+        return false;
+    }
+    if (device_->GetSlotSuffix() != old_slot) {
+        // We're booted into the target slot, which means we just rebooted
+        // after applying the update.
+        return false;
+    }
+
+    // The only way we can get here is if:
+    //  (1) The device rolled back to the previous slot.
+    //  (2) This function was called prematurely before rebooting the device.
+    //  (3) fastboot set_active was used.
+    //
+    // In any case, delete the snapshots. It may be worth using the boot_control
+    // HAL to differentiate case (2).
+    RemoveAllUpdateState(lock);
+    return true;
+}
+
+bool SnapshotManager::IsCancelledSnapshot(const std::string& snapshot_name) {
+    const auto& opener = device_->GetPartitionOpener();
+    uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
+    auto super_device = device_->GetSuperDevice(slot);
+    auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
+    if (!metadata) {
+        LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device;
+        return false;
+    }
+    auto partition = android::fs_mgr::FindPartition(*metadata.get(), snapshot_name);
+    if (!partition) return false;
+    return (partition->attributes & LP_PARTITION_ATTR_UPDATED) == 0;
 }
 
 bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) {
@@ -342,6 +1010,12 @@
 }
 
 UpdateState SnapshotManager::GetUpdateState(double* progress) {
+    // If we've never started an update, the state file won't exist.
+    auto state_file = GetStateFilePath();
+    if (access(state_file.c_str(), F_OK) != 0 && errno == ENOENT) {
+        return UpdateState::None;
+    }
+
     auto file = LockShared();
     if (!file) {
         return UpdateState::None;
@@ -377,6 +1051,132 @@
     return true;
 }
 
+bool SnapshotManager::IsSnapshotManagerNeeded() {
+    return access(kBootIndicatorPath, F_OK) == 0;
+}
+
+bool SnapshotManager::NeedSnapshotsInFirstStageMount() {
+    // If we fail to read, we'll wind up using CreateLogicalPartitions, which
+    // will create devices that look like the old slot, except with extra
+    // content at the end of each device. This will confuse dm-verity, and
+    // 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.";
+        return false;
+    }
+
+    // If we can't read the update state, it's unlikely anything else will
+    // succeed, so this is a fatal error. We'll eventually exhaust boot
+    // attempts and revert to the old slot.
+    auto lock = LockShared();
+    if (!lock) {
+        LOG(FATAL) << "Could not read update state to determine snapshot status";
+        return false;
+    }
+    switch (ReadUpdateState(lock.get())) {
+        case UpdateState::Unverified:
+        case UpdateState::Merging:
+        case UpdateState::MergeFailed:
+            return true;
+        default:
+            return false;
+    }
+}
+
+bool SnapshotManager::CreateLogicalAndSnapshotPartitions(const std::string& super_device) {
+    LOG(INFO) << "Creating logical partitions with snapshots as needed";
+
+    auto lock = LockExclusive();
+    if (!lock) return false;
+
+    std::vector<std::string> snapshot_list;
+    if (!ListSnapshots(lock.get(), &snapshot_list)) {
+        return false;
+    }
+
+    std::unordered_set<std::string> live_snapshots;
+    for (const auto& snapshot : snapshot_list) {
+        SnapshotStatus status;
+        if (!ReadSnapshotStatus(lock.get(), snapshot, &status)) {
+            return false;
+        }
+        if (status.state != SnapshotState::MergeCompleted) {
+            live_snapshots.emplace(snapshot);
+        }
+    }
+
+    const auto& opener = device_->GetPartitionOpener();
+    uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
+    auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
+    if (!metadata) {
+        LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device;
+        return false;
+    }
+
+    // Map logical partitions.
+    auto& dm = DeviceMapper::Instance();
+    for (const auto& partition : metadata->partitions) {
+        auto partition_name = GetPartitionName(partition);
+        if (!partition.num_extents) {
+            LOG(INFO) << "Skipping zero-length logical partition: " << partition_name;
+            continue;
+        }
+
+        if (!(partition.attributes & LP_PARTITION_ATTR_UPDATED)) {
+            LOG(INFO) << "Detected re-flashing of partition, will skip snapshot: "
+                      << partition_name;
+            live_snapshots.erase(partition_name);
+        }
+
+        CreateLogicalPartitionParams params = {
+                .block_device = super_device,
+                .metadata = metadata.get(),
+                .partition = &partition,
+                .partition_opener = &opener,
+        };
+
+        if (auto iter = live_snapshots.find(partition_name); iter != live_snapshots.end()) {
+            // If the device has a snapshot, it'll need to be writable, and
+            // we'll need to create the logical partition with a marked-up name
+            // (since the snapshot will use the partition name).
+            params.force_writable = true;
+            params.device_name = GetBaseDeviceName(partition_name);
+        }
+
+        std::string ignore_path;
+        if (!CreateLogicalPartition(params, &ignore_path)) {
+            LOG(ERROR) << "Could not create logical partition " << partition_name << " as device "
+                       << params.GetDeviceName();
+            return false;
+        }
+        if (!params.force_writable) {
+            // No snapshot.
+            continue;
+        }
+
+        // We don't have ueventd in first-stage init, so use device major:minor
+        // strings instead.
+        std::string base_device;
+        if (!dm.GetDeviceString(params.GetDeviceName(), &base_device)) {
+            LOG(ERROR) << "Could not determine major/minor for: " << params.GetDeviceName();
+            return false;
+        }
+        if (!MapSnapshot(lock.get(), partition_name, base_device, {}, &ignore_path)) {
+            LOG(ERROR) << "Could not map snapshot for partition: " << partition_name;
+            return false;
+        }
+    }
+    return true;
+}
+
 auto SnapshotManager::OpenFile(const std::string& file, int open_flags, int lock_flags)
         -> std::unique_ptr<LockedFile> {
     unique_fd fd(open(file.c_str(), open_flags | O_CLOEXEC | O_NOFOLLOW | O_SYNC, 0660));
@@ -388,7 +1188,10 @@
         PLOG(ERROR) << "Acquire flock failed: " << file;
         return nullptr;
     }
-    return std::make_unique<LockedFile>(file, std::move(fd));
+    // For simplicity, we want to CHECK that lock_mode == LOCK_EX, in some
+    // calls, so strip extra flags.
+    int lock_mode = lock_flags & (LOCK_EX | LOCK_SH);
+    return std::make_unique<LockedFile>(file, std::move(fd), lock_mode);
 }
 
 SnapshotManager::LockedFile::~LockedFile() {
@@ -397,9 +1200,13 @@
     }
 }
 
+std::string SnapshotManager::GetStateFilePath() const {
+    return metadata_dir_ + "/state"s;
+}
+
 std::unique_ptr<SnapshotManager::LockedFile> SnapshotManager::OpenStateFile(int open_flags,
                                                                             int lock_flags) {
-    auto state_file = metadata_dir_ + "/state"s;
+    auto state_file = GetStateFilePath();
     return OpenFile(state_file, open_flags, lock_flags);
 }
 
@@ -434,6 +1241,10 @@
         return UpdateState::Merging;
     } else if (contents == "merge-completed") {
         return UpdateState::MergeCompleted;
+    } else if (contents == "merge-needs-reboot") {
+        return UpdateState::MergeNeedsReboot;
+    } else if (contents == "merge-failed") {
+        return UpdateState::MergeFailed;
     } else {
         LOG(ERROR) << "Unknown merge state in update state file";
         return UpdateState::None;
@@ -458,6 +1269,12 @@
         case UpdateState::MergeCompleted:
             contents = "merge-completed";
             break;
+        case UpdateState::MergeNeedsReboot:
+            contents = "merge-needs-reboot";
+            break;
+        case UpdateState::MergeFailed:
+            contents = "merge-failed";
+            break;
         default:
             LOG(ERROR) << "Unknown update state";
             return false;
@@ -471,53 +1288,91 @@
     return true;
 }
 
-auto SnapshotManager::OpenSnapshotStatusFile(const std::string& name, int open_flags,
-                                             int lock_flags) -> std::unique_ptr<LockedFile> {
+std::string SnapshotManager::GetSnapshotStatusFilePath(const std::string& name) {
     auto file = metadata_dir_ + "/snapshots/"s + name;
-    return OpenFile(file, open_flags, lock_flags);
+    return file;
 }
 
-bool SnapshotManager::ReadSnapshotStatus(LockedFile* file, SnapshotStatus* status) {
-    // Reset position since some calls read+write.
-    if (lseek(file->fd(), 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek status file failed";
+bool SnapshotManager::ReadSnapshotStatus(LockedFile* lock, const std::string& name,
+                                         SnapshotStatus* status) {
+    CHECK(lock);
+    auto path = GetSnapshotStatusFilePath(name);
+
+    unique_fd fd(open(path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
+    if (fd < 0) {
+        PLOG(ERROR) << "Open failed: " << path;
         return false;
     }
 
     std::string contents;
-    if (!android::base::ReadFdToString(file->fd(), &contents)) {
-        PLOG(ERROR) << "read status file failed";
+    if (!android::base::ReadFdToString(fd, &contents)) {
+        PLOG(ERROR) << "read failed: " << path;
         return false;
     }
     auto pieces = android::base::Split(contents, " ");
     if (pieces.size() != 5) {
-        LOG(ERROR) << "Invalid status line for snapshot: " << file->path();
+        LOG(ERROR) << "Invalid status line for snapshot: " << path;
         return false;
     }
 
-    status->state = pieces[0];
+    if (pieces[0] == "created") {
+        status->state = SnapshotState::Created;
+    } else if (pieces[0] == "merging") {
+        status->state = SnapshotState::Merging;
+    } else if (pieces[0] == "merge-completed") {
+        status->state = SnapshotState::MergeCompleted;
+    } else {
+        LOG(ERROR) << "Unrecognized state " << pieces[0] << " for snapshot: " << name;
+    }
+
     if (!android::base::ParseUint(pieces[1], &status->device_size)) {
-        LOG(ERROR) << "Invalid device size in status line for: " << file->path();
+        LOG(ERROR) << "Invalid device size in status line for: " << path;
         return false;
     }
     if (!android::base::ParseUint(pieces[2], &status->snapshot_size)) {
-        LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path();
+        LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
         return false;
     }
     if (!android::base::ParseUint(pieces[3], &status->sectors_allocated)) {
-        LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path();
+        LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
         return false;
     }
     if (!android::base::ParseUint(pieces[4], &status->metadata_sectors)) {
-        LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path();
+        LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
         return false;
     }
     return true;
 }
 
-bool SnapshotManager::WriteSnapshotStatus(LockedFile* file, const SnapshotStatus& status) {
+std::string SnapshotManager::to_string(SnapshotState state) {
+    switch (state) {
+        case SnapshotState::Created:
+            return "created";
+        case SnapshotState::Merging:
+            return "merging";
+        case SnapshotState::MergeCompleted:
+            return "merge-completed";
+        default:
+            LOG(ERROR) << "Unknown snapshot state: " << (int)state;
+            return "unknown";
+    }
+}
+
+bool SnapshotManager::WriteSnapshotStatus(LockedFile* lock, const std::string& name,
+                                          const SnapshotStatus& status) {
+    // The caller must take an exclusive lock to modify snapshots.
+    CHECK(lock);
+    CHECK(lock->lock_mode() == LOCK_EX);
+
+    auto path = GetSnapshotStatusFilePath(name);
+    unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW | O_CREAT | O_SYNC, 0660));
+    if (fd < 0) {
+        PLOG(ERROR) << "Open failed: " << path;
+        return false;
+    }
+
     std::vector<std::string> pieces = {
-            status.state,
+            to_string(status.state),
             std::to_string(status.device_size),
             std::to_string(status.snapshot_size),
             std::to_string(status.sectors_allocated),
@@ -525,9 +1380,8 @@
     };
     auto contents = android::base::Join(pieces, " ");
 
-    if (!Truncate(file)) return false;
-    if (!android::base::WriteStringToFd(contents, file->fd())) {
-        PLOG(ERROR) << "write to status file failed: " << file->path();
+    if (!android::base::WriteStringToFd(contents, fd)) {
+        PLOG(ERROR) << "write failed: " << path;
         return false;
     }
     return true;
@@ -565,5 +1419,15 @@
     return true;
 }
 
+bool SnapshotManager::ForceLocalImageManager() {
+    images_ = android::fiemap::ImageManager::Open(gsid_dir_);
+    if (!images_) {
+        LOG(ERROR) << "Could not open ImageManager";
+        return false;
+    }
+    has_local_image_manager_ = true;
+    return true;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 38ba364..8487339 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -22,54 +22,109 @@
 #include <chrono>
 #include <iostream>
 
+#include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <fs_mgr_dm_linear.h>
 #include <gtest/gtest.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+#include <liblp/builder.h>
+#include <liblp/mock_property_fetcher.h>
+
+#include "test_helpers.h"
 
 namespace android {
 namespace snapshot {
 
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::fiemap::IImageManager;
+using android::fs_mgr::BlockDeviceInfo;
+using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::GetPartitionName;
+using android::fs_mgr::MetadataBuilder;
+using namespace ::testing;
+using namespace android::fs_mgr::testing;
 using namespace std::chrono_literals;
 using namespace std::string_literals;
 
-class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
-  public:
-    std::string GetGsidDir() const override { return "ota/test"s; }
-    std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
-    bool IsRunningSnapshot() const override { return is_running_snapshot_; }
-
-    void set_is_running_snapshot(bool value) { is_running_snapshot_ = value; }
-
-  private:
-    bool is_running_snapshot_;
-};
-
+// These are not reset between each test because it's expensive to create
+// these resources (starting+connecting to gsid, zero-filling images).
 std::unique_ptr<SnapshotManager> sm;
 TestDeviceInfo* test_device = nullptr;
+std::string fake_super;
+
+static constexpr uint64_t kSuperSize = 16 * 1024 * 1024;
 
 class SnapshotTest : public ::testing::Test {
+  public:
+    SnapshotTest() : dm_(DeviceMapper::Instance()) {}
+
+    // This is exposed for main.
+    void Cleanup() {
+        InitializeState();
+        CleanupTestArtifacts();
+    }
+
   protected:
     void SetUp() override {
-        test_device->set_is_running_snapshot(false);
+        ResetMockPropertyFetcher();
+        InitializeState();
+        CleanupTestArtifacts();
+        FormatFakeSuper();
 
-        if (sm->GetUpdateState() != UpdateState::None) {
-            ASSERT_TRUE(sm->CancelUpdate());
-        }
         ASSERT_TRUE(sm->BeginUpdate());
-        ASSERT_TRUE(sm->EnsureImageManager());
-
-        image_manager_ = sm->image_manager();
-        ASSERT_NE(image_manager_, nullptr);
     }
 
     void TearDown() override {
         lock_ = nullptr;
 
-        if (sm->GetUpdateState() != UpdateState::None) {
-            ASSERT_TRUE(sm->CancelUpdate());
+        CleanupTestArtifacts();
+        ResetMockPropertyFetcher();
+    }
+
+    void InitializeState() {
+        ASSERT_TRUE(sm->EnsureImageManager());
+        image_manager_ = sm->image_manager();
+
+        test_device->set_slot_suffix("_a");
+    }
+
+    void CleanupTestArtifacts() {
+        // Normally cancelling inside a merge is not allowed. Since these
+        // are tests, we don't care, destroy everything that might exist.
+        // Note we hardcode this list because of an annoying quirk: when
+        // completing a merge, the snapshot stops existing, so we can't
+        // get an accurate list to remove.
+        lock_ = nullptr;
+
+        std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
+                                              "test_partition_b"};
+        for (const auto& snapshot : snapshots) {
+            DeleteSnapshotDevice(snapshot);
+            DeleteBackingImage(image_manager_, snapshot + "-cow");
+
+            auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
+            android::base::RemoveFileIfExists(status_file);
         }
-        for (const auto& temp_image : temp_images_) {
-            image_manager_->UnmapImageDevice(temp_image);
-            image_manager_->DeleteBackingImage(temp_image);
+
+        // Remove stale partitions in fake super.
+        std::vector<std::string> partitions = {
+                "base-device",
+                "test_partition_b",
+                "test_partition_b-base",
+        };
+        for (const auto& partition : partitions) {
+            DeleteDevice(partition);
+        }
+
+        if (sm->GetUpdateState() != UpdateState::None) {
+            auto state_file = sm->GetStateFilePath();
+            unlink(state_file.c_str());
         }
     }
 
@@ -78,17 +133,98 @@
         return !!lock_;
     }
 
-    bool CreateTempDevice(const std::string& name, uint64_t size, std::string* path) {
-        if (!image_manager_->CreateBackingImage(name, size, false)) {
-            return false;
-        }
-        temp_images_.emplace_back(name);
-        return image_manager_->MapImageDevice(name, 10s, path);
+    // This is so main() can instantiate this to invoke Cleanup.
+    virtual void TestBody() override {}
+
+    void FormatFakeSuper() {
+        BlockDeviceInfo super_device("super", kSuperSize, 0, 0, 4096);
+        std::vector<BlockDeviceInfo> devices = {super_device};
+
+        auto builder = MetadataBuilder::New(devices, "super", 65536, 2);
+        ASSERT_NE(builder, nullptr);
+
+        auto metadata = builder->Export();
+        ASSERT_NE(metadata, nullptr);
+
+        TestPartitionOpener opener(fake_super);
+        ASSERT_TRUE(FlashPartitionTable(opener, fake_super, *metadata.get()));
     }
 
+    // If |path| is non-null, the partition will be mapped after creation.
+    bool CreatePartition(const std::string& name, uint64_t size, std::string* path = nullptr) {
+        TestPartitionOpener opener(fake_super);
+        auto builder = MetadataBuilder::New(opener, "super", 0);
+        if (!builder) return false;
+
+        auto partition = builder->AddPartition(name, 0);
+        if (!partition) return false;
+        if (!builder->ResizePartition(partition, size)) {
+            return false;
+        }
+
+        // Update the source slot.
+        auto metadata = builder->Export();
+        if (!metadata) return false;
+        if (!UpdatePartitionTable(opener, "super", *metadata.get(), 0)) {
+            return false;
+        }
+
+        if (!path) return true;
+
+        CreateLogicalPartitionParams params = {
+                .block_device = fake_super,
+                .metadata = metadata.get(),
+                .partition_name = name,
+                .force_writable = true,
+                .timeout_ms = 10s,
+        };
+        return CreateLogicalPartition(params, path);
+    }
+
+    bool MapUpdatePartitions() {
+        TestPartitionOpener opener(fake_super);
+        auto builder = MetadataBuilder::NewForUpdate(opener, "super", 0, 1);
+        if (!builder) return false;
+
+        auto metadata = builder->Export();
+        if (!metadata) return false;
+
+        // Update the destination slot, mark it as updated.
+        if (!UpdatePartitionTable(opener, "super", *metadata.get(), 1)) {
+            return false;
+        }
+
+        for (const auto& partition : metadata->partitions) {
+            CreateLogicalPartitionParams params = {
+                    .block_device = fake_super,
+                    .metadata = metadata.get(),
+                    .partition = &partition,
+                    .device_name = GetPartitionName(partition) + "-base",
+                    .force_writable = true,
+                    .timeout_ms = 10s,
+            };
+            std::string ignore_path;
+            if (!CreateLogicalPartition(params, &ignore_path)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    void DeleteSnapshotDevice(const std::string& snapshot) {
+        DeleteDevice(snapshot);
+        DeleteDevice(snapshot + "-inner");
+    }
+    void DeleteDevice(const std::string& device) {
+        if (dm_.GetState(device) != DmDeviceState::INVALID) {
+            ASSERT_TRUE(dm_.DeleteDevice(device));
+        }
+    }
+
+    DeviceMapper& dm_;
     std::unique_ptr<SnapshotManager::LockedFile> lock_;
-    std::vector<std::string> temp_images_;
     android::fiemap::IImageManager* image_manager_ = nullptr;
+    std::string fake_super_;
 };
 
 TEST_F(SnapshotTest, CreateSnapshot) {
@@ -103,18 +239,16 @@
     ASSERT_EQ(snapshots.size(), 1);
     ASSERT_EQ(snapshots[0], "test-snapshot");
 
-    // Scope so delete can re-acquire the status file lock.
+    // Scope so delete can re-acquire the snapshot file lock.
     {
-        auto file = sm->OpenSnapshotStatusFile("test-snapshot", O_RDONLY, LOCK_SH);
-        ASSERT_NE(file, nullptr);
-
         SnapshotManager::SnapshotStatus status;
-        ASSERT_TRUE(sm->ReadSnapshotStatus(file.get(), &status));
-        ASSERT_EQ(status.state, "created");
+        ASSERT_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status));
+        ASSERT_EQ(status.state, SnapshotManager::SnapshotState::Created);
         ASSERT_EQ(status.device_size, kDeviceSize);
         ASSERT_EQ(status.snapshot_size, kDeviceSize);
     }
 
+    ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot"));
     ASSERT_TRUE(sm->DeleteSnapshot(lock_.get(), "test-snapshot"));
 }
 
@@ -126,7 +260,7 @@
                                    kDeviceSize));
 
     std::string base_device;
-    ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+    ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
 
     std::string snap_device;
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
@@ -142,13 +276,269 @@
                                    kSnapshotSize));
 
     std::string base_device;
-    ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+    ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
 
     std::string snap_device;
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
     ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
 }
 
+TEST_F(SnapshotTest, NoMergeBeforeReboot) {
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+    // Merge should fail, since the slot hasn't changed.
+    ASSERT_FALSE(sm->InitiateMerge());
+}
+
+TEST_F(SnapshotTest, CleanFirstStageMount) {
+    // If there's no update in progress, there should be no first-stage mount
+    // needed.
+    TestDeviceInfo* info = new TestDeviceInfo(fake_super);
+    auto sm = SnapshotManager::NewForFirstStageMount(info);
+    ASSERT_NE(sm, nullptr);
+    ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount());
+}
+
+TEST_F(SnapshotTest, FirstStageMountAfterRollback) {
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+    // We didn't change the slot, so we shouldn't need snapshots.
+    TestDeviceInfo* info = new TestDeviceInfo(fake_super);
+    auto sm = SnapshotManager::NewForFirstStageMount(info);
+    ASSERT_NE(sm, nullptr);
+    ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount());
+}
+
+TEST_F(SnapshotTest, Merge) {
+    ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
+            .WillByDefault(Return(true));
+
+    ASSERT_TRUE(AcquireLock());
+
+    static const uint64_t kDeviceSize = 1024 * 1024;
+
+    std::string base_device, snap_device;
+    ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
+    ASSERT_TRUE(MapUpdatePartitions());
+    ASSERT_TRUE(dm_.GetDmDevicePathByName("test_partition_b-base", &base_device));
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b", kDeviceSize, kDeviceSize,
+                                   kDeviceSize));
+    ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, 10s, &snap_device));
+
+    std::string test_string = "This is a test string.";
+    {
+        unique_fd fd(open(snap_device.c_str(), O_RDWR | O_CLOEXEC | O_SYNC));
+        ASSERT_GE(fd, 0);
+        ASSERT_TRUE(android::base::WriteFully(fd, test_string.data(), test_string.size()));
+    }
+
+    // Note: we know there is no inner/outer dm device since we didn't request
+    // a linear segment.
+    DeviceMapper::TargetInfo target;
+    ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target));
+    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+
+    // Release the lock.
+    lock_ = nullptr;
+
+    // Done updating.
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+    test_device->set_slot_suffix("_b");
+    ASSERT_TRUE(sm->InitiateMerge());
+
+    // The device should have been switched to a snapshot-merge target.
+    ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target));
+    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+
+    // We should not be able to cancel an update now.
+    ASSERT_FALSE(sm->CancelUpdate());
+
+    ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::MergeCompleted);
+    ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
+
+    // The device should no longer be a snapshot or snapshot-merge.
+    ASSERT_FALSE(sm->IsSnapshotDevice("test_partition_b"));
+
+    // Test that we can read back the string we wrote to the snapshot. Note
+    // that the base device is gone now. |snap_device| contains the correct
+    // partition.
+    unique_fd fd(open(snap_device.c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_GE(fd, 0);
+
+    std::string buffer(test_string.size(), '\0');
+    ASSERT_TRUE(android::base::ReadFully(fd, buffer.data(), buffer.size()));
+    ASSERT_EQ(test_string, buffer);
+}
+
+TEST_F(SnapshotTest, MergeCannotRemoveCow) {
+    ASSERT_TRUE(AcquireLock());
+
+    static const uint64_t kDeviceSize = 1024 * 1024;
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
+                                   kDeviceSize));
+
+    std::string base_device, snap_device;
+    ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
+    ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+
+    // Keep an open handle to the cow device. This should cause the merge to
+    // be incomplete.
+    auto cow_path = android::base::GetProperty("gsid.mapped_image.test-snapshot-cow", "");
+    unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_GE(fd, 0);
+
+    // Release the lock.
+    lock_ = nullptr;
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+    test_device->set_slot_suffix("_b");
+    ASSERT_TRUE(sm->InitiateMerge());
+
+    // COW cannot be removed due to open fd, so expect a soft failure.
+    ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::MergeNeedsReboot);
+
+    // Forcefully delete the snapshot device, so it looks like we just rebooted.
+    DeleteSnapshotDevice("test-snapshot");
+
+    // Map snapshot should fail now, because we're in a merge-complete state.
+    ASSERT_TRUE(AcquireLock());
+    ASSERT_FALSE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+
+    // Release everything and now the merge should complete.
+    fd = {};
+    lock_ = nullptr;
+
+    ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::MergeCompleted);
+}
+
+TEST_F(SnapshotTest, FirstStageMountAndMerge) {
+    ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
+            .WillByDefault(Return(true));
+
+    ASSERT_TRUE(AcquireLock());
+
+    static const uint64_t kDeviceSize = 1024 * 1024;
+
+    ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
+    ASSERT_TRUE(MapUpdatePartitions());
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b", kDeviceSize, kDeviceSize,
+                                   kDeviceSize));
+
+    // Simulate a reboot into the new slot.
+    lock_ = nullptr;
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base"));
+
+    auto rebooted = new TestDeviceInfo(fake_super);
+    rebooted->set_slot_suffix("_b");
+
+    auto init = SnapshotManager::NewForFirstStageMount(rebooted);
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+    ASSERT_TRUE(AcquireLock());
+
+    // Validate that we have a snapshot device.
+    SnapshotManager::SnapshotStatus status;
+    ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
+    ASSERT_EQ(status.state, SnapshotManager::SnapshotState::Created);
+
+    DeviceMapper::TargetInfo target;
+    auto dm_name = init->GetSnapshotDeviceName("test_partition_b", status);
+    ASSERT_TRUE(init->IsSnapshotDevice(dm_name, &target));
+    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+}
+
+TEST_F(SnapshotTest, FlashSuperDuringUpdate) {
+    ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
+            .WillByDefault(Return(true));
+
+    ASSERT_TRUE(AcquireLock());
+
+    static const uint64_t kDeviceSize = 1024 * 1024;
+
+    ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
+    ASSERT_TRUE(MapUpdatePartitions());
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b", kDeviceSize, kDeviceSize,
+                                   kDeviceSize));
+
+    // Simulate a reboot into the new slot.
+    lock_ = nullptr;
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base"));
+
+    // Reflash the super partition.
+    FormatFakeSuper();
+    ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize));
+
+    auto rebooted = new TestDeviceInfo(fake_super);
+    rebooted->set_slot_suffix("_b");
+
+    auto init = SnapshotManager::NewForFirstStageMount(rebooted);
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+    ASSERT_TRUE(AcquireLock());
+
+    SnapshotManager::SnapshotStatus status;
+    ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
+
+    // We should not get a snapshot device now.
+    DeviceMapper::TargetInfo target;
+    auto dm_name = init->GetSnapshotDeviceName("test_partition_b", status);
+    ASSERT_FALSE(init->IsSnapshotDevice(dm_name, &target));
+
+    // We should see a cancelled update as well.
+    lock_ = nullptr;
+    ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::Cancelled);
+}
+
+TEST_F(SnapshotTest, FlashSuperDuringMerge) {
+    ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
+            .WillByDefault(Return(true));
+
+    ASSERT_TRUE(AcquireLock());
+
+    static const uint64_t kDeviceSize = 1024 * 1024;
+
+    ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
+    ASSERT_TRUE(MapUpdatePartitions());
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b", kDeviceSize, kDeviceSize,
+                                   kDeviceSize));
+
+    // Simulate a reboot into the new slot.
+    lock_ = nullptr;
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base"));
+
+    auto rebooted = new TestDeviceInfo(fake_super);
+    rebooted->set_slot_suffix("_b");
+
+    auto init = SnapshotManager::NewForFirstStageMount(rebooted);
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+    ASSERT_TRUE(init->InitiateMerge());
+
+    // Now, reflash super. Note that we haven't called ProcessUpdateState, so the
+    // status is still Merging.
+    DeleteSnapshotDevice("test_partition_b");
+    ASSERT_TRUE(init->image_manager()->UnmapImageDevice("test_partition_b-cow"));
+    FormatFakeSuper();
+    ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize));
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+    // Because the status is Merging, we must call ProcessUpdateState, which should
+    // detect a cancelled update.
+    ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::Cancelled);
+    ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
+}
+
 }  // namespace snapshot
 }  // namespace android
 
@@ -166,10 +556,14 @@
     ::testing::InitGoogleTest(&argc, argv);
 
     std::vector<std::string> paths = {
+            // clang-format off
             "/data/gsi/ota/test",
+            "/data/gsi/ota/test/super",
             "/metadata/gsi/ota/test",
+            "/metadata/gsi/ota/test/super",
             "/metadata/ota/test",
             "/metadata/ota/test/snapshots",
+            // clang-format on
     };
     for (const auto& path : paths) {
         if (!Mkdir(path)) {
@@ -181,9 +575,39 @@
     test_device = new TestDeviceInfo();
     sm = SnapshotManager::New(test_device);
     if (!sm) {
-        std::cerr << "Could not create snapshot manager";
+        std::cerr << "Could not create snapshot manager\n";
         return 1;
     }
 
-    return RUN_ALL_TESTS();
+    // Clean up previous run.
+    SnapshotTest().Cleanup();
+
+    // Use a separate image manager for our fake super partition.
+    auto super_images = IImageManager::Open("ota/test/super", 10s);
+    if (!super_images) {
+        std::cerr << "Could not create image manager\n";
+        return 1;
+    }
+
+    // Clean up any old copy.
+    DeleteBackingImage(super_images.get(), "fake-super");
+
+    // Create and map the fake super partition.
+    static constexpr int kImageFlags =
+            IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
+    if (!super_images->CreateBackingImage("fake-super", kSuperSize, kImageFlags)) {
+        std::cerr << "Could not create fake super partition\n";
+        return 1;
+    }
+    if (!super_images->MapImageDevice("fake-super", 10s, &fake_super)) {
+        std::cerr << "Could not map fake super partition\n";
+        return 1;
+    }
+    test_device->set_fake_super(fake_super);
+
+    auto result = RUN_ALL_TESTS();
+
+    DeleteBackingImage(super_images.get(), "fake-super");
+
+    return result;
 }
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
new file mode 100644
index 0000000..f67dd21
--- /dev/null
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -0,0 +1,57 @@
+// 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 "test_helpers.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace snapshot {
+
+using android::fiemap::IImageManager;
+
+void DeleteBackingImage(IImageManager* manager, const std::string& name) {
+    if (manager->IsImageMapped(name)) {
+        ASSERT_TRUE(manager->UnmapImageDevice(name));
+    }
+    if (manager->BackingImageExists(name)) {
+        ASSERT_TRUE(manager->DeleteBackingImage(name));
+    }
+}
+
+android::base::unique_fd TestPartitionOpener::Open(const std::string& partition_name,
+                                                   int flags) const {
+    if (partition_name == "super") {
+        return PartitionOpener::Open(fake_super_path_, flags);
+    }
+    return PartitionOpener::Open(partition_name, flags);
+}
+
+bool TestPartitionOpener::GetInfo(const std::string& partition_name,
+                                  android::fs_mgr::BlockDeviceInfo* info) const {
+    if (partition_name == "super") {
+        return PartitionOpener::GetInfo(fake_super_path_, info);
+    }
+    return PartitionOpener::GetInfo(partition_name, info);
+}
+
+std::string TestPartitionOpener::GetDeviceString(const std::string& partition_name) const {
+    if (partition_name == "super") {
+        return fake_super_path_;
+    }
+    return PartitionOpener::GetDeviceString(partition_name);
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/test_helpers.h
new file mode 100644
index 0000000..9f582d9
--- /dev/null
+++ b/fs_mgr/libsnapshot/test_helpers.h
@@ -0,0 +1,69 @@
+// 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 <string>
+
+#include <libfiemap/image_manager.h>
+#include <liblp/partition_opener.h>
+#include <libsnapshot/snapshot.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace std::string_literals;
+
+// Redirect requests for "super" to our fake super partition.
+class TestPartitionOpener final : public android::fs_mgr::PartitionOpener {
+  public:
+    explicit TestPartitionOpener(const std::string& fake_super_path)
+        : fake_super_path_(fake_super_path) {}
+
+    android::base::unique_fd Open(const std::string& partition_name, int flags) const override;
+    bool GetInfo(const std::string& partition_name,
+                 android::fs_mgr::BlockDeviceInfo* info) const override;
+    std::string GetDeviceString(const std::string& partition_name) const override;
+
+  private:
+    std::string fake_super_path_;
+};
+
+class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
+  public:
+    TestDeviceInfo() {}
+    explicit TestDeviceInfo(const std::string& fake_super) { set_fake_super(fake_super); }
+    std::string GetGsidDir() const override { return "ota/test"s; }
+    std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
+    std::string GetSlotSuffix() const override { return slot_suffix_; }
+    std::string GetSuperDevice([[maybe_unused]] uint32_t slot) const override { return "super"; }
+    const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const override {
+        return *opener_.get();
+    }
+
+    void set_slot_suffix(const std::string& suffix) { slot_suffix_ = suffix; }
+    void set_fake_super(const std::string& path) {
+        opener_ = std::make_unique<TestPartitionOpener>(path);
+    }
+
+  private:
+    std::string slot_suffix_ = "_a";
+    std::unique_ptr<TestPartitionOpener> opener_;
+};
+
+// Helper for error-spam-free cleanup.
+void DeleteBackingImage(android::fiemap::IImageManager* manager, const std::string& name);
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index 642f2c1..f445703 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -709,8 +709,9 @@
       EXPECT_EQ "${lval}" "${rval}" ${*}
       return
   fi
-  EXPECT_EQ "${lval}" "${rval}" ||
+  if ! EXPECT_EQ "${lval}" "${rval}"; then
     die "${@}"
+  fi
 }
 
 [ "USAGE: check_ne <lval> <rval> [--warning [message]]
@@ -724,8 +725,9 @@
       EXPECT_NE "${lval}" "${rval}" ${*}
       return
   fi
-  EXPECT_NE "${lval}" "${rval}" ||
+  if ! EXPECT_NE "${lval}" "${rval}"; then
     die "${@}"
+  fi
 }
 
 [ "USAGE: skip_administrative_mounts [data] < /proc/mounts
@@ -847,6 +849,8 @@
 
 # Do something.
 
+# Collect characteristics of the device and report.
+
 D=`get_property ro.serialno`
 [ -n "${D}" ] || D=`get_property ro.boot.serialno`
 [ -z "${D}" -o -n "${ANDROID_SERIAL}" ] || ANDROID_SERIAL=${D}
@@ -869,35 +873,78 @@
 [ -z "${ACTIVE_SLOT}" ] ||
   echo "${BLUE}[     INFO ]${NORMAL} active slot is ${ACTIVE_SLOT}" >&2
 
+# Acquire list of system partitions
+
+PARTITIONS=`adb_su cat /vendor/etc/fstab* |
+              skip_administrative_mounts |
+              sed -n "s@^\([^ ${TAB}/][^ ${TAB}/]*\)[ ${TAB}].*[, ${TAB}]ro[, ${TAB}].*@\1@p" |
+              sort -u |
+              tr '\n' ' '`
+PARTITIONS="${PARTITIONS:-system vendor}"
+# KISS (we do not support sub-mounts for system partitions currently)
+MOUNTS="`for i in ${PARTITIONS}; do
+           echo /${i}
+         done |
+         tr '\n' ' '`"
+echo "${BLUE}[     INFO ]${NORMAL} System Partitions list: ${PARTITIONS}" >&2
+
 # Report existing partition sizes
-adb_sh ls -l /dev/block/by-name/ </dev/null 2>/dev/null |
+adb_sh ls -l /dev/block/by-name/ /dev/block/mapper/ </dev/null 2>/dev/null |
   sed -n 's@.* \([^ ]*\) -> /dev/block/\([^ ]*\)$@\1 \2@p' |
   while read name device; do
-    case ${name} in
-      system_[ab] | system | vendor_[ab] | vendor | super | cache)
-        case ${device} in
-          sd*)
-            device=${device%%[0-9]*}/${device}
-            ;;
-        esac
-        size=`adb_su cat /sys/block/${device}/size 2>/dev/null </dev/null` &&
-          size=`expr ${size} / 2` &&
-          echo "${BLUE}[     INFO ]${NORMAL} partition ${name} device ${device} size ${size}K" >&2
+    [ super = ${name} -o cache = ${name} ] ||
+      (
+        for i in ${PARTITIONS}; do
+          [ ${i} = ${name} -o ${i} = ${name%_[ab]} ] && exit
+        done
+        exit 1
+      ) ||
+      continue
+
+    case ${device} in
+      sd*)
+        device=${device%%[0-9]*}/${device}
         ;;
     esac
+    size=`adb_su cat /sys/block/${device}/size 2>/dev/null </dev/null` &&
+      size=`expr ${size} / 2` &&
+      echo "${BLUE}[     INFO ]${NORMAL} partition ${name} device ${device} size ${size}K" >&2
   done
 
 # If reboot too soon after fresh flash, could trip device update failure logic
 wait_for_screen
 # Can we test remount -R command?
+OVERLAYFS_BACKING="cache mnt/scratch"
 overlayfs_supported=true
-if [ "orange" = "`get_property ro.boot.verifiedbootstate`" -a \
-     "2" = "`get_property partition.system.verified`" ]; then
+if [ "orange" != "`get_property ro.boot.verifiedbootstate`" -o \
+     "2" != "`get_property partition.system.verified`" ]; then
   restore() {
     ${overlayfs_supported} || return 0
     inFastboot &&
       fastboot reboot &&
-      adb_wait ${ADB_WAIT}
+      adb_wait ${ADB_WAIT} ||
+      true
+    if inAdb; then
+      reboot=false
+      for d in ${OVERLAYFS_BACKING}; do
+        if adb_su ls -d /${d}/overlay </dev/null >/dev/null 2>/dev/null; then
+          adb_su rm -rf /${d}/overlay </dev/null
+          reboot=true
+        fi
+      done
+      if ${reboot}; then
+        adb_reboot &&
+        adb_wait ${ADB_WAIT}
+      fi
+    fi
+  }
+else
+  restore() {
+    ${overlayfs_supported} || return 0
+    inFastboot &&
+      fastboot reboot &&
+      adb_wait ${ADB_WAIT} ||
+      true
     inAdb &&
       adb_root &&
       adb enable-verity >/dev/null 2>/dev/null &&
@@ -956,7 +1003,6 @@
 # So lets do our best to surgically wipe the overlayfs state without
 # having to go through enable-verity transition.
 reboot=false
-OVERLAYFS_BACKING="cache mnt/scratch"
 for d in ${OVERLAYFS_BACKING}; do
   if adb_sh ls -d /${d}/overlay </dev/null >/dev/null 2>/dev/null; then
     echo "${ORANGE}[  WARNING ]${NORMAL} /${d}/overlay is setup, surgically wiping" >&2
@@ -1085,7 +1131,7 @@
 
 # Feed log with selinux denials as baseline before overlays
 adb_unroot
-adb_sh find /system /vendor </dev/null >/dev/null 2>/dev/null
+adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
 adb_root
 
 D=`adb remount 2>&1`
@@ -1144,6 +1190,11 @@
     skip_unrelated_mounts |
     grep " overlay ro,") ||
     die "remount overlayfs missed a spot (ro)"
+  !(adb_sh grep -v noatime /proc/mounts </dev/null |
+    skip_administrative_mounts data |
+    skip_unrelated_mounts |
+    grep -v ' ro,') ||
+    die "mounts are not noatime"
   D=`adb_sh grep " rw," /proc/mounts </dev/null |
      skip_administrative_mounts data`
   if echo "${D}" | grep /dev/root >/dev/null; then
@@ -1177,21 +1228,19 @@
 
 # Check something.
 
-echo "${GREEN}[ RUN      ]${NORMAL} push content to /system and /vendor" >&2
+echo "${GREEN}[ RUN      ]${NORMAL} push content to ${MOUNTS}" >&2
 
 A="Hello World! $(date)"
-echo "${A}" | adb_sh cat - ">/system/hello"
+for i in ${MOUNTS}; do
+  echo "${A}" | adb_sh cat - ">${i}/hello"
+  B="`adb_cat ${i}/hello`" ||
+    die "${i#/} hello"
+  check_eq "${A}" "${B}" ${i} before reboot
+done
 echo "${A}" | adb_sh cat - ">/system/priv-app/hello"
-echo "${A}" | adb_sh cat - ">/vendor/hello"
-B="`adb_cat /system/hello`" ||
-  die "system hello"
-check_eq "${A}" "${B}" /system before reboot
 B="`adb_cat /system/priv-app/hello`" ||
   die "system priv-app hello"
 check_eq "${A}" "${B}" /system/priv-app before reboot
-B="`adb_cat /vendor/hello`" ||
-  die "vendor hello"
-check_eq "${A}" "${B}" /vendor before reboot
 SYSTEM_DEVT=`adb_sh stat --format=%D /system/hello </dev/null`
 VENDOR_DEVT=`adb_sh stat --format=%D /vendor/hello </dev/null`
 SYSTEM_INO=`adb_sh stat --format=%i /system/hello </dev/null`
@@ -1266,24 +1315,23 @@
   echo "${GREEN}[       OK ]${NORMAL} /vendor content correct MAC after reboot" >&2
   # Feed unprivileged log with selinux denials as a result of overlays
   wait_for_screen
-  adb_sh find /system /vendor </dev/null >/dev/null 2>/dev/null
+  adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
 fi
-B="`adb_cat /system/hello`"
-check_eq "${A}" "${B}" /system after reboot
 # If overlayfs has a nested security problem, this will fail.
 B="`adb_ls /system/`" ||
-  dir "adb ls /system"
+  die "adb ls /system"
 [ X"${B}" != X"${B#*priv-app}" ] ||
-  dir "adb ls /system/priv-app"
+  die "adb ls /system/priv-app"
 B="`adb_cat /system/priv-app/hello`"
 check_eq "${A}" "${B}" /system/priv-app after reboot
-echo "${GREEN}[       OK ]${NORMAL} /system content remains after reboot" >&2
 # Only root can read vendor if sepolicy permissions are as expected.
 adb_root ||
   die "adb root"
-B="`adb_cat /vendor/hello`"
-check_eq "${A}" "${B}" vendor after reboot
-echo "${GREEN}[       OK ]${NORMAL} /vendor content remains after reboot" >&2
+for i in ${MOUNTS}; do
+  B="`adb_cat ${i}/hello`"
+  check_eq "${A}" "${B}" ${i#/} after reboot
+  echo "${GREEN}[       OK ]${NORMAL} ${i} content remains after reboot" >&2
+done
 
 check_eq "${SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/hello </dev/null`" system devt after reboot
 check_eq "${VENDOR_DEVT}" "`adb_sh stat --format=%D /vendor/hello </dev/null`" vendor devt after reboot
@@ -1294,7 +1342,7 @@
 check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" devt for su after reboot
 
 # Feed log with selinux denials as a result of overlays
-adb_sh find /system /vendor </dev/null >/dev/null 2>/dev/null
+adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
 
 # Check if the updated libc.so is persistent after reboot.
 adb_root &&
@@ -1399,9 +1447,9 @@
   B="`adb_cat /system/hello`"
   check_eq "${A}" "${B}" system after flash vendor
   B="`adb_ls /system/`" ||
-    dir "adb ls /system"
+    die "adb ls /system"
   [ X"${B}" != X"${B#*priv-app}" ] ||
-    dir "adb ls /system/priv-app"
+    die "adb ls /system/priv-app"
   B="`adb_cat /system/priv-app/hello`"
   check_eq "${A}" "${B}" system/priv-app after flash vendor
   adb_root ||
@@ -1454,6 +1502,9 @@
 check_eq "cat: /system/priv-app/hello: No such file or directory" "${B}" after rm
 B="`adb_cat /vendor/hello`"
 check_eq "cat: /vendor/hello: No such file or directory" "${B}" after rm
+for i in ${MOUNTS}; do
+  adb_sh rm ${i}/hello </dev/null 2>/dev/null || true
+done
 
 if ${is_bootloader_fastboot} && [ -n "${scratch_partition}" ]; then
 
@@ -1468,7 +1519,7 @@
   }
   dd if=/dev/zero of=${img} bs=4096 count=16 2>/dev/null &&
     fastboot_wait ${FASTBOOT_WAIT} ||
-    die "reboot into fastboot `usb_status`"
+    die "reboot into fastboot to flash scratch `usb_status`"
   fastboot flash --force ${scratch_partition} ${img}
   err=${?}
   cleanup
diff --git a/healthd/api/charger_sysprop-current.txt b/healthd/api/charger_sysprop-current.txt
new file mode 100644
index 0000000..678c847
--- /dev/null
+++ b/healthd/api/charger_sysprop-current.txt
@@ -0,0 +1,29 @@
+props {
+  module: "android.sysprop.ChargerProperties"
+  prop {
+    api_name: "disable_init_blank"
+    scope: Internal
+    prop_name: "ro.charger.disable_init_blank"
+  }
+  prop {
+    api_name: "draw_split_offset"
+    type: Long
+    scope: Internal
+    prop_name: "ro.charger.draw_split_offset"
+  }
+  prop {
+    api_name: "draw_split_screen"
+    scope: Internal
+    prop_name: "ro.charger.draw_split_screen"
+  }
+  prop {
+    api_name: "enable_suspend"
+    scope: Internal
+    prop_name: "ro.charger.enable_suspend"
+  }
+  prop {
+    api_name: "no_ui"
+    scope: Internal
+    prop_name: "ro.charger.no_ui"
+  }
+}
diff --git a/healthd/api/charger_sysprop-latest.txt b/healthd/api/charger_sysprop-latest.txt
new file mode 100644
index 0000000..678c847
--- /dev/null
+++ b/healthd/api/charger_sysprop-latest.txt
@@ -0,0 +1,29 @@
+props {
+  module: "android.sysprop.ChargerProperties"
+  prop {
+    api_name: "disable_init_blank"
+    scope: Internal
+    prop_name: "ro.charger.disable_init_blank"
+  }
+  prop {
+    api_name: "draw_split_offset"
+    type: Long
+    scope: Internal
+    prop_name: "ro.charger.draw_split_offset"
+  }
+  prop {
+    api_name: "draw_split_screen"
+    scope: Internal
+    prop_name: "ro.charger.draw_split_screen"
+  }
+  prop {
+    api_name: "enable_suspend"
+    scope: Internal
+    prop_name: "ro.charger.enable_suspend"
+  }
+  prop {
+    api_name: "no_ui"
+    scope: Internal
+    prop_name: "ro.charger.no_ui"
+  }
+}
diff --git a/healthd/api/current.txt b/healthd/api/current.txt
deleted file mode 100644
index d802177..0000000
--- a/healthd/api/current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/healthd/api/removed.txt b/healthd/api/removed.txt
deleted file mode 100644
index d802177..0000000
--- a/healthd/api/removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/healthd/api/system-current.txt b/healthd/api/system-current.txt
deleted file mode 100644
index d802177..0000000
--- a/healthd/api/system-current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/healthd/api/system-removed.txt b/healthd/api/system-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/healthd/api/system-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/healthd/api/test-current.txt b/healthd/api/test-current.txt
deleted file mode 100644
index d802177..0000000
--- a/healthd/api/test-current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/healthd/api/test-removed.txt b/healthd/api/test-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/healthd/api/test-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/init/Android.bp b/init/Android.bp
index 6501900..57555f6 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -70,6 +70,7 @@
         "libprotobuf-cpp-lite",
         "libpropertyinfoserializer",
         "libpropertyinfoparser",
+        "libsnapshot_nobinder",
     ],
     shared_libs: [
         "libbacktrace",
@@ -83,6 +84,7 @@
         "libfscrypt",
         "libgsi",
         "libhidl-gen-utils",
+        "libjsoncpp",
         "libkeyutils",
         "liblog",
         "liblogwrap",
@@ -116,8 +118,10 @@
         "firmware_handler.cpp",
         "first_stage_init.cpp",
         "first_stage_mount.cpp",
+        "fscrypt_init_extensions.cpp",
         "import_parser.cpp",
         "init.cpp",
+        "interface_utils.cpp",
         "keychords.cpp",
         "modalias_handler.cpp",
         "mount_handler.cpp",
@@ -204,9 +208,20 @@
 // ------------------------------------------------------------------------------
 
 cc_test {
-    name: "init_tests",
+    name: "CtsInitTestCases",
     defaults: ["init_defaults"],
-    compile_multilib: "first",
+    require_root: true,
+
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
     srcs: [
         "devices_test.cpp",
         "init_test.cpp",
@@ -223,7 +238,12 @@
         "util_test.cpp",
     ],
     static_libs: ["libinit"],
-    test_suites: ["device-tests"],
+
+    test_suites: [
+        "cts",
+        "device-tests",
+        "vts",
+    ],
 }
 
 cc_benchmark {
@@ -242,7 +262,10 @@
     name: "generated_stub_builtin_function_map",
     tool_files: ["host_builtin_map.py"],
     out: ["generated_stub_builtin_function_map.h"],
-    srcs: ["builtins.cpp", "check_builtins.cpp"],
+    srcs: [
+        "builtins.cpp",
+        "check_builtins.cpp",
+    ],
     cmd: "$(location host_builtin_map.py) --builtins $(location builtins.cpp) --check_builtins $(location check_builtins.cpp) > $(out)",
 }
 
@@ -264,11 +287,13 @@
     shared_libs: [
         "libcutils",
         "libhidl-gen-utils",
-        "libjsoncpp",
         "liblog",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
     ],
+    header_libs: [
+        "libjsoncpp_headers",
+    ],
     srcs: [
         "action.cpp",
         "action_manager.cpp",
@@ -278,6 +303,7 @@
         "epoll.cpp",
         "keychords.cpp",
         "import_parser.cpp",
+        "interface_utils.cpp",
         "host_import_parser.cpp",
         "host_init_verifier.cpp",
         "parser.cpp",
diff --git a/init/Android.mk b/init/Android.mk
index 006e1bf..d7258a7 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -114,6 +114,7 @@
     libbacktrace \
     libmodprobe \
     libext2_uuid \
+    libsnapshot_nobinder \
 
 LOCAL_SANITIZE := signed-integer-overflow
 # First stage init is weird: it may start without stdout/stderr, and no /proc.
diff --git a/init/AndroidTest.xml b/init/AndroidTest.xml
new file mode 100644
index 0000000..94a02e6
--- /dev/null
+++ b/init/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?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 CTS init test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsInitTestCases->/data/local/tmp/CtsInitTestCases" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="CtsInitTestCases" />
+        <option name="runtime-hint" value="65s" />
+    </test>
+</configuration>
diff --git a/init/README.ueventd.md b/init/README.ueventd.md
new file mode 100644
index 0000000..c592c37
--- /dev/null
+++ b/init/README.ueventd.md
@@ -0,0 +1,112 @@
+# Ueventd
+-------
+Ueventd manages `/dev`, sets permissions for `/sys`, and handles firmware uevents. It has default
+behavior described below, along with a scripting language that allows customizing this behavior,
+built on the same parser as init.
+
+Ueventd has one generic customization parameter, the size of rcvbuf_size for the ueventd socket. It
+is customized by the `uevent_socket_rcvbuf_size` parameter, which takes the format of
+
+    uevent_socket_rcvbuf_size <size>
+For example
+
+    uevent_socket_rcvbuf_size 16M
+Sets the uevent socket rcvbuf_size to 16 megabytes.
+
+## /dev
+----
+Ueventd listens to the kernel uevent sockets and creates/deletes nodes in `/dev` based on the
+incoming add/remove uevents. It defaults to using `0600` mode and `root` user/group. It always
+creates the nodes with the SELabel from the current loaded SEPolicy. It has three default behaviors
+for the node path:
+
+  1. Block devices are created as `/dev/block/<basename uevent DEVPATH>`. There are symlinks created
+     to this node at `/dev/block/<type>/<parent device>/<basename uevent DEVPATH>`,
+     `/dev/block/<type>/<parent device>/by-name/<uevent PARTNAME>`, and `/dev/block/by-name/<uevent
+     PARTNAME>` if the device is a boot device.
+  2. USB devices are created as `/dev/<uevent DEVNAME>` if `DEVNAME` was specified for the uevent,
+     otherwise as `/dev/bus/usb/<bus_id>/<device_id>` where `bus_id` is `uevent MINOR / 128 + 1` and
+     `device_id` is `uevent MINOR % 128 + 1`.
+  3. All other devices are created as `/dev/<basename uevent DEVPATH>`
+
+The permissions can be modified using a ueventd.rc script and a line that beings with `/dev`. These
+lines take the format of
+
+    devname mode uid gid
+For example
+
+    /dev/null 0666 root root
+When `/dev/null` is created, its mode will be set to `0666`, its user to `root` and its group to
+`root`.
+
+The path can be modified using a ueventd.rc script and a `subsystem` section. There are three to set
+for a subsystem: the subsystem name, which device name to use, and which directory to place the
+device in. The section takes the below format of
+
+    subsystem <subsystem_name>
+      devname uevent_devname|uevent_devpath
+      [dirname <directory>]
+
+`subsystem_name` is used to match uevent `SUBSYSTEM` value
+
+`devname` takes one of two options
+  1. `uevent_devname` specifies that the name of the node will be the uevent `DEVNAME`
+  2. `uevent_devpath` specified that the name of the node will be basename uevent `DEVPATH`
+
+`dirname` is an optional parameter that specifies a directory within `/dev` where the node will be
+created.
+
+For example
+
+    subsystem sound
+      devname uevent_devpath
+      dirname /dev/snd
+Indicates that all uevents with `SUBSYSTEM=sound` will create nodes as `/dev/snd/<basename uevent
+DEVPATH>`.
+
+## /sys
+----
+Ueventd by default takes no action for `/sys`, however it can be instructed to set permissions for
+certain files in `/sys` when matching uevents are generated. This is done using a ueventd.rc script
+and a line that begins with `/sys`. These lines take the format of
+
+    nodename attr mode uid gid
+For example
+
+    /sys/devices/system/cpu/cpu* cpufreq/scaling_max_freq 0664 system system
+When a uevent that matches the pattern `/sys/devices/system/cpu/cpu*` is sent, the matching sysfs
+attribute, `cpufreq/scaling_max_freq`, will have its mode set to `0664`, its user to to `system` and
+its group set to `system`.
+
+Note that `*` matches as a wildcard and can be used anywhere in a path.
+
+## Firmware loading
+----------------
+Ueventd automatically serves firmware requests by searching through a list of firmware directories
+for a file matching the uevent `FIRMWARE`. It then forks a process to serve this firmware to the
+kernel.
+
+The list of firmware directories is customized by a `firmware_directories` line in a ueventd.rc
+file. This line takes the format of
+
+    firmware_directories <firmware_directory> [ <firmware_directory> ]*
+For example
+
+    firmware_directories /etc/firmware/ /odm/firmware/ /vendor/firmware/ /firmware/image/
+Adds those 4 directories, in that order to the list of firmware directories that will be tried by
+ueventd. Note that this option always accumulates to the list; it is not possible to remove previous
+entries.
+
+Ueventd will wait until after `post-fs` in init, to keep retrying before believing the firmwares are
+not present.
+
+## Coldboot
+--------
+Ueventd must create devices in `/dev` for all devices that have already sent their uevents before
+ueventd has started. To do so, when ueventd is started it does what it calls a 'coldboot' on `/sys`,
+in which it writes 'add' to every 'uevent' file that it finds in `/sys/class`, `/sys/block`, and
+`/sys/devices`. This causes the kernel to regenerate the uevents for these paths, and thus for
+ueventd to create the nodes.
+
+For boot time purposes, this is done in parallel across a set of child processes. `ueventd.cpp` in
+this directory contains documentation on how the parallelization is done.
diff --git a/init/action.cpp b/init/action.cpp
index 65ba25d..69e40d0 100644
--- a/init/action.cpp
+++ b/init/action.cpp
@@ -161,18 +161,8 @@
     auto result = command.InvokeFunc(subcontext_);
     auto duration = t.duration();
 
-    // There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
-    // device, such as '/sys/class/leds/jogball-backlight/brightness'.  As of this writing, there
-    // are 198 such failures on bullhead.  Instead of spamming the log reporting them, we do not
-    // report such failures unless we're running at the DEBUG log level.
-    bool report_failure = !result.has_value();
-    if (report_failure && android::base::GetMinimumLogSeverity() > android::base::DEBUG &&
-        result.error().code() == ENOENT) {
-        report_failure = false;
-    }
-
     // Any action longer than 50ms will be warned to user as slow operation
-    if (report_failure || duration > 50ms ||
+    if (!result.has_value() || duration > 50ms ||
         android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
         std::string trigger_name = BuildTriggersString();
         std::string cmd_str = command.BuildCommandString();
@@ -205,10 +195,11 @@
                 found = true;
             }
         } else {
-            std::string prop_val = android::base::GetProperty(trigger_name, "");
-            if (prop_val.empty() || (trigger_value != "*" && trigger_value != prop_val)) {
-                return false;
+            std::string prop_value = android::base::GetProperty(trigger_name, "");
+            if (trigger_value == "*" && !prop_value.empty()) {
+                continue;
             }
+            if (trigger_value != prop_value) return false;
         }
     }
     return found;
diff --git a/init/builtins.cpp b/init/builtins.cpp
index e75f5cb..7c66de5 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -55,7 +55,6 @@
 #include <cutils/android_reboot.h>
 #include <fs_mgr.h>
 #include <fscrypt/fscrypt.h>
-#include <fscrypt/fscrypt_init_extensions.h>
 #include <libgsi/libgsi.h>
 #include <selinux/android.h>
 #include <selinux/label.h>
@@ -64,6 +63,7 @@
 
 #include "action_manager.h"
 #include "bootchart.h"
+#include "fscrypt_init_extensions.h"
 #include "init.h"
 #include "mount_namespace.h"
 #include "parser.h"
@@ -89,6 +89,43 @@
 namespace android {
 namespace init {
 
+// There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
+// device, such as '/sys/class/leds/jogball-backlight/brightness'.  As of this writing, there
+// are 81 such failures on cuttlefish.  Instead of spamming the log reporting them, we do not
+// report such failures unless we're running at the DEBUG log level.
+class ErrorIgnoreEnoent {
+  public:
+    ErrorIgnoreEnoent()
+        : ignore_error_(errno == ENOENT &&
+                        android::base::GetMinimumLogSeverity() > android::base::DEBUG) {}
+    explicit ErrorIgnoreEnoent(int errno_to_append)
+        : error_(errno_to_append),
+          ignore_error_(errno_to_append == ENOENT &&
+                        android::base::GetMinimumLogSeverity() > android::base::DEBUG) {}
+
+    template <typename T>
+    operator android::base::expected<T, ResultError>() {
+        if (ignore_error_) {
+            return {};
+        }
+        return error_;
+    }
+
+    template <typename T>
+    ErrorIgnoreEnoent& operator<<(T&& t) {
+        error_ << t;
+        return *this;
+    }
+
+  private:
+    Error error_;
+    bool ignore_error_;
+};
+
+inline ErrorIgnoreEnoent ErrnoErrorIgnoreEnoent() {
+    return ErrorIgnoreEnoent(errno);
+}
+
 std::vector<std::string> late_import_paths;
 
 static constexpr std::chrono::nanoseconds kCommandRetryTimeout = 5s;
@@ -319,51 +356,64 @@
 // mkdir <path> [mode] [owner] [group]
 static Result<void> do_mkdir(const BuiltinArguments& args) {
     mode_t mode = 0755;
-    if (args.size() >= 3) {
-        mode = std::strtoul(args[2].c_str(), 0, 8);
-    }
+    Result<uid_t> uid = -1;
+    Result<gid_t> gid = -1;
 
-    if (!make_dir(args[1], mode)) {
-        /* chmod in case the directory already exists */
-        if (errno == EEXIST) {
-            if (fchmodat(AT_FDCWD, args[1].c_str(), mode, AT_SYMLINK_NOFOLLOW) == -1) {
-                return ErrnoError() << "fchmodat() failed";
-            }
-        } else {
-            return ErrnoError() << "mkdir() failed";
-        }
-    }
-
-    if (args.size() >= 4) {
-        auto uid = DecodeUid(args[3]);
-        if (!uid) {
-            return Error() << "Unable to decode UID for '" << args[3] << "': " << uid.error();
-        }
-        Result<gid_t> gid = -1;
-
-        if (args.size() == 5) {
+    switch (args.size()) {
+        case 5:
             gid = DecodeUid(args[4]);
             if (!gid) {
                 return Error() << "Unable to decode GID for '" << args[4] << "': " << gid.error();
             }
-        }
-
-        if (lchown(args[1].c_str(), *uid, *gid) == -1) {
-            return ErrnoError() << "lchown failed";
-        }
-
-        /* chown may have cleared S_ISUID and S_ISGID, chmod again */
-        if (mode & (S_ISUID | S_ISGID)) {
-            if (fchmodat(AT_FDCWD, args[1].c_str(), mode, AT_SYMLINK_NOFOLLOW) == -1) {
-                return ErrnoError() << "fchmodat failed";
+            FALLTHROUGH_INTENDED;
+        case 4:
+            uid = DecodeUid(args[3]);
+            if (!uid) {
+                return Error() << "Unable to decode UID for '" << args[3] << "': " << uid.error();
             }
+            FALLTHROUGH_INTENDED;
+        case 3:
+            mode = std::strtoul(args[2].c_str(), 0, 8);
+            FALLTHROUGH_INTENDED;
+        case 2:
+            break;
+        default:
+            return Error() << "Unexpected argument count: " << args.size();
+    }
+    std::string target = args[1];
+    struct stat mstat;
+    if (lstat(target.c_str(), &mstat) != 0) {
+        if (errno != ENOENT) {
+            return ErrnoError() << "lstat() failed on " << target;
+        }
+        if (!make_dir(target, mode)) {
+            return ErrnoErrorIgnoreEnoent() << "mkdir() failed on " << target;
+        }
+        if (lstat(target.c_str(), &mstat) != 0) {
+            return ErrnoError() << "lstat() failed on new " << target;
         }
     }
-
+    if (!S_ISDIR(mstat.st_mode)) {
+        return Error() << "Not a directory on " << target;
+    }
+    bool needs_chmod = (mstat.st_mode & ~S_IFMT) != mode;
+    if ((*uid != static_cast<uid_t>(-1) && *uid != mstat.st_uid) ||
+        (*gid != static_cast<gid_t>(-1) && *gid != mstat.st_gid)) {
+        if (lchown(target.c_str(), *uid, *gid) == -1) {
+            return ErrnoError() << "lchown failed on " << target;
+        }
+        // chown may have cleared S_ISUID and S_ISGID, chmod again
+        needs_chmod = true;
+    }
+    if (needs_chmod) {
+        if (fchmodat(AT_FDCWD, target.c_str(), mode, AT_SYMLINK_NOFOLLOW) == -1) {
+            return ErrnoError() << "fchmodat() failed on " << target;
+        }
+    }
     if (fscrypt_is_native()) {
-        if (fscrypt_set_directory_policy(args[1].c_str())) {
+        if (fscrypt_set_directory_policy(target)) {
             return reboot_into_recovery(
-                {"--prompt_and_wipe_data", "--reason=set_policy_failed:"s + args[1]});
+                    {"--prompt_and_wipe_data", "--reason=set_policy_failed:"s + target});
         }
     }
     return {};
@@ -459,7 +509,7 @@
         if (wait)
             wait_for_file(source, kCommandRetryTimeout);
         if (mount(source, target, system, flags, options) < 0) {
-            return ErrnoError() << "mount() failed";
+            return ErrnoErrorIgnoreEnoent() << "mount() failed";
         }
 
     }
@@ -603,12 +653,7 @@
     if (!ReadFstabFromFile(fstab_file, &fstab)) {
         return Error() << "Could not read fstab";
     }
-
-    auto mount_fstab_return_code =
-            CallFunctionAndHandleProperties(fs_mgr_mount_all, &fstab, mount_mode);
-    if (!mount_fstab_return_code) {
-        return Error() << "Could not call fs_mgr_mount_all(): " << mount_fstab_return_code.error();
-    }
+    auto mount_fstab_return_code = fs_mgr_mount_all(&fstab, mount_mode);
     property_set(prop_name, std::to_string(t.duration().count()));
 
     if (import_rc && SelinuxGetVendorAndroidVersion() <= __ANDROID_API_Q__) {
@@ -619,7 +664,7 @@
     if (queue_event) {
         /* queue_fs_event will queue event based on mount_fstab return code
          * and return processed return code*/
-        auto queue_fs_result = queue_fs_event(*mount_fstab_return_code);
+        auto queue_fs_result = queue_fs_event(mount_fstab_return_code);
         if (!queue_fs_result) {
             return Error() << "queue_fs_event() failed: " << queue_fs_result.error();
         }
@@ -635,13 +680,8 @@
         return Error() << "Could not read fstab";
     }
 
-    auto result = CallFunctionAndHandleProperties(fs_mgr_umount_all, &fstab);
-    if (!result) {
-        return Error() << "Could not call fs_mgr_mount_all() " << result.error();
-    }
-
-    if (*result != 0) {
-        return Error() << "fs_mgr_mount_all() failed: " << *result;
+    if (auto result = fs_mgr_umount_all(&fstab); result != 0) {
+        return Error() << "umount_fstab() failed " << result;
     }
     return {};
 }
@@ -652,13 +692,8 @@
         return Error() << "Could not read fstab '" << args[1] << "'";
     }
 
-    auto result = CallFunctionAndHandleProperties(fs_mgr_swapon_all, fstab);
-    if (!result) {
-        return Error() << "Could not call fs_mgr_swapon_all() " << result.error();
-    }
-
-    if (*result == 0) {
-        return Error() << "fs_mgr_swapon_all() failed.";
+    if (!fs_mgr_swapon_all(fstab)) {
+        return Error() << "fs_mgr_swapon_all() failed";
     }
 
     return {};
@@ -683,7 +718,7 @@
     Service* svc = ServiceList::GetInstance().FindService(args[1]);
     if (!svc) return Error() << "service " << args[1] << " not found";
     if (auto result = svc->Start(); !result) {
-        return Error() << "Could not start service: " << result.error();
+        return ErrorIgnoreEnoent() << "Could not start service: " << result.error();
     }
     return {};
 }
@@ -729,10 +764,7 @@
     if (MakeSymlink(args[1], args[2]) < 0) {
         // The symlink builtin is often used to create symlinks for older devices to be backwards
         // compatible with new paths, therefore we skip reporting this error.
-        if (errno == EEXIST && android::base::GetMinimumLogSeverity() > android::base::DEBUG) {
-            return {};
-        }
-        return ErrnoError() << "symlink() failed";
+        return ErrnoErrorIgnoreEnoent() << "symlink() failed";
     }
     return {};
 }
@@ -790,7 +822,8 @@
 
 static Result<void> do_write(const BuiltinArguments& args) {
     if (auto result = WriteFile(args[1], args[2]); !result) {
-        return Error() << "Unable to write to file '" << args[1] << "': " << result.error();
+        return ErrorIgnoreEnoent()
+               << "Unable to write to file '" << args[1] << "': " << result.error();
     }
 
     return {};
@@ -908,7 +941,7 @@
     }
 
     if (lchown(path.c_str(), *uid, *gid) == -1) {
-        return ErrnoError() << "lchown() failed";
+        return ErrnoErrorIgnoreEnoent() << "lchown() failed";
     }
 
     return {};
@@ -930,7 +963,7 @@
 static Result<void> do_chmod(const BuiltinArguments& args) {
     mode_t mode = get_mode(args[1].c_str());
     if (fchmodat(AT_FDCWD, args[2].c_str(), mode, AT_SYMLINK_NOFOLLOW) < 0) {
-        return ErrnoError() << "fchmodat() failed";
+        return ErrnoErrorIgnoreEnoent() << "fchmodat() failed";
     }
     return {};
 }
@@ -950,7 +983,7 @@
         }
     }
 
-    if (ret) return ErrnoError() << "selinux_android_restorecon() failed";
+    if (ret) return ErrnoErrorIgnoreEnoent() << "selinux_android_restorecon() failed";
     return {};
 }
 
diff --git a/init/check_builtins.cpp b/init/check_builtins.cpp
index 3bd4774..771f1d7 100644
--- a/init/check_builtins.cpp
+++ b/init/check_builtins.cpp
@@ -29,6 +29,7 @@
 #include <android-base/strings.h>
 
 #include "builtin_arguments.h"
+#include "interface_utils.h"
 #include "rlimit_parser.h"
 #include "service.h"
 #include "util.h"
@@ -80,6 +81,21 @@
     return check_exec(std::move(args));
 }
 
+Result<void> check_interface_restart(const BuiltinArguments& args) {
+    if (auto result = IsKnownInterface(args[1]); !result) {
+        return result.error();
+    }
+    return {};
+}
+
+Result<void> check_interface_start(const BuiltinArguments& args) {
+    return check_interface_restart(std::move(args));
+}
+
+Result<void> check_interface_stop(const BuiltinArguments& args) {
+    return check_interface_restart(std::move(args));
+}
+
 Result<void> check_load_system_props(const BuiltinArguments& args) {
     return Error() << "'load_system_props' is deprecated";
 }
diff --git a/init/check_builtins.h b/init/check_builtins.h
index c974e88..4ff0d0c 100644
--- a/init/check_builtins.h
+++ b/init/check_builtins.h
@@ -25,6 +25,9 @@
 Result<void> check_chown(const BuiltinArguments& args);
 Result<void> check_exec(const BuiltinArguments& args);
 Result<void> check_exec_background(const BuiltinArguments& args);
+Result<void> check_interface_restart(const BuiltinArguments& args);
+Result<void> check_interface_start(const BuiltinArguments& args);
+Result<void> check_interface_stop(const BuiltinArguments& args);
 Result<void> check_load_system_props(const BuiltinArguments& args);
 Result<void> check_loglevel(const BuiltinArguments& args);
 Result<void> check_mkdir(const BuiltinArguments& args);
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index b60c450..fd2d766 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -121,9 +121,9 @@
         _exit(127);
     }
     ioctl(fd, TIOCSCTTY, 0);
-    dup2(fd, 0);
-    dup2(fd, 1);
-    dup2(fd, 2);
+    dup2(fd, STDIN_FILENO);
+    dup2(fd, STDOUT_FILENO);
+    dup2(fd, STDERR_FILENO);
     close(fd);
 
     const char* path = "/system/bin/sh";
@@ -291,6 +291,10 @@
 
     const char* path = "/system/bin/init";
     const char* args[] = {path, "selinux_setup", nullptr};
+    auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
+    dup2(fd, STDOUT_FILENO);
+    dup2(fd, STDERR_FILENO);
+    close(fd);
     execv(path, const_cast<char**>(args));
 
     // execv() only returns if an error happened, in which case we
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 1a5ed28..6b4216f 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -36,6 +36,7 @@
 #include <fs_mgr_overlayfs.h>
 #include <libgsi/libgsi.h>
 #include <liblp/liblp.h>
+#include <libsnapshot/snapshot.h>
 
 #include "devices.h"
 #include "switch_root.h"
@@ -55,6 +56,7 @@
 using android::fs_mgr::ReadDefaultFstab;
 using android::fs_mgr::ReadFstabFromDt;
 using android::fs_mgr::SkipMountingPartitions;
+using android::snapshot::SnapshotManager;
 
 using namespace std::literals;
 
@@ -244,8 +246,6 @@
 
     if (!InitDevices()) return false;
 
-    if (!CreateLogicalPartitions()) return false;
-
     if (!MountPartitions()) return false;
 
     return true;
@@ -366,6 +366,16 @@
         return false;
     }
 
+    if (SnapshotManager::IsSnapshotManagerNeeded()) {
+        auto sm = SnapshotManager::NewForFirstStageMount();
+        if (!sm) {
+            return false;
+        }
+        if (sm->NeedSnapshotsInFirstStageMount()) {
+            return sm->CreateLogicalAndSnapshotPartitions(lp_metadata_partition_);
+        }
+    }
+
     auto metadata = android::fs_mgr::ReadCurrentMetadata(lp_metadata_partition_);
     if (!metadata) {
         LOG(ERROR) << "Could not read logical partition metadata from " << lp_metadata_partition_;
@@ -493,14 +503,7 @@
 // this case, we mount system first then pivot to it.  From that point on,
 // we are effectively identical to a system-as-root device.
 bool FirstStageMount::TrySwitchSystemAsRoot() {
-    auto metadata_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) {
-        return entry.mount_point == "/metadata";
-    });
-    if (metadata_partition != fstab_.end()) {
-        if (MountPartition(metadata_partition, true /* erase_same_mounts */)) {
-            UseGsiIfPresent();
-        }
-    }
+    UseGsiIfPresent();
 
     auto system_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) {
         return entry.mount_point == "/system";
@@ -523,6 +526,17 @@
 }
 
 bool FirstStageMount::MountPartitions() {
+    // Mount /metadata before creating logical partitions, since we need to
+    // know whether a snapshot merge is in progress.
+    auto metadata_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) {
+        return entry.mount_point == "/metadata";
+    });
+    if (metadata_partition != fstab_.end()) {
+        MountPartition(metadata_partition, true /* erase_same_mounts */);
+    }
+
+    if (!CreateLogicalPartitions()) return false;
+
     if (!TrySwitchSystemAsRoot()) return false;
 
     if (!SkipMountingPartitions(&fstab_)) return false;
@@ -602,19 +616,11 @@
         return;
     }
 
-    // Find the name of the super partition for the GSI. It will either be
-    // "userdata", or a block device such as an sdcard. There are no by-name
-    // partitions other than userdata that we support installing GSIs to.
+    // Find the super name. PartitionOpener will ensure this translates to the
+    // correct block device path.
     auto super = GetMetadataSuperBlockDevice(*metadata.get());
-    std::string super_name = android::fs_mgr::GetBlockDevicePartitionName(*super);
-    std::string super_path;
-    if (super_name == "userdata") {
-        super_path = "/dev/block/by-name/" + super_name;
-    } else {
-        super_path = "/dev/block/" + super_name;
-    }
-
-    if (!android::fs_mgr::CreateLogicalPartitions(*metadata.get(), super_path)) {
+    auto super_name = android::fs_mgr::GetBlockDevicePartitionName(*super);
+    if (!android::fs_mgr::CreateLogicalPartitions(*metadata.get(), super_name)) {
         LOG(ERROR) << "GSI partition layout could not be instantiated";
         return;
     }
diff --git a/init/fscrypt_init_extensions.cpp b/init/fscrypt_init_extensions.cpp
new file mode 100644
index 0000000..0f5a864
--- /dev/null
+++ b/init/fscrypt_init_extensions.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include "fscrypt_init_extensions.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fts.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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 <cutils/properties.h>
+#include <cutils/sockets.h>
+#include <fscrypt/fscrypt.h>
+#include <keyutils.h>
+#include <logwrap/logwrap.h>
+
+#define TAG "fscrypt"
+
+static int set_system_de_policy_on(const std::string& dir);
+
+int fscrypt_install_keyring() {
+    key_serial_t device_keyring = add_key("keyring", "fscrypt", 0, 0, KEY_SPEC_SESSION_KEYRING);
+
+    if (device_keyring == -1) {
+        PLOG(ERROR) << "Failed to create keyring";
+        return -1;
+    }
+
+    LOG(INFO) << "Keyring created with id " << device_keyring << " in process " << getpid();
+
+    return 0;
+}
+
+// TODO(b/139378601): use a single central implementation of this.
+static void delete_dir_contents(const std::string& dir) {
+    char* const paths[2] = {const_cast<char*>(dir.c_str()), nullptr};
+    FTS* fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, nullptr);
+    FTSENT* cur;
+    while ((cur = fts_read(fts)) != nullptr) {
+        if (cur->fts_info == FTS_ERR) {
+            PLOG(ERROR) << "fts_read";
+            break;
+        }
+        if (dir == cur->fts_path) {
+            continue;
+        }
+        switch (cur->fts_info) {
+            case FTS_D:
+                break;  // Ignore these
+            case FTS_DP:
+                if (rmdir(cur->fts_path) == -1) {
+                    PLOG(ERROR) << "rmdir " << cur->fts_path;
+                }
+                break;
+            default:
+                PLOG(ERROR) << "FTS unexpected type " << cur->fts_info << " at " << cur->fts_path;
+                if (rmdir(cur->fts_path) != -1) break;
+                // FALLTHRU (for gcc, lint, pcc, etc; and following for clang)
+                FALLTHROUGH_INTENDED;
+            case FTS_F:
+            case FTS_SL:
+            case FTS_SLNONE:
+                if (unlink(cur->fts_path) == -1) {
+                    PLOG(ERROR) << "unlink " << cur->fts_path;
+                }
+                break;
+        }
+    }
+
+    if (fts_close(fts) != 0) {
+        PLOG(ERROR) << "fts_close";
+    }
+}
+
+int fscrypt_set_directory_policy(const std::string& dir) {
+    const std::string prefix = "/data/";
+
+    if (!android::base::StartsWith(dir, prefix)) {
+        return 0;
+    }
+
+    // Special-case /data/media/obb per b/64566063
+    if (dir == "/data/media/obb") {
+        // Try to set policy on this directory, but if it is non-empty this may fail.
+        set_system_de_policy_on(dir);
+        return 0;
+    }
+
+    // Only set policy on first level /data directories
+    // To make this less restrictive, consider using a policy file.
+    // However this is overkill for as long as the policy is simply
+    // to apply a global policy to all /data folders created via makedir
+    if (dir.find_first_of('/', prefix.size()) != std::string::npos) {
+        return 0;
+    }
+
+    // Special case various directories that must not be encrypted,
+    // often because their subdirectories must be encrypted.
+    // This isn't a nice way to do this, see b/26641735
+    std::vector<std::string> directories_to_exclude = {
+        "lost+found",
+        "system_ce", "system_de",
+        "misc_ce", "misc_de",
+        "vendor_ce", "vendor_de",
+        "media",
+        "data", "user", "user_de",
+        "apex", "preloads", "app-staging",
+        "gsi",
+    };
+    for (const auto& d : directories_to_exclude) {
+        if ((prefix + d) == dir) {
+            LOG(INFO) << "Not setting policy on " << dir;
+            return 0;
+        }
+    }
+    int err = set_system_de_policy_on(dir);
+    if (err == 0) {
+        return 0;
+    }
+    // Empty these directories if policy setting fails.
+    std::vector<std::string> wipe_on_failure = {
+            "rollback", "rollback-observer",  // b/139193659
+    };
+    for (const auto& d : wipe_on_failure) {
+        if ((prefix + d) == dir) {
+            LOG(ERROR) << "Setting policy failed, deleting: " << dir;
+            delete_dir_contents(dir);
+            err = set_system_de_policy_on(dir);
+            break;
+        }
+    }
+    return err;
+}
+
+static int set_system_de_policy_on(const std::string& dir) {
+    std::string ref_filename = std::string("/data") + fscrypt_key_ref;
+    std::string policy;
+    if (!android::base::ReadFileToString(ref_filename, &policy)) {
+        LOG(ERROR) << "Unable to read system policy to set on " << dir;
+        return -1;
+    }
+
+    auto type_filename = std::string("/data") + fscrypt_key_mode;
+    std::string modestring;
+    if (!android::base::ReadFileToString(type_filename, &modestring)) {
+        LOG(ERROR) << "Cannot read mode";
+    }
+
+    std::vector<std::string> modes = android::base::Split(modestring, ":");
+
+    if (modes.size() < 1 || modes.size() > 2) {
+        LOG(ERROR) << "Invalid encryption mode string: " << modestring;
+        return -1;
+    }
+
+    LOG(INFO) << "Setting policy on " << dir;
+    int result =
+            fscrypt_policy_ensure(dir.c_str(), policy.c_str(), policy.length(), modes[0].c_str(),
+                                  modes.size() >= 2 ? modes[1].c_str() : "aes-256-cts");
+    if (result) {
+        LOG(ERROR) << android::base::StringPrintf("Setting %02x%02x%02x%02x policy on %s failed!",
+                                                  policy[0], policy[1], policy[2], policy[3],
+                                                  dir.c_str());
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/init/fscrypt_init_extensions.h b/init/fscrypt_init_extensions.h
new file mode 100644
index 0000000..2163ef6
--- /dev/null
+++ b/init/fscrypt_init_extensions.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#pragma once
+
+#include <string>
+
+int fscrypt_install_keyring();
+int fscrypt_set_directory_policy(const std::string& dir);
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index dce3eda..b2402b3 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -30,7 +30,6 @@
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
-#include <json/json.h>
 
 #include "action.h"
 #include "action_manager.h"
@@ -38,6 +37,7 @@
 #include "check_builtins.h"
 #include "host_import_parser.h"
 #include "host_init_stubs.h"
+#include "interface_utils.h"
 #include "parser.h"
 #include "result.h"
 #include "service.h"
@@ -132,35 +132,6 @@
     return nullptr;
 }
 
-static std::optional<android::init::InterfaceInheritanceHierarchyMap>
-ReadInterfaceInheritanceHierarchy(const std::string& interface_inheritance_hierarchy_file) {
-    if (interface_inheritance_hierarchy_file.empty()) {
-        LOG(WARNING) << "Missing an interface inheritance hierarchy file.";
-        return {};
-    }
-
-    Json::Value root;
-    Json::Reader reader;
-    std::ifstream stream(interface_inheritance_hierarchy_file);
-    if (!reader.parse(stream, root)) {
-        LOG(ERROR) << "Failed to read interface inheritance hierarchy file: "
-                   << interface_inheritance_hierarchy_file << "\n"
-                   << reader.getFormattedErrorMessages();
-        return {};
-    }
-
-    android::init::InterfaceInheritanceHierarchyMap result;
-    for (const Json::Value& entry : root) {
-        std::set<std::string> inherited_interfaces;
-        for (const Json::Value& intf : entry["inheritedInterfaces"]) {
-            inherited_interfaces.insert(intf.asString());
-        }
-        result[entry["interface"].asString()] = inherited_interfaces;
-    }
-
-    return result;
-}
-
 namespace android {
 namespace init {
 
@@ -222,16 +193,21 @@
         return EXIT_FAILURE;
     }
 
+    auto interface_inheritance_hierarchy_map =
+            ReadInterfaceInheritanceHierarchy(interface_inheritance_hierarchy_file);
+    if (!interface_inheritance_hierarchy_map) {
+        LOG(ERROR) << interface_inheritance_hierarchy_map.error();
+        return EXIT_FAILURE;
+    }
+    SetKnownInterfaces(*interface_inheritance_hierarchy_map);
+
     const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
     Action::set_function_map(&function_map);
     ActionManager& am = ActionManager::GetInstance();
     ServiceList& sl = ServiceList::GetInstance();
     Parser parser;
-    parser.AddSectionParser(
-            "service",
-            std::make_unique<ServiceParser>(
-                    &sl, nullptr,
-                    ReadInterfaceInheritanceHierarchy(interface_inheritance_hierarchy_file)));
+    parser.AddSectionParser("service", std::make_unique<ServiceParser>(
+                                               &sl, nullptr, *interface_inheritance_hierarchy_map));
     parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, nullptr));
     parser.AddSectionParser("import", std::make_unique<HostImportParser>());
 
@@ -239,7 +215,7 @@
         LOG(ERROR) << "Failed to open init rc script '" << *argv << "'";
         return EXIT_FAILURE;
     }
-    size_t failures = parser.parse_error_count() + am.CheckAllCommands();
+    size_t failures = parser.parse_error_count() + am.CheckAllCommands() + sl.CheckAllCommands();
     if (failures > 0) {
         LOG(ERROR) << "Failed to parse init script '" << *argv << "' with " << failures
                    << " errors";
diff --git a/init/init.cpp b/init/init.cpp
index 18fb0c3..ce898de 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -305,9 +305,6 @@
         process_cmdline = "unknown process";
     }
 
-    LOG(INFO) << "Received control message '" << msg << "' for '" << name << "' from pid: " << pid
-              << " (" << process_cmdline << ")";
-
     const ControlMessageFunction& function = it->second;
 
     Service* svc = nullptr;
@@ -320,20 +317,25 @@
             svc = ServiceList::GetInstance().FindInterface(name);
             break;
         default:
-            LOG(ERROR) << "Invalid function target from static map key '" << msg << "': "
+            LOG(ERROR) << "Invalid function target from static map key ctl." << msg << ": "
                        << static_cast<std::underlying_type<ControlTarget>::type>(function.target);
             return false;
     }
 
     if (svc == nullptr) {
-        LOG(ERROR) << "Could not find '" << name << "' for ctl." << msg;
+        LOG(ERROR) << "Control message: Could not find '" << name << "' for ctl." << msg
+                   << " from pid: " << pid << " (" << process_cmdline << ")";
         return false;
     }
 
     if (auto result = function.action(svc); !result) {
-        LOG(ERROR) << "Could not ctl." << msg << " for '" << name << "': " << result.error();
+        LOG(ERROR) << "Control message: Could not ctl." << msg << " for '" << name
+                   << "' from pid: " << pid << " (" << process_cmdline << "): " << result.error();
         return false;
     }
+
+    LOG(INFO) << "Control message: Processed ctl." << msg << " for '" << name
+              << "' from pid: " << pid << " (" << process_cmdline << ")";
     return true;
 }
 
@@ -715,6 +717,7 @@
 
     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
 
+    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
     am.QueueEventTrigger("early-init");
 
     // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
@@ -722,7 +725,6 @@
     // ... so that we can start queuing up actions that require stuff from /dev.
     am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
     am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
-    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
     Keychords keychords;
     am.QueueBuiltinAction(
             [&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
diff --git a/init/interface_utils.cpp b/init/interface_utils.cpp
new file mode 100644
index 0000000..a54860f
--- /dev/null
+++ b/init/interface_utils.cpp
@@ -0,0 +1,149 @@
+/*
+ * 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 "interface_utils.h"
+
+#include <fstream>
+#include <sstream>
+
+#include <android-base/strings.h>
+#include <hidl-util/FqInstance.h>
+#include <json/json.h>
+
+using android::FqInstance;
+using android::FQName;
+using android::base::Error;
+
+namespace android {
+namespace init {
+
+namespace {
+
+std::string FQNamesToString(const std::set<FQName>& fqnames) {
+    std::set<std::string> fqname_strings;
+    for (const FQName& fqname : fqnames) {
+        fqname_strings.insert(fqname.string());
+    }
+    return android::base::Join(fqname_strings, " ");
+}
+
+}  // namespace
+
+Result<InterfaceInheritanceHierarchyMap> ReadInterfaceInheritanceHierarchy(
+        const std::string& path) {
+    Json::Value root;
+    Json::Reader reader;
+    std::ifstream stream(path);
+    if (!reader.parse(stream, root)) {
+        return Error() << "Failed to read interface inheritance hierarchy file: " << path << "\n"
+                       << reader.getFormattedErrorMessages();
+    }
+
+    InterfaceInheritanceHierarchyMap result;
+    for (const Json::Value& entry : root) {
+        std::set<FQName> inherited_interfaces;
+        for (const Json::Value& intf : entry["inheritedInterfaces"]) {
+            FQName fqname;
+            if (!fqname.setTo(intf.asString())) {
+                return Error() << "Unable to parse interface '" << intf.asString() << "'";
+            }
+            inherited_interfaces.insert(fqname);
+        }
+        std::string intf_string = entry["interface"].asString();
+        FQName fqname;
+        if (!fqname.setTo(intf_string)) {
+            return Error() << "Unable to parse interface '" << intf_string << "'";
+        }
+        result[fqname] = inherited_interfaces;
+    }
+
+    return result;
+}
+
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<std::string>& instances,
+                                                const InterfaceInheritanceHierarchyMap& hierarchy) {
+    std::set<FQName> interface_fqnames;
+    for (const std::string& instance : instances) {
+        FqInstance fqinstance;
+        if (!fqinstance.setTo(instance)) {
+            return Error() << "Unable to parse interface instance '" << instance << "'";
+        }
+        interface_fqnames.insert(fqinstance.getFqName());
+    }
+    return CheckInterfaceInheritanceHierarchy(interface_fqnames, hierarchy);
+}
+
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<FQName>& interfaces,
+                                                const InterfaceInheritanceHierarchyMap& hierarchy) {
+    std::ostringstream error_stream;
+    for (const FQName& intf : interfaces) {
+        if (hierarchy.count(intf) == 0) {
+            error_stream << "\nInterface is not in the known set of hidl_interfaces: '"
+                         << intf.string()
+                         << "'. Please ensure the interface is spelled correctly and built "
+                         << "by a hidl_interface target.";
+            continue;
+        }
+        const std::set<FQName>& required_interfaces = hierarchy.at(intf);
+        std::set<FQName> diff;
+        std::set_difference(required_interfaces.begin(), required_interfaces.end(),
+                            interfaces.begin(), interfaces.end(),
+                            std::inserter(diff, diff.begin()));
+        if (!diff.empty()) {
+            error_stream << "\nInterface '" << intf.string() << "' requires its full inheritance "
+                         << "hierarchy to be listed in this init_rc file. Missing "
+                         << "interfaces: [" << FQNamesToString(diff) << "]";
+        }
+    }
+    const std::string& errors = error_stream.str();
+    if (!errors.empty()) {
+        return Error() << errors;
+    }
+
+    return {};
+}
+
+std::optional<std::set<FQName>> known_interfaces;
+
+void SetKnownInterfaces(const InterfaceInheritanceHierarchyMap& hierarchy) {
+    known_interfaces = std::set<FQName>();
+    for (const auto& [intf, inherited_interfaces] : hierarchy) {
+        known_interfaces->insert(intf);
+    }
+}
+
+Result<void> IsKnownInterface(const std::string& instance) {
+    FqInstance fqinstance;
+    if (!fqinstance.setTo(instance)) {
+        return Error() << "Unable to parse interface instance '" << instance << "'";
+    }
+    return IsKnownInterface(fqinstance.getFqName());
+}
+
+Result<void> IsKnownInterface(const FQName& intf) {
+    if (!known_interfaces) {
+        return Error() << "No known interfaces have been loaded.";
+    }
+    if (known_interfaces->count(intf) == 0) {
+        return Error() << "Interface is not in the known set of hidl_interfaces: '" << intf.string()
+                       << "'. Please ensure the interface is spelled correctly and built "
+                       << "by a hidl_interface target.";
+    }
+    return {};
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/interface_utils.h b/init/interface_utils.h
new file mode 100644
index 0000000..bd0c104
--- /dev/null
+++ b/init/interface_utils.h
@@ -0,0 +1,53 @@
+/*
+ * 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 <map>
+#include <set>
+#include <string>
+
+#include <hidl-util/FQName.h>
+
+#include "result.h"
+
+namespace android {
+namespace init {
+
+using InterfaceInheritanceHierarchyMap = std::map<android::FQName, std::set<android::FQName>>;
+
+// Reads the HIDL interface inheritance hierarchy JSON file at the given path.
+Result<InterfaceInheritanceHierarchyMap> ReadInterfaceInheritanceHierarchy(const std::string& path);
+
+// For the given set of interfaces / interface instances, checks that each
+// interface's hierarchy of inherited interfaces is also included in the given
+// interface set. Uses the provided hierarchy data.
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<std::string>& instances,
+                                                const InterfaceInheritanceHierarchyMap& hierarchy);
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<android::FQName>& interfaces,
+                                                const InterfaceInheritanceHierarchyMap& hierarchy);
+
+// Saves the set of known interfaces using the provided HIDL interface
+// inheritance hierarchy.
+void SetKnownInterfaces(const InterfaceInheritanceHierarchyMap& hierarchy);
+
+// Checks if the provided interface is in the set of known interfaces. Returns
+// an empty Result if present, otherwise an Error.
+Result<void> IsKnownInterface(const std::string& instance);
+Result<void> IsKnownInterface(const FQName& intf);
+
+}  // namespace init
+}  // namespace android
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 17622a3..3408ff3 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -39,7 +39,6 @@
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
 #include <sys/_system_properties.h>
 
-#include <atomic>
 #include <map>
 #include <memory>
 #include <mutex>
@@ -53,7 +52,6 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <android-base/unique_fd.h>
 #include <property_info_parser/property_info_parser.h>
 #include <property_info_serializer/property_info_serializer.h>
 #include <selinux/android.h>
@@ -61,6 +59,7 @@
 #include <selinux/selinux.h>
 
 #include "debug_ramdisk.h"
+#include "epoll.h"
 #include "init.h"
 #include "persistent_properties.h"
 #include "property_type.h"
@@ -77,7 +76,6 @@
 using android::base::StringPrintf;
 using android::base::Timer;
 using android::base::Trim;
-using android::base::unique_fd;
 using android::base::WriteStringToFile;
 using android::properties::BuildTrie;
 using android::properties::ParsePropertyInfoFile;
@@ -1004,42 +1002,5 @@
     }
 }
 
-Result<int> CallFunctionAndHandlePropertiesImpl(const std::function<int()>& f) {
-    unique_fd reader;
-    unique_fd writer;
-    if (!Socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, &reader, &writer)) {
-        return ErrnoError() << "Could not create socket pair";
-    }
-
-    int result = 0;
-    std::atomic<bool> end = false;
-    auto thread = std::thread{[&f, &result, &end, &writer] {
-        result = f();
-        end = true;
-        send(writer, "1", 1, 0);
-    }};
-
-    Epoll epoll;
-    if (auto result = epoll.Open(); !result) {
-        return Error() << "Could not create epoll: " << result.error();
-    }
-    if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
-        return Error() << "Could not register epoll handler for property fd: " << result.error();
-    }
-
-    // No-op function, just used to break from loop.
-    if (auto result = epoll.RegisterHandler(reader, [] {}); !result) {
-        return Error() << "Could not register epoll handler for ending thread:" << result.error();
-    }
-
-    while (!end) {
-        epoll.Wait({});
-    }
-
-    thread.join();
-
-    return result;
-}
-
 }  // namespace init
 }  // namespace android
diff --git a/init/property_service.h b/init/property_service.h
index dc47b4d..7f9f844 100644
--- a/init/property_service.h
+++ b/init/property_service.h
@@ -18,11 +18,9 @@
 
 #include <sys/socket.h>
 
-#include <functional>
 #include <string>
 
 #include "epoll.h"
-#include "result.h"
 
 namespace android {
 namespace init {
@@ -39,13 +37,5 @@
 void load_persist_props();
 void StartPropertyService(Epoll* epoll);
 
-template <typename F, typename... Args>
-Result<int> CallFunctionAndHandleProperties(F&& f, Args&&... args) {
-    Result<int> CallFunctionAndHandlePropertiesImpl(const std::function<int()>& f);
-
-    auto func = [&] { return f(args...); };
-    return CallFunctionAndHandlePropertiesImpl(func);
-}
-
 }  // namespace init
 }  // namespace android
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index c038aff..0f4cd0d 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -56,6 +56,11 @@
 }
 
 TEST(property_service, non_utf8_value) {
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Skipping test, must be run as root.";
+        return;
+    }
+
     ASSERT_TRUE(SetProperty("property_service_utf8_test", "base_success"));
     EXPECT_FALSE(SetProperty("property_service_utf8_test", "\x80"));
     EXPECT_FALSE(SetProperty("property_service_utf8_test", "\xC2\x01"));
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 143cdfd..6842820 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -51,6 +51,8 @@
 
 #include <android/api-level.h>
 #include <fcntl.h>
+#include <linux/audit.h>
+#include <linux/netlink.h>
 #include <stdlib.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -446,6 +448,35 @@
     }
 }
 
+constexpr size_t kKlogMessageSize = 1024;
+
+void SelinuxAvcLog(char* buf, size_t buf_len) {
+    CHECK_GT(buf_len, 0u);
+
+    size_t str_len = strnlen(buf, buf_len);
+    // trim newline at end of string
+    if (buf[str_len - 1] == '\n') {
+        buf[str_len - 1] = '\0';
+    }
+
+    struct NetlinkMessage {
+        nlmsghdr hdr;
+        char buf[kKlogMessageSize];
+    } request = {};
+
+    request.hdr.nlmsg_flags = NLM_F_REQUEST;
+    request.hdr.nlmsg_type = AUDIT_USER_AVC;
+    request.hdr.nlmsg_len = sizeof(request);
+    strlcpy(request.buf, buf, sizeof(request.buf));
+
+    auto fd = unique_fd{socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_AUDIT)};
+    if (!fd.ok()) {
+        return;
+    }
+
+    TEMP_FAILURE_RETRY(send(fd, &request, sizeof(request), 0));
+}
+
 }  // namespace
 
 // The files and directories that were created before initial sepolicy load or
@@ -478,12 +509,19 @@
     } else if (type == SELINUX_INFO) {
         severity = android::base::INFO;
     }
-    char buf[1024];
+    char buf[kKlogMessageSize];
     va_list ap;
     va_start(ap, fmt);
-    vsnprintf(buf, sizeof(buf), fmt, ap);
+    int length_written = vsnprintf(buf, sizeof(buf), fmt, ap);
     va_end(ap);
-    android::base::KernelLogger(android::base::MAIN, severity, "selinux", nullptr, 0, buf);
+    if (length_written <= 0) {
+        return 0;
+    }
+    if (type == SELINUX_AVC) {
+        SelinuxAvcLog(buf, sizeof(buf));
+    } else {
+        android::base::KernelLogger(android::base::MAIN, severity, "selinux", nullptr, 0, buf);
+    }
     return 0;
 }
 
@@ -523,6 +561,7 @@
 
 // This function initializes SELinux then execs init to run in the init SELinux context.
 int SetupSelinux(char** argv) {
+    SetStdioToDevNull(argv);
     InitKernelLogging(argv);
 
     if (REBOOT_BOOTLOADER_ON_PANIC) {
diff --git a/init/service.h b/init/service.h
index 6f79faa..ccefc8e 100644
--- a/init/service.h
+++ b/init/service.h
@@ -97,6 +97,7 @@
     void AddReapCallback(std::function<void(const siginfo_t& siginfo)> callback) {
         reap_callbacks_.emplace_back(std::move(callback));
     }
+    size_t CheckAllCommands() const { return onrestart_.CheckAllCommands(); }
 
     static bool is_exec_service_running() { return is_exec_service_running_; }
 
diff --git a/init/service_list.cpp b/init/service_list.cpp
index 3a48183..c51a9cf 100644
--- a/init/service_list.cpp
+++ b/init/service_list.cpp
@@ -28,6 +28,14 @@
     return instance;
 }
 
+size_t ServiceList::CheckAllCommands() {
+    size_t failures = 0;
+    for (const auto& service : services_) {
+        failures += service->CheckAllCommands();
+    }
+    return failures;
+}
+
 void ServiceList::AddService(std::unique_ptr<Service> service) {
     services_.emplace_back(std::move(service));
 }
diff --git a/init/service_list.h b/init/service_list.h
index 2136a21..ee2c702 100644
--- a/init/service_list.h
+++ b/init/service_list.h
@@ -30,6 +30,7 @@
 
     // Exposed for testing
     ServiceList();
+    size_t CheckAllCommands();
 
     void AddService(std::unique_ptr<Service> service);
     void RemoveService(const Service& svc);
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index e45e804..dd552fb 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -568,33 +568,10 @@
     }
 
     if (interface_inheritance_hierarchy_) {
-        std::set<std::string> interface_names;
-        for (const std::string& intf : service_->interfaces()) {
-            interface_names.insert(Split(intf, "/")[0]);
-        }
-        std::ostringstream error_stream;
-        for (const std::string& intf : interface_names) {
-            if (interface_inheritance_hierarchy_->count(intf) == 0) {
-                error_stream << "\nInterface is not in the known set of hidl_interfaces: '" << intf
-                             << "'. Please ensure the interface is spelled correctly and built "
-                             << "by a hidl_interface target.";
-                continue;
-            }
-            const std::set<std::string>& required_interfaces =
-                    (*interface_inheritance_hierarchy_)[intf];
-            std::set<std::string> diff;
-            std::set_difference(required_interfaces.begin(), required_interfaces.end(),
-                                interface_names.begin(), interface_names.end(),
-                                std::inserter(diff, diff.begin()));
-            if (!diff.empty()) {
-                error_stream << "\nInterface '" << intf << "' requires its full inheritance "
-                             << "hierarchy to be listed in this init_rc file. Missing "
-                             << "interfaces: [" << base::Join(diff, " ") << "]";
-            }
-        }
-        const std::string& errors = error_stream.str();
-        if (!errors.empty()) {
-            return Error() << errors;
+        if (const auto& check_hierarchy_result = CheckInterfaceInheritanceHierarchy(
+                    service_->interfaces(), *interface_inheritance_hierarchy_);
+            !check_hierarchy_result) {
+            return Error() << check_hierarchy_result.error();
         }
     }
 
diff --git a/init/service_parser.h b/init/service_parser.h
index 98ab15a..4729874 100644
--- a/init/service_parser.h
+++ b/init/service_parser.h
@@ -18,6 +18,7 @@
 
 #include <vector>
 
+#include "interface_utils.h"
 #include "parser.h"
 #include "service.h"
 #include "service_list.h"
@@ -26,8 +27,6 @@
 namespace android {
 namespace init {
 
-using InterfaceInheritanceHierarchyMap = std::map<std::string, std::set<std::string>>;
-
 class ServiceParser : public SectionParser {
   public:
     ServiceParser(
diff --git a/init/subcontext_test.cpp b/init/subcontext_test.cpp
index ae89c38..dcbff82 100644
--- a/init/subcontext_test.cpp
+++ b/init/subcontext_test.cpp
@@ -43,7 +43,7 @@
 template <typename F>
 void RunTest(F&& test_function) {
     if (getuid() != 0) {
-        GTEST_LOG_(INFO) << "Skipping test, must be run as root.";
+        GTEST_SKIP() << "Skipping test, must be run as root.";
         return;
     }
 
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 8b2cf62..cffc1b9 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -17,12 +17,15 @@
 #include "ueventd.h"
 
 #include <ctype.h>
+#include <dirent.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
 #include <sys/wait.h>
+#include <unistd.h>
 
 #include <set>
 #include <thread>
@@ -121,8 +124,9 @@
     void UeventHandlerMain(unsigned int process_num, unsigned int total_processes);
     void RegenerateUevents();
     void ForkSubProcesses();
-    void DoRestoreCon();
     void WaitForSubProcesses();
+    void RestoreConHandler(unsigned int process_num, unsigned int total_processes);
+    void GenerateRestoreCon(const std::string& directory);
 
     UeventListener& uevent_listener_;
     std::vector<std::unique_ptr<UeventHandler>>& uevent_handlers_;
@@ -131,6 +135,8 @@
     std::vector<Uevent> uevent_queue_;
 
     std::set<pid_t> subprocess_pids_;
+
+    std::vector<std::string> restorecon_queue_;
 };
 
 void ColdBoot::UeventHandlerMain(unsigned int process_num, unsigned int total_processes) {
@@ -141,9 +147,38 @@
             uevent_handler->HandleUevent(uevent);
         }
     }
+}
+
+void ColdBoot::RestoreConHandler(unsigned int process_num, unsigned int total_processes) {
+    for (unsigned int i = process_num; i < restorecon_queue_.size(); i += total_processes) {
+        auto& dir = restorecon_queue_[i];
+
+        selinux_android_restorecon(dir.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE);
+    }
     _exit(EXIT_SUCCESS);
 }
 
+void ColdBoot::GenerateRestoreCon(const std::string& directory) {
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(directory.c_str()), &closedir);
+
+    if (!dir) return;
+
+    struct dirent* dent;
+    while ((dent = readdir(dir.get())) != NULL) {
+        if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) continue;
+
+        struct stat st;
+        if (fstatat(dirfd(dir.get()), dent->d_name, &st, 0) == -1) continue;
+
+        if (S_ISDIR(st.st_mode)) {
+            std::string fullpath = directory + "/" + dent->d_name;
+            if (fullpath != "/sys/devices") {
+                restorecon_queue_.emplace_back(fullpath);
+            }
+        }
+    }
+}
+
 void ColdBoot::RegenerateUevents() {
     uevent_listener_.RegenerateUevents([this](const Uevent& uevent) {
         uevent_queue_.emplace_back(uevent);
@@ -160,16 +195,13 @@
 
         if (pid == 0) {
             UeventHandlerMain(i, num_handler_subprocesses_);
+            RestoreConHandler(i, num_handler_subprocesses_);
         }
 
         subprocess_pids_.emplace(pid);
     }
 }
 
-void ColdBoot::DoRestoreCon() {
-    selinux_android_restorecon("/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
-}
-
 void ColdBoot::WaitForSubProcesses() {
     // Treat subprocesses that crash or get stuck the same as if ueventd itself has crashed or gets
     // stuck.
@@ -208,9 +240,13 @@
 
     RegenerateUevents();
 
-    ForkSubProcesses();
+    selinux_android_restorecon("/sys", 0);
+    selinux_android_restorecon("/sys/devices", 0);
+    GenerateRestoreCon("/sys");
+    // takes long time for /sys/devices, parallelize it
+    GenerateRestoreCon("/sys/devices");
 
-    DoRestoreCon();
+    ForkSubProcesses();
 
     WaitForSubProcesses();
 
diff --git a/init/ueventd_test.cpp b/init/ueventd_test.cpp
index bfdc28e..2d7d2f8 100644
--- a/init/ueventd_test.cpp
+++ b/init/ueventd_test.cpp
@@ -68,7 +68,7 @@
 
 TEST(ueventd, setegid_IsPerThread) {
     if (getuid() != 0) {
-        GTEST_LOG_(INFO) << "Skipping test, must be run as root.";
+        GTEST_SKIP() << "Skipping test, must be run as root.";
         return;
     }
 
@@ -92,11 +92,11 @@
 
 TEST(ueventd, setfscreatecon_IsPerThread) {
     if (getuid() != 0) {
-        GTEST_LOG_(INFO) << "Skipping test, must be run as root.";
+        GTEST_SKIP() << "Skipping test, must be run as root.";
         return;
     }
     if (!is_selinux_enabled() || security_getenforce() == 1) {
-        GTEST_LOG_(INFO) << "Skipping test, SELinux must be enabled and in permissive mode.";
+        GTEST_SKIP() << "Skipping test, SELinux must be enabled and in permissive mode.";
         return;
     }
 
@@ -127,7 +127,7 @@
 
 TEST(ueventd, selabel_lookup_MultiThreaded) {
     if (getuid() != 0) {
-        GTEST_LOG_(INFO) << "Skipping test, must be run as root.";
+        GTEST_SKIP() << "Skipping test, must be run as root.";
         return;
     }
 
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 319a73a..b9420d4 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -67,8 +67,6 @@
         "native_handle.cpp",
         "record_stream.cpp",
         "sockets.cpp",
-        "strdup16to8.cpp",
-        "strdup8to16.cpp",
         "strlcpy.c",
         "threads.cpp",
     ],
diff --git a/libcutils/include/cutils/jstring.h b/libcutils/include/cutils/jstring.h
deleted file mode 100644
index a342608..0000000
--- a/libcutils/include/cutils/jstring.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __CUTILS_STRING16_H
-#define __CUTILS_STRING16_H
-
-#include <stdint.h>
-#include <stddef.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#if __STDC_VERSION__ < 201112L && __cplusplus < 201103L
-  typedef uint16_t char16_t;
-#endif
-  // otherwise char16_t is a keyword with the right semantics
-
-extern char * strndup16to8 (const char16_t* s, size_t n);
-extern size_t strnlen16to8 (const char16_t* s, size_t n);
-extern char * strncpy16to8 (char *dest, const char16_t*s, size_t n);
-
-extern char16_t * strdup8to16 (const char* s, size_t *out_len);
-extern size_t strlen8to16 (const char* utf8Str);
-extern char16_t * strcpy8to16 (char16_t *dest, const char*s, size_t *out_len);
-extern char16_t * strcpylen8to16 (char16_t *dest, const char*s, int length,
-    size_t *out_len);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __CUTILS_STRING16_H */
diff --git a/libcutils/include_vndk/cutils/jstring.h b/libcutils/include_vndk/cutils/jstring.h
deleted file mode 120000
index f3fd546..0000000
--- a/libcutils/include_vndk/cutils/jstring.h
+++ /dev/null
@@ -1 +0,0 @@
-../../include/cutils/jstring.h
\ No newline at end of file
diff --git a/libcutils/strdup16to8.cpp b/libcutils/strdup16to8.cpp
deleted file mode 100644
index d89181e..0000000
--- a/libcutils/strdup16to8.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/* libs/cutils/strdup16to8.c
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
-
-#include <cutils/jstring.h>
-
-#include <assert.h>
-#include <limits.h>  /* for SIZE_MAX */
-#include <stdlib.h>
-
-
-/**
- * Given a UTF-16 string, compute the length of the corresponding UTF-8
- * string in bytes.
- */
-extern size_t strnlen16to8(const char16_t* utf16Str, size_t len)
-{
-    size_t utf8Len = 0;
-
-    /* A small note on integer overflow. The result can
-     * potentially be as big as 3*len, which will overflow
-     * for len > SIZE_MAX/3.
-     *
-     * Moreover, the result of a strnlen16to8 is typically used
-     * to allocate a destination buffer to strncpy16to8 which
-     * requires one more byte to terminate the UTF-8 copy, and
-     * this is generally done by careless users by incrementing
-     * the result without checking for integer overflows, e.g.:
-     *
-     *   dst = malloc(strnlen16to8(utf16,len)+1)
-     *
-     * Due to this, the following code will try to detect
-     * overflows, and never return more than (SIZE_MAX-1)
-     * when it detects one. A careless user will try to malloc
-     * SIZE_MAX bytes, which will return NULL which can at least
-     * be detected appropriately.
-     *
-     * As far as I know, this function is only used by strndup16(),
-     * but better be safe than sorry.
-     */
-
-    /* Fast path for the usual case where 3*len is < SIZE_MAX-1.
-     */
-    if (len < (SIZE_MAX-1)/3) {
-        while (len != 0) {
-            len--;
-            unsigned int uic = *utf16Str++;
-
-            if (uic > 0x07ff)
-                utf8Len += 3;
-            else if (uic > 0x7f || uic == 0)
-                utf8Len += 2;
-            else
-                utf8Len++;
-        }
-        return utf8Len;
-    }
-
-    /* The slower but paranoid version */
-    while (len != 0) {
-        len--;
-        unsigned int  uic     = *utf16Str++;
-        size_t        utf8Cur = utf8Len;
-
-        if (uic > 0x07ff)
-            utf8Len += 3;
-        else if (uic > 0x7f || uic == 0)
-            utf8Len += 2;
-        else
-            utf8Len++;
-
-        if (utf8Len < utf8Cur) /* overflow detected */
-            return SIZE_MAX-1;
-    }
-
-    /* don't return SIZE_MAX to avoid common user bug */
-    if (utf8Len == SIZE_MAX)
-        utf8Len = SIZE_MAX-1;
-
-    return utf8Len;
-}
-
-
-/**
- * Convert a Java-Style UTF-16 string + length to a JNI-Style UTF-8 string.
- *
- * This basically means: embedded \0's in the UTF-16 string are encoded
- * as "0xc0 0x80"
- *
- * Make sure you allocate "utf8Str" with the result of strlen16to8() + 1,
- * not just "len".
- *
- * Please note, a terminated \0 is always added, so your result will always
- * be "strlen16to8() + 1" bytes long.
- */
-extern char* strncpy16to8(char* utf8Str, const char16_t* utf16Str, size_t len)
-{
-    char* utf8cur = utf8Str;
-
-    /* Note on overflows: We assume the user did check the result of
-     * strnlen16to8() properly or at a minimum checked the result of
-     * its malloc(SIZE_MAX) in case of overflow.
-     */
-    while (len != 0) {
-        len--;
-        unsigned int uic = *utf16Str++;
-
-        if (uic > 0x07ff) {
-            *utf8cur++ = (uic >> 12) | 0xe0;
-            *utf8cur++ = ((uic >> 6) & 0x3f) | 0x80;
-            *utf8cur++ = (uic & 0x3f) | 0x80;
-        } else if (uic > 0x7f || uic == 0) {
-            *utf8cur++ = (uic >> 6) | 0xc0;
-            *utf8cur++ = (uic & 0x3f) | 0x80;
-        } else {
-            *utf8cur++ = uic;
-
-            if (uic == 0) {
-                break;
-            }
-        }
-    }
-
-   *utf8cur = '\0';
-
-   return utf8Str;
-}
-
-/**
- * Convert a UTF-16 string to UTF-8.
- *
- */
-char * strndup16to8 (const char16_t* s, size_t n)
-{
-    if (s == NULL) {
-        return NULL;
-    }
-
-    size_t len = strnlen16to8(s, n);
-
-    /* We are paranoid, and we check for SIZE_MAX-1
-     * too since it is an overflow value for our
-     * strnlen16to8 implementation.
-     */
-    if (len >= SIZE_MAX-1)
-        return NULL;
-
-    char* ret = static_cast<char*>(malloc(len + 1));
-    if (ret == NULL)
-        return NULL;
-
-    strncpy16to8 (ret, s, n);
-
-    return ret;
-}
diff --git a/libcutils/strdup8to16.cpp b/libcutils/strdup8to16.cpp
deleted file mode 100644
index d1e51b9..0000000
--- a/libcutils/strdup8to16.cpp
+++ /dev/null
@@ -1,215 +0,0 @@
-/* libs/cutils/strdup8to16.c
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
-
-#include <cutils/jstring.h>
-
-#include <assert.h>
-#include <limits.h>
-#include <stdlib.h>
-
-/* See http://www.unicode.org/reports/tr22/ for discussion
- * on invalid sequences
- */
-
-#define UTF16_REPLACEMENT_CHAR 0xfffd
-
-/* Clever trick from Dianne that returns 1-4 depending on leading bit sequence*/
-#define UTF8_SEQ_LENGTH(ch) (((0xe5000000 >> (((ch) >> 3) & 0x1e)) & 3) + 1)
-
-/* note: macro expands to multiple lines */
-#define UTF8_SHIFT_AND_MASK(unicode, byte)  \
-            (unicode)<<=6; (unicode) |= (0x3f & (byte));
-
-#define UNICODE_UPPER_LIMIT 0x10fffd    
-
-/**
- * out_len is an out parameter (which may not be null) containing the
- * length of the UTF-16 string (which may contain embedded \0's)
- */
-
-extern char16_t * strdup8to16 (const char* s, size_t *out_len)
-{
-    char16_t *ret;
-    size_t len;
-
-    if (s == NULL) return NULL;
-
-    len = strlen8to16(s);
-
-    // fail on overflow
-    if (len && SIZE_MAX/len < sizeof(char16_t))
-        return NULL;
-
-    // no plus-one here. UTF-16 strings are not null terminated
-    ret = (char16_t *) malloc (sizeof(char16_t) * len);
-
-    return strcpy8to16 (ret, s, out_len);
-}
-
-/**
- * Like "strlen", but for strings encoded with Java's modified UTF-8.
- *
- * The value returned is the number of UTF-16 characters required
- * to represent this string.
- */
-extern size_t strlen8to16 (const char* utf8Str)
-{
-    size_t len = 0;
-    int ic;
-    int expected = 0;
-
-    while ((ic = *utf8Str++) != '\0') {
-        /* bytes that start 0? or 11 are lead bytes and count as characters.*/
-        /* bytes that start 10 are extention bytes and are not counted */
-         
-        if ((ic & 0xc0) == 0x80) {
-            /* count the 0x80 extention bytes. if we have more than
-             * expected, then start counting them because strcpy8to16
-             * will insert UTF16_REPLACEMENT_CHAR's
-             */
-            expected--;
-            if (expected < 0) {
-                len++;
-            }
-        } else {
-            len++;
-            expected = UTF8_SEQ_LENGTH(ic) - 1;
-
-            /* this will result in a surrogate pair */
-            if (expected == 3) {
-                len++;
-            }
-        }
-    }
-
-    return len;
-}
-
-
-
-/*
- * Retrieve the next UTF-32 character from a UTF-8 string.
- *
- * Stops at inner \0's
- *
- * Returns UTF16_REPLACEMENT_CHAR if an invalid sequence is encountered
- *
- * Advances "*pUtf8Ptr" to the start of the next character.
- */
-static inline uint32_t getUtf32FromUtf8(const char** pUtf8Ptr)
-{
-    uint32_t ret;
-    int seq_len;
-    int i;
-
-    /* Mask for leader byte for lengths 1, 2, 3, and 4 respectively*/
-    static const unsigned char leaderMask[4] = {0xff, 0x1f, 0x0f, 0x07};
-
-    /* Bytes that start with bits "10" are not leading characters. */
-    if (((**pUtf8Ptr) & 0xc0) == 0x80) {
-        (*pUtf8Ptr)++;
-        return UTF16_REPLACEMENT_CHAR;
-    }
-
-    /* note we tolerate invalid leader 11111xxx here */    
-    seq_len = UTF8_SEQ_LENGTH(**pUtf8Ptr);
-
-    ret = (**pUtf8Ptr) & leaderMask [seq_len - 1];
-
-    if (**pUtf8Ptr == '\0') return ret;
-
-    (*pUtf8Ptr)++;
-    for (i = 1; i < seq_len ; i++, (*pUtf8Ptr)++) {
-        if ((**pUtf8Ptr) == '\0') return UTF16_REPLACEMENT_CHAR;
-        if (((**pUtf8Ptr) & 0xc0) != 0x80) return UTF16_REPLACEMENT_CHAR;
-
-        UTF8_SHIFT_AND_MASK(ret, **pUtf8Ptr);
-    }
-
-    return ret;
-}
-
-
-/**
- * out_len is an out parameter (which may not be null) containing the
- * length of the UTF-16 string (which may contain embedded \0's)
- */
-
-extern char16_t * strcpy8to16 (char16_t *utf16Str, const char*utf8Str, 
-                                       size_t *out_len)
-{   
-    char16_t *dest = utf16Str;
-
-    while (*utf8Str != '\0') {
-        uint32_t ret;
-
-        ret = getUtf32FromUtf8(&utf8Str);
-
-        if (ret <= 0xffff) {
-            *dest++ = (char16_t) ret;
-        } else if (ret <= UNICODE_UPPER_LIMIT)  {
-            /* Create surrogate pairs */
-            /* See http://en.wikipedia.org/wiki/UTF-16/UCS-2#Method_for_code_points_in_Plane_1.2C_Plane_2 */
-
-            *dest++ = 0xd800 | ((ret - 0x10000) >> 10);
-            *dest++ = 0xdc00 | ((ret - 0x10000) &  0x3ff);
-        } else {
-            *dest++ = UTF16_REPLACEMENT_CHAR;
-        }
-    }
-
-    *out_len = dest - utf16Str;
-
-    return utf16Str;
-}
-
-/**
- * length is the number of characters in the UTF-8 string.
- * out_len is an out parameter (which may not be null) containing the
- * length of the UTF-16 string (which may contain embedded \0's)
- */
-
-extern char16_t * strcpylen8to16 (char16_t *utf16Str, const char*utf8Str,
-                                       int length, size_t *out_len)
-{
-    /* TODO: Share more of this code with the method above. Only 2 lines changed. */
-    
-    char16_t *dest = utf16Str;
-
-    const char *end = utf8Str + length; /* This line */
-    while (utf8Str < end) {             /* and this line changed. */
-        uint32_t ret;
-
-        ret = getUtf32FromUtf8(&utf8Str);
-
-        if (ret <= 0xffff) {
-            *dest++ = (char16_t) ret;
-        } else if (ret <= UNICODE_UPPER_LIMIT)  {
-            /* Create surrogate pairs */
-            /* See http://en.wikipedia.org/wiki/UTF-16/UCS-2#Method_for_code_points_in_Plane_1.2C_Plane_2 */
-
-            *dest++ = 0xd800 | ((ret - 0x10000) >> 10);
-            *dest++ = 0xdc00 | ((ret - 0x10000) &  0x3ff);
-        } else {
-            *dest++ = UTF16_REPLACEMENT_CHAR;
-        }
-    }
-
-    *out_len = dest - utf16Str;
-
-    return utf16Str;
-}
diff --git a/liblog/Android.bp b/liblog/Android.bp
index 53d3ab3..c38d8cd 100644
--- a/liblog/Android.bp
+++ b/liblog/Android.bp
@@ -15,7 +15,6 @@
 //
 
 liblog_sources = [
-    "config_read.cpp",
     "config_write.cpp",
     "log_event_list.cpp",
     "log_event_write.cpp",
diff --git a/liblog/config_read.cpp b/liblog/config_read.cpp
deleted file mode 100644
index 3139f78..0000000
--- a/liblog/config_read.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#include <log/log_transport.h>
-
-#include "config_read.h"
-#include "logger.h"
-
-struct listnode __android_log_transport_read = {&__android_log_transport_read,
-                                                &__android_log_transport_read};
-struct listnode __android_log_persist_read = {&__android_log_persist_read,
-                                              &__android_log_persist_read};
-
-static void __android_log_add_transport(struct listnode* list,
-                                        struct android_log_transport_read* transport) {
-  uint32_t i;
-
-  /* Try to keep one functioning transport for each log buffer id */
-  for (i = LOG_ID_MIN; i < LOG_ID_MAX; i++) {
-    struct android_log_transport_read* transp;
-
-    if (list_empty(list)) {
-      if (!transport->available || ((*transport->available)(static_cast<log_id_t>(i)) >= 0)) {
-        list_add_tail(list, &transport->node);
-        return;
-      }
-    } else {
-      read_transport_for_each(transp, list) {
-        if (!transp->available) {
-          return;
-        }
-        if (((*transp->available)(static_cast<log_id_t>(i)) < 0) &&
-            (!transport->available || ((*transport->available)(static_cast<log_id_t>(i)) >= 0))) {
-          list_add_tail(list, &transport->node);
-          return;
-        }
-      }
-    }
-  }
-}
-
-void __android_log_config_read() {
-#if (FAKE_LOG_DEVICE == 0)
-  if ((__android_log_transport == LOGGER_DEFAULT) || (__android_log_transport & LOGGER_LOGD)) {
-    extern struct android_log_transport_read logdLoggerRead;
-    extern struct android_log_transport_read pmsgLoggerRead;
-
-    __android_log_add_transport(&__android_log_transport_read, &logdLoggerRead);
-    __android_log_add_transport(&__android_log_persist_read, &pmsgLoggerRead);
-  }
-#endif
-}
-
-void __android_log_config_read_close() {
-  struct android_log_transport_read* transport;
-  struct listnode* n;
-
-  read_transport_for_each_safe(transport, n, &__android_log_transport_read) {
-    list_remove(&transport->node);
-  }
-  read_transport_for_each_safe(transport, n, &__android_log_persist_read) {
-    list_remove(&transport->node);
-  }
-}
diff --git a/liblog/config_read.h b/liblog/config_read.h
deleted file mode 100644
index 212b8a0..0000000
--- a/liblog/config_read.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#pragma once
-
-#include <cutils/list.h>
-
-#include "log_portability.h"
-
-__BEGIN_DECLS
-
-extern struct listnode __android_log_transport_read;
-extern struct listnode __android_log_persist_read;
-
-#define read_transport_for_each(transp, transports)                           \
-  for ((transp) = node_to_item((transports)->next,                            \
-                               struct android_log_transport_read, node);      \
-       ((transp) != node_to_item((transports),                                \
-                                 struct android_log_transport_read, node)) && \
-       ((transp) != node_to_item((transp)->node.next,                         \
-                                 struct android_log_transport_read, node));   \
-       (transp) = node_to_item((transp)->node.next,                           \
-                               struct android_log_transport_read, node))
-
-#define read_transport_for_each_safe(transp, n, transports)                   \
-  for ((transp) = node_to_item((transports)->next,                            \
-                               struct android_log_transport_read, node),      \
-      (n) = (transp)->node.next;                                              \
-       ((transp) != node_to_item((transports),                                \
-                                 struct android_log_transport_read, node)) && \
-       ((transp) !=                                                           \
-        node_to_item((n), struct android_log_transport_read, node));          \
-       (transp) = node_to_item((n), struct android_log_transport_read, node), \
-      (n) = (transp)->node.next)
-
-void __android_log_config_read();
-void __android_log_config_read_close();
-
-__END_DECLS
diff --git a/liblog/logd_reader.cpp b/liblog/logd_reader.cpp
index b7ba782..eba305f 100644
--- a/liblog/logd_reader.cpp
+++ b/liblog/logd_reader.cpp
@@ -35,7 +35,6 @@
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
-#include "config_read.h"
 #include "log_portability.h"
 #include "logd_reader.h"
 #include "logger.h"
diff --git a/liblog/logger.h b/liblog/logger.h
index 1f632c0..4b4ef5f 100644
--- a/liblog/logger.h
+++ b/liblog/logger.h
@@ -17,7 +17,6 @@
 #pragma once
 
 #include <stdatomic.h>
-#include <stdbool.h>
 
 #include <cutils/list.h>
 #include <log/log.h>
@@ -96,9 +95,19 @@
                       size_t len);
 };
 
+struct android_log_transport_context {
+  union android_log_context_union context; /* zero init per-transport context */
+
+  struct android_log_transport_read* transport;
+  unsigned logMask;      /* mask of requested log buffers */
+  int ret;               /* return value associated with following data */
+  struct log_msg logMsg; /* peek at upcoming data, valid if logMsg.len != 0 */
+};
+
 struct android_log_logger_list {
   struct listnode logger;
-  struct listnode transport;
+  android_log_transport_context transport_context;
+  bool transport_initialized;
   int mode;
   unsigned int tail;
   log_time start;
@@ -112,27 +121,7 @@
   log_id_t logId;
 };
 
-struct android_log_transport_context {
-  struct listnode node;
-  union android_log_context_union context; /* zero init per-transport context */
-  struct android_log_logger_list* parent;
-
-  struct android_log_transport_read* transport;
-  unsigned logMask;      /* mask of requested log buffers */
-  int ret;               /* return value associated with following data */
-  struct log_msg logMsg; /* peek at upcoming data, valid if logMsg.len != 0 */
-};
-
 /* assumes caller has structures read-locked, single threaded, or fenced */
-#define transport_context_for_each(transp, logger_list)                          \
-  for ((transp) = node_to_item((logger_list)->transport.next,                    \
-                               struct android_log_transport_context, node);      \
-       ((transp) != node_to_item(&(logger_list)->transport,                      \
-                                 struct android_log_transport_context, node)) && \
-       ((transp)->parent == (logger_list));                                      \
-       (transp) = node_to_item((transp)->node.next,                              \
-                               struct android_log_transport_context, node))
-
 #define logger_for_each(logp, logger_list)                          \
   for ((logp) = node_to_item((logger_list)->logger.next,            \
                              struct android_log_logger, node);      \
diff --git a/liblog/logger_read.cpp b/liblog/logger_read.cpp
index 4cf0846..ff816b7 100644
--- a/liblog/logger_read.cpp
+++ b/liblog/logger_read.cpp
@@ -29,7 +29,6 @@
 #include <cutils/list.h>
 #include <private/android_filesystem_config.h>
 
-#include "config_read.h"
 #include "log_portability.h"
 #include "logger.h"
 
@@ -55,9 +54,6 @@
 }
 
 static int init_transport_context(struct android_log_logger_list* logger_list) {
-  struct android_log_transport_read* transport;
-  struct listnode* node;
-
   if (!logger_list) {
     return -EINVAL;
   }
@@ -66,77 +62,63 @@
     return -EINVAL;
   }
 
-  if (!list_empty(&logger_list->transport)) {
+  if (logger_list->transport_initialized) {
     return 0;
   }
 
-  __android_log_lock();
-  /* mini __write_to_log_initialize() to populate transports */
-  if (list_empty(&__android_log_transport_read) && list_empty(&__android_log_persist_read)) {
-    __android_log_config_read();
-  }
-  __android_log_unlock();
+#if (FAKE_LOG_DEVICE == 0)
+  extern struct android_log_transport_read logdLoggerRead;
+  extern struct android_log_transport_read pmsgLoggerRead;
 
-  node = (logger_list->mode & ANDROID_LOG_PSTORE) ? &__android_log_persist_read
-                                                  : &__android_log_transport_read;
+  struct android_log_transport_read* transport;
+  transport = (logger_list->mode & ANDROID_LOG_PSTORE) ? &pmsgLoggerRead : &logdLoggerRead;
 
-  read_transport_for_each(transport, node) {
-    struct android_log_transport_context* transp;
-    struct android_log_logger* logger;
-    unsigned logMask = 0;
+  struct android_log_logger* logger;
+  unsigned logMask = 0;
 
-    logger_for_each(logger, logger_list) {
-      log_id_t logId = logger->logId;
+  logger_for_each(logger, logger_list) {
+    log_id_t logId = logger->logId;
 
-      if ((logId == LOG_ID_SECURITY) && (__android_log_uid() != AID_SYSTEM)) {
-        continue;
-      }
-      if (transport->read && (!transport->available || (transport->available(logId) >= 0))) {
-        logMask |= 1 << logId;
-      }
-    }
-    if (!logMask) {
+    if (logId == LOG_ID_SECURITY && __android_log_uid() != AID_SYSTEM) {
       continue;
     }
-    transp = static_cast<android_log_transport_context*>(calloc(1, sizeof(*transp)));
-    if (!transp) {
-      return -ENOMEM;
+    if (transport->read && (!transport->available || transport->available(logId) >= 0)) {
+      logMask |= 1 << logId;
     }
-    transp->parent = logger_list;
-    transp->transport = transport;
-    transp->logMask = logMask;
-    transp->ret = 1;
-    list_add_tail(&logger_list->transport, &transp->node);
   }
-  if (list_empty(&logger_list->transport)) {
+  if (!logMask) {
     return -ENODEV;
   }
+
+  logger_list->transport_context.transport = transport;
+  logger_list->transport_context.logMask = logMask;
+  logger_list->transport_context.ret = 1;
+#endif
   return 0;
 }
 
-#define LOGGER_FUNCTION(logger, def, func, args...)                                  \
-  ssize_t ret = -EINVAL;                                                             \
-  struct android_log_transport_context* transp;                                      \
-  struct android_log_logger* logger_internal = (struct android_log_logger*)(logger); \
-                                                                                     \
-  if (!logger_internal) {                                                            \
-    return ret;                                                                      \
-  }                                                                                  \
-  ret = init_transport_context(logger_internal->parent);                             \
-  if (ret < 0) {                                                                     \
-    return ret;                                                                      \
-  }                                                                                  \
-                                                                                     \
-  ret = (def);                                                                       \
-  transport_context_for_each(transp, logger_internal->parent) {                      \
-    if ((transp->logMask & (1 << logger_internal->logId)) && transp->transport &&    \
-        transp->transport->func) {                                                   \
-      ssize_t retval = (transp->transport->func)(logger_internal, transp, ##args);   \
-      if ((ret >= 0) || (ret == (def))) {                                            \
-        ret = retval;                                                                \
-      }                                                                              \
-    }                                                                                \
-  }                                                                                  \
+#define LOGGER_FUNCTION(logger, def, func, args...)                                               \
+  ssize_t ret = -EINVAL;                                                                          \
+  android_log_logger* logger_internal = reinterpret_cast<android_log_logger*>(logger);            \
+                                                                                                  \
+  if (!logger_internal) {                                                                         \
+    return ret;                                                                                   \
+  }                                                                                               \
+  ret = init_transport_context(logger_internal->parent);                                          \
+  if (ret < 0) {                                                                                  \
+    return ret;                                                                                   \
+  }                                                                                               \
+                                                                                                  \
+  ret = (def);                                                                                    \
+  android_log_transport_context* transport_context = &logger_internal->parent->transport_context; \
+  if (transport_context->logMask & (1 << logger_internal->logId) &&                               \
+      transport_context->transport && transport_context->transport->func) {                       \
+    ssize_t retval =                                                                              \
+        (transport_context->transport->func)(logger_internal, transport_context, ##args);         \
+    if (ret >= 0 || ret == (def)) {                                                               \
+      ret = retval;                                                                               \
+    }                                                                                             \
+  }                                                                                               \
   return ret
 
 int android_logger_clear(struct logger* logger) {
@@ -167,25 +149,24 @@
   LOGGER_FUNCTION(logger, 4, version);
 }
 
-#define LOGGER_LIST_FUNCTION(logger_list, def, func, args...)                           \
-  struct android_log_transport_context* transp;                                         \
-  struct android_log_logger_list* logger_list_internal =                                \
-      (struct android_log_logger_list*)(logger_list);                                   \
-                                                                                        \
-  ssize_t ret = init_transport_context(logger_list_internal);                           \
-  if (ret < 0) {                                                                        \
-    return ret;                                                                         \
-  }                                                                                     \
-                                                                                        \
-  ret = (def);                                                                          \
-  transport_context_for_each(transp, logger_list_internal) {                            \
-    if (transp->transport && (transp->transport->func)) {                               \
-      ssize_t retval = (transp->transport->func)(logger_list_internal, transp, ##args); \
-      if ((ret >= 0) || (ret == (def))) {                                               \
-        ret = retval;                                                                   \
-      }                                                                                 \
-    }                                                                                   \
-  }                                                                                     \
+#define LOGGER_LIST_FUNCTION(logger_list, def, func, args...)                                  \
+  android_log_logger_list* logger_list_internal =                                              \
+      reinterpret_cast<android_log_logger_list*>(logger_list);                                 \
+                                                                                               \
+  ssize_t ret = init_transport_context(logger_list_internal);                                  \
+  if (ret < 0) {                                                                               \
+    return ret;                                                                                \
+  }                                                                                            \
+                                                                                               \
+  ret = (def);                                                                                 \
+  android_log_transport_context* transport_context = &logger_list_internal->transport_context; \
+  if (transport_context->transport && transport_context->transport->func) {                    \
+    ssize_t retval =                                                                           \
+        (transport_context->transport->func)(logger_list_internal, transport_context, ##args); \
+    if (ret >= 0 || ret == (def)) {                                                            \
+      ret = retval;                                                                            \
+    }                                                                                          \
+  }                                                                                            \
   return ret
 
 /*
@@ -212,7 +193,6 @@
   }
 
   list_init(&logger_list->logger);
-  list_init(&logger_list->transport);
   logger_list->mode = mode;
   logger_list->tail = tail;
   logger_list->pid = pid;
@@ -229,7 +209,6 @@
   }
 
   list_init(&logger_list->logger);
-  list_init(&logger_list->transport);
   logger_list->mode = mode;
   logger_list->start = start;
   logger_list->pid = pid;
@@ -247,38 +226,27 @@
   struct android_log_logger* logger;
 
   if (!logger_list_internal || (logId >= LOG_ID_MAX)) {
-    goto err;
+    return nullptr;
   }
 
   logger_for_each(logger, logger_list_internal) {
     if (logger->logId == logId) {
-      goto ok;
+      return reinterpret_cast<struct logger*>(logger);
     }
   }
 
   logger = static_cast<android_log_logger*>(calloc(1, sizeof(*logger)));
   if (!logger) {
-    goto err;
+    return nullptr;
   }
 
   logger->logId = logId;
   list_add_tail(&logger_list_internal->logger, &logger->node);
   logger->parent = logger_list_internal;
 
-  /* Reset known transports to re-evaluate, we just added one */
-  while (!list_empty(&logger_list_internal->transport)) {
-    struct listnode* node = list_head(&logger_list_internal->transport);
-    struct android_log_transport_context* transp =
-        node_to_item(node, struct android_log_transport_context, node);
+  // Reset known transport to re-evaluate, since we added a new logger.
+  logger_list_internal->transport_initialized = false;
 
-    list_remove(&transp->node);
-    free(transp);
-  }
-  goto ok;
-
-err:
-  logger = NULL;
-ok:
   return (struct logger*)logger;
 }
 
@@ -340,7 +308,6 @@
 
 /* Read from the selected logs */
 int android_logger_list_read(struct logger_list* logger_list, struct log_msg* log_msg) {
-  struct android_log_transport_context* transp;
   struct android_log_logger_list* logger_list_internal =
       (struct android_log_logger_list*)logger_list;
 
@@ -349,84 +316,8 @@
     return ret;
   }
 
-  /* at least one transport */
-  transp = node_to_item(logger_list_internal->transport.next, struct android_log_transport_context,
-                        node);
-
-  /* more than one transport? */
-  if (transp->node.next != &logger_list_internal->transport) {
-    /* Poll and merge sort the entries if from multiple transports */
-    struct android_log_transport_context* oldest = NULL;
-    int ret;
-    int polled = 0;
-    do {
-      if (polled) {
-        sched_yield();
-      }
-      ret = -1000;
-      polled = 0;
-      do {
-        int retval = transp->ret;
-        if ((retval > 0) && !transp->logMsg.entry.len) {
-          if (!transp->transport->read) {
-            retval = transp->ret = 0;
-          } else if ((logger_list_internal->mode & ANDROID_LOG_NONBLOCK) ||
-                     !transp->transport->poll) {
-            retval = android_transport_read(logger_list_internal, transp, &transp->logMsg);
-          } else {
-            int pollval = (*transp->transport->poll)(logger_list_internal, transp);
-            if (pollval <= 0) {
-              sched_yield();
-              pollval = (*transp->transport->poll)(logger_list_internal, transp);
-            }
-            polled = 1;
-            if (pollval < 0) {
-              if ((pollval == -EINTR) || (pollval == -EAGAIN)) {
-                return -EAGAIN;
-              }
-              retval = transp->ret = pollval;
-            } else if (pollval > 0) {
-              retval = android_transport_read(logger_list_internal, transp, &transp->logMsg);
-            }
-          }
-        }
-        if (ret < retval) {
-          ret = retval;
-        }
-        if ((transp->ret > 0) && transp->logMsg.entry.len &&
-            (!oldest || (oldest->logMsg.entry.sec > transp->logMsg.entry.sec) ||
-             ((oldest->logMsg.entry.sec == transp->logMsg.entry.sec) &&
-              (oldest->logMsg.entry.nsec > transp->logMsg.entry.nsec)))) {
-          oldest = transp;
-        }
-        transp = node_to_item(transp->node.next, struct android_log_transport_context, node);
-      } while (transp != node_to_item(&logger_list_internal->transport,
-                                      struct android_log_transport_context, node));
-      if (!oldest && (logger_list_internal->mode & ANDROID_LOG_NONBLOCK)) {
-        return (ret < 0) ? ret : -EAGAIN;
-      }
-      transp = node_to_item(logger_list_internal->transport.next,
-                            struct android_log_transport_context, node);
-    } while (!oldest && (ret > 0));
-    if (!oldest) {
-      return ret;
-    }
-    // ret is a positive value less than sizeof(struct log_msg)
-    ret = oldest->ret;
-    if (ret < oldest->logMsg.entry.hdr_size) {
-      // zero truncated header fields.
-      memset(
-          log_msg, 0,
-          (oldest->logMsg.entry.hdr_size > sizeof(oldest->logMsg) ? sizeof(oldest->logMsg)
-                                                                  : oldest->logMsg.entry.hdr_size));
-    }
-    memcpy(log_msg, &oldest->logMsg, ret);
-    oldest->logMsg.entry.len = 0; /* Mark it as copied */
-    return ret;
-  }
-
-  /* if only one, no need to copy into transport_context and merge-sort */
-  return android_transport_read(logger_list_internal, transp, log_msg);
+  android_log_transport_context* transport_context = &logger_list_internal->transport_context;
+  return android_transport_read(logger_list_internal, transport_context, log_msg);
 }
 
 /* Close all the logs */
@@ -438,16 +329,10 @@
     return;
   }
 
-  while (!list_empty(&logger_list_internal->transport)) {
-    struct listnode* node = list_head(&logger_list_internal->transport);
-    struct android_log_transport_context* transp =
-        node_to_item(node, struct android_log_transport_context, node);
+  android_log_transport_context* transport_context = &logger_list_internal->transport_context;
 
-    if (transp->transport && transp->transport->close) {
-      (*transp->transport->close)(logger_list_internal, transp);
-    }
-    list_remove(&transp->node);
-    free(transp);
+  if (transport_context->transport && transport_context->transport->close) {
+    (*transport_context->transport->close)(logger_list_internal, transport_context);
   }
 
   while (!list_empty(&logger_list_internal->logger)) {
diff --git a/liblog/logger_write.cpp b/liblog/logger_write.cpp
index a4b3cd7..4fbab4b 100644
--- a/liblog/logger_write.cpp
+++ b/liblog/logger_write.cpp
@@ -29,7 +29,6 @@
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
-#include "config_read.h" /* __android_log_config_read_close() definition */
 #include "config_write.h"
 #include "log_portability.h"
 #include "logger.h"
@@ -624,7 +623,6 @@
   if (__android_log_transport != transport_flag) {
     __android_log_transport = transport_flag;
     __android_log_config_write_close();
-    __android_log_config_read_close();
 
     write_to_log = __write_to_log_init;
     /* generically we only expect these two values for write_to_log */
diff --git a/liblog/logprint.cpp b/liblog/logprint.cpp
index 6b5ea4c..3a54445 100644
--- a/liblog/logprint.cpp
+++ b/liblog/logprint.cpp
@@ -26,7 +26,6 @@
 #ifndef __MINGW32__
 #include <pwd.h>
 #endif
-#include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
diff --git a/liblog/pmsg_reader.cpp b/liblog/pmsg_reader.cpp
index ba27fd7..81563bc 100644
--- a/liblog/pmsg_reader.cpp
+++ b/liblog/pmsg_reader.cpp
@@ -17,7 +17,6 @@
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
@@ -25,7 +24,6 @@
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
 
-#include "config_read.h"
 #include "logger.h"
 
 static int pmsgAvailable(log_id_t logId);
diff --git a/liblog/pmsg_writer.cpp b/liblog/pmsg_writer.cpp
index e851100..4632b32 100644
--- a/liblog/pmsg_writer.cpp
+++ b/liblog/pmsg_writer.cpp
@@ -20,7 +20,6 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
diff --git a/liblog/stderr_write.cpp b/liblog/stderr_write.cpp
index e324a7c..e76673f 100644
--- a/liblog/stderr_write.cpp
+++ b/liblog/stderr_write.cpp
@@ -27,7 +27,6 @@
  */
 
 #include <errno.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
diff --git a/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp
index 1f87b3e..45a9bc9 100644
--- a/liblog/tests/liblog_test.cpp
+++ b/liblog/tests/liblog_test.cpp
@@ -1062,6 +1062,7 @@
 when you depart from me, sorrow abides and happiness\n\
 takes his leave.";
 
+#ifdef USING_LOGGER_DEFAULT
 TEST(liblog, max_payload) {
 #ifdef TEST_PREFIX
   TEST_PREFIX
@@ -1130,6 +1131,7 @@
   GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
 }
+#endif
 
 TEST(liblog, __android_log_buf_print__maxtag) {
 #ifdef TEST_PREFIX
@@ -1271,6 +1273,7 @@
 #endif
 }
 
+#ifdef USING_LOGGER_DEFAULT
 TEST(liblog, dual_reader) {
 #ifdef TEST_PREFIX
   TEST_PREFIX
@@ -1334,6 +1337,7 @@
   GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
 }
+#endif
 
 #ifdef USING_LOGGER_DEFAULT  // Do not retest logprint
 static bool checkPriForTag(AndroidLogFormat* p_format, const char* tag,
@@ -3004,21 +3008,6 @@
   EXPECT_LE(0, android_log_destroy(&ctx));
   ASSERT_TRUE(NULL == ctx);
 }
-
-TEST(liblog, android_log_write_list_buffer) {
-  __android_log_event_list ctx(1005);
-  ctx << 1005 << "tag_def"
-      << "(tag|1),(name|3),(format|3)";
-  std::string buffer(ctx);
-  ctx.close();
-
-  char msgBuf[1024];
-  memset(msgBuf, 0, sizeof(msgBuf));
-  EXPECT_EQ(android_log_buffer_to_string(buffer.data(), buffer.length(), msgBuf,
-                                         sizeof(msgBuf)),
-            0);
-  EXPECT_STREQ(msgBuf, "[1005,tag_def,(tag|1),(name|3),(format|3)]");
-}
 #endif  // USING_LOGGER_DEFAULT
 
 #ifdef USING_LOGGER_DEFAULT  // Do not retest pmsg functionality
diff --git a/liblog/tests/liblog_test_default.cpp b/liblog/tests/liblog_test_default.cpp
index 9fc443c..2edea27 100644
--- a/liblog/tests/liblog_test_default.cpp
+++ b/liblog/tests/liblog_test_default.cpp
@@ -2,4 +2,5 @@
 #include <log/log_transport.h>
 #define TEST_LOGGER LOGGER_DEFAULT
 #endif
+#define USING_LOGGER_DEFAULT
 #include "liblog_test.cpp"
diff --git a/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp b/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp
index eb53e57..7bba599 100644
--- a/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp
+++ b/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp
@@ -17,6 +17,7 @@
 #include <inttypes.h>
 #include <linux/dma-buf.h>
 #include <poll.h>
+#include <string.h>
 #include <sys/mman.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -230,7 +231,7 @@
     DmaBufTester() : ion_fd(ion_open()), ion_heap_mask(get_ion_heap_mask()) {}
 
     ~DmaBufTester() {
-        if (is_valid()) {
+        if (ion_fd >= 0) {
             ion_close(ion_fd);
         }
     }
@@ -241,12 +242,16 @@
         int fd;
         int err = ion_alloc_fd(ion_fd, size, 0, ion_heap_mask, 0, &fd);
         if (err < 0) {
-            return unique_fd{err};
+            printf("Failed ion_alloc_fd, return value: %d\n", err);
+            return unique_fd{};
         }
 
         if (!name.empty()) {
-            err = ioctl(fd, DMA_BUF_SET_NAME, name.c_str());
-            if (err < 0) return unique_fd{-errno};
+            if (ioctl(fd, DMA_BUF_SET_NAME, name.c_str()) == -1) {
+                printf("Failed ioctl(DMA_BUF_SET_NAME): %s\n", strerror(errno));
+                close(fd);
+                return unique_fd{};
+            }
         }
 
         return unique_fd{fd};
@@ -306,7 +311,7 @@
         return ret;
     }
 
-    unique_fd ion_fd;
+    int ion_fd;
     const int ion_heap_mask;
 };
 
diff --git a/libmemunreachable/Android.bp b/libmemunreachable/Android.bp
index 62a7266..d864d1b 100644
--- a/libmemunreachable/Android.bp
+++ b/libmemunreachable/Android.bp
@@ -105,6 +105,8 @@
 cc_test {
     name: "memunreachable_binder_test",
     defaults: ["libmemunreachable_defaults"],
+    require_root: true,
+
     srcs: [
         "tests/Binder_test.cpp",
     ],
diff --git a/libmodprobe/Android.bp b/libmodprobe/Android.bp
index a2824d1..78da46c 100644
--- a/libmodprobe/Android.bp
+++ b/libmodprobe/Android.bp
@@ -3,6 +3,7 @@
     cflags: [
         "-Werror",
     ],
+    vendor_available: true,
     recovery_available: true,
     srcs: [
         "libmodprobe.cpp",
diff --git a/libmodprobe/OWNERS b/libmodprobe/OWNERS
new file mode 100644
index 0000000..4b770b1
--- /dev/null
+++ b/libmodprobe/OWNERS
@@ -0,0 +1,2 @@
+tomcherry@google.com
+smuckle@google.com
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index 0ec766a..dcb4ffb 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <set>
 #include <string>
 #include <unordered_map>
 #include <vector>
@@ -25,12 +26,21 @@
     Modprobe(const std::vector<std::string>&);
 
     bool LoadListedModules();
-    bool LoadWithAliases(const std::string& module_name, bool strict);
+    bool LoadWithAliases(const std::string& module_name, bool strict,
+                         const std::string& parameters = "");
+    bool Remove(const std::string& module_name);
+    std::vector<std::string> ListModules(const std::string& pattern);
+    bool GetAllDependencies(const std::string& module, std::vector<std::string>* pre_dependencies,
+                            std::vector<std::string>* dependencies,
+                            std::vector<std::string>* post_dependencies);
+    void EnableBlacklist(bool enable);
+    void EnableVerbose(bool enable);
 
   private:
     std::string MakeCanonical(const std::string& module_path);
-    bool InsmodWithDeps(const std::string& module_name);
-    bool Insmod(const std::string& path_name);
+    bool InsmodWithDeps(const std::string& module_name, const std::string& parameters);
+    bool Insmod(const std::string& path_name, const std::string& parameters);
+    bool Rmmod(const std::string& module_name);
     std::vector<std::string> GetDependencies(const std::string& module);
     bool ModuleExists(const std::string& module_name);
 
@@ -39,6 +49,7 @@
     bool ParseSoftdepCallback(const std::vector<std::string>& args);
     bool ParseLoadCallback(const std::vector<std::string>& args);
     bool ParseOptionsCallback(const std::vector<std::string>& args);
+    bool ParseBlacklistCallback(const std::vector<std::string>& args);
     void ParseCfg(const std::string& cfg, std::function<bool(const std::vector<std::string>&)> f);
 
     std::vector<std::pair<std::string, std::string>> module_aliases_;
@@ -47,4 +58,6 @@
     std::vector<std::pair<std::string, std::string>> module_post_softdep_;
     std::vector<std::string> module_load_;
     std::unordered_map<std::string, std::string> module_options_;
+    std::set<std::string> module_blacklist_;
+    bool blacklist_enabled = false;
 };
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index 01cf2e3..73ae15b 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -194,6 +194,31 @@
     return true;
 }
 
+bool Modprobe::ParseBlacklistCallback(const std::vector<std::string>& args) {
+    auto it = args.begin();
+    const std::string& type = *it++;
+
+    if (type != "blacklist") {
+        LOG(ERROR) << "non-blacklist line encountered in modules.blacklist";
+        return false;
+    }
+
+    if (args.size() != 2) {
+        LOG(ERROR) << "lines in modules.blacklist must have exactly 2 entries, not " << args.size();
+        return false;
+    }
+
+    const std::string& module = *it++;
+
+    const std::string& canonical_name = MakeCanonical(module);
+    if (canonical_name.empty()) {
+        return false;
+    }
+    this->module_blacklist_.emplace(canonical_name);
+
+    return true;
+}
+
 void Modprobe::ParseCfg(const std::string& cfg,
                         std::function<bool(const std::vector<std::string>&)> f) {
     std::string cfg_contents;
@@ -231,6 +256,23 @@
 
         auto options_callback = std::bind(&Modprobe::ParseOptionsCallback, this, _1);
         ParseCfg(base_path + "/modules.options", options_callback);
+
+        auto blacklist_callback = std::bind(&Modprobe::ParseBlacklistCallback, this, _1);
+        ParseCfg(base_path + "/modules.blacklist", blacklist_callback);
+    }
+
+    android::base::SetMinimumLogSeverity(android::base::INFO);
+}
+
+void Modprobe::EnableBlacklist(bool enable) {
+    blacklist_enabled = enable;
+}
+
+void Modprobe::EnableVerbose(bool enable) {
+    if (enable) {
+        android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+    } else {
+        android::base::SetMinimumLogSeverity(android::base::INFO);
     }
 }
 
@@ -242,7 +284,7 @@
     return it->second;
 }
 
-bool Modprobe::InsmodWithDeps(const std::string& module_name) {
+bool Modprobe::InsmodWithDeps(const std::string& module_name, const std::string& parameters) {
     if (module_name.empty()) {
         LOG(ERROR) << "Need valid module name, given: " << module_name;
         return false;
@@ -256,11 +298,8 @@
 
     // load module dependencies in reverse order
     for (auto dep = dependencies.rbegin(); dep != dependencies.rend() - 1; ++dep) {
-        const std::string& canonical_name = MakeCanonical(*dep);
-        if (canonical_name.empty()) {
-            return false;
-        }
-        if (!LoadWithAliases(canonical_name, true)) {
+        LOG(VERBOSE) << "Loading hard dep for '" << module_name << "': " << *dep;
+        if (!LoadWithAliases(*dep, true)) {
             return false;
         }
     }
@@ -268,18 +307,20 @@
     // try to load soft pre-dependencies
     for (const auto& [module, softdep] : module_pre_softdep_) {
         if (module_name == module) {
+            LOG(VERBOSE) << "Loading soft pre-dep for '" << module << "': " << softdep;
             LoadWithAliases(softdep, false);
         }
     }
 
     // load target module itself with args
-    if (!Insmod(dependencies[0])) {
+    if (!Insmod(dependencies[0], parameters)) {
         return false;
     }
 
     // try to load soft post-dependencies
     for (const auto& [module, softdep] : module_post_softdep_) {
         if (module_name == module) {
+            LOG(VERBOSE) << "Loading soft post-dep for '" << module << "': " << softdep;
             LoadWithAliases(softdep, false);
         }
     }
@@ -287,25 +328,27 @@
     return true;
 }
 
-bool Modprobe::LoadWithAliases(const std::string& module_name, bool strict) {
-    std::set<std::string> modules_to_load = {module_name};
+bool Modprobe::LoadWithAliases(const std::string& module_name, bool strict,
+                               const std::string& parameters) {
+    std::set<std::string> modules_to_load = {MakeCanonical(module_name)};
     bool module_loaded = false;
 
     // use aliases to expand list of modules to load (multiple modules
     // may alias themselves to the requested name)
     for (const auto& [alias, aliased_module] : module_aliases_) {
         if (fnmatch(alias.c_str(), module_name.c_str(), 0) != 0) continue;
+        LOG(VERBOSE) << "Found alias for '" << module_name << "': '" << aliased_module;
         modules_to_load.emplace(aliased_module);
     }
 
     // attempt to load all modules aliased to this name
     for (const auto& module : modules_to_load) {
         if (!ModuleExists(module)) continue;
-        if (InsmodWithDeps(module)) module_loaded = true;
+        if (InsmodWithDeps(module, parameters)) module_loaded = true;
     }
 
     if (strict && !module_loaded) {
-        LOG(ERROR) << "LoadWithAliases did not find a module for " << module_name;
+        LOG(ERROR) << "LoadWithAliases was unable to load " << module_name;
         return false;
     }
     return true;
@@ -319,3 +362,64 @@
     }
     return true;
 }
+
+bool Modprobe::Remove(const std::string& module_name) {
+    auto dependencies = GetDependencies(MakeCanonical(module_name));
+    if (dependencies.empty()) {
+        LOG(ERROR) << "Empty dependencies for module " << module_name;
+        return false;
+    }
+    if (!Rmmod(dependencies[0])) {
+        return false;
+    }
+    for (auto dep = dependencies.begin() + 1; dep != dependencies.end(); ++dep) {
+        Rmmod(*dep);
+    }
+    return true;
+}
+
+std::vector<std::string> Modprobe::ListModules(const std::string& pattern) {
+    std::vector<std::string> rv;
+    for (const auto& [module, deps] : module_deps_) {
+        // Attempt to match both the canonical module name and the module filename.
+        if (!fnmatch(pattern.c_str(), module.c_str(), 0)) {
+            rv.emplace_back(module);
+        } else if (!fnmatch(pattern.c_str(), basename(deps[0].c_str()), 0)) {
+            rv.emplace_back(deps[0]);
+        }
+    }
+    return rv;
+}
+
+bool Modprobe::GetAllDependencies(const std::string& module,
+                                  std::vector<std::string>* pre_dependencies,
+                                  std::vector<std::string>* dependencies,
+                                  std::vector<std::string>* post_dependencies) {
+    std::string canonical_name = MakeCanonical(module);
+    if (pre_dependencies) {
+        pre_dependencies->clear();
+        for (const auto& [it_module, it_softdep] : module_pre_softdep_) {
+            if (canonical_name == it_module) {
+                pre_dependencies->emplace_back(it_softdep);
+            }
+        }
+    }
+    if (dependencies) {
+        dependencies->clear();
+        auto hard_deps = GetDependencies(canonical_name);
+        if (hard_deps.empty()) {
+            return false;
+        }
+        for (auto dep = hard_deps.rbegin(); dep != hard_deps.rend(); dep++) {
+            dependencies->emplace_back(*dep);
+        }
+    }
+    if (post_dependencies) {
+        for (const auto& [it_module, it_softdep] : module_post_softdep_) {
+            if (canonical_name == it_module) {
+                post_dependencies->emplace_back(it_softdep);
+            }
+        }
+    }
+    return true;
+}
diff --git a/libmodprobe/libmodprobe_ext.cpp b/libmodprobe/libmodprobe_ext.cpp
index 5f3a04d..2efcac2 100644
--- a/libmodprobe/libmodprobe_ext.cpp
+++ b/libmodprobe/libmodprobe_ext.cpp
@@ -22,7 +22,7 @@
 
 #include <modprobe/modprobe.h>
 
-bool Modprobe::Insmod(const std::string& path_name) {
+bool Modprobe::Insmod(const std::string& path_name, const std::string& parameters) {
     android::base::unique_fd fd(
             TEMP_FAILURE_RETRY(open(path_name.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
     if (fd == -1) {
@@ -35,6 +35,9 @@
     if (options_iter != module_options_.end()) {
         options = options_iter->second;
     }
+    if (!parameters.empty()) {
+        options = options + " " + parameters;
+    }
 
     LOG(INFO) << "Loading module " << path_name << " with args \"" << options << "\"";
     int ret = syscall(__NR_finit_module, fd.get(), options.c_str(), 0);
@@ -51,17 +54,32 @@
     return true;
 }
 
+bool Modprobe::Rmmod(const std::string& module_name) {
+    int ret = syscall(__NR_delete_module, MakeCanonical(module_name).c_str(), O_NONBLOCK);
+    if (ret != 0) {
+        PLOG(ERROR) << "Failed to remove module '" << module_name << "'";
+        return false;
+    }
+    return true;
+}
+
 bool Modprobe::ModuleExists(const std::string& module_name) {
     struct stat fileStat;
+    if (blacklist_enabled && module_blacklist_.count(module_name)) {
+        LOG(INFO) << "module " << module_name << " is blacklisted";
+        return false;
+    }
     auto deps = GetDependencies(module_name);
     if (deps.empty()) {
         // missing deps can happen in the case of an alias
         return false;
     }
     if (stat(deps.front().c_str(), &fileStat)) {
+        LOG(INFO) << "module " << module_name << " does not exist";
         return false;
     }
     if (!S_ISREG(fileStat.st_mode)) {
+        LOG(INFO) << "module " << module_name << " is not a regular file";
         return false;
     }
     return true;
diff --git a/libmodprobe/libmodprobe_ext_test.cpp b/libmodprobe/libmodprobe_ext_test.cpp
index 0f073cb..7d817b1 100644
--- a/libmodprobe/libmodprobe_ext_test.cpp
+++ b/libmodprobe/libmodprobe_ext_test.cpp
@@ -29,7 +29,7 @@
 
 #include "libmodprobe_test.h"
 
-bool Modprobe::Insmod(const std::string& path_name) {
+bool Modprobe::Insmod(const std::string& path_name, const std::string& parameters) {
     auto deps = GetDependencies(MakeCanonical(path_name));
     if (deps.empty()) {
         return false;
@@ -47,12 +47,29 @@
     if (options_iter != module_options_.end()) {
         options = " " + options_iter->second;
     }
+    if (!parameters.empty()) {
+        options = options + " " + parameters;
+    }
+
     modules_loaded.emplace_back(path_name + options);
     return true;
 }
 
+bool Modprobe::Rmmod(const std::string& module_name) {
+    for (auto it = modules_loaded.begin(); it != modules_loaded.end(); it++) {
+        if (*it == module_name) {
+            modules_loaded.erase(it);
+            return true;
+        }
+    }
+    return false;
+}
+
 bool Modprobe::ModuleExists(const std::string& module_name) {
     auto deps = GetDependencies(module_name);
+    if (blacklist_enabled && module_blacklist_.count(module_name)) {
+        return false;
+    }
     if (deps.empty()) {
         // missing deps can happen in the case of an alias
         return false;
diff --git a/libmodprobe/libmodprobe_test.cpp b/libmodprobe/libmodprobe_test.cpp
index 481658d..a711631 100644
--- a/libmodprobe/libmodprobe_test.cpp
+++ b/libmodprobe/libmodprobe_test.cpp
@@ -56,6 +56,14 @@
             "/test13.ko",
     };
 
+    std::vector<std::string> expected_after_remove = {
+            "/test14.ko", "/test15.ko",         "/test1.ko",
+            "/test6.ko",  "/test2.ko",          "/test5.ko",
+            "/test8.ko",  "/test7.ko param1=4", "/test9.ko param_x=1 param_y=2 param_z=3",
+            "/test10.ko", "/test12.ko",         "/test11.ko",
+            "/test13.ko",
+    };
+
     const std::string modules_dep =
             "test1.ko:\n"
             "test2.ko:\n"
@@ -91,6 +99,10 @@
             "options test9.ko param_x=1 param_y=2 param_z=3\n"
             "options test100.ko param_1=1\n";
 
+    const std::string modules_blacklist =
+            "blacklist test9.ko\n"
+            "blacklist test3.ko\n";
+
     const std::string modules_load =
             "test4.ko\n"
             "test1.ko\n"
@@ -101,17 +113,20 @@
             "test11.ko\n";
 
     TemporaryDir dir;
-    ASSERT_TRUE(android::base::WriteStringToFile(
-            modules_alias, std::string(dir.path) + "/modules.alias", 0600, getuid(), getgid()));
+    auto dir_path = std::string(dir.path);
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_alias, dir_path + "/modules.alias", 0600,
+                                                 getuid(), getgid()));
 
-    ASSERT_TRUE(android::base::WriteStringToFile(
-            modules_dep, std::string(dir.path) + "/modules.dep", 0600, getuid(), getgid()));
-    ASSERT_TRUE(android::base::WriteStringToFile(
-            modules_softdep, std::string(dir.path) + "/modules.softdep", 0600, getuid(), getgid()));
-    ASSERT_TRUE(android::base::WriteStringToFile(
-            modules_options, std::string(dir.path) + "/modules.options", 0600, getuid(), getgid()));
-    ASSERT_TRUE(android::base::WriteStringToFile(
-            modules_load, std::string(dir.path) + "/modules.load", 0600, getuid(), getgid()));
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_dep, dir_path + "/modules.dep", 0600,
+                                                 getuid(), getgid()));
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_softdep, dir_path + "/modules.softdep",
+                                                 0600, getuid(), getgid()));
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_options, dir_path + "/modules.options",
+                                                 0600, getuid(), getgid()));
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_load, dir_path + "/modules.load", 0600,
+                                                 getuid(), getgid()));
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_blacklist, dir_path + "/modules.blacklist",
+                                                 0600, getuid(), getgid()));
 
     for (auto i = test_modules.begin(); i != test_modules.end(); ++i) {
         *i = dir.path + *i;
@@ -131,4 +146,21 @@
     }
 
     EXPECT_TRUE(modules_loaded == expected_modules_loaded);
+
+    EXPECT_TRUE(m.Remove("test4"));
+
+    GTEST_LOG_(INFO) << "Expected modules loaded after removing test4 (in order):";
+    for (auto i = expected_after_remove.begin(); i != expected_after_remove.end(); ++i) {
+        *i = dir.path + *i;
+        GTEST_LOG_(INFO) << "\"" << *i << "\"";
+    }
+    GTEST_LOG_(INFO) << "Actual modules loaded after removing test4 (in order):";
+    for (auto i = modules_loaded.begin(); i != modules_loaded.end(); ++i) {
+        GTEST_LOG_(INFO) << "\"" << *i << "\"";
+    }
+
+    EXPECT_TRUE(modules_loaded == expected_after_remove);
+
+    m.EnableBlacklist(true);
+    EXPECT_FALSE(m.LoadWithAliases("test4", true));
 }
diff --git a/libnativeloader/include/nativeloader/dlext_namespaces.h b/libnativeloader/include/nativeloader/dlext_namespaces.h
index 2d6ce85..8937636 100644
--- a/libnativeloader/include/nativeloader/dlext_namespaces.h
+++ b/libnativeloader/include/nativeloader/dlext_namespaces.h
@@ -22,18 +22,6 @@
 
 __BEGIN_DECLS
 
-/*
- * Initializes anonymous namespaces. The shared_libs_sonames is the list of sonames
- * to be shared by default namespace separated by colon. Example: "libc.so:libm.so:libdl.so".
- *
- * The library_search_path is the search path for anonymous namespace. The anonymous namespace
- * is used in the case when linker cannot identify the caller of dlopen/dlsym. This happens
- * for the code not loaded by dynamic linker; for example calls from the mono-compiled code.
- */
-extern bool android_init_anonymous_namespace(const char* shared_libs_sonames,
-                                             const char* library_search_path);
-
-
 enum {
   /* A regular namespace is the namespace with a custom search path that does
    * not impose any restrictions on the location of native libraries.
@@ -62,8 +50,18 @@
    */
   ANDROID_NAMESPACE_TYPE_GREYLIST_ENABLED = 0x08000000,
 
-  ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED = ANDROID_NAMESPACE_TYPE_SHARED |
-                                           ANDROID_NAMESPACE_TYPE_ISOLATED,
+  /* This flag instructs linker to use this namespace as the anonymous
+   * namespace. The anonymous namespace is used in the case when linker cannot
+   * identify the caller of dlopen/dlsym. This happens for the code not loaded
+   * by dynamic linker; for example calls from the mono-compiled code. There can
+   * be only one anonymous namespace in a process. If there already is an
+   * anonymous namespace in the process, using this flag when creating a new
+   * namespace causes an error.
+   */
+  ANDROID_NAMESPACE_TYPE_ALSO_USED_AS_ANONYMOUS = 0x10000000,
+
+  ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED =
+      ANDROID_NAMESPACE_TYPE_SHARED | ANDROID_NAMESPACE_TYPE_ISOLATED,
 };
 
 /*
diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp
index a9eea8c..9a33b55 100644
--- a/libnativeloader/library_namespaces.cpp
+++ b/libnativeloader/library_namespaces.cpp
@@ -124,7 +124,7 @@
   // we might as well end up loading them from /system/lib or /product/lib
   // For now we rely on CTS test to catch things like this but
   // it should probably be addressed in the future.
-  for (const auto& soname : android::base::Split(default_public_libraries(), ":")) {
+  for (const auto& soname : android::base::Split(preloadable_public_libraries(), ":")) {
     LOG_ALWAYS_FATAL_IF(dlopen(soname.c_str(), RTLD_NOW | RTLD_NODELETE) == nullptr,
                         "Error preloading public library %s: %s", soname.c_str(), dlerror());
   }
@@ -159,13 +159,6 @@
     }
   }
 
-  // Initialize the anonymous namespace with the first non-empty library path.
-  Result<void> ret;
-  if (!library_path.empty() && !initialized_ &&
-      !(ret = InitPublicNamespace(library_path.c_str()))) {
-    return ret.error();
-  }
-
   LOG_ALWAYS_FATAL_IF(FindNamespaceByClassLoader(env, class_loader) != nullptr,
                       "There is already a namespace associated with this classloader");
 
@@ -215,13 +208,22 @@
 
   // Create the app namespace
   NativeLoaderNamespace* parent_ns = FindParentNamespaceByClassLoader(env, class_loader);
-  auto app_ns =
-      NativeLoaderNamespace::Create(namespace_name, library_path, permitted_path, parent_ns,
-                                    is_shared, target_sdk_version < 24 /* is_greylist_enabled */);
+  // Heuristic: the first classloader with non-empty library_path is assumed to
+  // be the main classloader for app
+  // TODO(b/139178525) remove this heuristic by determining this in LoadedApk (or its
+  // friends) and then passing it down to here.
+  bool is_main_classloader = app_main_namespace_ == nullptr && !library_path.empty();
+  // Policy: the namespace for the main classloader is also used as the
+  // anonymous namespace.
+  bool also_used_as_anonymous = is_main_classloader;
+  // Note: this function is executed with g_namespaces_mutex held, thus no
+  // racing here.
+  auto app_ns = NativeLoaderNamespace::Create(
+      namespace_name, library_path, permitted_path, parent_ns, is_shared,
+      target_sdk_version < 24 /* is_greylist_enabled */, also_used_as_anonymous);
   if (!app_ns) {
     return app_ns.error();
   }
-
   // ... and link to other namespaces to allow access to some public libraries
   bool is_bridged = app_ns->IsBridged();
 
@@ -278,6 +280,9 @@
   }
 
   namespaces_.push_back(std::make_pair(env->NewWeakGlobalRef(class_loader), *app_ns));
+  if (is_main_classloader) {
+    app_main_namespace_ = &(*app_ns);
+  }
 
   return &(namespaces_.back().second);
 }
@@ -295,32 +300,6 @@
   return nullptr;
 }
 
-Result<void> LibraryNamespaces::InitPublicNamespace(const char* library_path) {
-  // Ask native bride if this apps library path should be handled by it
-  bool is_native_bridge = NativeBridgeIsPathSupported(library_path);
-
-  // (http://b/25844435) - Some apps call dlopen from generated code (mono jited
-  // code is one example) unknown to linker in which  case linker uses anonymous
-  // namespace. The second argument specifies the search path for the anonymous
-  // namespace which is the library_path of the classloader.
-  initialized_ = android_init_anonymous_namespace(default_public_libraries().c_str(),
-                                                  is_native_bridge ? nullptr : library_path);
-  if (!initialized_) {
-    return Error() << dlerror();
-  }
-
-  // and now initialize native bridge namespaces if necessary.
-  if (NativeBridgeInitialized()) {
-    initialized_ = NativeBridgeInitAnonymousNamespace(default_public_libraries().c_str(),
-                                                      is_native_bridge ? library_path : nullptr);
-    if (!initialized_) {
-      return Error() << NativeBridgeGetError();
-    }
-  }
-
-  return {};
-}
-
 NativeLoaderNamespace* LibraryNamespaces::FindParentNamespaceByClassLoader(JNIEnv* env,
                                                                            jobject class_loader) {
   jobject parent_class_loader = GetParentClassLoader(env, class_loader);
diff --git a/libnativeloader/library_namespaces.h b/libnativeloader/library_namespaces.h
index e54bc0a..7b3efff 100644
--- a/libnativeloader/library_namespaces.h
+++ b/libnativeloader/library_namespaces.h
@@ -38,7 +38,7 @@
 // object for a given ClassLoader.
 class LibraryNamespaces {
  public:
-  LibraryNamespaces() : initialized_(false) {}
+  LibraryNamespaces() : initialized_(false), app_main_namespace_(nullptr) {}
 
   LibraryNamespaces(LibraryNamespaces&&) = default;
   LibraryNamespaces(const LibraryNamespaces&) = delete;
@@ -48,6 +48,7 @@
   void Reset() {
     namespaces_.clear();
     initialized_ = false;
+    app_main_namespace_ = nullptr;
   }
   Result<NativeLoaderNamespace*> Create(JNIEnv* env, uint32_t target_sdk_version,
                                         jobject class_loader, bool is_shared, jstring dex_path,
@@ -59,6 +60,7 @@
   NativeLoaderNamespace* FindParentNamespaceByClassLoader(JNIEnv* env, jobject class_loader);
 
   bool initialized_;
+  NativeLoaderNamespace* app_main_namespace_;
   std::list<std::pair<jweak, NativeLoaderNamespace>> namespaces_;
 };
 
diff --git a/libnativeloader/native_loader_namespace.cpp b/libnativeloader/native_loader_namespace.cpp
index 4add6e6..a81fddf 100644
--- a/libnativeloader/native_loader_namespace.cpp
+++ b/libnativeloader/native_loader_namespace.cpp
@@ -85,7 +85,8 @@
 
 Result<NativeLoaderNamespace> NativeLoaderNamespace::Create(
     const std::string& name, const std::string& search_paths, const std::string& permitted_paths,
-    const NativeLoaderNamespace* parent, bool is_shared, bool is_greylist_enabled) {
+    const NativeLoaderNamespace* parent, bool is_shared, bool is_greylist_enabled,
+    bool also_used_as_anonymous) {
   bool is_bridged = false;
   if (parent != nullptr) {
     is_bridged = parent->IsBridged();
@@ -100,7 +101,17 @@
   }
   const NativeLoaderNamespace& effective_parent = parent != nullptr ? *parent : *platform_ns;
 
+  // All namespaces for apps are isolated
   uint64_t type = ANDROID_NAMESPACE_TYPE_ISOLATED;
+
+  // The namespace is also used as the anonymous namespace
+  // which is used when the linker fails to determine the caller address
+  if (also_used_as_anonymous) {
+    type |= ANDROID_NAMESPACE_TYPE_ALSO_USED_AS_ANONYMOUS;
+  }
+
+  // Bundled apps have access to all system libraries that are currently loaded
+  // in the default namespace
   if (is_shared) {
     type |= ANDROID_NAMESPACE_TYPE_SHARED;
   }
diff --git a/libnativeloader/native_loader_namespace.h b/libnativeloader/native_loader_namespace.h
index 29b759c..7200ee7 100644
--- a/libnativeloader/native_loader_namespace.h
+++ b/libnativeloader/native_loader_namespace.h
@@ -39,7 +39,8 @@
                                               const std::string& search_paths,
                                               const std::string& permitted_paths,
                                               const NativeLoaderNamespace* parent, bool is_shared,
-                                              bool is_greylist_enabled);
+                                              bool is_greylist_enabled,
+                                              bool also_used_as_anonymous);
 
   NativeLoaderNamespace(NativeLoaderNamespace&&) = default;
   NativeLoaderNamespace(const NativeLoaderNamespace&) = default;
diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp
index b939eee..75255b6 100644
--- a/libnativeloader/native_loader_test.cpp
+++ b/libnativeloader/native_loader_test.cpp
@@ -29,6 +29,7 @@
 #include "public_libraries.h"
 
 using namespace ::testing;
+using namespace ::android::nativeloader::internal;
 
 namespace android {
 namespace nativeloader {
@@ -289,7 +290,7 @@
 
   void SetExpectations() {
     std::vector<std::string> default_public_libs =
-        android::base::Split(default_public_libraries(), ":");
+        android::base::Split(preloadable_public_libraries(), ":");
     for (auto l : default_public_libs) {
       EXPECT_CALL(*mock, dlopen(StrEq(l.c_str()), RTLD_NOW | RTLD_NODELETE))
           .WillOnce(Return(any_nonnull));
@@ -331,7 +332,8 @@
 
   // expected output (.. for the default test inputs)
   std::string expected_namespace_name = "classloader-namespace";
-  uint64_t expected_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED;
+  uint64_t expected_namespace_flags =
+      ANDROID_NAMESPACE_TYPE_ISOLATED | ANDROID_NAMESPACE_TYPE_ALSO_USED_AS_ANONYMOUS;
   std::string expected_library_path = library_path;
   std::string expected_permitted_path = std::string("/data:/mnt/expand:") + permitted_path;
   std::string expected_parent_namespace = "platform";
@@ -356,17 +358,6 @@
     EXPECT_CALL(*mock, NativeBridgeIsPathSupported(_)).Times(AnyNumber());
     EXPECT_CALL(*mock, NativeBridgeInitialized()).Times(AnyNumber());
 
-    if (IsBridged()) {
-      EXPECT_CALL(*mock,
-                  mock_init_anonymous_namespace(false, StrEq(default_public_libraries()), nullptr))
-          .WillOnce(Return(true));
-
-      EXPECT_CALL(*mock, NativeBridgeInitialized()).WillOnce(Return(true));
-    }
-
-    EXPECT_CALL(*mock, mock_init_anonymous_namespace(
-                           Eq(IsBridged()), StrEq(default_public_libraries()), StrEq(library_path)))
-        .WillOnce(Return(true));
     EXPECT_CALL(*mock, mock_create_namespace(
                            Eq(IsBridged()), StrEq(expected_namespace_name), nullptr,
                            StrEq(expected_library_path), expected_namespace_flags,
@@ -443,7 +434,7 @@
   dex_path = "/system/app/foo/foo.apk";
   is_shared = true;
 
-  expected_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED | ANDROID_NAMESPACE_TYPE_SHARED;
+  expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED;
   SetExpectations();
   RunTest();
 }
@@ -452,7 +443,7 @@
   dex_path = "/vendor/app/foo/foo.apk";
   is_shared = true;
 
-  expected_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED | ANDROID_NAMESPACE_TYPE_SHARED;
+  expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED;
   SetExpectations();
   RunTest();
 }
@@ -475,7 +466,7 @@
   dex_path = "/product/app/foo/foo.apk";
   is_shared = true;
 
-  expected_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED | ANDROID_NAMESPACE_TYPE_SHARED;
+  expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED;
   SetExpectations();
   RunTest();
 }
@@ -485,7 +476,7 @@
   is_shared = true;
   target_sdk_version = 30;
 
-  expected_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED | ANDROID_NAMESPACE_TYPE_SHARED;
+  expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED;
   SetExpectations();
   RunTest();
 }
@@ -512,6 +503,22 @@
   RunTest();
 }
 
+TEST_P(NativeLoaderTest_Create, NamespaceForSharedLibIsNotUsedAsAnonymousNamespace) {
+  if (IsBridged()) {
+    // There is no shared lib in translated arch
+    // TODO(jiyong): revisit this
+    return;
+  }
+  // compared to apks, for java shared libs, library_path is empty; java shared
+  // libs don't have their own native libs. They use platform's.
+  library_path = "";
+  expected_library_path = library_path;
+  // no ALSO_USED_AS_ANONYMOUS
+  expected_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED;
+  SetExpectations();
+  RunTest();
+}
+
 TEST_P(NativeLoaderTest_Create, TwoApks) {
   SetExpectations();
   const uint32_t second_app_target_sdk_version = 29;
@@ -523,6 +530,8 @@
   const std::string expected_second_app_permitted_path =
       std::string("/data:/mnt/expand:") + second_app_permitted_path;
   const std::string expected_second_app_parent_namespace = "classloader-namespace";
+  // no ALSO_USED_AS_ANONYMOUS
+  const uint64_t expected_second_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED;
 
   // The scenario is that second app is loaded by the first app.
   // So the first app's classloader (`classloader`) is parent of the second
@@ -532,10 +541,10 @@
 
   // namespace for the second app is created. Its parent is set to the namespace
   // of the first app.
-  EXPECT_CALL(*mock, mock_create_namespace(Eq(IsBridged()), StrEq(expected_namespace_name), nullptr,
-                                           StrEq(second_app_library_path), expected_namespace_flags,
-                                           StrEq(expected_second_app_permitted_path),
-                                           NsEq(dex_path.c_str())))
+  EXPECT_CALL(*mock, mock_create_namespace(
+                         Eq(IsBridged()), StrEq(expected_namespace_name), nullptr,
+                         StrEq(second_app_library_path), expected_second_namespace_flags,
+                         StrEq(expected_second_app_permitted_path), NsEq(dex_path.c_str())))
       .WillOnce(Return(TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE(second_app_dex_path.c_str()))));
   EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), NsEq(second_app_dex_path.c_str()), _, _))
       .WillRepeatedly(Return(true));
@@ -568,7 +577,87 @@
 
 INSTANTIATE_TEST_SUITE_P(NativeLoaderTests_Create, NativeLoaderTest_Create, testing::Bool());
 
-// TODO(b/130388701#comment22) add a test for anonymous namespace
+const std::function<Result<bool>(const struct ConfigEntry&)> always_true =
+    [](const struct ConfigEntry&) -> Result<bool> { return true; };
+
+TEST(NativeLoaderConfigParser, NamesAndComments) {
+  const char file_content[] = R"(
+######
+
+libA.so
+#libB.so
+
+
+      libC.so
+libD.so
+    #### libE.so
+)";
+  const std::vector<std::string> expected_result = {"libA.so", "libC.so", "libD.so"};
+  Result<std::vector<std::string>> result = ParseConfig(file_content, always_true);
+  ASSERT_TRUE(result) << result.error().message();
+  ASSERT_EQ(expected_result, *result);
+}
+
+TEST(NativeLoaderConfigParser, WithBitness) {
+  const char file_content[] = R"(
+libA.so 32
+libB.so 64
+libC.so
+)";
+#if defined(__LP64__)
+  const std::vector<std::string> expected_result = {"libB.so", "libC.so"};
+#else
+  const std::vector<std::string> expected_result = {"libA.so", "libC.so"};
+#endif
+  Result<std::vector<std::string>> result = ParseConfig(file_content, always_true);
+  ASSERT_TRUE(result) << result.error().message();
+  ASSERT_EQ(expected_result, *result);
+}
+
+TEST(NativeLoaderConfigParser, WithNoPreload) {
+  const char file_content[] = R"(
+libA.so nopreload
+libB.so nopreload
+libC.so
+)";
+
+  const std::vector<std::string> expected_result = {"libC.so"};
+  Result<std::vector<std::string>> result =
+      ParseConfig(file_content,
+                  [](const struct ConfigEntry& entry) -> Result<bool> { return !entry.nopreload; });
+  ASSERT_TRUE(result) << result.error().message();
+  ASSERT_EQ(expected_result, *result);
+}
+
+TEST(NativeLoaderConfigParser, WithNoPreloadAndBitness) {
+  const char file_content[] = R"(
+libA.so nopreload 32
+libB.so 64 nopreload
+libC.so 32
+libD.so 64
+libE.so nopreload
+)";
+
+#if defined(__LP64__)
+  const std::vector<std::string> expected_result = {"libD.so"};
+#else
+  const std::vector<std::string> expected_result = {"libC.so"};
+#endif
+  Result<std::vector<std::string>> result =
+      ParseConfig(file_content,
+                  [](const struct ConfigEntry& entry) -> Result<bool> { return !entry.nopreload; });
+  ASSERT_TRUE(result) << result.error().message();
+  ASSERT_EQ(expected_result, *result);
+}
+
+TEST(NativeLoaderConfigParser, RejectMalformed) {
+  ASSERT_FALSE(ParseConfig("libA.so 32 64", always_true));
+  ASSERT_FALSE(ParseConfig("libA.so 32 32", always_true));
+  ASSERT_FALSE(ParseConfig("libA.so 32 nopreload 64", always_true));
+  ASSERT_FALSE(ParseConfig("32 libA.so nopreload", always_true));
+  ASSERT_FALSE(ParseConfig("nopreload libA.so 32", always_true));
+  ASSERT_FALSE(ParseConfig("libA.so nopreload # comment", always_true));
+}
 
 }  // namespace nativeloader
 }  // namespace android
diff --git a/libnativeloader/public_libraries.cpp b/libnativeloader/public_libraries.cpp
index 6cee668..3694360 100644
--- a/libnativeloader/public_libraries.cpp
+++ b/libnativeloader/public_libraries.cpp
@@ -34,7 +34,8 @@
 
 namespace android::nativeloader {
 
-using namespace std::string_literals;
+using namespace internal;
+using namespace ::std::string_literals;
 using android::base::ErrnoError;
 using android::base::Errorf;
 using android::base::Result;
@@ -95,53 +96,21 @@
   file_name->insert(insert_pos, vndk_version_str());
 }
 
-const std::function<Result<void>(const std::string&)> always_true =
-    [](const std::string&) -> Result<void> { return {}; };
+const std::function<Result<bool>(const struct ConfigEntry&)> always_true =
+    [](const struct ConfigEntry&) -> Result<bool> { return true; };
 
 Result<std::vector<std::string>> ReadConfig(
     const std::string& configFile,
-    const std::function<Result<void>(const std::string& /* soname */)>& check_soname) {
-  // Read list of public native libraries from the config file.
+    const std::function<Result<bool>(const ConfigEntry& /* entry */)>& filter_fn) {
   std::string file_content;
   if (!base::ReadFileToString(configFile, &file_content)) {
     return ErrnoError();
   }
-
-  std::vector<std::string> lines = base::Split(file_content, "\n");
-
-  std::vector<std::string> sonames;
-  for (auto& line : lines) {
-    auto trimmed_line = base::Trim(line);
-    if (trimmed_line[0] == '#' || trimmed_line.empty()) {
-      continue;
-    }
-    size_t space_pos = trimmed_line.rfind(' ');
-    if (space_pos != std::string::npos) {
-      std::string type = trimmed_line.substr(space_pos + 1);
-      if (type != "32" && type != "64") {
-        return Errorf("Malformed line: {}", line);
-      }
-#if defined(__LP64__)
-      // Skip 32 bit public library.
-      if (type == "32") {
-        continue;
-      }
-#else
-      // Skip 64 bit public library.
-      if (type == "64") {
-        continue;
-      }
-#endif
-      trimmed_line.resize(space_pos);
-    }
-
-    auto ret = check_soname(trimmed_line);
-    if (!ret) {
-      return ret.error();
-    }
-    sonames.push_back(trimmed_line);
+  Result<std::vector<std::string>> result = ParseConfig(file_content, filter_fn);
+  if (!result) {
+    return Errorf("Cannot parse {}: {}", configFile, result.error().message());
   }
-  return sonames;
+  return result;
 }
 
 void ReadExtensionLibraries(const char* dirname, std::vector<std::string>* sonames) {
@@ -165,13 +134,13 @@
             config_file_path.c_str());
 
         auto ret = ReadConfig(
-            config_file_path, [&company_name](const std::string& soname) -> Result<void> {
-              if (android::base::StartsWith(soname, "lib") &&
-                  android::base::EndsWith(soname, "." + company_name + ".so")) {
-                return {};
+            config_file_path, [&company_name](const struct ConfigEntry& entry) -> Result<bool> {
+              if (android::base::StartsWith(entry.soname, "lib") &&
+                  android::base::EndsWith(entry.soname, "." + company_name + ".so")) {
+                return true;
               } else {
-                return Errorf("Library name \"{}\" does not end with the company name {}.", soname,
-                              company_name);
+                return Errorf("Library name \"{}\" does not end with the company name {}.",
+                              entry.soname, company_name);
               }
             });
         if (ret) {
@@ -185,9 +154,16 @@
   }
 }
 
-static std::string InitDefaultPublicLibraries() {
+static std::string InitDefaultPublicLibraries(bool for_preload) {
   std::string config_file = root_dir() + kDefaultPublicLibrariesFile;
-  auto sonames = ReadConfig(config_file, always_true);
+  auto sonames =
+      ReadConfig(config_file, [&for_preload](const struct ConfigEntry& entry) -> Result<bool> {
+        if (for_preload) {
+          return !entry.nopreload;
+        } else {
+          return true;
+        }
+      });
   if (!sonames) {
     LOG_ALWAYS_FATAL("Error reading public native library list from \"%s\": %s",
                      config_file.c_str(), sonames.error().message().c_str());
@@ -290,8 +266,13 @@
 
 }  // namespace
 
+const std::string& preloadable_public_libraries() {
+  static std::string list = InitDefaultPublicLibraries(/*for_preload*/ true);
+  return list;
+}
+
 const std::string& default_public_libraries() {
-  static std::string list = InitDefaultPublicLibraries();
+  static std::string list = InitDefaultPublicLibraries(/*for_preload*/ false);
   return list;
 }
 
@@ -325,4 +306,61 @@
   return list;
 }
 
+namespace internal {
+// Exported for testing
+Result<std::vector<std::string>> ParseConfig(
+    const std::string& file_content,
+    const std::function<Result<bool>(const ConfigEntry& /* entry */)>& filter_fn) {
+  std::vector<std::string> lines = base::Split(file_content, "\n");
+
+  std::vector<std::string> sonames;
+  for (auto& line : lines) {
+    auto trimmed_line = base::Trim(line);
+    if (trimmed_line[0] == '#' || trimmed_line.empty()) {
+      continue;
+    }
+
+    std::vector<std::string> tokens = android::base::Split(trimmed_line, " ");
+    if (tokens.size() < 1 || tokens.size() > 3) {
+      return Errorf("Malformed line \"{}\"", line);
+    }
+    struct ConfigEntry entry = {.soname = "", .nopreload = false, .bitness = ALL};
+    size_t i = tokens.size();
+    while (i-- > 0) {
+      if (tokens[i] == "nopreload") {
+        entry.nopreload = true;
+      } else if (tokens[i] == "32" || tokens[i] == "64") {
+        if (entry.bitness != ALL) {
+          return Errorf("Malformed line \"{}\": bitness can be specified only once", line);
+        }
+        entry.bitness = tokens[i] == "32" ? ONLY_32 : ONLY_64;
+      } else {
+        if (i != 0) {
+          return Errorf("Malformed line \"{}\"", line);
+        }
+        entry.soname = tokens[i];
+      }
+    }
+
+    // skip 32-bit lib on 64-bit process and vice versa
+#if defined(__LP64__)
+    if (entry.bitness == ONLY_32) continue;
+#else
+    if (entry.bitness == ONLY_64) continue;
+#endif
+
+    Result<bool> ret = filter_fn(entry);
+    if (!ret) {
+      return ret.error();
+    }
+    if (*ret) {
+      // filter_fn has returned true.
+      sonames.push_back(entry.soname);
+    }
+  }
+  return sonames;
+}
+
+}  // namespace internal
+
 }  // namespace android::nativeloader
diff --git a/libnativeloader/public_libraries.h b/libnativeloader/public_libraries.h
index 9bb3366..2de4172 100644
--- a/libnativeloader/public_libraries.h
+++ b/libnativeloader/public_libraries.h
@@ -15,13 +15,19 @@
  */
 #pragma once
 
+#include <algorithm>
 #include <string>
 
+#include <android-base/result.h>
+
 namespace android::nativeloader {
 
+using android::base::Result;
+
 // These provide the list of libraries that are available to the namespace for apps.
 // Not all of the libraries are available to apps. Depending on the context,
 // e.g., if it is a vendor app or not, different set of libraries are made available.
+const std::string& preloadable_public_libraries();
 const std::string& default_public_libraries();
 const std::string& runtime_public_libraries();
 const std::string& vendor_public_libraries();
@@ -30,4 +36,21 @@
 const std::string& llndk_libraries();
 const std::string& vndksp_libraries();
 
-};  // namespace android::nativeloader
+// These are exported for testing
+namespace internal {
+
+enum Bitness { ALL = 0, ONLY_32, ONLY_64 };
+
+struct ConfigEntry {
+  std::string soname;
+  bool nopreload;
+  Bitness bitness;
+};
+
+Result<std::vector<std::string>> ParseConfig(
+    const std::string& file_content,
+    const std::function<Result<bool>(const ConfigEntry& /* entry */)>& filter_fn);
+
+}  // namespace internal
+
+}  // namespace android::nativeloader
diff --git a/libpackagelistparser/.clang-format b/libpackagelistparser/.clang-format
new file mode 120000
index 0000000..fd0645f
--- /dev/null
+++ b/libpackagelistparser/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2
\ No newline at end of file
diff --git a/libpackagelistparser/Android.bp b/libpackagelistparser/Android.bp
index c38594a..0740e7d 100644
--- a/libpackagelistparser/Android.bp
+++ b/libpackagelistparser/Android.bp
@@ -1,12 +1,7 @@
 cc_library {
-
     name: "libpackagelistparser",
     recovery_available: true,
-    srcs: ["packagelistparser.c"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
+    srcs: ["packagelistparser.cpp"],
     shared_libs: ["liblog"],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
@@ -15,3 +10,13 @@
         misc_undefined: ["integer"],
     },
 }
+
+cc_test {
+    name: "libpackagelistparser_test",
+    srcs: ["packagelistparser_test.cpp"],
+    shared_libs: [
+        "libbase",
+        "libpackagelistparser",
+    ],
+    test_suites: ["device-tests"],
+}
diff --git a/libpackagelistparser/include/packagelistparser/packagelistparser.h b/libpackagelistparser/include/packagelistparser/packagelistparser.h
index 3cb6b9a..e89cb54 100644
--- a/libpackagelistparser/include/packagelistparser/packagelistparser.h
+++ b/libpackagelistparser/include/packagelistparser/packagelistparser.h
@@ -1,94 +1,81 @@
 /*
- * Copyright 2015, Intel Corporation
- * Copyright (C) 2015 The Android Open Source Project
+ * 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
+ *      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.
- *
- * Written by William Roberts <william.c.roberts@intel.com>
- *
- * This is a parser library for parsing the packages.list file generated
- * by PackageManager service.
- *
- * This simple parser is sensitive to format changes in
- * frameworks/base/services/core/java/com/android/server/pm/Settings.java
- * A dependency note has been added to that file to correct
- * this parser.
  */
 
-#ifndef PACKAGELISTPARSER_H_
-#define PACKAGELISTPARSER_H_
+#pragma once
 
 #include <stdbool.h>
-#include <sys/cdefs.h>
 #include <sys/types.h>
 
 __BEGIN_DECLS
 
-/** The file containing the list of installed packages on the system */
-#define PACKAGES_LIST_FILE  "/data/system/packages.list"
+typedef struct gid_list {
+  /** Number of gids. */
+  size_t cnt;
 
-typedef struct pkg_info pkg_info;
-typedef struct gid_list gid_list;
+  /** Array of gids. */
+  gid_t* gids;
+} gid_list;
 
-struct gid_list {
-    size_t cnt;
-    gid_t *gids;
-};
+typedef struct pkg_info {
+  /** Package name like "com.android.blah". */
+  char* name;
 
-struct pkg_info {
-    char *name;
-    uid_t uid;
-    bool debuggable;
-    char *data_dir;
-    char *seinfo;
-    gid_list gids;
-    void *private_data;
-    bool profileable_from_shell;
-    long version_code;
-};
+  /** Package uid like 10014. */
+  uid_t uid;
+
+  /** Package's AndroidManifest.xml debuggable flag. */
+  bool debuggable;
+
+  /** Package data directory like "/data/user/0/com.android.blah" */
+  char* data_dir;
+
+  /** Package SELinux info. */
+  char* seinfo;
+
+  /** Package's list of gids. */
+  gid_list gids;
+
+  /** Spare pointer for the caller to stash extra data off. */
+  void* private_data;
+
+  /** Package's AndroidManifest.xml profileable flag. */
+  bool profileable_from_shell;
+
+  /** Package's AndroidManifest.xml version code. */
+  long version_code;
+} pkg_info;
 
 /**
- * Callback function to be used by packagelist_parse() routine.
- * @param info
- *  The parsed package information
- * @param userdata
- *  The supplied userdata pointer to packagelist_parse()
- * @return
- *  true to keep processing, false to stop.
+ * Parses the system's default package list.
+ * Invokes `callback` once for each package.
+ * The callback owns the `pkg_info*` and should call packagelist_free().
+ * The callback should return `false` to exit early or `true` to continue.
  */
-typedef bool (*pfn_on_package)(pkg_info *info, void *userdata);
+bool packagelist_parse(bool (*callback)(pkg_info* info, void* user_data), void* user_data);
 
 /**
- * Parses the file specified by PACKAGES_LIST_FILE and invokes the callback on
- * each entry found. Once the callback is invoked, ownership of the pkg_info pointer
- * is passed to the callback routine, thus they are required to perform any cleanup
- * desired.
- * @param callback
- *  The callback function called on each parsed line of the packages list.
- * @param userdata
- *  An optional userdata supplied pointer to pass to the callback function.
- * @return
- *  true on success false on failure.
+ * Parses the given package list.
+ * Invokes `callback` once for each package.
+ * The callback owns the `pkg_info*` and should call packagelist_free().
+ * The callback should return `false` to exit early or `true` to continue.
  */
-extern bool packagelist_parse(pfn_on_package callback, void *userdata);
+bool packagelist_parse_file(const char* path, bool (*callback)(pkg_info* info, void* user_data),
+                            void* user_data);
 
-/**
- * Frees a pkg_info structure.
- * @param info
- *  The struct to free
- */
-extern void packagelist_free(pkg_info *info);
+/** Frees the given `pkg_info`. */
+void packagelist_free(pkg_info* info);
 
 __END_DECLS
-
-#endif /* PACKAGELISTPARSER_H_ */
diff --git a/libpackagelistparser/packagelistparser.c b/libpackagelistparser/packagelistparser.c
deleted file mode 100644
index edc533c..0000000
--- a/libpackagelistparser/packagelistparser.c
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright 2015, Intel Corporation
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Written by William Roberts <william.c.roberts@intel.com>
- *
- */
-
-#define LOG_TAG "packagelistparser"
-
-#include <errno.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/limits.h>
-
-#include <log/log.h>
-#include <packagelistparser/packagelistparser.h>
-
-#define CLOGE(fmt, ...) \
-    do {\
-        IF_ALOGE() {\
-            ALOGE(fmt, ##__VA_ARGS__);\
-        }\
-    } while(0)
-
-static size_t get_gid_cnt(const char *gids)
-{
-    size_t cnt;
-
-    if (*gids == '\0') {
-        return 0;
-    }
-
-    if (!strcmp(gids, "none")) {
-        return 0;
-    }
-
-    for (cnt = 1; gids[cnt]; gids[cnt] == ',' ? cnt++ : *gids++)
-        ;
-
-    return cnt;
-}
-
-static bool parse_gids(char *gids, gid_t *gid_list, size_t *cnt)
-{
-    gid_t gid;
-    char* token;
-    char *endptr;
-    size_t cmp = 0;
-
-    while ((token = strsep(&gids, ",\r\n"))) {
-
-        if (cmp > *cnt) {
-            return false;
-        }
-
-        gid = strtoul(token, &endptr, 10);
-        if (*endptr != '\0') {
-            return false;
-        }
-
-        /*
-         * if unsigned long is greater than size of gid_t,
-         * prevent a truncation based roll-over
-         */
-        if (gid > GID_MAX) {
-            CLOGE("A gid in field \"gid list\" greater than GID_MAX");
-            return false;
-        }
-
-        gid_list[cmp++] = gid;
-    }
-    return true;
-}
-
-extern bool packagelist_parse(pfn_on_package callback, void *userdata)
-{
-
-    FILE *fp;
-    char *cur;
-    char *next;
-    char *endptr;
-    unsigned long tmp;
-    ssize_t bytesread;
-
-    bool rc = false;
-    char *buf = NULL;
-    size_t buflen = 0;
-    unsigned long lineno = 1;
-    const char *errmsg = NULL;
-    struct pkg_info *pkg_info = NULL;
-
-    fp = fopen(PACKAGES_LIST_FILE, "re");
-    if (!fp) {
-        CLOGE("Could not open: \"%s\", error: \"%s\"\n", PACKAGES_LIST_FILE,
-                strerror(errno));
-        return false;
-    }
-
-    while ((bytesread = getline(&buf, &buflen, fp)) > 0) {
-
-        pkg_info = calloc(1, sizeof(*pkg_info));
-        if (!pkg_info) {
-            goto err;
-        }
-
-        next = buf;
-
-        cur = strsep(&next, " \t\r\n");
-        if (!cur) {
-            errmsg = "Could not get next token for \"package name\"";
-            goto err;
-        }
-
-        pkg_info->name = strdup(cur);
-        if (!pkg_info->name) {
-            goto err;
-        }
-
-        cur = strsep(&next, " \t\r\n");
-        if (!cur) {
-            errmsg = "Could not get next token for field \"uid\"";
-            goto err;
-        }
-
-        tmp = strtoul(cur, &endptr, 10);
-        if (*endptr != '\0') {
-            errmsg = "Could not convert field \"uid\" to integer value";
-            goto err;
-        }
-
-        /*
-         * if unsigned long is greater than size of uid_t,
-         * prevent a truncation based roll-over
-         */
-        if (tmp > UID_MAX) {
-            errmsg = "Field \"uid\" greater than UID_MAX";
-            goto err;
-        }
-
-        pkg_info->uid = (uid_t) tmp;
-
-        cur = strsep(&next, " \t\r\n");
-        if (!cur) {
-            errmsg = "Could not get next token for field \"debuggable\"";
-            goto err;
-        }
-
-        tmp = strtoul(cur, &endptr, 10);
-        if (*endptr != '\0') {
-            errmsg = "Could not convert field \"debuggable\" to integer value";
-            goto err;
-        }
-
-        /* should be a valid boolean of 1 or 0 */
-        if (!(tmp == 0 || tmp == 1)) {
-            errmsg = "Field \"debuggable\" is not 0 or 1 boolean value";
-            goto err;
-        }
-
-        pkg_info->debuggable = (bool) tmp;
-
-        cur = strsep(&next, " \t\r\n");
-        if (!cur) {
-            errmsg = "Could not get next token for field \"data dir\"";
-            goto err;
-        }
-
-        pkg_info->data_dir = strdup(cur);
-        if (!pkg_info->data_dir) {
-            goto err;
-        }
-
-        cur = strsep(&next, " \t\r\n");
-        if (!cur) {
-            errmsg = "Could not get next token for field \"seinfo\"";
-            goto err;
-        }
-
-        pkg_info->seinfo = strdup(cur);
-        if (!pkg_info->seinfo) {
-            goto err;
-        }
-
-        cur = strsep(&next, " \t\r\n");
-        if (!cur) {
-            errmsg = "Could not get next token for field \"gid(s)\"";
-            goto err;
-        }
-
-        /*
-         * Parse the gid list, could be in the form of none, single gid or list:
-         * none
-         * gid
-         * gid, gid ...
-         */
-        pkg_info->gids.cnt = get_gid_cnt(cur);
-        if (pkg_info->gids.cnt > 0) {
-
-            pkg_info->gids.gids = calloc(pkg_info->gids.cnt, sizeof(gid_t));
-            if (!pkg_info->gids.gids) {
-                goto err;
-            }
-
-            rc = parse_gids(cur, pkg_info->gids.gids, &pkg_info->gids.cnt);
-            if (!rc) {
-                errmsg = "Could not parse field \"gid list\"";
-                goto err;
-            }
-        }
-
-        cur = strsep(&next, " \t\r\n");
-        if (cur) {
-            tmp = strtoul(cur, &endptr, 10);
-            if (*endptr != '\0') {
-                errmsg = "Could not convert field \"profileable_from_shell\" to integer value";
-                goto err;
-            }
-
-            /* should be a valid boolean of 1 or 0 */
-            if (!(tmp == 0 || tmp == 1)) {
-                errmsg = "Field \"profileable_from_shell\" is not 0 or 1 boolean value";
-                goto err;
-            }
-
-            pkg_info->profileable_from_shell = (bool)tmp;
-        }
-        cur = strsep(&next, " \t\r\n");
-        if (cur) {
-            tmp = strtoul(cur, &endptr, 10);
-            if (*endptr != '\0') {
-                errmsg = "Could not convert field \"versionCode\" to integer value";
-                goto err;
-            }
-            pkg_info->version_code = tmp;
-        }
-
-        rc = callback(pkg_info, userdata);
-        if (rc == false) {
-            /*
-             * We do not log this as this can be intentional from
-             * callback to abort processing. We go to out to not
-             * free the pkg_info
-             */
-            rc = true;
-            goto out;
-        }
-        lineno++;
-    }
-
-    rc = true;
-
-out:
-    free(buf);
-    fclose(fp);
-    return rc;
-
-err:
-    if (errmsg) {
-        CLOGE("Error Parsing \"%s\" on line: %lu for reason: %s",
-                PACKAGES_LIST_FILE, lineno, errmsg);
-    }
-    rc = false;
-    packagelist_free(pkg_info);
-    goto out;
-}
-
-void packagelist_free(pkg_info *info)
-{
-    if (info) {
-        free(info->name);
-        free(info->data_dir);
-        free(info->seinfo);
-        free(info->gids.gids);
-        free(info);
-    }
-}
diff --git a/libpackagelistparser/packagelistparser.cpp b/libpackagelistparser/packagelistparser.cpp
new file mode 100644
index 0000000..59c3a74
--- /dev/null
+++ b/libpackagelistparser/packagelistparser.cpp
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "packagelistparser"
+
+#include <packagelistparser/packagelistparser.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/limits.h>
+
+#include <memory>
+
+#include <log/log.h>
+
+static bool parse_gids(const char* path, size_t line_number, const char* gids, pkg_info* info) {
+  // Nothing to do?
+  if (!gids || !strcmp(gids, "none")) return true;
+
+  // How much space do we need?
+  info->gids.cnt = 1;
+  for (const char* p = gids; *p; ++p) {
+    if (*p == ',') ++info->gids.cnt;
+  }
+
+  // Allocate the space.
+  info->gids.gids = new gid_t[info->gids.cnt];
+  if (!info->gids.gids) return false;
+
+  // And parse the individual gids.
+  size_t i = 0;
+  while (true) {
+    char* end;
+    unsigned long gid = strtoul(gids, &end, 10);
+    if (gid > GID_MAX) {
+      ALOGE("%s:%zu: gid %lu > GID_MAX", path, line_number, gid);
+      return false;
+    }
+
+    if (i >= info->gids.cnt) return false;
+    info->gids.gids[i++] = gid;
+
+    if (*end == '\0') return true;
+    if (*end != ',') return false;
+    gids = end + 1;
+  }
+  return true;
+}
+
+static bool parse_line(const char* path, size_t line_number, const char* line, pkg_info* info) {
+  unsigned long uid;
+  int debuggable;
+  char* gid_list;
+  int profileable_from_shell = 0;
+
+  int fields =
+      sscanf(line, "%ms %lu %d %ms %ms %ms %d %ld", &info->name, &uid, &debuggable, &info->data_dir,
+             &info->seinfo, &gid_list, &profileable_from_shell, &info->version_code);
+
+  // Handle the more complicated gids field and free the temporary string.
+  bool gids_okay = parse_gids(path, line_number, gid_list, info);
+  free(gid_list);
+  if (!gids_okay) return false;
+
+  // Did we see enough fields to be getting on with?
+  // The final fields are optional (and not usually present).
+  if (fields < 6) {
+    ALOGE("%s:%zu: too few fields in line", path, line_number);
+    return false;
+  }
+
+  // Extra validation.
+  if (uid > UID_MAX) {
+    ALOGE("%s:%zu: uid %lu > UID_MAX", path, line_number, uid);
+    return false;
+  }
+  info->uid = uid;
+
+  // Integer to bool conversions.
+  info->debuggable = debuggable;
+  info->profileable_from_shell = profileable_from_shell;
+
+  return true;
+}
+
+bool packagelist_parse_file(const char* path, bool (*callback)(pkg_info*, void*), void* user_data) {
+  std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(path, "re"), &fclose);
+  if (!fp) {
+    ALOGE("couldn't open '%s': %s", path, strerror(errno));
+    return false;
+  }
+
+  size_t line_number = 0;
+  char* line = nullptr;
+  size_t allocated_length = 0;
+  while (getline(&line, &allocated_length, fp.get()) > 0) {
+    ++line_number;
+    std::unique_ptr<pkg_info, decltype(&packagelist_free)> info(
+        static_cast<pkg_info*>(calloc(1, sizeof(pkg_info))), &packagelist_free);
+    if (!info) {
+      ALOGE("%s:%zu: couldn't allocate pkg_info", path, line_number);
+      return false;
+    }
+
+    if (!parse_line(path, line_number, line, info.get())) return false;
+
+    if (!callback(info.release(), user_data)) break;
+  }
+  free(line);
+  return true;
+}
+
+bool packagelist_parse(bool (*callback)(pkg_info*, void*), void* user_data) {
+  return packagelist_parse_file("/data/system/packages.list", callback, user_data);
+}
+
+void packagelist_free(pkg_info* info) {
+  if (!info) return;
+
+  free(info->name);
+  free(info->data_dir);
+  free(info->seinfo);
+  delete[] info->gids.gids;
+  free(info);
+}
diff --git a/libpackagelistparser/packagelistparser_test.cpp b/libpackagelistparser/packagelistparser_test.cpp
new file mode 100644
index 0000000..76cb886
--- /dev/null
+++ b/libpackagelistparser/packagelistparser_test.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 <packagelistparser/packagelistparser.h>
+
+#include <memory>
+
+#include <android-base/file.h>
+
+#include <gtest/gtest.h>
+
+TEST(packagelistparser, smoke) {
+  TemporaryFile tf;
+  android::base::WriteStringToFile(
+      // No gids.
+      "com.test.a0 10014 0 /data/user/0/com.test.a0 platform:privapp:targetSdkVersion=19 none\n"
+      // One gid.
+      "com.test.a1 10007 1 /data/user/0/com.test.a1 platform:privapp:targetSdkVersion=21 1023\n"
+      // Multiple gids.
+      "com.test.a2 10011 0 /data/user/0/com.test.a2 media:privapp:targetSdkVersion=30 "
+      "2001,1065,1023,3003,3007,1024\n"
+      // The two new fields (profileable flag and version code).
+      "com.test.a3 10022 0 /data/user/0/com.test.a3 selabel:blah none 1 123\n",
+      tf.path);
+
+  std::vector<pkg_info*> packages;
+  packagelist_parse_file(
+      tf.path,
+      [](pkg_info* info, void* user_data) -> bool {
+        reinterpret_cast<std::vector<pkg_info*>*>(user_data)->push_back(info);
+        return true;
+      },
+      &packages);
+
+  ASSERT_EQ(4U, packages.size());
+
+  ASSERT_STREQ("com.test.a0", packages[0]->name);
+  ASSERT_EQ(10014, packages[0]->uid);
+  ASSERT_FALSE(packages[0]->debuggable);
+  ASSERT_STREQ("/data/user/0/com.test.a0", packages[0]->data_dir);
+  ASSERT_STREQ("platform:privapp:targetSdkVersion=19", packages[0]->seinfo);
+  ASSERT_EQ(0U, packages[0]->gids.cnt);
+  ASSERT_FALSE(packages[0]->profileable_from_shell);
+  ASSERT_EQ(0, packages[0]->version_code);
+
+  ASSERT_STREQ("com.test.a1", packages[1]->name);
+  ASSERT_EQ(10007, packages[1]->uid);
+  ASSERT_TRUE(packages[1]->debuggable);
+  ASSERT_STREQ("/data/user/0/com.test.a1", packages[1]->data_dir);
+  ASSERT_STREQ("platform:privapp:targetSdkVersion=21", packages[1]->seinfo);
+  ASSERT_EQ(1U, packages[1]->gids.cnt);
+  ASSERT_EQ(1023U, packages[1]->gids.gids[0]);
+  ASSERT_FALSE(packages[0]->profileable_from_shell);
+  ASSERT_EQ(0, packages[0]->version_code);
+
+  ASSERT_STREQ("com.test.a2", packages[2]->name);
+  ASSERT_EQ(10011, packages[2]->uid);
+  ASSERT_FALSE(packages[2]->debuggable);
+  ASSERT_STREQ("/data/user/0/com.test.a2", packages[2]->data_dir);
+  ASSERT_STREQ("media:privapp:targetSdkVersion=30", packages[2]->seinfo);
+  ASSERT_EQ(6U, packages[2]->gids.cnt);
+  ASSERT_EQ(2001U, packages[2]->gids.gids[0]);
+  ASSERT_EQ(1024U, packages[2]->gids.gids[5]);
+  ASSERT_FALSE(packages[0]->profileable_from_shell);
+  ASSERT_EQ(0, packages[0]->version_code);
+
+  ASSERT_STREQ("com.test.a3", packages[3]->name);
+  ASSERT_EQ(10022, packages[3]->uid);
+  ASSERT_FALSE(packages[3]->debuggable);
+  ASSERT_STREQ("/data/user/0/com.test.a3", packages[3]->data_dir);
+  ASSERT_STREQ("selabel:blah", packages[3]->seinfo);
+  ASSERT_EQ(0U, packages[3]->gids.cnt);
+  ASSERT_TRUE(packages[3]->profileable_from_shell);
+  ASSERT_EQ(123, packages[3]->version_code);
+
+  for (auto& package : packages) packagelist_free(package);
+}
+
+TEST(packagelistparser, early_exit) {
+  TemporaryFile tf;
+  android::base::WriteStringToFile(
+      "com.test.a0 1 0 / a none\n"
+      "com.test.a1 1 0 / a none\n"
+      "com.test.a2 1 0 / a none\n",
+      tf.path);
+
+  std::vector<pkg_info*> packages;
+  packagelist_parse_file(
+      tf.path,
+      [](pkg_info* info, void* user_data) -> bool {
+        std::vector<pkg_info*>* p = reinterpret_cast<std::vector<pkg_info*>*>(user_data);
+        p->push_back(info);
+        return p->size() < 2;
+      },
+      &packages);
+
+  ASSERT_EQ(2U, packages.size());
+
+  ASSERT_STREQ("com.test.a0", packages[0]->name);
+  ASSERT_STREQ("com.test.a1", packages[1]->name);
+
+  for (auto& package : packages) packagelist_free(package);
+}
+
+TEST(packagelistparser, system_package_list) {
+  // Check that we can actually read the packages.list installed on the device.
+  std::vector<pkg_info*> packages;
+  packagelist_parse(
+      [](pkg_info* info, void* user_data) -> bool {
+        reinterpret_cast<std::vector<pkg_info*>*>(user_data)->push_back(info);
+        return true;
+      },
+      &packages);
+  // Not much we can say for sure about what we expect, other than that there
+  // are likely to be lots of packages...
+  ASSERT_GT(packages.size(), 10U);
+}
+
+TEST(packagelistparser, packagelist_free_nullptr) {
+  packagelist_free(nullptr);
+}
diff --git a/libprocessgroup/sched_policy.cpp b/libprocessgroup/sched_policy.cpp
index 15f8139..c83df1a 100644
--- a/libprocessgroup/sched_policy.cpp
+++ b/libprocessgroup/sched_policy.cpp
@@ -134,15 +134,27 @@
 
     switch (policy) {
         case SP_BACKGROUND:
-            return SetTaskProfiles(tid, {"HighEnergySaving", "TimerSlackHigh"}, true) ? 0 : -1;
+            return SetTaskProfiles(tid, {"HighEnergySaving", "LowIoPriority", "TimerSlackHigh"},
+                                   true)
+                           ? 0
+                           : -1;
         case SP_FOREGROUND:
         case SP_AUDIO_APP:
         case SP_AUDIO_SYS:
-            return SetTaskProfiles(tid, {"HighPerformance", "TimerSlackNormal"}, true) ? 0 : -1;
+            return SetTaskProfiles(tid, {"HighPerformance", "HighIoPriority", "TimerSlackNormal"},
+                                   true)
+                           ? 0
+                           : -1;
         case SP_TOP_APP:
-            return SetTaskProfiles(tid, {"MaxPerformance", "TimerSlackNormal"}, true) ? 0 : -1;
+            return SetTaskProfiles(tid, {"MaxPerformance", "MaxIoPriority", "TimerSlackNormal"},
+                                   true)
+                           ? 0
+                           : -1;
         case SP_RT_APP:
-            return SetTaskProfiles(tid, {"RealtimePerformance", "TimerSlackNormal"}, true) ? 0 : -1;
+            return SetTaskProfiles(
+                           tid, {"RealtimePerformance", "MaxIoPriority", "TimerSlackNormal"}, true)
+                           ? 0
+                           : -1;
         default:
             return SetTaskProfiles(tid, {"TimerSlackNormal"}, true) ? 0 : -1;
     }
diff --git a/libprocinfo/Android.bp b/libprocinfo/Android.bp
index 15f03d0..0c9a2b8 100644
--- a/libprocinfo/Android.bp
+++ b/libprocinfo/Android.bp
@@ -34,6 +34,7 @@
     host_supported: true,
     srcs: [
         "process.cpp",
+        "process_map.cpp",
     ],
 
     local_include_dirs: ["include"],
@@ -58,6 +59,7 @@
     name: "libprocinfo_test",
     defaults: ["libprocinfo_defaults"],
     host_supported: true,
+    isolated: true,
     srcs: [
         "process_test.cpp",
         "process_map_test.cpp",
diff --git a/libprocinfo/include/procinfo/process_map.h b/libprocinfo/include/procinfo/process_map.h
index b6ec3cb..569a022 100644
--- a/libprocinfo/include/procinfo/process_map.h
+++ b/libprocinfo/include/procinfo/process_map.h
@@ -176,5 +176,9 @@
                const char* name) { maps->emplace_back(start, end, flags, pgoff, inode, name); });
 }
 
+bool ReadMapFileAsyncSafe(const char* map_file, void* buffer, size_t buffer_size,
+                          const std::function<void(uint64_t, uint64_t, uint16_t, uint64_t, ino_t,
+                                                   const char*)>& callback);
+
 } /* namespace procinfo */
 } /* namespace android */
diff --git a/libprocinfo/process_map.cpp b/libprocinfo/process_map.cpp
new file mode 100644
index 0000000..5e240b9
--- /dev/null
+++ b/libprocinfo/process_map.cpp
@@ -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.
+ */
+
+#include <procinfo/process_map.h>
+
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <procinfo/process.h>
+
+namespace android {
+namespace procinfo {
+
+bool ReadMapFileAsyncSafe(const char* map_file, void* buffer, size_t buffer_size,
+                          const std::function<void(uint64_t, uint64_t, uint16_t, uint64_t, ino_t,
+                                                   const char*)>& callback) {
+  if (buffer == nullptr || buffer_size == 0) {
+    return false;
+  }
+
+  int fd = open(map_file, O_RDONLY | O_CLOEXEC);
+  if (fd == -1) {
+    return false;
+  }
+
+  char* char_buffer = reinterpret_cast<char*>(buffer);
+  size_t start = 0;
+  size_t read_bytes = 0;
+  char* line = nullptr;
+  bool read_complete = false;
+  while (true) {
+    ssize_t bytes =
+        TEMP_FAILURE_RETRY(read(fd, char_buffer + read_bytes, buffer_size - read_bytes - 1));
+    if (bytes <= 0) {
+      if (read_bytes == 0) {
+        close(fd);
+        return bytes == 0;
+      }
+      // Treat the last piece of data as the last line.
+      char_buffer[start + read_bytes] = '\n';
+      bytes = 1;
+      read_complete = true;
+    }
+    read_bytes += bytes;
+
+    while (read_bytes > 0) {
+      char* newline = reinterpret_cast<char*>(memchr(&char_buffer[start], '\n', read_bytes));
+      if (newline == nullptr) {
+        break;
+      }
+      *newline = '\0';
+      line = &char_buffer[start];
+      start = newline - char_buffer + 1;
+      read_bytes -= newline - line + 1;
+
+      // Ignore the return code, errors are okay.
+      ReadMapFileContent(line, callback);
+    }
+
+    if (read_complete) {
+      close(fd);
+      return true;
+    }
+
+    if (start == 0 && read_bytes == buffer_size - 1) {
+      // The buffer provided is too small to contain this line, give up
+      // and indicate failure.
+      close(fd);
+      return false;
+    }
+
+    // Copy any leftover data to the front  of the buffer.
+    if (start > 0) {
+      if (read_bytes > 0) {
+        memmove(char_buffer, &char_buffer[start], read_bytes);
+      }
+      start = 0;
+    }
+  }
+}
+
+} /* namespace procinfo */
+} /* namespace android */
diff --git a/libprocinfo/process_map_test.cpp b/libprocinfo/process_map_test.cpp
index 562d864..b1bdc08 100644
--- a/libprocinfo/process_map_test.cpp
+++ b/libprocinfo/process_map_test.cpp
@@ -16,9 +16,14 @@
 
 #include <procinfo/process_map.h>
 
+#include <inttypes.h>
+#include <sys/mman.h>
+
 #include <string>
+#include <vector>
 
 #include <android-base/file.h>
+#include <android-base/stringprintf.h>
 
 #include <gtest/gtest.h>
 
@@ -63,3 +68,215 @@
   ASSERT_TRUE(android::procinfo::ReadProcessMaps(getpid(), &maps));
   ASSERT_GT(maps.size(), 0u);
 }
+
+extern "C" void malloc_disable();
+extern "C" void malloc_enable();
+
+struct TestMapInfo {
+  TestMapInfo() = default;
+  TestMapInfo(uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, ino_t inode,
+              const char* new_name)
+      : start(start), end(end), flags(flags), pgoff(pgoff), inode(inode) {
+    strcpy(name, new_name);
+  }
+  uint64_t start = 0;
+  uint64_t end = 0;
+  uint16_t flags = 0;
+  uint64_t pgoff = 0;
+  ino_t inode = 0;
+  char name[100] = {};
+};
+
+void VerifyReadMapFileAsyncSafe(const char* maps_data,
+                                const std::vector<TestMapInfo>& expected_info) {
+  TemporaryFile tf;
+  ASSERT_TRUE(android::base::WriteStringToFd(maps_data, tf.fd));
+
+  std::vector<TestMapInfo> saved_info(expected_info.size());
+  size_t num_maps = 0;
+
+  auto callback = [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, ino_t inode,
+                      const char* name) {
+    if (num_maps != saved_info.size()) {
+      TestMapInfo& saved = saved_info[num_maps];
+      saved.start = start;
+      saved.end = end;
+      saved.flags = flags;
+      saved.pgoff = pgoff;
+      saved.inode = inode;
+      strcpy(saved.name, name);
+    }
+    num_maps++;
+  };
+
+  std::vector<char> buffer(64 * 1024);
+
+#if defined(__BIONIC__)
+  // Any allocations will block after this call.
+  malloc_disable();
+#endif
+
+  bool parsed =
+      android::procinfo::ReadMapFileAsyncSafe(tf.path, buffer.data(), buffer.size(), callback);
+
+#if defined(__BIONIC__)
+  malloc_enable();
+#endif
+
+  ASSERT_TRUE(parsed) << "Parsing of data failed:\n" << maps_data;
+  ASSERT_EQ(expected_info.size(), num_maps);
+  for (size_t i = 0; i < expected_info.size(); i++) {
+    const TestMapInfo& expected = expected_info[i];
+    const TestMapInfo& saved = saved_info[i];
+    EXPECT_EQ(expected.start, saved.start);
+    EXPECT_EQ(expected.end, saved.end);
+    EXPECT_EQ(expected.flags, saved.flags);
+    EXPECT_EQ(expected.pgoff, saved.pgoff);
+    EXPECT_EQ(expected.inode, saved.inode);
+    EXPECT_STREQ(expected.name, saved.name);
+  }
+}
+
+TEST(process_map, ReadMapFileAsyncSafe_invalid) {
+  std::vector<TestMapInfo> expected_info;
+
+  VerifyReadMapFileAsyncSafe("12c00000-2ac00000", expected_info);
+}
+
+TEST(process_map, ReadMapFileAsyncSafe_single) {
+  std::vector<TestMapInfo> expected_info;
+  expected_info.emplace_back(0x12c00000, 0x2ac00000, PROT_READ | PROT_WRITE, 0x100, 10267643,
+                             "/lib/fake.so");
+
+  VerifyReadMapFileAsyncSafe("12c00000-2ac00000 rw-p 00000100 00:05 10267643 /lib/fake.so",
+                             expected_info);
+}
+
+TEST(process_map, ReadMapFileAsyncSafe_single_with_newline) {
+  std::vector<TestMapInfo> expected_info;
+  expected_info.emplace_back(0x12c00000, 0x2ac00000, PROT_READ | PROT_WRITE, 0x100, 10267643,
+                             "/lib/fake.so");
+
+  VerifyReadMapFileAsyncSafe("12c00000-2ac00000 rw-p 00000100 00:05 10267643 /lib/fake.so\n",
+                             expected_info);
+}
+
+TEST(process_map, ReadMapFileAsyncSafe_single_no_library) {
+  std::vector<TestMapInfo> expected_info;
+  expected_info.emplace_back(0xa0000, 0xc0000, PROT_READ | PROT_WRITE | PROT_EXEC, 0xb00, 101, "");
+
+  VerifyReadMapFileAsyncSafe("a0000-c0000 rwxp 00000b00 00:05 101", expected_info);
+}
+
+TEST(process_map, ReadMapFileAsyncSafe_multiple) {
+  std::vector<TestMapInfo> expected_info;
+  expected_info.emplace_back(0xa0000, 0xc0000, PROT_READ | PROT_WRITE | PROT_EXEC, 1, 100, "");
+  expected_info.emplace_back(0xd0000, 0xe0000, PROT_READ, 2, 101, "/lib/libsomething1.so");
+  expected_info.emplace_back(0xf0000, 0x100000, PROT_WRITE, 3, 102, "/lib/libsomething2.so");
+  expected_info.emplace_back(0x110000, 0x120000, PROT_EXEC, 4, 103, "[anon:something or another]");
+
+  std::string map_data =
+      "0a0000-0c0000 rwxp 00000001 00:05 100\n"
+      "0d0000-0e0000 r--p 00000002 00:05 101  /lib/libsomething1.so\n"
+      "0f0000-100000 -w-p 00000003 00:05 102  /lib/libsomething2.so\n"
+      "110000-120000 --xp 00000004 00:05 103  [anon:something or another]\n";
+
+  VerifyReadMapFileAsyncSafe(map_data.c_str(), expected_info);
+}
+
+TEST(process_map, ReadMapFileAsyncSafe_multiple_reads) {
+  std::vector<TestMapInfo> expected_info;
+  std::string map_data;
+  uint64_t start = 0xa0000;
+  for (size_t i = 0; i < 10000; i++) {
+    map_data += android::base::StringPrintf("%" PRIx64 "-%" PRIx64 " r--p %zx 01:20 %zu fake.so\n",
+                                            start, start + 0x1000, i, 1000 + i);
+    expected_info.emplace_back(start, start + 0x1000, PROT_READ, i, 1000 + i, "fake.so");
+  }
+
+  VerifyReadMapFileAsyncSafe(map_data.c_str(), expected_info);
+}
+
+TEST(process_map, ReadMapFileAsyncSafe_buffer_nullptr) {
+  size_t num_calls = 0;
+  auto callback = [&](uint64_t, uint64_t, uint16_t, uint64_t, ino_t, const char*) { num_calls++; };
+
+#if defined(__BIONIC__)
+  // Any allocations will block after this call.
+  malloc_disable();
+#endif
+
+  bool parsed = android::procinfo::ReadMapFileAsyncSafe("/proc/self/maps", nullptr, 10, callback);
+
+#if defined(__BIONIC__)
+  malloc_enable();
+#endif
+
+  ASSERT_FALSE(parsed);
+  EXPECT_EQ(0UL, num_calls);
+}
+
+TEST(process_map, ReadMapFileAsyncSafe_buffer_size_zero) {
+  size_t num_calls = 0;
+  auto callback = [&](uint64_t, uint64_t, uint16_t, uint64_t, ino_t, const char*) { num_calls++; };
+
+#if defined(__BIONIC__)
+  // Any allocations will block after this call.
+  malloc_disable();
+#endif
+
+  char buffer[10];
+  bool parsed = android::procinfo::ReadMapFileAsyncSafe("/proc/self/maps", buffer, 0, callback);
+
+#if defined(__BIONIC__)
+  malloc_enable();
+#endif
+
+  ASSERT_FALSE(parsed);
+  EXPECT_EQ(0UL, num_calls);
+}
+
+TEST(process_map, ReadMapFileAsyncSafe_buffer_too_small_no_calls) {
+  size_t num_calls = 0;
+  auto callback = [&](uint64_t, uint64_t, uint16_t, uint64_t, ino_t, const char*) { num_calls++; };
+
+#if defined(__BIONIC__)
+  // Any allocations will block after this call.
+  malloc_disable();
+#endif
+
+  char buffer[10];
+  bool parsed =
+      android::procinfo::ReadMapFileAsyncSafe("/proc/self/maps", buffer, sizeof(buffer), callback);
+
+#if defined(__BIONIC__)
+  malloc_enable();
+#endif
+
+  ASSERT_FALSE(parsed);
+  EXPECT_EQ(0UL, num_calls);
+}
+
+TEST(process_map, ReadMapFileAsyncSafe_buffer_too_small_could_parse) {
+  TemporaryFile tf;
+  ASSERT_TRUE(android::base::WriteStringToFd(
+      "0a0000-0c0000 rwxp 00000001 00:05 100    /fake/lib.so\n", tf.fd));
+
+  size_t num_calls = 0;
+  auto callback = [&](uint64_t, uint64_t, uint16_t, uint64_t, ino_t, const char*) { num_calls++; };
+
+#if defined(__BIONIC__)
+  // Any allocations will block after this call.
+  malloc_disable();
+#endif
+
+  char buffer[39];
+  bool parsed = android::procinfo::ReadMapFileAsyncSafe(tf.path, buffer, sizeof(buffer), callback);
+
+#if defined(__BIONIC__)
+  malloc_enable();
+#endif
+
+  ASSERT_FALSE(parsed);
+  EXPECT_EQ(0UL, num_calls);
+}
diff --git a/libsparse/output_file.cpp b/libsparse/output_file.cpp
index 5b8179f..c5c4960 100644
--- a/libsparse/output_file.cpp
+++ b/libsparse/output_file.cpp
@@ -17,6 +17,7 @@
 #define _FILE_OFFSET_BITS 64
 #define _LARGEFILE64_SOURCE 1
 
+#include <algorithm>
 #include <fcntl.h>
 #include <inttypes.h>
 #include <limits.h>
@@ -48,13 +49,6 @@
 #define off64_t off_t
 #endif
 
-#define min(a, b)        \
-  ({                     \
-    typeof(a) _a = (a);  \
-    typeof(b) _b = (b);  \
-    (_a < _b) ? _a : _b; \
-  })
-
 #define SPARSE_HEADER_MAJOR_VER 1
 #define SPARSE_HEADER_MINOR_VER 0
 #define SPARSE_HEADER_LEN (sizeof(sparse_header_t))
@@ -231,7 +225,7 @@
   struct output_file_gz* outgz = to_output_file_gz(out);
 
   while (len > 0) {
-    ret = gzwrite(outgz->gz_fd, data, min(len, (unsigned int)INT_MAX));
+    ret = gzwrite(outgz->gz_fd, data, std::min<unsigned int>(len, (unsigned int)INT_MAX));
     if (ret == 0) {
       error("gzwrite %s", gzerror(outgz->gz_fd, nullptr));
       return -1;
@@ -268,7 +262,7 @@
   int ret;
 
   while (off > 0) {
-    to_write = min(off, (int64_t)INT_MAX);
+    to_write = std::min(off, (int64_t)INT_MAX);
     ret = outc->write(outc->priv, nullptr, to_write);
     if (ret < 0) {
       return ret;
@@ -470,7 +464,7 @@
   }
 
   while (len) {
-    write_len = min(len, out->block_size);
+    write_len = std::min(len, out->block_size);
     ret = out->ops->write(out, out->fill_buf, write_len);
     if (ret < 0) {
       return ret;
diff --git a/libstats/include/stats_event_list.h b/libstats/include/stats_event_list.h
index 845a197..b7ada0c 100644
--- a/libstats/include/stats_event_list.h
+++ b/libstats/include/stats_event_list.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_STATS_LOG_STATS_EVENT_LIST_H
-#define ANDROID_STATS_LOG_STATS_EVENT_LIST_H
+#pragma once
 
 #include <log/log_event_list.h>
 #include <sys/uio.h>
@@ -133,7 +132,6 @@
         return *this;
     }
 
-#if defined(_USING_LIBCXX)
     stats_event_list& operator<<(const std::string& value) {
         int retval = android_log_write_string8_len(ctx, value.data(), value.length());
         if (retval < 0) {
@@ -141,7 +139,6 @@
         }
         return *this;
     }
-#endif
 
     stats_event_list& operator<<(float value) {
         int retval = android_log_write_float32(ctx, value);
@@ -203,7 +200,6 @@
         return ret >= 0;
     }
 
-#if defined(_USING_LIBCXX)
     bool AppendString(const std::string& value) {
         int retval = android_log_write_string8_len(ctx, value.data(), value.length());
         if (retval < 0) {
@@ -219,7 +215,6 @@
         }
         return ret;
     }
-#endif
 
     bool AppendFloat(float value) {
         int retval = android_log_write_float32(ctx, value);
@@ -253,4 +248,3 @@
 };
 
 #endif
-#endif  // ANDROID_STATS_LOG_STATS_EVENT_LIST_H
diff --git a/libunwindstack/tools/unwind_reg_info.cpp b/libunwindstack/tools/unwind_reg_info.cpp
index d0562d9..0cbcac5 100644
--- a/libunwindstack/tools/unwind_reg_info.cpp
+++ b/libunwindstack/tools/unwind_reg_info.cpp
@@ -165,8 +165,8 @@
   }
 }
 
-int GetInfo(const char* file, uint64_t pc) {
-  Elf elf(Memory::CreateFileMemory(file, pc).release());
+int GetInfo(const char* file, uint64_t offset, uint64_t pc) {
+  Elf elf(Memory::CreateFileMemory(file, offset).release());
   if (!elf.Init() || !elf.valid()) {
     printf("%s is not a valid elf file.\n", file);
     return 1;
@@ -243,12 +243,14 @@
 }  // namespace unwindstack
 
 int main(int argc, char** argv) {
-  if (argc != 3) {
-    printf("Usage: unwind_reg_info ELF_FILE PC\n");
+  if (argc != 3 && argc != 4) {
+    printf("Usage: unwind_reg_info ELF_FILE PC [OFFSET]\n");
     printf("  ELF_FILE\n");
     printf("    The path to an elf file.\n");
     printf("  PC\n");
     printf("    The pc for which the register information should be obtained.\n");
+    printf("  OFFSET\n");
+    printf("    Use the offset into the ELF file as the beginning of the elf.\n");
     return 1;
   }
 
@@ -270,5 +272,15 @@
     return 1;
   }
 
-  return unwindstack::GetInfo(argv[1], pc);
+  uint64_t offset = 0;
+  if (argc == 4) {
+    char* end;
+    offset = strtoull(argv[3], &end, 16);
+    if (*end != '\0') {
+      printf("Malformed OFFSET value: %s\n", argv[3]);
+      return 1;
+    }
+  }
+
+  return unwindstack::GetInfo(argv[1], offset, pc);
 }
diff --git a/logcat/Android.bp b/logcat/Android.bp
index 5030b15..f1b18b2 100644
--- a/logcat/Android.bp
+++ b/logcat/Android.bp
@@ -24,7 +24,6 @@
     ],
     shared_libs: [
         "libbase",
-        "libpcrecpp",
         "libprocessgroup",
     ],
     static_libs: ["liblog"],
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 6507711..e2711af 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -41,6 +41,7 @@
 
 #include <atomic>
 #include <memory>
+#include <regex>
 #include <string>
 #include <utility>
 #include <vector>
@@ -56,8 +57,6 @@
 #include <processgroup/sched_policy.h>
 #include <system/thread_defs.h>
 
-#include <pcrecpp.h>
-
 #define DEFAULT_MAX_ROTATED_LOGS 4
 
 struct log_device_t {
@@ -113,7 +112,7 @@
     size_t outByteCount;
     int printBinary;
     int devCount;  // >1 means multiple
-    pcrecpp::RE* regex;
+    std::unique_ptr<std::regex> regex;
     log_device_t* devices;
     EventTagMap* eventTagMap;
     // 0 means "infinite"
@@ -307,9 +306,7 @@
                     const AndroidLogEntry& entry) {
     if (!context->regex) return true;
 
-    std::string messageString(entry.message, entry.messageLen);
-
-    return context->regex->PartialMatch(messageString);
+    return std::regex_search(entry.message, entry.message + entry.messageLen, *context->regex);
 }
 
 static void processBuffer(android_logcat_context_internal* context,
@@ -460,7 +457,7 @@
                     "  -d              Dump the log and then exit (don't block)\n"
                     "  -e <expr>, --regex=<expr>\n"
                     "                  Only print lines where the log message matches <expr>\n"
-                    "                  where <expr> is a Perl-compatible regular expression\n"
+                    "                  where <expr> is a regular expression\n"
                     // Leave --head undocumented as alias for -m
                     "  -m <count>, --max-count=<count>\n"
                     "                  Quit after printing <count> lines. This is meant to be\n"
@@ -1000,7 +997,7 @@
                 break;
 
             case 'e':
-                context->regex = new pcrecpp::RE(optarg);
+                context->regex.reset(new std::regex(optarg));
                 break;
 
             case 'm': {
@@ -1701,7 +1698,6 @@
         sched_yield();
     }
 
-    delete context->regex;
     context->argv_hold.clear();
     context->args.clear();
     context->envp_hold.clear();
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index 2fd9f95..82c4fb9 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -56,14 +56,7 @@
       mLogId(elem.mLogId),
       mDropped(elem.mDropped) {
     if (mDropped) {
-        if (elem.isBinary() && elem.mMsg != nullptr) {
-            // for the following "len" value, refer to : setDropped(uint16_t value), getTag()
-            const int len = sizeof(android_event_header_t);
-            mMsg = new char[len];
-            memcpy(mMsg, elem.mMsg, len);
-        } else {
-            mMsg = nullptr;
-        }
+        mTag = elem.getTag();
     } else {
         mMsg = new char[mMsgLen];
         memcpy(mMsg, elem.mMsg, mMsgLen);
@@ -71,31 +64,43 @@
 }
 
 LogBufferElement::~LogBufferElement() {
-    delete[] mMsg;
+    if (!mDropped) {
+        delete[] mMsg;
+    }
 }
 
 uint32_t LogBufferElement::getTag() const {
-    return (isBinary() &&
-            ((mDropped && mMsg != nullptr) ||
-             (!mDropped && mMsgLen >= sizeof(android_event_header_t))))
-               ? reinterpret_cast<const android_event_header_t*>(mMsg)->tag
-               : 0;
+    // Binary buffers have no tag.
+    if (!isBinary()) {
+        return 0;
+    }
+
+    // Dropped messages store the tag in place of mMsg.
+    if (mDropped) {
+        return mTag;
+    }
+
+    // For non-dropped messages, we get the tag from the message header itself.
+    if (mMsgLen < sizeof(android_event_header_t)) {
+        return 0;
+    }
+
+    return reinterpret_cast<const android_event_header_t*>(mMsg)->tag;
 }
 
 uint16_t LogBufferElement::setDropped(uint16_t value) {
-    // The tag information is saved in mMsg data, if the tag is non-zero
-    // save only the information needed to get the tag.
-    if (getTag() != 0) {
-        if (mMsgLen > sizeof(android_event_header_t)) {
-            char* truncated_msg = new char[sizeof(android_event_header_t)];
-            memcpy(truncated_msg, mMsg, sizeof(android_event_header_t));
-            delete[] mMsg;
-            mMsg = truncated_msg;
-        }  // mMsgLen == sizeof(android_event_header_t), already at minimum.
-    } else {
-        delete[] mMsg;
-        mMsg = nullptr;
+    if (mDropped) {
+        return mDroppedCount = value;
     }
+
+    // The tag information is saved in mMsg data, which is in a union with mTag, used after mDropped
+    // is set to true. Therefore we save the tag value aside, delete mMsg, then set mTag to the tag
+    // value in its place.
+    auto old_tag = getTag();
+    delete[] mMsg;
+    mMsg = nullptr;
+
+    mTag = old_tag;
     mDropped = true;
     return mDroppedCount = value;
 }
diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h
index 57b0a95..da4991b 100644
--- a/logd/LogBufferElement.h
+++ b/logd/LogBufferElement.h
@@ -41,7 +41,10 @@
     const uint32_t mPid;
     const uint32_t mTid;
     log_time mRealTime;
-    char* mMsg;
+    union {
+        char* mMsg;    // mDropped == false
+        int32_t mTag;  // mDropped == true
+    };
     union {
         const uint16_t mMsgLen;  // mDropped == false
         uint16_t mDroppedCount;  // mDropped == true
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 76d6f7e..4559050 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -230,7 +230,7 @@
 # A symlink can't overwrite a directory and the /system/usr/icu directory once
 # existed so the required structure must be created whatever we find.
 LOCAL_POST_INSTALL_CMD = mkdir -p $(TARGET_OUT)/usr && rm -rf $(TARGET_OUT)/usr/icu
-LOCAL_POST_INSTALL_CMD += && ln -sf /apex/com.android.runtime/etc/icu $(TARGET_OUT)/usr/icu
+LOCAL_POST_INSTALL_CMD += && ln -sf /apex/com.android.i18n/etc/icu $(TARGET_OUT)/usr/icu
 
 # TODO(b/124106384): Clean up compat symlinks for ART binaries.
 ART_BINARIES := \
diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt
index 3321425..a603be2 100644
--- a/rootdir/etc/ld.config.txt
+++ b/rootdir/etc/ld.config.txt
@@ -328,7 +328,7 @@
 namespace.rs.asan.permitted.paths +=           /vendor/${LIB}
 namespace.rs.asan.permitted.paths += /data
 
-namespace.rs.links = default,vndk,neuralnetworks
+namespace.rs.links = default,neuralnetworks
 
 namespace.rs.link.default.shared_libs  = %LLNDK_LIBRARIES%
 namespace.rs.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
@@ -336,8 +336,6 @@
 # namespace because RS framework libs are using them.
 namespace.rs.link.default.shared_libs += %PRIVATE_LLNDK_LIBRARIES%
 
-namespace.rs.link.vndk.shared_libs = %VNDK_SAMEPROCESS_LIBRARIES%
-
 # LLNDK library moved into apex
 namespace.rs.link.neuralnetworks.shared_libs = libneuralnetworks.so
 
@@ -633,15 +631,15 @@
 
 namespace.neuralnetworks.search.paths = /apex/com.android.neuralnetworks/${LIB}
 namespace.neuralnetworks.asan.search.paths = /apex/com.android.neuralnetworks/${LIB}
-namespace.neuralnetworks.links = default
-namespace.neuralnetworks.link.default.shared_libs  = libc.so
-namespace.neuralnetworks.link.default.shared_libs += libcgrouprc.so
-namespace.neuralnetworks.link.default.shared_libs += libdl.so
-namespace.neuralnetworks.link.default.shared_libs += liblog.so
-namespace.neuralnetworks.link.default.shared_libs += libm.so
-namespace.neuralnetworks.link.default.shared_libs += libnativewindow.so
-namespace.neuralnetworks.link.default.shared_libs += libsync.so
-namespace.neuralnetworks.link.default.shared_libs += libvndksupport.so
+namespace.neuralnetworks.links = system
+namespace.neuralnetworks.link.system.shared_libs  = libc.so
+namespace.neuralnetworks.link.system.shared_libs += libcgrouprc.so
+namespace.neuralnetworks.link.system.shared_libs += libdl.so
+namespace.neuralnetworks.link.system.shared_libs += liblog.so
+namespace.neuralnetworks.link.system.shared_libs += libm.so
+namespace.neuralnetworks.link.system.shared_libs += libnativewindow.so
+namespace.neuralnetworks.link.system.shared_libs += libsync.so
+namespace.neuralnetworks.link.system.shared_libs += libvndksupport.so
 
 ###############################################################################
 # Namespace config for native tests that need access to both system and vendor
@@ -795,5 +793,3 @@
 namespace.default.search.paths  = /system/${LIB}
 namespace.default.search.paths += /%SYSTEM_EXT%/${LIB}
 namespace.default.search.paths += /%PRODUCT%/${LIB}
-
-namespace.default.link.runtime.shared_libs = %SANITIZER_RUNTIME_LIBRARIES%
diff --git a/rootdir/etc/ld.config.vndk_lite.txt b/rootdir/etc/ld.config.vndk_lite.txt
index 0bb60ab..2e213ec 100644
--- a/rootdir/etc/ld.config.vndk_lite.txt
+++ b/rootdir/etc/ld.config.vndk_lite.txt
@@ -265,7 +265,7 @@
 namespace.rs.asan.permitted.paths +=           /vendor/${LIB}
 namespace.rs.asan.permitted.paths += /data
 
-namespace.rs.links = default,vndk,neuralnetworks
+namespace.rs.links = default,neuralnetworks
 
 namespace.rs.link.default.shared_libs  = %LLNDK_LIBRARIES%
 namespace.rs.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
@@ -273,8 +273,6 @@
 # namespace because RS framework libs are using them.
 namespace.rs.link.default.shared_libs += %PRIVATE_LLNDK_LIBRARIES%
 
-namespace.rs.link.vndk.shared_libs = %VNDK_SAMEPROCESS_LIBRARIES%
-
 # LLNDK library moved into apex
 namespace.rs.link.neuralnetworks.shared_libs = libneuralnetworks.so
 
diff --git a/rootdir/init.environ.rc.in b/rootdir/init.environ.rc.in
index 455c9a8..93b7f43 100644
--- a/rootdir/init.environ.rc.in
+++ b/rootdir/init.environ.rc.in
@@ -6,6 +6,7 @@
     export ANDROID_DATA /data
     export ANDROID_STORAGE /storage
     export ANDROID_RUNTIME_ROOT /apex/com.android.runtime
+    export ANDROID_I18N_ROOT /apex/com.android.i18n
     export ANDROID_TZDATA_ROOT /apex/com.android.tzdata
     export EXTERNAL_STORAGE /sdcard
     export ASEC_MOUNTPOINT /mnt/asec
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 86d8042..bb36139 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -121,9 +121,16 @@
     mkdir /mnt/media_rw 0750 root media_rw
     mkdir /mnt/user 0755 root root
     mkdir /mnt/user/0 0755 root root
+    mkdir /mnt/user/0/self 0755 root root
+    mkdir /mnt/user/0/emulated 0755 root root
+    mkdir /mnt/user/0/emulated/0 0755 root root
     mkdir /mnt/expand 0771 system system
     mkdir /mnt/appfuse 0711 root root
 
+    # tmpfs place for BORINGSSL_self_test() to remember whether it has run
+    mkdir /dev/boringssl 0755 root root
+    mkdir /dev/boringssl/selftest 0755 root root
+
     # Storage views to support runtime permissions
     mkdir /mnt/runtime 0700 root root
     mkdir /mnt/runtime/default 0755 root root
@@ -367,9 +374,6 @@
     # Once everything is setup, no need to modify /.
     # The bind+remount combination allows this to work in containers.
     mount rootfs rootfs / remount bind ro nodev
-    # Mount default storage into root namespace
-    mount none /mnt/runtime/default /storage bind rec
-    mount none none /storage slave rec
 
     # Make sure /sys/kernel/debug (if present) is labeled properly
     # Note that tracefs may be mounted under debug, so we need to cross filesystems
@@ -532,6 +536,7 @@
     mkdir /data/misc/profiles/ref 0771 system system
     mkdir /data/misc/profman 0770 system shell
     mkdir /data/misc/gcov 0770 root root
+    mkdir /data/misc/installd 0700 root root
 
     mkdir /data/preloads 0775 system system
 
@@ -614,6 +619,9 @@
     mkdir /data/cache/backup_stage 0700 system system
     mkdir /data/cache/backup 0700 system system
 
+    mkdir /data/rollback 0700 system system
+    mkdir /data/rollback-observer 0700 system system
+
     # Wait for apexd to finish activating APEXes before starting more processes.
     wait_for_prop apexd.status ready
     parse_apex_configs
@@ -642,6 +650,22 @@
     chown root system /dev/fscklogs/log
     chmod 0770 /dev/fscklogs/log
 
+# Switch between sdcardfs and FUSE depending on persist property
+# TODO: Move this to ro property before launch because FDE devices
+# interact with persistent properties differently during boot
+on zygote-start && property:persist.sys.fuse=true
+  # Mount default storage into root namespace
+  mount none /mnt/user/0 /storage bind rec
+  mount none none /storage slave rec
+on zygote-start && property:persist.sys.fuse=false
+  # Mount default storage into root namespace
+  mount none /mnt/runtime/default /storage bind rec
+  mount none none /storage slave rec
+on zygote-start && property:persist.sys.fuse=""
+  # Mount default storage into root namespace
+  mount none /mnt/runtime/default /storage bind rec
+  mount none none /storage slave rec
+
 # It is recommended to put unnecessary data/ initialization from post-fs-data
 # to start-zygote in device's init.rc to unblock zygote start.
 on zygote-start && property:ro.crypto.state=unencrypted
@@ -844,6 +868,8 @@
     chmod 0773 /data/misc/trace
     # Give reads to anyone for the window trace folder on debug builds.
     chmod 0775 /data/misc/wmtrace
+
+on init && property:ro.debuggable=1
     start console
 
 service flash_recovery /system/bin/install-recovery.sh
diff --git a/sdcard/sdcard.cpp b/sdcard/sdcard.cpp
index 2b35819..622de5b 100644
--- a/sdcard/sdcard.cpp
+++ b/sdcard/sdcard.cpp
@@ -315,7 +315,8 @@
         PLOG(ERROR) << "setting RLIMIT_NOFILE failed";
     }
 
-    while ((fs_read_atomic_int("/data/.layout_version", &fs_version) == -1) || (fs_version < 3)) {
+    while ((fs_read_atomic_int("/data/misc/installd/layout_version", &fs_version) == -1) ||
+           (fs_version < 3)) {
         LOG(ERROR) << "installd fs upgrade not yet complete; waiting...";
         sleep(1);
     }
diff --git a/storaged/storaged_info.cpp b/storaged/storaged_info.cpp
index ca2421b..6668cf3 100644
--- a/storaged/storaged_info.cpp
+++ b/storaged/storaged_info.cpp
@@ -41,7 +41,6 @@
 using android::hardware::health::V2_0::StorageInfo;
 
 const string emmc_info_t::emmc_sysfs = "/sys/bus/mmc/devices/mmc0:0001/";
-const string emmc_info_t::emmc_debugfs = "/d/mmc0/mmc0:0001/ext_csd";
 const char* emmc_info_t::emmc_ver_str[9] = {
     "4.0", "4.1", "4.2", "4.3", "Obsolete", "4.41", "4.5", "5.0", "5.1"
 };
@@ -62,10 +61,8 @@
     if (healthService != nullptr) {
         return new health_storage_info_t(healthService);
     }
-    if (FileExists(emmc_info_t::emmc_sysfs) ||
-        FileExists(emmc_info_t::emmc_debugfs)) {
-        return new emmc_info_t;
-    }
+    if (FileExists(emmc_info_t::emmc_sysfs)) return new emmc_info_t;
+
     if (FileExists(ufs_info_t::health_file)) {
         return new ufs_info_t;
     }
@@ -241,8 +238,7 @@
 
 void emmc_info_t::report()
 {
-    if (!report_sysfs() && !report_debugfs())
-        return;
+    if (!report_sysfs()) return;
 
     publish();
 }
@@ -284,54 +280,6 @@
     return true;
 }
 
-namespace {
-
-const size_t EXT_CSD_FILE_MIN_SIZE = 1024;
-/* 2 characters in string for each byte */
-const size_t EXT_CSD_REV_IDX = 192 * 2;
-const size_t EXT_PRE_EOL_INFO_IDX = 267 * 2;
-const size_t EXT_DEVICE_LIFE_TIME_EST_A_IDX = 268 * 2;
-const size_t EXT_DEVICE_LIFE_TIME_EST_B_IDX = 269 * 2;
-
-} // namespace
-
-bool emmc_info_t::report_debugfs()
-{
-    string buffer;
-    uint16_t rev = 0;
-
-    if (!ReadFileToString(emmc_debugfs, &buffer) ||
-        buffer.length() < (size_t)EXT_CSD_FILE_MIN_SIZE) {
-        return false;
-    }
-
-    string str = buffer.substr(EXT_CSD_REV_IDX, 2);
-    if (!ParseUint(str, &rev) ||
-        rev < 7 || rev > ARRAY_SIZE(emmc_ver_str)) {
-        return false;
-    }
-
-    version = "emmc ";
-    version += emmc_ver_str[rev];
-
-    str = buffer.substr(EXT_PRE_EOL_INFO_IDX, 2);
-    if (!ParseUint(str, &eol)) {
-        return false;
-    }
-
-    str = buffer.substr(EXT_DEVICE_LIFE_TIME_EST_A_IDX, 2);
-    if (!ParseUint(str, &lifetime_a)) {
-        return false;
-    }
-
-    str = buffer.substr(EXT_DEVICE_LIFE_TIME_EST_B_IDX, 2);
-    if (!ParseUint(str, &lifetime_b)) {
-        return false;
-    }
-
-    return true;
-}
-
 void ufs_info_t::report()
 {
     string buffer;
diff --git a/toolbox/Android.bp b/toolbox/Android.bp
index 0cc603a..4ca5f5a 100644
--- a/toolbox/Android.bp
+++ b/toolbox/Android.bp
@@ -24,6 +24,7 @@
         "toolbox.c",
         "getevent.c",
         "getprop.cpp",
+        "modprobe.cpp",
         "setprop.cpp",
         "start.cpp",
     ],
@@ -33,11 +34,15 @@
     shared_libs: [
         "libbase",
     ],
-    static_libs: ["libpropertyinfoparser"],
+    static_libs: [
+        "libmodprobe",
+        "libpropertyinfoparser",
+    ],
 
     symlinks: [
         "getevent",
         "getprop",
+        "modprobe",
         "setprop",
         "start",
         "stop",
diff --git a/toolbox/modprobe.cpp b/toolbox/modprobe.cpp
new file mode 100644
index 0000000..1b5f54e
--- /dev/null
+++ b/toolbox/modprobe.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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 <ctype.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <iostream>
+
+#include <android-base/strings.h>
+#include <modprobe/modprobe.h>
+
+enum modprobe_mode {
+    AddModulesMode,
+    RemoveModulesMode,
+    ListModulesMode,
+    ShowDependenciesMode,
+};
+
+static void print_usage(void) {
+    std::cerr << "Usage:" << std::endl;
+    std::cerr << std::endl;
+    std::cerr << "  modprobe [-alrqvsDb] [-d DIR] [MODULE]+" << std::endl;
+    std::cerr << "  modprobe [-alrqvsDb] [-d DIR] MODULE [symbol=value][...]" << std::endl;
+    std::cerr << std::endl;
+    std::cerr << "Options:" << std::endl;
+    std::cerr << "  -b: Apply blacklist to module names too" << std::endl;
+    std::cerr << "  -d: Load modules from DIR, option may be used multiple times" << std::endl;
+    std::cerr << "  -D: Print dependencies for modules only, do not load";
+    std::cerr << "  -h: Print this help" << std::endl;
+    std::cerr << "  -l: List modules matching pattern" << std::endl;
+    std::cerr << "  -r: Remove MODULE (multiple modules may be specified)" << std::endl;
+    std::cerr << "  -q: Quiet" << std::endl;
+    std::cerr << "  -v: Verbose" << std::endl;
+    std::cerr << std::endl;
+}
+
+#define check_mode()                                                      \
+    if (mode != AddModulesMode) {                                         \
+        std::cerr << "Error, multiple mode flags specified" << std::endl; \
+        print_usage();                                                    \
+        return EXIT_FAILURE;                                              \
+    }
+
+extern "C" int modprobe_main(int argc, char** argv) {
+    std::vector<std::string> modules;
+    std::string module_parameters;
+    std::vector<std::string> mod_dirs;
+    modprobe_mode mode = AddModulesMode;
+    bool blacklist = false;
+    bool verbose = false;
+    int rv = EXIT_SUCCESS;
+
+    int opt;
+    while ((opt = getopt(argc, argv, "abd:Dhlqrv")) != -1) {
+        switch (opt) {
+            case 'a':
+                // toybox modprobe supported -a to load multiple modules, this
+                // is supported here by default, ignore flag
+                check_mode();
+                break;
+            case 'b':
+                blacklist = true;
+                break;
+            case 'd':
+                mod_dirs.emplace_back(optarg);
+                break;
+            case 'D':
+                check_mode();
+                mode = ShowDependenciesMode;
+                break;
+            case 'h':
+                print_usage();
+                return EXIT_SUCCESS;
+            case 'l':
+                check_mode();
+                mode = ListModulesMode;
+                break;
+            case 'q':
+                verbose = false;
+                break;
+            case 'r':
+                check_mode();
+                mode = RemoveModulesMode;
+                break;
+            case 'v':
+                verbose = true;
+                break;
+            default:
+                std::cerr << "Unrecognized option: " << opt << std::endl;
+                return EXIT_FAILURE;
+        }
+    }
+
+    int parameter_count = 0;
+    for (opt = optind; opt < argc; opt++) {
+        if (!strchr(argv[opt], '=')) {
+            modules.emplace_back(argv[opt]);
+        } else {
+            parameter_count++;
+            if (module_parameters.empty()) {
+                module_parameters = argv[opt];
+            } else {
+                module_parameters = module_parameters + " " + argv[opt];
+            }
+        }
+    }
+
+    if (verbose) {
+        std::cout << "mode is " << mode << std::endl;
+        std::cout << "verbose is " << verbose << std::endl;
+        std::cout << "mod_dirs is: " << android::base::Join(mod_dirs, "") << std::endl;
+        std::cout << "modules is: " << android::base::Join(modules, "") << std::endl;
+        std::cout << "module parameters is: " << android::base::Join(module_parameters, "")
+                  << std::endl;
+    }
+
+    if (modules.empty()) {
+        if (mode == ListModulesMode) {
+            // emulate toybox modprobe list with no pattern (list all)
+            modules.emplace_back("*");
+        } else {
+            std::cerr << "No modules given." << std::endl;
+            print_usage();
+            return EXIT_FAILURE;
+        }
+    }
+    if (mod_dirs.empty()) {
+        std::cerr << "No module configuration directories given." << std::endl;
+        print_usage();
+        return EXIT_FAILURE;
+    }
+    if (parameter_count && modules.size() > 1) {
+        std::cerr << "Only one module may be loaded when specifying module parameters."
+                  << std::endl;
+        print_usage();
+        return EXIT_FAILURE;
+    }
+
+    Modprobe m(mod_dirs);
+    m.EnableVerbose(verbose);
+    if (blacklist) {
+        m.EnableBlacklist(true);
+    }
+
+    for (const auto& module : modules) {
+        switch (mode) {
+            case AddModulesMode:
+                if (!m.LoadWithAliases(module, true, module_parameters)) {
+                    std::cerr << "Failed to load module " << module;
+                    rv = EXIT_FAILURE;
+                }
+                break;
+            case RemoveModulesMode:
+                if (!m.Remove(module)) {
+                    std::cerr << "Failed to remove module " << module;
+                    rv = EXIT_FAILURE;
+                }
+                break;
+            case ListModulesMode: {
+                std::vector<std::string> list = m.ListModules(module);
+                std::cout << android::base::Join(list, "\n") << std::endl;
+                break;
+            }
+            case ShowDependenciesMode: {
+                std::vector<std::string> pre_deps;
+                std::vector<std::string> deps;
+                std::vector<std::string> post_deps;
+                if (!m.GetAllDependencies(module, &pre_deps, &deps, &post_deps)) {
+                    rv = EXIT_FAILURE;
+                    break;
+                }
+                std::cout << "Dependencies for " << module << ":" << std::endl;
+                std::cout << "Soft pre-dependencies:" << std::endl;
+                std::cout << android::base::Join(pre_deps, "\n") << std::endl;
+                std::cout << "Hard dependencies:" << std::endl;
+                std::cout << android::base::Join(deps, "\n") << std::endl;
+                std::cout << "Soft post-dependencies:" << std::endl;
+                std::cout << android::base::Join(post_deps, "\n") << std::endl;
+                break;
+            }
+            default:
+                std::cerr << "Bad mode";
+                rv = EXIT_FAILURE;
+        }
+    }
+
+    return rv;
+}
diff --git a/toolbox/tools.h b/toolbox/tools.h
index 9a7ebd2..bb57e67 100644
--- a/toolbox/tools.h
+++ b/toolbox/tools.h
@@ -1,5 +1,6 @@
 TOOL(getevent)
 TOOL(getprop)
+TOOL(modprobe)
 TOOL(setprop)
 TOOL(start)
 TOOL(stop)