Merge "Fix incorrect closing of fds."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 66d0c92..a3bd44f 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,9 @@
       "name": "libbase_test"
     },
     {
+      "name": "libpackagelistparser_test"
+    },
+    {
       "name": "libprocinfo_test"
     },
     {
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/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 f1f080a..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");
     }
 
@@ -420,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";
@@ -545,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;
     }
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 48853b7..599e0e6 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -357,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;
@@ -1739,41 +1739,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];
             }
         }
 
@@ -1781,7 +1773,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/fdevent/fdevent.h b/adb/fdevent/fdevent.h
index ccb0c92..2424252 100644
--- a/adb/fdevent/fdevent.h
+++ b/adb/fdevent/fdevent.h
@@ -20,6 +20,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <atomic>
 #include <chrono>
 #include <deque>
 #include <functional>
diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp
index 6372b3d..dc2525c 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -688,7 +688,7 @@
               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);
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..10efaa3 100644
--- a/base/include/android-base/endian.h
+++ b/base/include/android-base/endian.h
@@ -41,23 +41,28 @@
 
 #else
 
-/* Mac OS and Windows have nothing. */
-
-#define __LITTLE_ENDIAN 1234
+#if defined(__APPLE__)
+/* macOS has some of the basics. */
+#include <sys/_endian.h>
+#else
+/* Windows really has nothing. */
 #define LITTLE_ENDIAN __LITTLE_ENDIAN
-
-#define __BIG_ENDIAN 4321
 #define BIG_ENDIAN __BIG_ENDIAN
-
-#define __BYTE_ORDER __LITTLE_ENDIAN
 #define BYTE_ORDER __BYTE_ORDER
-
 #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/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..4ee7db9 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -150,6 +150,31 @@
     return (vst.f_bfree >= (vst.f_blocks * kPercentThreshold / 100));
 }
 
+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) {
     // readonly filesystem, can not be mount -o remount,rw
     // for squashfs, erofs or if free space is (near) zero making such a remount
@@ -157,19 +182,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 +413,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);
 }
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index f5783cb..cf306f3 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -197,6 +197,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/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index b504161..5b377ae 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",
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index a434c93..777743c 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -19,23 +19,17 @@
 #include <string.h>
 
 #include <algorithm>
-#include <string_view>
 
-#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 constexpr std::string_view kDefaultGroup = "default";
-
 bool LinearExtent::AddTo(LpMetadata* out) const {
     if (device_index_ >= out->block_devices.size()) {
         LERROR << "Extent references unknown block device.";
@@ -165,16 +159,27 @@
         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 (GetBlockDevicePartitionName(*super_device) != "super" &&
+        IsRetrofitDynamicPartitionsDevice()) {
+        if (!UpdateMetadataForOtherSuper(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();
@@ -194,7 +199,7 @@
             // 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()) +
@@ -203,20 +208,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) {
@@ -1051,17 +1048,12 @@
 }
 
 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 {
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index 377ec68..6d27873 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -19,36 +19,40 @@
 #include <gtest/gtest.h>
 #include <liblp/builder.h>
 
+#include "mock_property_fetcher.h"
 #include "utility.h"
 
 using namespace std;
 using namespace android::fs_mgr;
+using ::android::fs_mgr::MockPropertyFetcher;
+using ::testing::_;
+using ::testing::AnyNumber;
 using ::testing::ElementsAre;
+using ::testing::NiceMock;
+using ::testing::Return;
+
+static void ResetPropertyFetcher() {
+    IPropertyFetcher::OverrideForTesting(std::make_unique<NiceMock<MockPropertyFetcher>>());
+}
+
+MockPropertyFetcher* GetMockedInstance() {
+    return static_cast<MockPropertyFetcher*>(IPropertyFetcher::GetInstance());
+}
 
 class Environment : public ::testing::Environment {
   public:
-    void SetUp() override {
-        MetadataBuilder::OverrideABForTesting(false);
-        MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
-    }
+    void SetUp() override { ResetPropertyFetcher(); }
 };
 
 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);
-    }
+    void SetUp() override { ResetPropertyFetcher(); }
+    void TearDown() override { ResetPropertyFetcher(); }
 };
 
 TEST_F(BuilderTest, BuildBasic) {
@@ -785,7 +789,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(*GetMockedInstance(), 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/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index a2221ef..3b229bd 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:
@@ -196,12 +200,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.
@@ -347,8 +345,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/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..2990863 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -23,10 +23,12 @@
 #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 "mock_property_fetcher.h"
 #include "reader.h"
 #include "test_partition_opener.h"
 #include "utility.h"
@@ -34,6 +36,8 @@
 
 using namespace std;
 using namespace android::fs_mgr;
+using ::testing::_;
+using ::testing::Return;
 using unique_fd = android::base::unique_fd;
 
 // Our tests assume a 128KiB disk with two 512 byte metadata slots.
@@ -664,7 +668,8 @@
 }
 
 TEST(liblp, UpdateRetrofit) {
-    MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(true);
+    ON_CALL(*GetMockedInstance(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+            .WillByDefault(Return(true));
 
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
@@ -695,7 +700,8 @@
 }
 
 TEST(liblp, UpdateNonRetrofit) {
-    MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
+    ON_CALL(*GetMockedInstance(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+            .WillByDefault(Return(false));
 
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
diff --git a/fs_mgr/liblp/mock_property_fetcher.h b/fs_mgr/liblp/mock_property_fetcher.h
new file mode 100644
index 0000000..eb91de2
--- /dev/null
+++ b/fs_mgr/liblp/mock_property_fetcher.h
@@ -0,0 +1,47 @@
+/*
+ * 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 {
+
+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;
+        }));
+    }
+};
+
+}  // namespace fs_mgr
+}  // namespace android
+
+android::fs_mgr::MockPropertyFetcher* GetMockedInstance();
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..8dbe955 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>
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/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index ce979cf..f7608dc 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -22,7 +22,8 @@
 #include <vector>
 
 #include <android-base/unique_fd.h>
-#include <libdm/dm_target.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
 
 #ifndef FRIEND_TEST
 #define FRIEND_TEST(test_set_name, individual_test) \
@@ -38,7 +39,7 @@
 
 namespace snapshot {
 
-enum class UpdateState {
+enum class UpdateState : unsigned int {
     // No update or merge is in progress.
     None,
 
@@ -51,6 +52,10 @@
     // 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,
 
@@ -94,8 +99,23 @@
 
     // 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();
+    // a merge state is detected during boot.
+    //
+    // Note that after calling WaitForMerge(), 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 WaitForMerge();
 
     // Find the status of the current update, if any.
     //
@@ -109,9 +129,14 @@
     FRIEND_TEST(SnapshotTest, CreateSnapshot);
     FRIEND_TEST(SnapshotTest, MapSnapshot);
     FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
+    FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
+    FRIEND_TEST(SnapshotTest, Merge);
+    FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
     friend class SnapshotTest;
 
+    using DmTargetSnapshot = android::dm::DmTargetSnapshot;
     using IImageManager = android::fiemap::IImageManager;
+    using TargetInfo = android::dm::DeviceMapper::TargetInfo;
 
     explicit SnapshotManager(IDeviceInfo* info);
 
@@ -126,16 +151,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);
@@ -170,9 +197,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().
@@ -190,6 +216,7 @@
     std::unique_ptr<LockedFile> LockExclusive();
     UpdateState ReadUpdateState(LockedFile* file);
     bool WriteUpdateState(LockedFile* file, UpdateState state);
+    std::string GetStateFilePath() const;
 
     // This state is persisted per-snapshot in /metadata/ota/snapshots/.
     struct SnapshotStatus {
@@ -201,11 +228,38 @@
         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();
+
+    // 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);
 
     // Return the name of the device holding the "snapshot" or "snapshot-merge"
     // target. This may not be the final device presented via MapSnapshot(), if
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 7542d82..63a01f3 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -19,6 +19,8 @@
 #include <sys/types.h>
 #include <sys/unistd.h>
 
+#include <thread>
+
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
@@ -49,7 +51,7 @@
 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; }
+    std::string GetMetadataDir() const override { return "/metadata/ota"s; }
     bool IsRunningSnapshot() const override;
 };
 
@@ -140,9 +142,6 @@
 
     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.
@@ -151,7 +150,7 @@
             .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;
     }
@@ -168,11 +167,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;
+    }
+    if (status.state == "merge-completed") {
+        LOG(ERROR) << "Should not create a snapshot device for " << name
+                   << " after merging has completed.";
         return false;
     }
 
@@ -210,19 +211,31 @@
 
     std::string cow_dev;
     if (!images_->MapImageDevice(cow_name, timeout_ms, &cow_dev)) {
+        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
@@ -267,11 +280,8 @@
     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;
     }
 
@@ -300,36 +310,525 @@
     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_->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;
+    }
+    if (!device_->IsRunningSnapshot()) {
+        LOG(ERROR) << "Cannot begin a merge if the device is not booted off a snapshot";
+        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 != "created") {
+        LOG(WARNING) << "Snapshot " << name << " has unexpected state: " << 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 = "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::WaitForMerge() {
+    while (true) {
+        UpdateState state = CheckMergeState();
+        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) {
+        AcknowledgeMergeFailure();
+        return UpdateState::MergeFailed;
+    }
+
+    auto state = CheckMergeState(lock.get());
+    if (state == UpdateState::MergeCompleted) {
+        AcknowledgeMergeSuccess(lock.get());
+    } else if (state == UpdateState::MergeFailed) {
+        AcknowledgeMergeFailure();
+    }
+    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;
+
+        default:
+            LOG(ERROR) << "No merge exists, cannot wait. Update state: "
+                       << static_cast<uint32_t>(state);
+            return UpdateState::None;
+    }
+
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock, &snapshots)) {
+        return UpdateState::MergeFailed;
+    }
+
+    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;
+            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;
+    }
+    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);
+
+    // 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 == "merge-complete" && !IsSnapshotDevice(dm_name)) {
+        // NB: It's okay if this fails now, we gave cleanup our best effort.
+        OnSnapshotMergeComplete(lock, name, snapshot_status);
+        return UpdateState::MergeCompleted;
+    }
+
+    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 == "merge-complete") {
+            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 = "merge-complete";
+    if (!WriteSnapshotStatus(lock, name, snapshot_status)) {
+        return UpdateState::MergeFailed;
+    }
+    if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) {
+        return UpdateState::MergeNeedsReboot;
+    }
+    return UpdateState::MergeCompleted;
+}
+
+void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) {
+    if (!WriteUpdateState(lock, UpdateState::None)) {
+        // We'll try again next reboot, ad infinitum.
+        return;
+    }
+}
+
+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) {
+    // Ideally, we would complete the following steps to collapse the device:
+    //  (1) Rewrite the snapshot table to be identical to the base device table.
+    //  (2) Rewrite the verity table to use the "snapshot" (now linear) device.
+    //  (3) Delete the base device.
+    //
+    // This should be possible once libsnapshot understands LpMetadata. In the
+    // meantime, we implement a simpler solution: rewriting the snapshot table
+    // to be a single dm-linear segment against the base device. While not as
+    // ideal, it still lets us remove the COW device. We can remove this
+    // implementation once the new method has been tested.
+    auto& dm = DeviceMapper::Instance();
+    auto dm_name = GetSnapshotDeviceName(name, status);
+
+    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 above.
+        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 tabble " << 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;
+        }
+    }
+
+    // Note: we are replacing the OUTER table here, so we do not use dm_name.
+    DmTargetLinear new_target(0, num_sectors, base_device, 0);
+    LOG(INFO) << "Replacing snapshot device " << name
+              << " table with: " << new_target.GetParameterString();
+
+    DmTable table;
+    table.Emplace<DmTargetLinear>(new_target);
+    if (!dm.LoadTableAndActivate(name, table)) {
+        return false;
+    }
+
+    if (dm_name != name) {
+        // Attempt to delete the snapshot device. 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.DeleteDevice(dm_name)) {
+            LOG(ERROR) << "Unable to delete snapshot device " << dm_name << ", COW cannot be "
+                       << "reclaimed until after reboot.";
+            return false;
+        }
+    }
+    return true;
 }
 
 bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) {
@@ -347,6 +846,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;
@@ -393,7 +898,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() {
@@ -402,9 +910,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);
 }
 
@@ -439,6 +951,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;
@@ -463,6 +979,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;
@@ -476,51 +998,66 @@
     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 (!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) {
+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,
             std::to_string(status.device_size),
@@ -530,9 +1067,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;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 1b3289b..4903224 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -22,13 +22,20 @@
 #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 <gtest/gtest.h>
+#include <libdm/dm.h>
 #include <libfiemap/image_manager.h>
 
 namespace android {
 namespace snapshot {
 
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
 using namespace std::chrono_literals;
 using namespace std::string_literals;
 
@@ -48,12 +55,15 @@
 TestDeviceInfo* test_device = nullptr;
 
 class SnapshotTest : public ::testing::Test {
+  public:
+    SnapshotTest() : dm_(DeviceMapper::Instance()) {}
+
   protected:
     void SetUp() override {
         test_device->set_is_running_snapshot(false);
 
         if (sm->GetUpdateState() != UpdateState::None) {
-            ASSERT_TRUE(sm->CancelUpdate());
+            CleanupTestArtifacts();
         }
         ASSERT_TRUE(sm->BeginUpdate());
         ASSERT_TRUE(sm->EnsureImageManager());
@@ -65,13 +75,32 @@
     void TearDown() override {
         lock_ = nullptr;
 
-        if (sm->GetUpdateState() != UpdateState::None) {
-            ASSERT_TRUE(sm->CancelUpdate());
+        CleanupTestArtifacts();
+    }
+
+    void CleanupTestArtifacts() {
+        // Normally cancelling inside a merge is not allowed. Since these
+        // are tests, we don't care, destroy everything that might exist.
+        std::vector<std::string> snapshots = {"test-snapshot"};
+        for (const auto& snapshot : snapshots) {
+            DeleteSnapshotDevice(snapshot);
+            temp_images_.emplace_back(snapshot + "-cow");
+
+            auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
+            android::base::RemoveFileIfExists(status_file);
         }
+
+        // Remove all images.
+        temp_images_.emplace_back("test-snapshot-cow");
         for (const auto& temp_image : temp_images_) {
             image_manager_->UnmapImageDevice(temp_image);
             image_manager_->DeleteBackingImage(temp_image);
         }
+
+        if (sm->GetUpdateState() != UpdateState::None) {
+            auto state_file = sm->GetStateFilePath();
+            unlink(state_file.c_str());
+        }
     }
 
     bool AcquireLock() {
@@ -87,6 +116,17 @@
         return image_manager_->MapImageDevice(name, 10s, path);
     }
 
+    bool DeleteSnapshotDevice(const std::string& snapshot) {
+        if (dm_.GetState(snapshot) != DmDeviceState::INVALID) {
+            if (!dm_.DeleteDevice(snapshot)) return false;
+        }
+        if (dm_.GetState(snapshot + "-inner") != DmDeviceState::INVALID) {
+            if (!dm_.DeleteDevice(snapshot + "-inner")) return false;
+        }
+        return true;
+    }
+
+    DeviceMapper& dm_;
     std::unique_ptr<SnapshotManager::LockedFile> lock_;
     std::vector<std::string> temp_images_;
     android::fiemap::IImageManager* image_manager_ = nullptr;
@@ -104,18 +144,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_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status));
         ASSERT_EQ(status.state, "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"));
 }
 
@@ -150,6 +188,117 @@
     ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
 }
 
+TEST_F(SnapshotTest, NoMergeBeforeReboot) {
+    ASSERT_TRUE(AcquireLock());
+
+    // Set the state to Unverified, as if we finished an update.
+    ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
+
+    // Release the lock.
+    lock_ = nullptr;
+
+    // Merge should fail, since we didn't mark the device as rebooted.
+    ASSERT_FALSE(sm->InitiateMerge());
+}
+
+TEST_F(SnapshotTest, Merge) {
+    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(CreateTempDevice("base-device", kDeviceSize, &base_device));
+    ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", 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 the name of the device is test-snapshot because we didn't
+    // request a linear segment.
+    DeviceMapper::TargetInfo target;
+    ASSERT_TRUE(sm->IsSnapshotDevice("test-snapshot", &target));
+    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+
+    // Set the state to Unverified, as if we finished an update.
+    ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
+
+    // Release the lock.
+    lock_ = nullptr;
+
+    test_device->set_is_running_snapshot(true);
+    ASSERT_TRUE(sm->InitiateMerge());
+
+    // The device should have been switched to a snapshot-merge target.
+    ASSERT_TRUE(sm->IsSnapshotDevice("test-snapshot", &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->WaitForMerge(), UpdateState::MergeCompleted);
+    ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
+
+    // The device should no longer be a snapshot or snapshot-merge.
+    ASSERT_FALSE(sm->IsSnapshotDevice("test-snapshot"));
+
+    // Test that we can read back the string we wrote to the snapshot.
+    unique_fd fd(open(base_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(CreateTempDevice("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);
+
+    // Set the state to Unverified, as if we finished an update.
+    ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
+
+    // Release the lock.
+    lock_ = nullptr;
+
+    test_device->set_is_running_snapshot(true);
+    ASSERT_TRUE(sm->InitiateMerge());
+
+    // COW cannot be removed due to open fd, so expect a soft failure.
+    ASSERT_EQ(sm->WaitForMerge(), UpdateState::MergeNeedsReboot);
+
+    // Forcefully delete the snapshot device, so it looks like we just rebooted.
+    ASSERT_TRUE(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->WaitForMerge(), UpdateState::MergeCompleted);
+}
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/init/Android.bp b/init/Android.bp
index 6501900..3233cc3 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -83,6 +83,7 @@
         "libfscrypt",
         "libgsi",
         "libhidl-gen-utils",
+        "libjsoncpp",
         "libkeyutils",
         "liblog",
         "liblogwrap",
@@ -118,6 +119,7 @@
         "first_stage_mount.cpp",
         "import_parser.cpp",
         "init.cpp",
+        "interface_utils.cpp",
         "keychords.cpp",
         "modalias_handler.cpp",
         "mount_handler.cpp",
@@ -242,7 +244,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)",
 }
 
@@ -278,6 +283,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/action.cpp b/init/action.cpp
index 1a66eee..69e40d0 100644
--- a/init/action.cpp
+++ b/init/action.cpp
@@ -195,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/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/host_init_verifier.cpp b/init/host_init_verifier.cpp
index 076e8b0..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>());
 
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/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/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/logger.h b/liblog/logger.h
index 1f632c0..accb6e7 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>
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..eaac97f 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>
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/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/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/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..ddf558b
--- /dev/null
+++ b/libpackagelistparser/packagelistparser.cpp
@@ -0,0 +1,143 @@
+/*
+ * 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.
+  if (debuggable != 0 && debuggable != 1) return false;
+  info->debuggable = debuggable;
+
+  if (profileable_from_shell != 0 && profileable_from_shell != 1) return false;
+  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/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/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/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/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)