Merge "Move ICU .dat"
diff --git a/adb/Android.bp b/adb/Android.bp
index 47dafff..06cfcbf 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -27,6 +27,7 @@
         "-DADB_HOST=1",         // overridden by adbd_defaults
         "-DALLOW_ADBD_ROOT=0",  // overridden by adbd_defaults
         "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION=1",
+        "-DENABLE_FASTDEPLOY=1", // enable fast deploy
     ],
     cpp_std: "experimental",
 
@@ -270,22 +271,33 @@
         "client/console.cpp",
         "client/adb_install.cpp",
         "client/line_printer.cpp",
+        "client/fastdeploy.cpp",
+        "client/fastdeploycallbacks.cpp",
         "shell_service_protocol.cpp",
     ],
 
+    generated_headers: [
+        "bin2c_fastdeployagent",
+        "bin2c_fastdeployagentscript"
+    ],
+
     static_libs: [
         "libadb_host",
+        "libandroidfw",
         "libbase",
         "libcutils",
         "libcrypto_utils",
         "libcrypto",
+        "libfastdeploy_host",
         "libdiagnose_usb",
         "liblog",
         "libmdnssd",
+        "libprotobuf-cpp-lite",
         "libusb",
         "libutils",
         "liblog",
-        "libcutils",
+        "libziparchive",
+        "libz",
     ],
 
     stl: "libc++_static",
@@ -295,10 +307,6 @@
     // will violate ODR
     shared_libs: [],
 
-    required: [
-        "deploypatchgenerator",
-    ],
-
     // Archive adb, adb.exe.
     dist: {
         targets: [
@@ -658,3 +666,90 @@
         },
     },
 }
+
+// Note: using pipe for xxd to control the variable name generated
+// the default name used by xxd is the path to the input file.
+java_genrule {
+    name: "bin2c_fastdeployagent",
+    out: ["deployagent.inc"],
+    srcs: [":deployagent"],
+    cmd: "(echo 'unsigned char kDeployAgent[] = {' && xxd -i <$(in) && echo '};') > $(out)",
+}
+
+genrule {
+    name: "bin2c_fastdeployagentscript",
+    out: ["deployagentscript.inc"],
+    srcs: ["fastdeploy/deployagent/deployagent.sh"],
+    cmd: "(echo 'unsigned char kDeployAgentScript[] = {' && xxd -i <$(in) && echo '};') > $(out)",
+}
+
+cc_library_host_static {
+    name: "libfastdeploy_host",
+    defaults: ["adb_defaults"],
+    srcs: [
+        "fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp",
+        "fastdeploy/deploypatchgenerator/patch_utils.cpp",
+        "fastdeploy/proto/ApkEntry.proto",
+    ],
+    static_libs: [
+        "libadb_host",
+        "libandroidfw",
+        "libbase",
+        "libcutils",
+        "libcrypto_utils",
+        "libcrypto",
+        "libdiagnose_usb",
+        "liblog",
+        "libmdnssd",
+        "libusb",
+        "libutils",
+        "libziparchive",
+        "libz",
+    ],
+    stl: "libc++_static",
+    proto: {
+        type: "lite",
+        export_proto_headers: true,
+    },
+    target: {
+        windows: {
+            enabled: true,
+            shared_libs: ["AdbWinApi"],
+        },
+    },
+}
+
+cc_test_host {
+    name: "fastdeploy_test",
+    defaults: ["adb_defaults"],
+    srcs: [
+        "fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp",
+        "fastdeploy/deploypatchgenerator/patch_utils_test.cpp",
+    ],
+    static_libs: [
+        "libadb_host",
+        "libandroidfw",
+        "libbase",
+        "libcutils",
+        "libcrypto_utils",
+        "libcrypto",
+        "libdiagnose_usb",
+        "libfastdeploy_host",
+        "liblog",
+        "libmdnssd",
+        "libprotobuf-cpp-lite",
+        "libusb",
+        "libutils",
+        "libziparchive",
+        "libz",
+    ],
+    target: {
+        windows: {
+            enabled: true,
+            shared_libs: ["AdbWinApi"],
+        },
+    },
+    data: [
+        "fastdeploy/testdata/rotating_cube-release.apk",
+    ],
+}
diff --git a/adb/adb_utils.cpp b/adb/adb_utils.cpp
index cf5fbc8..d1910f1 100644
--- a/adb/adb_utils.cpp
+++ b/adb/adb_utils.cpp
@@ -320,6 +320,10 @@
 }
 
 std::string GetLogFilePath() {
+    // https://issuetracker.google.com/112588493
+    const char* path = getenv("ANDROID_ADB_LOG_PATH");
+    if (path) return path;
+
 #if defined(_WIN32)
     const char log_name[] = "adb.log";
     WCHAR temp_path[MAX_PATH];
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
index 16fa215..f1f080a 100644
--- a/adb/client/adb_install.cpp
+++ b/adb/client/adb_install.cpp
@@ -409,7 +409,8 @@
             android::base::EndsWithIgnoreCase(file, ".dm") ||
             android::base::EndsWithIgnoreCase(file, ".fsv_sig")) {
             struct stat sb;
-            if (stat(file, &sb) != -1) total_size += sb.st_size;
+            if (stat(file, &sb) == -1) perror_exit("failed to stat \"%s\"", file);
+            total_size += sb.st_size;
             first_apk = i;
         } else {
             break;
@@ -459,13 +460,13 @@
     }
 
     // Valid session, now stream the APKs
-    int success = 1;
+    bool success = true;
     for (int i = first_apk; i < argc; i++) {
         const char* file = argv[i];
         struct stat sb;
         if (stat(file, &sb) == -1) {
-            fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
-            success = 0;
+            fprintf(stderr, "adb: failed to stat \"%s\": %s\n", file, strerror(errno));
+            success = false;
             goto finalize_session;
         }
 
@@ -476,8 +477,8 @@
 
         unique_fd local_fd(adb_open(file, O_RDONLY | O_CLOEXEC));
         if (local_fd < 0) {
-            fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
-            success = 0;
+            fprintf(stderr, "adb: failed to open \"%s\": %s\n", file, strerror(errno));
+            success = false;
             goto finalize_session;
         }
 
@@ -485,7 +486,7 @@
         unique_fd remote_fd(adb_connect(cmd, &error));
         if (remote_fd < 0) {
             fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
-            success = 0;
+            success = false;
             goto finalize_session;
         }
 
@@ -493,15 +494,15 @@
         read_status_line(remote_fd.get(), buf, sizeof(buf));
 
         if (strncmp("Success", buf, 7)) {
-            fprintf(stderr, "adb: failed to write %s\n", file);
+            fprintf(stderr, "adb: failed to write \"%s\"\n", file);
             fputs(buf, stderr);
-            success = 0;
+            success = false;
             goto finalize_session;
         }
     }
 
 finalize_session:
-    // Commit session if we streamed everything okay; otherwise abandon
+    // Commit session if we streamed everything okay; otherwise abandon.
     std::string service = android::base::StringPrintf("%s install-%s %d", install_cmd.c_str(),
                                                       success ? "commit" : "abandon", session_id);
     {
@@ -512,14 +513,16 @@
         }
         read_status_line(fd.get(), buf, sizeof(buf));
     }
+    if (!success) return EXIT_FAILURE;
 
-    if (!strncmp("Success", buf, 7)) {
-        fputs(buf, stdout);
-        return 0;
+    if (strncmp("Success", buf, 7)) {
+        fprintf(stderr, "adb: failed to finalize session\n");
+        fputs(buf, stderr);
+        return EXIT_FAILURE;
     }
-    fprintf(stderr, "adb: failed to finalize session\n");
-    fputs(buf, stderr);
-    return EXIT_FAILURE;
+
+    fputs(buf, stdout);
+    return EXIT_SUCCESS;
 }
 
 int install_multi_package(int argc, const char** argv) {
@@ -739,6 +742,20 @@
 }
 
 int delete_device_file(const std::string& filename) {
-    std::string cmd = "rm -f " + escape_arg(filename);
-    return send_shell_command(cmd);
+    // http://b/17339227 "Sideloading a Readonly File Results in a Prompt to
+    // Delete" caused us to add `-f` here, to avoid the equivalent of the `-i`
+    // prompt that you get from BSD rm (used in Android 5) if you have a
+    // non-writable file and stdin is a tty (which is true for old versions of
+    // adbd).
+    //
+    // Unfortunately, `rm -f` requires Android 4.3, so that workaround broke
+    // earlier Android releases. This was reported as http://b/37704384 "adb
+    // install -r passes invalid argument to rm on Android 4.1" and
+    // http://b/37035817 "ADB Fails: rm failed for -f, No such file or
+    // directory".
+    //
+    // Testing on a variety of devices and emulators shows that redirecting
+    // stdin is sufficient to avoid the pseudo-`-i`, and works on toolbox,
+    // BSD, and toybox versions of rm.
+    return send_shell_command("rm " + escape_arg(filename) + " </dev/null");
 }
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 11a3dfd..48853b7 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -96,9 +96,8 @@
         " version                  show version num\n"
         "\n"
         "networking:\n"
-        " connect HOST[:PORT]      connect to a device via TCP/IP [default port=5555]\n"
-        " disconnect [HOST[:PORT]]\n"
-        "     disconnect from given TCP/IP device [default port=5555], or all\n"
+        " connect HOST[:PORT]      connect to a device via TCP/IP\n"
+        " disconnect [[HOST]:PORT] disconnect from given TCP/IP device, or all\n"
         " forward --list           list all forward socket connections\n"
         " forward [--no-rebind] LOCAL REMOTE\n"
         "     forward socket connection using:\n"
@@ -130,7 +129,7 @@
         "     -a: preserve file timestamp and mode\n"
         " sync [all|data|odm|oem|product|system|system_ext|vendor]\n"
         "     sync a local build from $ANDROID_PRODUCT_OUT to the device (default all)\n"
-        "     -l: list but don't copy\n"
+        "     -l: list files that would be copied, but don't copy them\n"
         "\n"
         "shell:\n"
         " shell [-e ESCAPE] [-n] [-Tt] [-x] [COMMAND...]\n"
@@ -154,6 +153,7 @@
         "     -d: allow version code downgrade (debuggable packages only)\n"
         "     -p: partial application install (install-multiple only)\n"
         "     -g: grant all runtime permissions\n"
+        "     --abi ABI: override platform's default ABI\n"
         "     --instant: cause the app to be installed as an ephemeral install app\n"
         "     --no-streaming: always push APK to device and invoke Package Manager as separate steps\n"
         "     --streaming: force streaming APK directly into Package Manager\n"
@@ -165,6 +165,7 @@
 #ifndef _WIN32
         "     --local-agent: locate agent files from local source build (instead of SDK location)\n"
 #endif
+        "     (See also `adb shell pm help` for more options.)\n"
         //TODO--installlog <filename>
         " uninstall [-k] PACKAGE\n"
         "     remove this app package from the device\n"
@@ -1615,13 +1616,13 @@
         return adb_query_command(query);
     }
     else if (!strcmp(argv[0], "connect")) {
-        if (argc != 2) error_exit("usage: adb connect <host>[:<port>]");
+        if (argc != 2) error_exit("usage: adb connect HOST[:PORT>]");
 
         std::string query = android::base::StringPrintf("host:connect:%s", argv[1]);
         return adb_query_command(query);
     }
     else if (!strcmp(argv[0], "disconnect")) {
-        if (argc > 2) error_exit("usage: adb disconnect [<host>[:<port>]]");
+        if (argc > 2) error_exit("usage: adb disconnect [HOST[:PORT]]");
 
         std::string query = android::base::StringPrintf("host:disconnect:%s",
                                                         (argc == 2) ? argv[1] : "");
@@ -1891,7 +1892,10 @@
     } else if (!strcmp(argv[0], "track-jdwp")) {
         return adb_connect_command("track-jdwp");
     } else if (!strcmp(argv[0], "track-devices")) {
-        return adb_connect_command("host:track-devices");
+        if (argc > 2 || (argc == 2 && strcmp(argv[1], "-l"))) {
+            error_exit("usage: adb track-devices [-l]");
+        }
+        return adb_connect_command(argc == 2 ? "host:track-devices-l" : "host:track-devices");
     } else if (!strcmp(argv[0], "raw")) {
         if (argc != 2) {
             error_exit("usage: adb raw SERVICE");
diff --git a/adb/client/fastdeploy.cpp b/adb/client/fastdeploy.cpp
index f4e8664..fbae219 100644
--- a/adb/client/fastdeploy.cpp
+++ b/adb/client/fastdeploy.cpp
@@ -27,6 +27,9 @@
 #include "androidfw/ZipFileRO.h"
 #include "client/file_sync_client.h"
 #include "commandline.h"
+#include "deployagent.inc"        // Generated include via build rule.
+#include "deployagentscript.inc"  // Generated include via build rule.
+#include "fastdeploy/deploypatchgenerator/deploy_patch_generator.h"
 #include "fastdeploycallbacks.h"
 #include "sysdeps.h"
 
@@ -35,6 +38,8 @@
 static constexpr long kRequiredAgentVersion = 0x00000002;
 
 static constexpr const char* kDeviceAgentPath = "/data/local/tmp/";
+static constexpr const char* kDeviceAgentFile = "/data/local/tmp/deployagent.jar";
+static constexpr const char* kDeviceAgentScript = "/data/local/tmp/deployagent";
 
 static bool g_use_localagent = false;
 
@@ -71,46 +76,32 @@
     g_use_localagent = use_localagent;
 }
 
-// local_path - must start with a '/' and be relative to $ANDROID_PRODUCT_OUT
-static std::string get_agent_component_host_path(const char* local_path, const char* sdk_path) {
-    std::string adb_dir = android::base::GetExecutableDirectory();
-    if (adb_dir.empty()) {
-        error_exit("Could not determine location of adb!");
-    }
-
-    if (g_use_localagent) {
-        const char* product_out = getenv("ANDROID_PRODUCT_OUT");
-        if (product_out == nullptr) {
-            error_exit("Could not locate %s because $ANDROID_PRODUCT_OUT is not defined",
-                       local_path);
-        }
-        return android::base::StringPrintf("%s%s", product_out, local_path);
-    } else {
-        return adb_dir + sdk_path;
-    }
-}
-
 static bool deploy_agent(bool checkTimeStamps) {
     std::vector<const char*> srcs;
-    std::string jar_path =
-            get_agent_component_host_path("/system/framework/deployagent.jar", "/deployagent.jar");
-    std::string script_path =
-            get_agent_component_host_path("/system/bin/deployagent", "/deployagent");
-    srcs.push_back(jar_path.c_str());
-    srcs.push_back(script_path.c_str());
-
-    if (do_sync_push(srcs, kDeviceAgentPath, checkTimeStamps)) {
-        // on windows the shell script might have lost execute permission
-        // so need to set this explicitly
-        const char* kChmodCommandPattern = "chmod 777 %sdeployagent";
-        std::string chmodCommand =
-                android::base::StringPrintf(kChmodCommandPattern, kDeviceAgentPath);
-        int ret = send_shell_command(chmodCommand);
-        if (ret != 0) {
-            error_exit("Error executing %s returncode: %d", chmodCommand.c_str(), ret);
-        }
-    } else {
-        error_exit("Error pushing agent files to device");
+    // TODO: Deploy agent from bin2c directly instead of writing to disk first.
+    TemporaryFile tempAgent;
+    android::base::WriteFully(tempAgent.fd, kDeployAgent, sizeof(kDeployAgent));
+    srcs.push_back(tempAgent.path);
+    if (!do_sync_push(srcs, kDeviceAgentFile, checkTimeStamps)) {
+        error_exit("Failed to push fastdeploy agent to device.");
+    }
+    srcs.clear();
+    // TODO: Deploy agent from bin2c directly instead of writing to disk first.
+    TemporaryFile tempAgentScript;
+    android::base::WriteFully(tempAgentScript.fd, kDeployAgentScript, sizeof(kDeployAgentScript));
+    srcs.push_back(tempAgentScript.path);
+    if (!do_sync_push(srcs, kDeviceAgentScript, checkTimeStamps)) {
+        error_exit("Failed to push fastdeploy agent script to device.");
+    }
+    srcs.clear();
+    // on windows the shell script might have lost execute permission
+    // so need to set this explicitly
+    const char* kChmodCommandPattern = "chmod 777 %s";
+    std::string chmodCommand =
+            android::base::StringPrintf(kChmodCommandPattern, kDeviceAgentScript);
+    int ret = send_shell_command(chmodCommand);
+    if (ret != 0) {
+        error_exit("Error executing %s returncode: %d", chmodCommand.c_str(), ret);
     }
 
     return true;
@@ -238,34 +229,15 @@
     }
 }
 
-static std::string get_patch_generator_command() {
-    if (g_use_localagent) {
-        // This should never happen on a Windows machine
-        const char* host_out = getenv("ANDROID_HOST_OUT");
-        if (host_out == nullptr) {
-            error_exit(
-                    "Could not locate deploypatchgenerator.jar because $ANDROID_HOST_OUT "
-                    "is not defined");
-        }
-        return android::base::StringPrintf("java -jar %s/framework/deploypatchgenerator.jar",
-                                           host_out);
-    }
-
-    std::string adb_dir = android::base::GetExecutableDirectory();
-    if (adb_dir.empty()) {
-        error_exit("Could not locate deploypatchgenerator.jar");
-    }
-    return android::base::StringPrintf(R"(java -jar "%s/deploypatchgenerator.jar")",
-                                       adb_dir.c_str());
-}
-
 void create_patch(const char* apkPath, const char* metadataPath, const char* patchPath) {
-    std::string generatePatchCommand = android::base::StringPrintf(
-            R"(%s "%s" "%s" > "%s")", get_patch_generator_command().c_str(), apkPath, metadataPath,
-            patchPath);
-    int returnCode = system(generatePatchCommand.c_str());
-    if (returnCode != 0) {
-        error_exit("Executing %s returned %d", generatePatchCommand.c_str(), returnCode);
+    DeployPatchGenerator generator(false);
+    unique_fd patchFd(adb_open(patchPath, O_WRONLY | O_CREAT | O_CLOEXEC));
+    if (patchFd < 0) {
+        perror_exit("adb: failed to create %s", patchPath);
+    }
+    bool success = generator.CreatePatch(apkPath, metadataPath, patchFd);
+    if (!success) {
+        error_exit("Failed to create patch for %s", apkPath);
     }
 }
 
diff --git a/adb/daemon/shell_service.cpp b/adb/daemon/shell_service.cpp
index de97068..0fb14c4 100644
--- a/adb/daemon/shell_service.cpp
+++ b/adb/daemon/shell_service.cpp
@@ -222,7 +222,7 @@
 bool Subprocess::ForkAndExec(std::string* error) {
     unique_fd child_stdinout_sfd, child_stderr_sfd;
     unique_fd parent_error_sfd, child_error_sfd;
-    char pts_name[PATH_MAX];
+    const char* pts_name = nullptr;
 
     if (command_.empty()) {
         __android_log_security_bswrite(SEC_TAG_ADB_SHELL_INTERACTIVE, "");
@@ -283,10 +283,22 @@
     cenv.push_back(nullptr);
 
     if (type_ == SubprocessType::kPty) {
-        int fd;
-        pid_ = forkpty(&fd, pts_name, nullptr, nullptr);
+        unique_fd pty_master(posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC));
+        if (pty_master == -1) {
+            *error =
+                    android::base::StringPrintf("failed to create pty master: %s", strerror(errno));
+            return false;
+        }
+        if (unlockpt(pty_master.get()) != 0) {
+            *error = android::base::StringPrintf("failed to unlockpt pty master: %s",
+                                                 strerror(errno));
+            return false;
+        }
+
+        pid_ = fork();
+        pts_name = ptsname(pty_master.get());
         if (pid_ > 0) {
-          stdinout_sfd_.reset(fd);
+            stdinout_sfd_ = std::move(pty_master);
         }
     } else {
         if (!CreateSocketpair(&stdinout_sfd_, &child_stdinout_sfd)) {
diff --git a/adb/fastdeploy/Android.bp b/adb/fastdeploy/Android.bp
index 1ba0de0..95e1a28 100644
--- a/adb/fastdeploy/Android.bp
+++ b/adb/fastdeploy/Android.bp
@@ -13,27 +13,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-
 java_binary {
     name: "deployagent",
     sdk_version: "24",
     srcs: ["deployagent/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"],
     static_libs: ["apkzlib_zip"],
-    wrapper: "deployagent/deployagent.sh",
     proto: {
         type: "lite",
     },
     dex_preopt: {
         enabled: false,
     }
-}
-
-java_binary_host {
-    name: "deploypatchgenerator",
-    srcs: ["deploypatchgenerator/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"],
-    static_libs: ["apkzlib"],
-    manifest: "deploypatchgenerator/manifest.txt",
-    proto: {
-        type: "full",
-    }
-}
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
index 2d3b135..a8103c4 100644
--- a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
+++ b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
@@ -181,7 +181,7 @@
     private static void extractMetaData(String packageName) throws IOException {
         File apkFile = getFileFromPackageName(packageName);
         APKMetaData apkMetaData = PatchUtils.getAPKMetaData(apkFile);
-        apkMetaData.writeDelimitedTo(System.out);
+        apkMetaData.writeTo(System.out);
     }
 
     private static int createInstallSession(String[] args) throws IOException {
@@ -223,7 +223,6 @@
         }
 
         int writeExitCode = writePatchedDataToSession(new RandomAccessFile(deviceFile, "r"), deltaStream, sessionId);
-
         if (writeExitCode == 0) {
             return commitInstallSession(sessionId);
         } else {
@@ -285,7 +284,6 @@
             if (oldDataLen > 0) {
                 PatchUtils.pipe(oldData, outputStream, buffer, (int) oldDataLen);
             }
-
             newDataBytesWritten += copyLen + oldDataLen;
         }
 
diff --git a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
index f0f00e1..c60f9a6 100644
--- a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
+++ b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
@@ -39,7 +39,7 @@
 class PatchUtils {
     private static final long NEGATIVE_MASK = 1L << 63;
     private static final long NEGATIVE_LONG_SIGN_MASK = 1L << 63;
-    public static final String SIGNATURE = "HAMADI/IHD";
+    public static final String SIGNATURE = "FASTDEPLOY";
 
     private static long getOffsetFromEntry(StoredEntry entry) {
         return entry.getCentralDirectoryHeader().getOffset() + entry.getLocalHeaderSize();
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
new file mode 100644
index 0000000..22c9243
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
@@ -0,0 +1,167 @@
+/*
+ * 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 "deploy_patch_generator.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <algorithm>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "adb_unique_fd.h"
+#include "android-base/file.h"
+#include "patch_utils.h"
+#include "sysdeps.h"
+
+using namespace com::android::fastdeploy;
+
+void DeployPatchGenerator::Log(const char* fmt, ...) {
+    if (!is_verbose_) {
+        return;
+    }
+    va_list ap;
+    va_start(ap, fmt);
+    vprintf(fmt, ap);
+    printf("\n");
+    va_end(ap);
+}
+
+void DeployPatchGenerator::APKEntryToLog(const APKEntry& entry) {
+    Log("Filename: %s", entry.filename().c_str());
+    Log("CRC32: 0x%08llX", entry.crc32());
+    Log("Data Offset: %lld", entry.dataoffset());
+    Log("Compressed Size: %lld", entry.compressedsize());
+    Log("Uncompressed Size: %lld", entry.uncompressedsize());
+}
+
+void DeployPatchGenerator::APKMetaDataToLog(const char* file, const APKMetaData& metadata) {
+    if (!is_verbose_) {
+        return;
+    }
+    Log("APK Metadata: %s", file);
+    for (int i = 0; i < metadata.entries_size(); i++) {
+        const APKEntry& entry = metadata.entries(i);
+        APKEntryToLog(entry);
+    }
+}
+
+void DeployPatchGenerator::ReportSavings(const std::vector<SimpleEntry>& identicalEntries,
+                                         uint64_t totalSize) {
+    long totalEqualBytes = 0;
+    int totalEqualFiles = 0;
+    for (size_t i = 0; i < identicalEntries.size(); i++) {
+        if (identicalEntries[i].deviceEntry != nullptr) {
+            totalEqualBytes += identicalEntries[i].localEntry->compressedsize();
+            totalEqualFiles++;
+        }
+    }
+    float savingPercent = (totalEqualBytes * 100.0f) / totalSize;
+    fprintf(stderr, "Detected %d equal APK entries\n", totalEqualFiles);
+    fprintf(stderr, "%ld bytes are equal out of %" PRIu64 " (%.2f%%)\n", totalEqualBytes, totalSize,
+            savingPercent);
+}
+
+void DeployPatchGenerator::GeneratePatch(const std::vector<SimpleEntry>& entriesToUseOnDevice,
+                                         const char* localApkPath, borrowed_fd output) {
+    unique_fd input(adb_open(localApkPath, O_RDONLY | O_CLOEXEC));
+    size_t newApkSize = adb_lseek(input, 0L, SEEK_END);
+    adb_lseek(input, 0L, SEEK_SET);
+
+    PatchUtils::WriteSignature(output);
+    PatchUtils::WriteLong(newApkSize, output);
+    size_t currentSizeOut = 0;
+    // Write data from the host upto the first entry we have that matches a device entry. Then write
+    // the metadata about the device entry and repeat for all entries that match on device. Finally
+    // write out any data left. If the device and host APKs are exactly the same this ends up
+    // writing out zip metadata from the local APK followed by offsets to the data to use from the
+    // device APK.
+    for (auto&& entry : entriesToUseOnDevice) {
+        int64_t deviceDataOffset = entry.deviceEntry->dataoffset();
+        int64_t hostDataOffset = entry.localEntry->dataoffset();
+        int64_t deviceDataLength = entry.deviceEntry->compressedsize();
+        int64_t deltaFromDeviceDataStart = hostDataOffset - currentSizeOut;
+        PatchUtils::WriteLong(deltaFromDeviceDataStart, output);
+        if (deltaFromDeviceDataStart > 0) {
+            PatchUtils::Pipe(input, output, deltaFromDeviceDataStart);
+        }
+        PatchUtils::WriteLong(deviceDataOffset, output);
+        PatchUtils::WriteLong(deviceDataLength, output);
+        adb_lseek(input, deviceDataLength, SEEK_CUR);
+        currentSizeOut += deltaFromDeviceDataStart + deviceDataLength;
+    }
+    if (currentSizeOut != newApkSize) {
+        PatchUtils::WriteLong(newApkSize - currentSizeOut, output);
+        PatchUtils::Pipe(input, output, newApkSize - currentSizeOut);
+        PatchUtils::WriteLong(0, output);
+        PatchUtils::WriteLong(0, output);
+    }
+}
+
+bool DeployPatchGenerator::CreatePatch(const char* localApkPath, const char* deviceApkMetadataPath,
+                                       borrowed_fd output) {
+    std::string content;
+    APKMetaData deviceApkMetadata;
+    if (android::base::ReadFileToString(deviceApkMetadataPath, &content)) {
+        deviceApkMetadata.ParsePartialFromString(content);
+    } else {
+        // TODO: What do we want to do if we don't find any metadata.
+        // The current fallback behavior is to build a patch with the contents of |localApkPath|.
+    }
+
+    APKMetaData localApkMetadata = PatchUtils::GetAPKMetaData(localApkPath);
+    // Log gathered metadata info.
+    APKMetaDataToLog(deviceApkMetadataPath, deviceApkMetadata);
+    APKMetaDataToLog(localApkPath, localApkMetadata);
+
+    std::vector<SimpleEntry> identicalEntries;
+    uint64_t totalSize =
+            BuildIdenticalEntries(identicalEntries, localApkMetadata, deviceApkMetadata);
+    ReportSavings(identicalEntries, totalSize);
+    GeneratePatch(identicalEntries, localApkPath, output);
+    return true;
+}
+
+uint64_t DeployPatchGenerator::BuildIdenticalEntries(std::vector<SimpleEntry>& outIdenticalEntries,
+                                                     const APKMetaData& localApkMetadata,
+                                                     const APKMetaData& deviceApkMetadata) {
+    uint64_t totalSize = 0;
+    for (int i = 0; i < localApkMetadata.entries_size(); i++) {
+        const APKEntry& localEntry = localApkMetadata.entries(i);
+        totalSize += localEntry.compressedsize();
+        for (int j = 0; j < deviceApkMetadata.entries_size(); j++) {
+            const APKEntry& deviceEntry = deviceApkMetadata.entries(j);
+            if (deviceEntry.crc32() == localEntry.crc32() &&
+                deviceEntry.filename().compare(localEntry.filename()) == 0) {
+                SimpleEntry simpleEntry;
+                simpleEntry.localEntry = const_cast<APKEntry*>(&localEntry);
+                simpleEntry.deviceEntry = const_cast<APKEntry*>(&deviceEntry);
+                APKEntryToLog(localEntry);
+                outIdenticalEntries.push_back(simpleEntry);
+                break;
+            }
+        }
+    }
+    std::sort(outIdenticalEntries.begin(), outIdenticalEntries.end(),
+              [](const SimpleEntry& lhs, const SimpleEntry& rhs) {
+                  return lhs.localEntry->dataoffset() < rhs.localEntry->dataoffset();
+              });
+    return totalSize;
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h
new file mode 100644
index 0000000..30e41a5
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h
@@ -0,0 +1,102 @@
+/*
+ * 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 <vector>
+
+#include "adb_unique_fd.h"
+#include "fastdeploy/proto/ApkEntry.pb.h"
+
+/**
+ * This class is responsible for creating a patch that can be accepted by the deployagent. The
+ * patch format is documented in GeneratePatch.
+ */
+class DeployPatchGenerator {
+  public:
+    /**
+     * Simple struct to hold mapping between local metadata and device metadata.
+     */
+    struct SimpleEntry {
+        com::android::fastdeploy::APKEntry* localEntry;
+        com::android::fastdeploy::APKEntry* deviceEntry;
+    };
+
+    /**
+     * If |is_verbose| is true ApkEntries that are similar between device and host are written to
+     * the console.
+     */
+    explicit DeployPatchGenerator(bool is_verbose) : is_verbose_(is_verbose) {}
+    /**
+     * Given a |localApkPath|, and the |deviceApkMetadataPath| from an installed APK this function
+     * writes a patch to the given |output|.
+     */
+    bool CreatePatch(const char* localApkPath, const char* deviceApkMetadataPath,
+                     android::base::borrowed_fd output);
+
+  private:
+    bool is_verbose_;
+
+    /**
+     * Log function only logs data to stdout when |is_verbose_| is true.
+     */
+    void Log(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3)));
+
+    /**
+     * Helper function to log the APKMetaData structure. If |is_verbose_| is false this function
+     * early outs. |file| is the path to the file represented by |metadata|. This function is used
+     * for debugging / information.
+     */
+    void APKMetaDataToLog(const char* file, const com::android::fastdeploy::APKMetaData& metadata);
+    /**
+     * Helper function to log APKEntry.
+     */
+    void APKEntryToLog(const com::android::fastdeploy::APKEntry& entry);
+
+    /**
+     * Helper function to report savings by fastdeploy. This function prints out savings even with
+     * |is_verbose_| set to false. |totalSize| is used to show a percentage of savings. Note:
+     * |totalSize| is the size of the ZipEntries. Not the size of the entire file. The metadata of
+     * the zip data needs to be sent across with every iteration.
+     * [Patch format]
+     * |Fixed String| Signature
+     * |long|         New Size of Apk
+     * |Packets[]|    Array of Packets
+     *
+     * [Packet Format]
+     * |long|     Size of data to use from patch
+     * |byte[]|   Patch data
+     * |long|     Offset of data to use already on device
+     * |long|     Length of data to read from device APK
+     * TODO(b/138306784): Move the patch format to a proto.
+     */
+    void ReportSavings(const std::vector<SimpleEntry>& identicalEntries, uint64_t totalSize);
+
+    /**
+     * This enumerates each entry in |entriesToUseOnDevice| and builds a patch file copying data
+     * from |localApkPath| where we are unable to use entries already on the device. The new patch
+     * is written to |output|. The entries are expected to be sorted by data offset from lowest to
+     * highest.
+     */
+    void GeneratePatch(const std::vector<SimpleEntry>& entriesToUseOnDevice,
+                       const char* localApkPath, android::base::borrowed_fd output);
+
+  protected:
+    uint64_t BuildIdenticalEntries(
+            std::vector<SimpleEntry>& outIdenticalEntries,
+            const com::android::fastdeploy::APKMetaData& localApkMetadata,
+            const com::android::fastdeploy::APKMetaData& deviceApkMetadataPath);
+};
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp
new file mode 100644
index 0000000..9cdc44e
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 "deploy_patch_generator.h"
+#include "patch_utils.h"
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+
+#include "sysdeps.h"
+
+using namespace com::android::fastdeploy;
+
+static std::string GetTestFile(const std::string& name) {
+    return "fastdeploy/testdata/" + name;
+}
+
+class TestPatchGenerator : DeployPatchGenerator {
+  public:
+    TestPatchGenerator() : DeployPatchGenerator(false) {}
+    void GatherIdenticalEntries(std::vector<DeployPatchGenerator::SimpleEntry>& outIdenticalEntries,
+                                const APKMetaData& metadataA, const APKMetaData& metadataB) {
+        BuildIdenticalEntries(outIdenticalEntries, metadataA, metadataB);
+    }
+};
+
+TEST(DeployPatchGeneratorTest, IdenticalFileEntries) {
+    std::string apkPath = GetTestFile("rotating_cube-release.apk");
+    APKMetaData metadataA = PatchUtils::GetAPKMetaData(apkPath.c_str());
+    TestPatchGenerator generator;
+    std::vector<DeployPatchGenerator::SimpleEntry> entries;
+    generator.GatherIdenticalEntries(entries, metadataA, metadataA);
+    // Expect the entry count to match the number of entries in the metadata.
+    const uint32_t identicalCount = entries.size();
+    const uint32_t entriesCount = metadataA.entries_size();
+    EXPECT_EQ(identicalCount, entriesCount);
+}
+
+TEST(DeployPatchGeneratorTest, NoDeviceMetadata) {
+    std::string apkPath = GetTestFile("rotating_cube-release.apk");
+    // Get size of our test apk.
+    long apkSize = 0;
+    {
+        unique_fd apkFile(adb_open(apkPath.c_str(), O_RDWR));
+        apkSize = adb_lseek(apkFile, 0L, SEEK_END);
+    }
+
+    // Create a patch that is 100% different.
+    TemporaryFile output;
+    DeployPatchGenerator generator(true);
+    generator.CreatePatch(apkPath.c_str(), "", output.fd);
+
+    // Expect a patch file that has a size at least the size of our initial APK.
+    long patchSize = adb_lseek(output.fd, 0L, SEEK_END);
+    EXPECT_GT(patchSize, apkSize);
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/manifest.txt b/adb/fastdeploy/deploypatchgenerator/manifest.txt
deleted file mode 100644
index 5c00505..0000000
--- a/adb/fastdeploy/deploypatchgenerator/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-Class: com.android.fastdeploy.DeployPatchGenerator
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp b/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp
new file mode 100644
index 0000000..f11ddd1
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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 "patch_utils.h"
+
+#include <androidfw/ZipFileRO.h>
+#include <stdio.h>
+
+#include "adb_io.h"
+#include "android-base/endian.h"
+#include "sysdeps.h"
+
+using namespace com::android;
+using namespace com::android::fastdeploy;
+using namespace android::base;
+
+static constexpr char kSignature[] = "FASTDEPLOY";
+
+APKMetaData PatchUtils::GetAPKMetaData(const char* apkPath) {
+    APKMetaData apkMetaData;
+#undef open
+    std::unique_ptr<android::ZipFileRO> zipFile(android::ZipFileRO::open(apkPath));
+#define open ___xxx_unix_open
+    if (zipFile == nullptr) {
+        printf("Could not open %s", apkPath);
+        exit(1);
+    }
+    void* cookie;
+    if (zipFile->startIteration(&cookie)) {
+        android::ZipEntryRO entry;
+        while ((entry = zipFile->nextEntry(cookie)) != NULL) {
+            char fileName[256];
+            // Make sure we have a file name.
+            // TODO: Handle filenames longer than 256.
+            if (zipFile->getEntryFileName(entry, fileName, sizeof(fileName))) {
+                continue;
+            }
+
+            uint32_t uncompressedSize, compressedSize, crc32;
+            int64_t dataOffset;
+            zipFile->getEntryInfo(entry, nullptr, &uncompressedSize, &compressedSize, &dataOffset,
+                                  nullptr, &crc32);
+            APKEntry* apkEntry = apkMetaData.add_entries();
+            apkEntry->set_crc32(crc32);
+            apkEntry->set_filename(fileName);
+            apkEntry->set_compressedsize(compressedSize);
+            apkEntry->set_uncompressedsize(uncompressedSize);
+            apkEntry->set_dataoffset(dataOffset);
+        }
+    }
+    return apkMetaData;
+}
+
+void PatchUtils::WriteSignature(borrowed_fd output) {
+    WriteFdExactly(output, kSignature, sizeof(kSignature) - 1);
+}
+
+void PatchUtils::WriteLong(int64_t value, borrowed_fd output) {
+    int64_t toLittleEndian = htole64(value);
+    WriteFdExactly(output, &toLittleEndian, sizeof(int64_t));
+}
+
+void PatchUtils::Pipe(borrowed_fd input, borrowed_fd output, size_t amount) {
+    constexpr static int BUFFER_SIZE = 128 * 1024;
+    char buffer[BUFFER_SIZE];
+    size_t transferAmount = 0;
+    while (transferAmount != amount) {
+        long chunkAmount =
+                amount - transferAmount > BUFFER_SIZE ? BUFFER_SIZE : amount - transferAmount;
+        long readAmount = adb_read(input, buffer, chunkAmount);
+        WriteFdExactly(output, buffer, readAmount);
+        transferAmount += readAmount;
+    }
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils.h b/adb/fastdeploy/deploypatchgenerator/patch_utils.h
new file mode 100644
index 0000000..0ebfe8f
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils.h
@@ -0,0 +1,46 @@
+/*
+ * 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 "adb_unique_fd.h"
+#include "fastdeploy/proto/ApkEntry.pb.h"
+
+/**
+ * Helper class that mirrors the PatchUtils from deploy agent.
+ */
+class PatchUtils {
+  public:
+    /**
+     * This function takes a local APK file and builds the APKMetaData required by the patching
+     * algorithm. The if this function has an error a string is printed to the terminal and exit(1)
+     * is called.
+     */
+    static com::android::fastdeploy::APKMetaData GetAPKMetaData(const char* file);
+    /**
+     * Writes a fixed signature string to the header of the patch.
+     */
+    static void WriteSignature(android::base::borrowed_fd output);
+    /**
+     * Writes an int64 to the |output| reversing the bytes.
+     */
+    static void WriteLong(int64_t value, android::base::borrowed_fd output);
+    /**
+     * Copy |amount| of data from |input| to |output|.
+     */
+    static void Pipe(android::base::borrowed_fd input, android::base::borrowed_fd output,
+                     size_t amount);
+};
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp b/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp
new file mode 100644
index 0000000..a7eeebf
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp
@@ -0,0 +1,96 @@
+/*
+ * 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 "patch_utils.h"
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sstream>
+#include <string>
+
+#include "adb_io.h"
+#include "sysdeps.h"
+
+using namespace com::android::fastdeploy;
+
+static std::string GetTestFile(const std::string& name) {
+    return "fastdeploy/testdata/" + name;
+}
+
+bool FileMatchesContent(android::base::borrowed_fd input, const char* contents,
+                        ssize_t contentsSize) {
+    adb_lseek(input, 0, SEEK_SET);
+    // Use a temp buffer larger than any test contents.
+    constexpr int BUFFER_SIZE = 2048;
+    char buffer[BUFFER_SIZE];
+    bool result = true;
+    // Validate size of files is equal.
+    ssize_t readAmount = adb_read(input, buffer, BUFFER_SIZE);
+    EXPECT_EQ(readAmount, contentsSize);
+    result = memcmp(buffer, contents, readAmount) == 0;
+    for (int i = 0; i < readAmount; i++) {
+        printf("%x", buffer[i]);
+    }
+    printf(" == ");
+    for (int i = 0; i < contentsSize; i++) {
+        printf("%x", contents[i]);
+    }
+    printf("\n");
+
+    return result;
+}
+
+TEST(PatchUtilsTest, SwapLongWrites) {
+    TemporaryFile output;
+    PatchUtils::WriteLong(0x0011223344556677, output.fd);
+    adb_lseek(output.fd, 0, SEEK_SET);
+    const char expected[] = {0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
+    EXPECT_TRUE(FileMatchesContent(output.fd, expected, 8));
+}
+
+TEST(PatchUtilsTest, PipeWritesAmountToOutput) {
+    std::string expected("Some Data");
+    TemporaryFile input;
+    TemporaryFile output;
+    // Populate input file.
+    WriteFdExactly(input.fd, expected);
+    adb_lseek(input.fd, 0, SEEK_SET);
+    // Open input file for read, and output file for write.
+    PatchUtils::Pipe(input.fd, output.fd, expected.size());
+    // Validate pipe worked
+    EXPECT_TRUE(FileMatchesContent(output.fd, expected.c_str(), expected.size()));
+}
+
+TEST(PatchUtilsTest, SignatureConstMatches) {
+    std::string apkFile = GetTestFile("rotating_cube-release.apk");
+    TemporaryFile output;
+    PatchUtils::WriteSignature(output.fd);
+    std::string contents("FASTDEPLOY");
+    EXPECT_TRUE(FileMatchesContent(output.fd, contents.c_str(), contents.size()));
+}
+
+TEST(PatchUtilsTest, GatherMetadata) {
+    std::string apkFile = GetTestFile("rotating_cube-release.apk");
+    APKMetaData metadata = PatchUtils::GetAPKMetaData(apkFile.c_str());
+    std::string expectedMetadata;
+    android::base::ReadFileToString(GetTestFile("rotating_cube-metadata-release.data"),
+                                    &expectedMetadata);
+    std::string actualMetadata;
+    metadata.SerializeToString(&actualMetadata);
+    EXPECT_EQ(expectedMetadata, actualMetadata);
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java b/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java
deleted file mode 100644
index 24b2eab..0000000
--- a/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.fastdeploy;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.StringBuilder;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.ArrayList;
-
-import java.nio.charset.StandardCharsets;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.AbstractMap.SimpleEntry;
-
-import com.android.fastdeploy.APKMetaData;
-import com.android.fastdeploy.APKEntry;
-
-public final class DeployPatchGenerator {
-    private static final int BUFFER_SIZE = 128 * 1024;
-
-    public static void main(String[] args) {
-        try {
-            if (args.length < 2) {
-                showUsage(0);
-            }
-
-            boolean verbose = false;
-            if (args.length > 2) {
-                String verboseFlag = args[2];
-                if (verboseFlag.compareTo("--verbose") == 0) {
-                    verbose = true;
-                }
-            }
-
-            StringBuilder sb = null;
-            String apkPath = args[0];
-            String deviceMetadataPath = args[1];
-            File hostFile = new File(apkPath);
-
-            List<APKEntry> deviceZipEntries = getMetadataFromFile(deviceMetadataPath);
-            System.err.println("Device Entries (" + deviceZipEntries.size() + ")");
-            if (verbose) {
-                sb = new StringBuilder();
-                for (APKEntry entry : deviceZipEntries) {
-                    APKEntryToString(entry, sb);
-                }
-                System.err.println(sb.toString());
-            }
-
-            List<APKEntry> hostFileEntries = PatchUtils.getAPKMetaData(hostFile).getEntriesList();
-            System.err.println("Host Entries (" + hostFileEntries.size() + ")");
-            if (verbose) {
-                sb = new StringBuilder();
-                for (APKEntry entry : hostFileEntries) {
-                    APKEntryToString(entry, sb);
-                }
-                System.err.println(sb.toString());
-            }
-
-            List<SimpleEntry<APKEntry, APKEntry>> identicalContentsEntrySet =
-                getIdenticalContents(deviceZipEntries, hostFileEntries);
-            reportIdenticalContents(identicalContentsEntrySet, hostFile);
-
-            if (verbose) {
-                sb = new StringBuilder();
-                for (SimpleEntry<APKEntry, APKEntry> identicalEntry : identicalContentsEntrySet) {
-                    APKEntry entry = identicalEntry.getValue();
-                    APKEntryToString(entry, sb);
-                }
-                System.err.println("Identical Entries (" + identicalContentsEntrySet.size() + ")");
-                System.err.println(sb.toString());
-            }
-
-            createPatch(identicalContentsEntrySet, hostFile, System.out);
-        } catch (Exception e) {
-            System.err.println("Error: " + e);
-            e.printStackTrace();
-            System.exit(2);
-        }
-        System.exit(0);
-    }
-
-    private static void showUsage(int exitCode) {
-        System.err.println("usage: deploypatchgenerator <apkpath> <deviceapkmetadata> [--verbose]");
-        System.err.println("");
-        System.exit(exitCode);
-    }
-
-    private static void APKEntryToString(APKEntry entry, StringBuilder outputString) {
-        outputString.append(String.format("Filename: %s\n", entry.getFileName()));
-        outputString.append(String.format("CRC32: 0x%08X\n", entry.getCrc32()));
-        outputString.append(String.format("Data Offset: %d\n", entry.getDataOffset()));
-        outputString.append(String.format("Compressed Size: %d\n", entry.getCompressedSize()));
-        outputString.append(String.format("Uncompressed Size: %d\n", entry.getUncompressedSize()));
-    }
-
-    private static List<APKEntry> getMetadataFromFile(String deviceMetadataPath) throws IOException {
-        InputStream is = new FileInputStream(new File(deviceMetadataPath));
-        APKMetaData apkMetaData = APKMetaData.parseDelimitedFrom(is);
-        return apkMetaData.getEntriesList();
-    }
-
-    private static List<SimpleEntry<APKEntry, APKEntry>> getIdenticalContents(
-        List<APKEntry> deviceZipEntries, List<APKEntry> hostZipEntries) throws IOException {
-        List<SimpleEntry<APKEntry, APKEntry>> identicalContents =
-            new ArrayList<SimpleEntry<APKEntry, APKEntry>>();
-
-        for (APKEntry deviceZipEntry : deviceZipEntries) {
-            for (APKEntry hostZipEntry : hostZipEntries) {
-                if (deviceZipEntry.getCrc32() == hostZipEntry.getCrc32() &&
-                    deviceZipEntry.getFileName().equals(hostZipEntry.getFileName())) {
-                    identicalContents.add(new SimpleEntry(deviceZipEntry, hostZipEntry));
-                }
-            }
-        }
-
-        Collections.sort(identicalContents, new Comparator<SimpleEntry<APKEntry, APKEntry>>() {
-            @Override
-            public int compare(
-                SimpleEntry<APKEntry, APKEntry> p1, SimpleEntry<APKEntry, APKEntry> p2) {
-                return Long.compare(p1.getValue().getDataOffset(), p2.getValue().getDataOffset());
-            }
-        });
-
-        return identicalContents;
-    }
-
-    private static void reportIdenticalContents(
-        List<SimpleEntry<APKEntry, APKEntry>> identicalContentsEntrySet, File hostFile)
-        throws IOException {
-        long totalEqualBytes = 0;
-        int totalEqualFiles = 0;
-        for (SimpleEntry<APKEntry, APKEntry> entries : identicalContentsEntrySet) {
-            APKEntry hostAPKEntry = entries.getValue();
-            totalEqualBytes += hostAPKEntry.getCompressedSize();
-            totalEqualFiles++;
-        }
-
-        float savingPercent = (float) (totalEqualBytes * 100) / hostFile.length();
-
-        System.err.println("Detected " + totalEqualFiles + " equal APK entries");
-        System.err.println(totalEqualBytes + " bytes are equal out of " + hostFile.length() + " ("
-            + savingPercent + "%)");
-    }
-
-    static void createPatch(List<SimpleEntry<APKEntry, APKEntry>> zipEntrySimpleEntrys,
-        File hostFile, OutputStream patchStream) throws IOException, PatchFormatException {
-        FileInputStream hostFileInputStream = new FileInputStream(hostFile);
-
-        patchStream.write(PatchUtils.SIGNATURE.getBytes(StandardCharsets.US_ASCII));
-        PatchUtils.writeFormattedLong(hostFile.length(), patchStream);
-
-        byte[] buffer = new byte[BUFFER_SIZE];
-        long totalBytesWritten = 0;
-        Iterator<SimpleEntry<APKEntry, APKEntry>> entrySimpleEntryIterator =
-            zipEntrySimpleEntrys.iterator();
-        while (entrySimpleEntryIterator.hasNext()) {
-            SimpleEntry<APKEntry, APKEntry> entrySimpleEntry = entrySimpleEntryIterator.next();
-            APKEntry deviceAPKEntry = entrySimpleEntry.getKey();
-            APKEntry hostAPKEntry = entrySimpleEntry.getValue();
-
-            long newDataLen = hostAPKEntry.getDataOffset() - totalBytesWritten;
-            long oldDataOffset = deviceAPKEntry.getDataOffset();
-            long oldDataLen = deviceAPKEntry.getCompressedSize();
-
-            PatchUtils.writeFormattedLong(newDataLen, patchStream);
-            PatchUtils.pipe(hostFileInputStream, patchStream, buffer, newDataLen);
-            PatchUtils.writeFormattedLong(oldDataOffset, patchStream);
-            PatchUtils.writeFormattedLong(oldDataLen, patchStream);
-
-            long skip = hostFileInputStream.skip(oldDataLen);
-            if (skip != oldDataLen) {
-                throw new PatchFormatException("skip error: attempted to skip " + oldDataLen
-                    + " bytes but return code was " + skip);
-            }
-            totalBytesWritten += oldDataLen + newDataLen;
-        }
-        long remainderLen = hostFile.length() - totalBytesWritten;
-        PatchUtils.writeFormattedLong(remainderLen, patchStream);
-        PatchUtils.pipe(hostFileInputStream, patchStream, buffer, remainderLen);
-        PatchUtils.writeFormattedLong(0, patchStream);
-        PatchUtils.writeFormattedLong(0, patchStream);
-        patchStream.flush();
-    }
-}
diff --git a/adb/fastdeploy/testdata/rotating_cube-metadata-release.data b/adb/fastdeploy/testdata/rotating_cube-metadata-release.data
new file mode 100644
index 0000000..0671bf3
--- /dev/null
+++ b/adb/fastdeploy/testdata/rotating_cube-metadata-release.data
@@ -0,0 +1,6 @@
+
+#ǂϫMETA-INF/MANIFEST.MFÇ Q(W
+#ƒ•ŽAndroidManifest.xml1 ä(è
+6¦µ€>#lib/armeabi-v7a/libvulkan_sample.so ÀÒQ(Œ²ì
+ —ã—‘resources.arscôàQ ´(´
+‹œÂÉclasses.dexÁ ÿ(ô
diff --git a/adb/fastdeploy/testdata/rotating_cube-release.apk b/adb/fastdeploy/testdata/rotating_cube-release.apk
new file mode 100644
index 0000000..d47e0ea
--- /dev/null
+++ b/adb/fastdeploy/testdata/rotating_cube-release.apk
Binary files differ
diff --git a/adb/socket_spec.cpp b/adb/socket_spec.cpp
index 1333724..98468b5 100644
--- a/adb/socket_spec.cpp
+++ b/adb/socket_spec.cpp
@@ -93,7 +93,7 @@
         }
     } else {
         std::string addr(spec.substr(4));
-        port_value = -1;
+        port_value = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
 
         // FIXME: ParseNetAddress rejects port 0. This currently doesn't hurt, because listening
         //        on an address that isn't 'localhost' is unsupported.
diff --git a/adb/socket_spec_test.cpp b/adb/socket_spec_test.cpp
index f5ec0f1..3a2f60c 100644
--- a/adb/socket_spec_test.cpp
+++ b/adb/socket_spec_test.cpp
@@ -20,38 +20,71 @@
 
 #include <gtest/gtest.h>
 
-TEST(socket_spec, parse_tcp_socket_spec) {
+TEST(socket_spec, parse_tcp_socket_spec_just_port) {
     std::string hostname, error, serial;
     int port;
     EXPECT_TRUE(parse_tcp_socket_spec("tcp:5037", &hostname, &port, &serial, &error));
     EXPECT_EQ("", hostname);
     EXPECT_EQ(5037, port);
     EXPECT_EQ("", serial);
+}
 
-    // Bad ports:
+TEST(socket_spec, parse_tcp_socket_spec_bad_ports) {
+    std::string hostname, error, serial;
+    int port;
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:", &hostname, &port, &serial, &error));
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:-1", &hostname, &port, &serial, &error));
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:65536", &hostname, &port, &serial, &error));
+}
 
+TEST(socket_spec, parse_tcp_socket_spec_host_and_port) {
+    std::string hostname, error, serial;
+    int port;
     EXPECT_TRUE(parse_tcp_socket_spec("tcp:localhost:1234", &hostname, &port, &serial, &error));
     EXPECT_EQ("localhost", hostname);
     EXPECT_EQ(1234, port);
     EXPECT_EQ("localhost:1234", serial);
+}
 
-    EXPECT_FALSE(parse_tcp_socket_spec("tcp:localhost", &hostname, &port, &serial, &error));
+TEST(socket_spec, parse_tcp_socket_spec_host_no_port) {
+    std::string hostname, error, serial;
+    int port;
+    EXPECT_TRUE(parse_tcp_socket_spec("tcp:localhost", &hostname, &port, &serial, &error));
+    EXPECT_EQ("localhost", hostname);
+    EXPECT_EQ(5555, port);
+    EXPECT_EQ("localhost:5555", serial);
+}
+
+TEST(socket_spec, parse_tcp_socket_spec_host_bad_ports) {
+    std::string hostname, error, serial;
+    int port;
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:localhost:", &hostname, &port, &serial, &error));
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:localhost:-1", &hostname, &port, &serial, &error));
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:localhost:65536", &hostname, &port, &serial, &error));
+}
 
-    // IPv6:
+TEST(socket_spec, parse_tcp_socket_spec_ipv6_and_port) {
+    std::string hostname, error, serial;
+    int port;
     EXPECT_TRUE(parse_tcp_socket_spec("tcp:[::1]:1234", &hostname, &port, &serial, &error));
     EXPECT_EQ("::1", hostname);
     EXPECT_EQ(1234, port);
     EXPECT_EQ("[::1]:1234", serial);
+}
 
+TEST(socket_spec, parse_tcp_socket_spec_ipv6_no_port) {
+    std::string hostname, error, serial;
+    int port;
+    EXPECT_TRUE(parse_tcp_socket_spec("tcp:::1", &hostname, &port, &serial, &error));
+    EXPECT_EQ("::1", hostname);
+    EXPECT_EQ(5555, port);
+    EXPECT_EQ("[::1]:5555", serial);
+}
+
+TEST(socket_spec, parse_tcp_socket_spec_ipv6_bad_ports) {
+    std::string hostname, error, serial;
+    int port;
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:[::1]", &hostname, &port, &serial, &error));
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:[::1]:", &hostname, &port, &serial, &error));
     EXPECT_FALSE(parse_tcp_socket_spec("tcp:[::1]:-1", &hostname, &port, &serial, &error));
-    EXPECT_FALSE(parse_tcp_socket_spec("tcp:::1", &hostname, &port, &serial, &error));
-    EXPECT_FALSE(parse_tcp_socket_spec("tcp:::1:1234", &hostname, &port, &serial, &error));
 }
diff --git a/adb/transport.cpp b/adb/transport.cpp
index 8bc925f..3d1d620 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -542,9 +542,7 @@
     // for the first time, even if no update occurred.
     if (tracker->update_needed) {
         tracker->update_needed = false;
-
-        std::string transports = list_transports(tracker->long_output);
-        device_tracker_send(tracker, transports);
+        device_tracker_send(tracker, list_transports(tracker->long_output));
     }
 }
 
@@ -587,13 +585,11 @@
     update_transport_status();
 
     // Notify `adb track-devices` clients.
-    std::string transports = list_transports(false);
-
     device_tracker* tracker = device_tracker_list;
     while (tracker != nullptr) {
         device_tracker* next = tracker->next;
         // This may destroy the tracker if the connection is closed.
-        device_tracker_send(tracker, transports);
+        device_tracker_send(tracker, list_transports(tracker->long_output));
         tracker = next;
     }
 }
diff --git a/fs_mgr/README.overlayfs.md b/fs_mgr/README.overlayfs.md
index d204bfd..fe2e052 100644
--- a/fs_mgr/README.overlayfs.md
+++ b/fs_mgr/README.overlayfs.md
@@ -95,8 +95,8 @@
 - Kernel must have CONFIG_OVERLAY_FS=y and will need to be patched
   with "*overlayfs: override_creds=off option bypass creator_cred*"
   if kernel is 4.4 or higher.
-  The patch is available on the upstream mailing list and the latest as of
-  Feb 8 2019 is https://lore.kernel.org/patchwork/patch/1009299/.
+  The patch series is available on the upstream mailing list and the latest as
+  of Jul 24 2019 is https://lore.kernel.org/patchwork/patch/1104577/.
   This patch adds an override_creds _mount_ option to overlayfs that
   permits legacy behavior for systems that do not have overlapping
   sepolicy rules, principals of least privilege, which is how Android behaves.
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/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 0ad8d9d..a4e0d76 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -150,6 +150,15 @@
     return true;
 }
 
+std::optional<DeviceMapper::Info> DeviceMapper::GetDetailedInfo(const std::string& name) const {
+    struct dm_ioctl io;
+    InitIo(&io, name);
+    if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) {
+        return std::nullopt;
+    }
+    return Info(io.flags);
+}
+
 DmDeviceState DeviceMapper::GetState(const std::string& name) const {
     struct dm_ioctl io;
     InitIo(&io, name);
@@ -162,6 +171,24 @@
     return DmDeviceState::SUSPENDED;
 }
 
+bool DeviceMapper::ChangeState(const std::string& name, DmDeviceState state) {
+    if (state != DmDeviceState::SUSPENDED && state != DmDeviceState::ACTIVE) {
+        return false;
+    }
+
+    struct dm_ioctl io;
+    InitIo(&io, name);
+
+    if (state == DmDeviceState::SUSPENDED) io.flags = DM_SUSPEND_FLAG;
+
+    if (ioctl(fd_, DM_DEV_SUSPEND, &io) < 0) {
+        PLOG(ERROR) << "DM_DEV_SUSPEND "
+                    << (state == DmDeviceState::SUSPENDED ? "suspend" : "resume") << " failed";
+        return false;
+    }
+    return true;
+}
+
 bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table) {
     std::string ignore_path;
     if (!CreateDevice(name, table, &ignore_path, 0ms)) {
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index da1013e..7c9804c 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -16,6 +16,7 @@
 
 #include "libdm/dm_target.h"
 
+#include <inttypes.h>
 #include <stdio.h>
 #include <sys/types.h>
 
@@ -149,6 +150,25 @@
     return base_device_ + " " + cow_device_ + " " + mode + " " + std::to_string(chunk_size_);
 }
 
+// Computes the percentage of complition for snapshot status.
+// @sectors_initial is the number of sectors_allocated stored right before
+// starting the merge.
+double DmTargetSnapshot::MergePercent(const DmTargetSnapshot::Status& status,
+                                      uint64_t sectors_initial) {
+    uint64_t s = status.sectors_allocated;
+    uint64_t t = status.total_sectors;
+    uint64_t m = status.metadata_sectors;
+    uint64_t i = sectors_initial == 0 ? t : sectors_initial;
+
+    if (t <= s || i <= s) {
+        return 0.0;
+    }
+    if (s == 0 || t == 0 || s <= m) {
+        return 100.0;
+    }
+    return 100.0 / (i - m) * (i - s);
+}
+
 bool DmTargetSnapshot::ReportsOverflow(const std::string& target_type) {
     DeviceMapper& dm = DeviceMapper::Instance();
     DmTargetTypeInfo info;
@@ -165,34 +185,40 @@
 }
 
 bool DmTargetSnapshot::ParseStatusText(const std::string& text, Status* status) {
+    // Try to parse the line as it should be
+    int args = sscanf(text.c_str(), "%" PRIu64 "/%" PRIu64 " %" PRIu64, &status->sectors_allocated,
+                      &status->total_sectors, &status->metadata_sectors);
+    if (args == 3) {
+        return true;
+    }
     auto sections = android::base::Split(text, " ");
+    if (sections.size() == 0) {
+        LOG(ERROR) << "could not parse empty status";
+        return false;
+    }
+    // Error codes are: "Invalid", "Overflow" and "Merge failed"
     if (sections.size() == 1) {
-        // This is probably an error code, "Invalid" is possible as is "Overflow"
-        // on 4.4+.
+        if (text == "Invalid" || text == "Overflow") {
+            status->error = text;
+            return true;
+        }
+    } else if (sections.size() == 2 && text == "Merge failed") {
         status->error = text;
         return true;
     }
-    if (sections.size() != 2) {
-        LOG(ERROR) << "snapshot status should have two components";
+    LOG(ERROR) << "could not parse snapshot status: wrong format";
+    return false;
+}
+
+bool DmTargetSnapshot::GetDevicesFromParams(const std::string& params, std::string* base_device,
+                                            std::string* cow_device) {
+    auto pieces = android::base::Split(params, " ");
+    if (pieces.size() < 2) {
+        LOG(ERROR) << "Parameter string is invalid: " << params;
         return false;
     }
-    auto sector_info = android::base::Split(sections[0], "/");
-    if (sector_info.size() != 2) {
-        LOG(ERROR) << "snapshot sector info should have two components";
-        return false;
-    }
-    if (!android::base::ParseUint(sections[1], &status->metadata_sectors)) {
-        LOG(ERROR) << "could not parse metadata sectors";
-        return false;
-    }
-    if (!android::base::ParseUint(sector_info[0], &status->sectors_allocated)) {
-        LOG(ERROR) << "could not parse sectors allocated";
-        return false;
-    }
-    if (!android::base::ParseUint(sector_info[1], &status->total_sectors)) {
-        LOG(ERROR) << "could not parse total sectors";
-        return false;
-    }
+    *base_device = pieces[0];
+    *cow_device = pieces[1];
     return true;
 }
 
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index b28a8f2..eed21dc 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -166,6 +166,34 @@
     ASSERT_TRUE(dev.Destroy());
 }
 
+TEST(libdm, DmSuspendResume) {
+    unique_fd tmp1(CreateTempFile("file_suspend_resume", 512));
+    ASSERT_GE(tmp1, 0);
+
+    LoopDevice loop_a(tmp1, 10s);
+    ASSERT_TRUE(loop_a.valid());
+
+    DmTable table;
+    ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop_a.device(), 0));
+    ASSERT_TRUE(table.valid());
+
+    TempDevice dev("libdm-test-dm-suspend-resume", table);
+    ASSERT_TRUE(dev.valid());
+    ASSERT_FALSE(dev.path().empty());
+
+    auto& dm = DeviceMapper::Instance();
+
+    // Test Set and Get status of device.
+    vector<DeviceMapper::TargetInfo> targets;
+    ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::ACTIVE);
+
+    ASSERT_TRUE(dm.ChangeState(dev.name(), DmDeviceState::SUSPENDED));
+    ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::SUSPENDED);
+
+    ASSERT_TRUE(dm.ChangeState(dev.name(), DmDeviceState::ACTIVE));
+    ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::ACTIVE);
+}
+
 TEST(libdm, DmVerityArgsAvb2) {
     std::string device = "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a";
     std::string algorithm = "sha1";
@@ -428,6 +456,87 @@
     }
 }
 
+TEST(libdm, ParseStatusText) {
+    DmTargetSnapshot::Status status;
+
+    // Bad inputs
+    EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("", &status));
+    EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("X", &status));
+    EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123", &status));
+    EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123/456", &status));
+    EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456", &status));
+    EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456", &status));
+    EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456 789", &status));
+    EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456/789", &status));
+    EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123/456/789", &status));
+    EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 / 456 789", &status));
+
+    // Good input
+    EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("123/456 789", &status));
+    EXPECT_EQ(status.sectors_allocated, 123);
+    EXPECT_EQ(status.total_sectors, 456);
+    EXPECT_EQ(status.metadata_sectors, 789);
+
+    // Known error codes
+    EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("Invalid", &status));
+    EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("Merge failed", &status));
+    EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("Overflow", &status));
+}
+
+TEST(libdm, DmSnapshotMergePercent) {
+    DmTargetSnapshot::Status status;
+
+    // Correct input
+    status.sectors_allocated = 1000;
+    status.total_sectors = 1000;
+    status.metadata_sectors = 0;
+    EXPECT_LE(DmTargetSnapshot::MergePercent(status), 1.0);
+
+    status.sectors_allocated = 500;
+    status.total_sectors = 1000;
+    status.metadata_sectors = 0;
+    EXPECT_GE(DmTargetSnapshot::MergePercent(status), 49.0);
+    EXPECT_LE(DmTargetSnapshot::MergePercent(status), 51.0);
+
+    status.sectors_allocated = 0;
+    status.total_sectors = 1000;
+    status.metadata_sectors = 0;
+    EXPECT_GE(DmTargetSnapshot::MergePercent(status), 99.0);
+
+    status.sectors_allocated = 500;
+    status.total_sectors = 1000;
+    status.metadata_sectors = 500;
+    EXPECT_GE(DmTargetSnapshot::MergePercent(status), 99.0);
+
+    status.sectors_allocated = 500;
+    status.total_sectors = 1000;
+    status.metadata_sectors = 0;
+    EXPECT_LE(DmTargetSnapshot::MergePercent(status, 500), 1.0);
+    EXPECT_LE(DmTargetSnapshot::MergePercent(status, 1000), 51.0);
+    EXPECT_GE(DmTargetSnapshot::MergePercent(status, 1000), 49.0);
+
+    // Robustness
+    status.sectors_allocated = 2000;
+    status.total_sectors = 1000;
+    status.metadata_sectors = 0;
+    EXPECT_LE(DmTargetSnapshot::MergePercent(status), 0.0);
+
+    status.sectors_allocated = 2000;
+    status.total_sectors = 1000;
+    status.metadata_sectors = 2000;
+    EXPECT_LE(DmTargetSnapshot::MergePercent(status), 0.0);
+
+    status.sectors_allocated = 2000;
+    status.total_sectors = 0;
+    status.metadata_sectors = 2000;
+    EXPECT_LE(DmTargetSnapshot::MergePercent(status), 0.0);
+
+    status.sectors_allocated = 1000;
+    status.total_sectors = 0;
+    status.metadata_sectors = 1000;
+    EXPECT_LE(DmTargetSnapshot::MergePercent(status, 0), 0.0);
+}
+
 TEST(libdm, CryptArgs) {
     DmTargetCrypt target1(0, 512, "sha1", "abcdefgh", 50, "/dev/loop0", 100);
     ASSERT_EQ(target1.name(), "crypt");
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 9c0c2f3..f5783cb 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -27,6 +27,7 @@
 
 #include <chrono>
 #include <memory>
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -46,6 +47,8 @@
 
 enum class DmDeviceState { INVALID, SUSPENDED, ACTIVE };
 
+static constexpr uint64_t kSectorSize = 512;
+
 class DeviceMapper final {
   public:
     class DmBlockDevice final {
@@ -70,15 +73,37 @@
         uint64_t dev_;
     };
 
+    class Info {
+        uint32_t flags_;
+
+      public:
+        explicit Info(uint32_t flags) : flags_(flags) {}
+
+        bool IsActiveTablePresent() const { return flags_ & DM_ACTIVE_PRESENT_FLAG; }
+        bool IsBufferFull() const { return flags_ & DM_BUFFER_FULL_FLAG; }
+        bool IsInactiveTablePresent() const { return flags_ & DM_INACTIVE_PRESENT_FLAG; }
+        bool IsReadOnly() const { return flags_ & DM_READONLY_FLAG; }
+        bool IsSuspended() const { return flags_ & DM_SUSPEND_FLAG; }
+    };
+
     // Removes a device mapper device with the given name.
     // Returns 'true' on success, false otherwise.
     bool DeleteDevice(const std::string& name);
 
+    // Fetches and returns the complete state of the underlying device mapper
+    // device with given name.
+    std::optional<Info> GetDetailedInfo(const std::string& name) const;
+
     // Returns the current state of the underlying device mapper device
     // with given name.
     // One of INVALID, SUSPENDED or ACTIVE.
     DmDeviceState GetState(const std::string& name) const;
 
+    // Puts the given device to the specified status, which must be either:
+    // - SUSPENDED: suspend the device, or
+    // - ACTIVE: resumes the device.
+    bool ChangeState(const std::string& name, DmDeviceState state);
+
     // Creates a device, loads the given table, and activates it. If the device
     // is not able to be activated, it is destroyed, and false is returned.
     // After creation, |path| contains the result of calling
diff --git a/fs_mgr/libdm/include/libdm/dm_target.h b/fs_mgr/libdm/include/libdm/dm_target.h
index 722922d..a66ab7a 100644
--- a/fs_mgr/libdm/include/libdm/dm_target.h
+++ b/fs_mgr/libdm/include/libdm/dm_target.h
@@ -216,8 +216,11 @@
         std::string error;
     };
 
+    static double MergePercent(const Status& status, uint64_t sectors_initial = 0);
     static bool ParseStatusText(const std::string& text, Status* status);
     static bool ReportsOverflow(const std::string& target_type);
+    static bool GetDevicesFromParams(const std::string& params, std::string* base_device,
+                                     std::string* cow_device);
 
   private:
     std::string base_device_;
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 25a042f..777743c 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -20,21 +20,16 @@
 
 #include <algorithm>
 
-#include <android-base/properties.h>
 #include <android-base/unique_fd.h>
 
 #include "liblp/liblp.h"
+#include "liblp/property_fetcher.h"
 #include "reader.h"
 #include "utility.h"
 
 namespace android {
 namespace fs_mgr {
 
-bool MetadataBuilder::sABOverrideSet;
-bool MetadataBuilder::sABOverrideValue;
-
-static const std::string kDefaultGroup = "default";
-
 bool LinearExtent::AddTo(LpMetadata* out) const {
     if (device_index_ >= out->block_devices.size()) {
         LERROR << "Extent references unknown block device.";
@@ -164,15 +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") {
-        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();
@@ -192,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()) +
@@ -201,17 +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) {
-    sABOverrideSet = true;
-    sABOverrideValue = ab_device;
+    return true;
 }
 
 MetadataBuilder::MetadataBuilder() : auto_slot_suffixing_(false) {
@@ -410,7 +412,7 @@
     geometry_.metadata_slot_count = metadata_slot_count;
     geometry_.logical_block_size = logical_block_size;
 
-    if (!AddGroup(kDefaultGroup, 0)) {
+    if (!AddGroup(std::string(kDefaultGroup), 0)) {
         return false;
     }
     return true;
@@ -426,7 +428,7 @@
 }
 
 Partition* MetadataBuilder::AddPartition(const std::string& name, uint32_t attributes) {
-    return AddPartition(name, kDefaultGroup, attributes);
+    return AddPartition(name, std::string(kDefaultGroup), attributes);
 }
 
 Partition* MetadataBuilder::AddPartition(const std::string& name, const std::string& group_name,
@@ -580,7 +582,8 @@
     CHECK_NE(sectors_per_block, 0);
     CHECK(sectors_needed % sectors_per_block == 0);
 
-    if (IsABDevice() && !IsRetrofitDevice() && GetPartitionSlotSuffix(partition->name()) == "_b") {
+    if (IsABDevice() && !IsRetrofitMetadata() &&
+        GetPartitionSlotSuffix(partition->name()) == "_b") {
         // Allocate "a" partitions top-down and "b" partitions bottom-up, to
         // minimize fragmentation during OTA.
         free_regions = PrioritizeSecondHalfOfSuper(free_regions);
@@ -1044,14 +1047,16 @@
     auto_slot_suffixing_ = true;
 }
 
-bool MetadataBuilder::IsABDevice() const {
-    if (sABOverrideSet) {
-        return sABOverrideValue;
-    }
-    return !android::base::GetProperty("ro.boot.slot_suffix", "").empty();
+bool MetadataBuilder::IsABDevice() {
+    return !IPropertyFetcher::GetInstance()->GetProperty("ro.boot.slot_suffix", "").empty();
 }
 
-bool MetadataBuilder::IsRetrofitDevice() const {
+bool MetadataBuilder::IsRetrofitDynamicPartitionsDevice() {
+    return IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions_retrofit",
+                                                            false);
+}
+
+bool MetadataBuilder::IsRetrofitMetadata() const {
     return GetBlockDevicePartitionName(block_devices_[0]) != LP_METADATA_DEFAULT_PARTITION_NAME;
 }
 
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index 34c68d4..6d27873 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -19,28 +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); }
+    void SetUp() override { ResetPropertyFetcher(); }
 };
 
 int main(int argc, char** argv) {
-    std::unique_ptr<Environment> env(new Environment);
-    ::testing::AddGlobalTestEnvironment(env.get());
     ::testing::InitGoogleTest(&argc, argv);
     return RUN_ALL_TESTS();
 }
 
 class BuilderTest : public ::testing::Test {
   public:
-    void SetUp() override { MetadataBuilder::OverrideABForTesting(false); }
-    void TearDown() override { MetadataBuilder::OverrideABForTesting(false); }
+    void SetUp() override { ResetPropertyFetcher(); }
+    void TearDown() override { ResetPropertyFetcher(); }
 };
 
 TEST_F(BuilderTest, BuildBasic) {
@@ -777,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 e70c552..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,9 +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);
-
     // 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.
@@ -306,8 +307,16 @@
     void ImportExtents(Partition* dest, const LpMetadata& metadata,
                        const LpMetadataPartition& source);
     bool ImportPartition(const LpMetadata& metadata, const LpMetadataPartition& source);
-    bool IsABDevice() const;
-    bool IsRetrofitDevice() const;
+
+    // Return true if the device is an AB device.
+    static bool IsABDevice();
+
+    // Return true if the device is retrofitting dynamic partitions.
+    static bool IsRetrofitDynamicPartitionsDevice();
+
+    // Return true if "this" metadata represents a metadata on a retrofit device.
+    bool IsRetrofitMetadata() const;
+
     bool ValidatePartitionGroups() const;
 
     struct Interval {
@@ -336,8 +345,8 @@
                                                     const std::vector<Interval>& free_list,
                                                     uint64_t sectors_needed) const;
 
-    static bool sABOverrideValue;
-    static bool sABOverrideSet;
+    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 fcef1f0..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,6 +668,9 @@
 }
 
 TEST(liblp, UpdateRetrofit) {
+    ON_CALL(*GetMockedInstance(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+            .WillByDefault(Return(true));
+
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(AddDefaultPartitions(builder.get()));
@@ -693,6 +700,9 @@
 }
 
 TEST(liblp, UpdateNonRetrofit) {
+    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/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 3a08049..52aad12 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -14,15 +14,13 @@
 // limitations under the License.
 //
 
-cc_library {
-    name: "libsnapshot",
-    recovery_available: true,
+cc_defaults {
+    name: "libsnapshot_defaults",
     defaults: ["fs_mgr_defaults"],
-    cppflags: [
+    cflags: [
         "-D_FILE_OFFSET_BITS=64",
-    ],
-    srcs: [
-        "snapshot.cpp",
+        "-Wall",
+        "-Werror",
     ],
     shared_libs: [
         "libbase",
@@ -30,7 +28,53 @@
     ],
     static_libs: [
         "libdm",
+    ],
+    whole_static_libs: [
         "libext2_uuid",
+        "libext4_utils",
+        "libfiemap",
     ],
     export_include_dirs: ["include"],
 }
+
+filegroup {
+    name: "libsnapshot_sources",
+    srcs: [
+        "snapshot.cpp",
+    ],
+}
+
+cc_library_static {
+    name: "libsnapshot",
+    defaults: ["libsnapshot_defaults"],
+    srcs: [":libsnapshot_sources"],
+    static_libs: [
+        "libfiemap_binder",
+    ],
+}
+
+cc_library_static {
+    name: "libsnapshot_nobinder",
+    defaults: ["libsnapshot_defaults"],
+    srcs: [":libsnapshot_sources"],
+    recovery_available: true,
+}
+
+cc_test {
+    name: "libsnapshot_test",
+    defaults: ["libsnapshot_defaults"],
+    srcs: [
+        "snapshot_test.cpp",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libutils",
+    ],
+    static_libs: [
+        "libcutils",
+        "libcrypto",
+        "libfs_mgr",
+        "liblp",
+        "libsnapshot",
+    ],
+}
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 5cfd7fa..062e00b 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -19,14 +19,32 @@
 #include <chrono>
 #include <memory>
 #include <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <libdm/dm_target.h>
+
+#ifndef FRIEND_TEST
+#define FRIEND_TEST(test_set_name, individual_test) \
+    friend class test_set_name##_##individual_test##_Test
+#define DEFINED_FRIEND_TEST
+#endif
 
 namespace android {
+
+namespace fiemap {
+class IImageManager;
+}  // namespace fiemap
+
 namespace snapshot {
 
-enum class UpdateStatus {
+enum class UpdateState {
     // No update or merge is in progress.
     None,
 
+    // An update is applying; snapshots may already exist.
+    Initiated,
+
     // An update is pending, but has not been successfully booted yet.
     Unverified,
 
@@ -34,36 +52,41 @@
     Merging,
 
     // Merging is complete, and needs to be acknowledged.
-    MergeCompleted
+    MergeCompleted,
+
+    // Merging failed due to an unrecoverable error.
+    MergeFailed
 };
 
 class SnapshotManager final {
   public:
-    // Return a new SnapshotManager instance, or null on error.
-    static std::unique_ptr<SnapshotManager> New();
+    // Dependency injection for testing.
+    class IDeviceInfo {
+      public:
+        virtual ~IDeviceInfo() {}
+        virtual std::string GetGsidDir() const = 0;
+        virtual std::string GetMetadataDir() const = 0;
 
-    // Create a new snapshot device with the given name, base device, and COW device
-    // size. The new device path will be returned in |dev_path|. If timeout_ms is
-    // greater than zero, this function will wait the given amount of time for
-    // |dev_path| to become available, and fail otherwise. If timeout_ms is 0, then
-    // no wait will occur and |dev_path| may not yet exist on return.
-    bool CreateSnapshot(const std::string& name, const std::string& base_device, uint64_t cow_size,
-                        std::string* dev_path, const std::chrono::milliseconds& timeout_ms);
+        // Return true if the device is currently running off snapshot devices,
+        // indicating that we have booted after applying (but not merging) an
+        // OTA.
+        virtual bool IsRunningSnapshot() const = 0;
+    };
 
-    // Map a snapshot device that was previously created with CreateSnapshot.
-    // If a merge was previously initiated, the device-mapper table will have a
-    // snapshot-merge target instead of a snapshot target. The timeout parameter
-    // is the same as in CreateSnapshotDevice.
-    bool MapSnapshotDevice(const std::string& name, const std::string& base_device,
-                           const std::chrono::milliseconds& timeout_ms, std::string* dev_path);
+    ~SnapshotManager();
 
-    // Unmap a snapshot device previously mapped with MapSnapshotDevice().
-    bool UnmapSnapshotDevice(const std::string& name);
+    // Return a new SnapshotManager instance, or null on error. The device
+    // pointer is owned for the lifetime of SnapshotManager. If null, a default
+    // instance will be created.
+    static std::unique_ptr<SnapshotManager> New(IDeviceInfo* device = nullptr);
 
-    // 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.
-    bool DeleteSnapshot(const std::string& name);
+    // Begin an update. This must be called before creating any snapshots. It
+    // will fail if GetUpdateState() != None.
+    bool BeginUpdate();
+
+    // Cancel an update; any snapshots will be deleted. This will fail if the
+    // state != Initiated or None.
+    bool CancelUpdate();
 
     // Initiate a merge on all snapshot devices. This should only be used after an
     // update has been marked successful after booting.
@@ -77,12 +100,128 @@
     // Find the status of the current update, if any.
     //
     // |progress| depends on the returned status:
-    //   None: 0
-    //   Unverified: 0
-    //   Merging: Value in the range [0, 100)
+    //   Merging: Value in the range [0, 100]
     //   MergeCompleted: 100
-    UpdateStatus GetUpdateStatus(double* progress);
+    //   Other: 0
+    UpdateState GetUpdateState(double* progress = nullptr);
+
+  private:
+    FRIEND_TEST(SnapshotTest, CreateSnapshot);
+    FRIEND_TEST(SnapshotTest, MapSnapshot);
+    FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
+    friend class SnapshotTest;
+
+    using IImageManager = android::fiemap::IImageManager;
+
+    explicit SnapshotManager(IDeviceInfo* info);
+
+    // This is created lazily since it connects via binder.
+    bool EnsureImageManager();
+
+    // Helper function for tests.
+    IImageManager* image_manager() const { return images_.get(); }
+
+    // Since libsnapshot is included into multiple processes, we flock() our
+    // files for simple synchronization. LockedFile is a helper to assist with
+    // 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() const { return path_; }
+        int fd() const { return fd_; }
+
+      private:
+        std::string path_;
+        android::base::unique_fd fd_;
+    };
+    std::unique_ptr<LockedFile> OpenFile(const std::string& file, int open_flags, int lock_flags);
+    bool Truncate(LockedFile* file);
+
+    // Create a new snapshot record. This creates the backing COW store and
+    // persists information needed to map the device. The device can be mapped
+    // with MapSnapshot().
+    //
+    // |device_size| should be the size of the base_device that will be passed
+    // via MapDevice(). |snapshot_size| should be the number of bytes in the
+    // base device, starting from 0, that will be snapshotted. The cow_size
+    // should be the amount of space that will be allocated to store snapshot
+    // deltas.
+    //
+    // If |snapshot_size| < device_size, then the device will always
+    // be mapped with two table entries: a dm-snapshot range covering
+    // snapshot_size, and a dm-linear range covering the remainder.
+    //
+    // All sizes are specified in bytes, and the device and snapshot sizes
+    // must be a multiple of the sector size (512 bytes). |cow_size| will
+    // be rounded up to the nearest sector.
+    bool CreateSnapshot(LockedFile* lock, const std::string& name, uint64_t device_size,
+                        uint64_t snapshot_size, uint64_t cow_size);
+
+    // Map a snapshot device that was previously created with CreateSnapshot.
+    // If a merge was previously initiated, the device-mapper table will have a
+    // snapshot-merge target instead of a snapshot target. If the timeout
+    // parameter greater than zero, this function will wait the given amount
+    // of time for |dev_path| to become available, and fail otherwise. If
+    // timeout_ms is 0, then no wait will occur and |dev_path| may not yet
+    // exist on return.
+    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. 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().
+    bool UnmapSnapshot(LockedFile* lock, const std::string& name);
+
+    // Unmap and remove all known snapshots.
+    bool RemoveAllSnapshots(LockedFile* lock);
+
+    // List the known snapshot names.
+    bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots);
+
+    // Interact with /metadata/ota/state.
+    std::unique_ptr<LockedFile> OpenStateFile(int open_flags, int lock_flags);
+    std::unique_ptr<LockedFile> LockShared();
+    std::unique_ptr<LockedFile> LockExclusive();
+    UpdateState ReadUpdateState(LockedFile* file);
+    bool WriteUpdateState(LockedFile* file, UpdateState state);
+
+    // This state is persisted per-snapshot in /metadata/ota/snapshots/.
+    struct SnapshotStatus {
+        std::string state;
+        uint64_t device_size;
+        uint64_t snapshot_size;
+        // These are non-zero when merging.
+        uint64_t sectors_allocated = 0;
+        uint64_t metadata_sectors = 0;
+    };
+
+    // 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);
+
+    // Return the name of the device holding the "snapshot" or "snapshot-merge"
+    // target. This may not be the final device presented via MapSnapshot(), if
+    // for example there is a linear segment.
+    std::string GetSnapshotDeviceName(const std::string& snapshot_name,
+                                      const SnapshotStatus& status);
+
+    std::string gsid_dir_;
+    std::string metadata_dir_;
+    std::unique_ptr<IDeviceInfo> device_;
+    std::unique_ptr<IImageManager> images_;
 };
 
 }  // namespace snapshot
 }  // namespace android
+
+#ifdef DEFINED_FRIEND_TEST
+#undef DEFINED_FRIEND_TEST
+#undef FRIEND_TEST
+#endif
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 3e80239..ef56179 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -14,45 +14,309 @@
 
 #include <libsnapshot/snapshot.h>
 
+#include <dirent.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+
 namespace android {
 namespace snapshot {
 
-std::unique_ptr<SnapshotManager> SnapshotManager::New() {
-    return std::make_unique<SnapshotManager>();
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::dm::DmTable;
+using android::dm::DmTargetLinear;
+using android::dm::DmTargetSnapshot;
+using android::dm::kSectorSize;
+using android::dm::SnapshotStorageMode;
+using android::fiemap::IImageManager;
+using namespace std::chrono_literals;
+using namespace std::string_literals;
+
+// Unit is sectors, this is a 4K chunk.
+static constexpr uint32_t kSnapshotChunkSize = 8;
+
+class DeviceInfo final : public SnapshotManager::IDeviceInfo {
+  public:
+    std::string GetGsidDir() const override { return "ota"s; }
+    std::string GetMetadataDir() const override { return "/metadata/ota"s; }
+    bool IsRunningSnapshot() const override;
+};
+
+bool DeviceInfo::IsRunningSnapshot() const {
+    // :TODO: implement this check.
+    return true;
 }
 
-bool SnapshotManager::CreateSnapshot(const std::string& name, const std::string& base_device,
-                                     uint64_t cow_size, std::string* dev_path,
-                                     const std::chrono::milliseconds& timeout_ms) {
-    // (1) Create COW device using libgsi_image.
-    // (2) Create snapshot device using libdm + DmTargetSnapshot.
-    // (3) Record partition in /metadata/ota.
-    (void)name;
-    (void)base_device;
-    (void)cow_size;
-    (void)dev_path;
-    (void)timeout_ms;
-    return false;
+// Note: IIMageManager is an incomplete type in the header, so the default
+// destructor doesn't work.
+SnapshotManager::~SnapshotManager() {}
+
+std::unique_ptr<SnapshotManager> SnapshotManager::New(IDeviceInfo* info) {
+    if (!info) {
+        info = new DeviceInfo();
+    }
+    return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
 }
 
-bool SnapshotManager::MapSnapshotDevice(const std::string& name, const std::string& base_device,
-                                        const std::chrono::milliseconds& timeout_ms,
-                                        std::string* dev_path) {
-    (void)name;
-    (void)base_device;
-    (void)dev_path;
-    (void)timeout_ms;
-    return false;
+SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
+    gsid_dir_ = device_->GetGsidDir();
+    metadata_dir_ = device_->GetMetadataDir();
 }
 
-bool SnapshotManager::UnmapSnapshotDevice(const std::string& name) {
-    (void)name;
-    return false;
+static std::string GetCowName(const std::string& snapshot_name) {
+    return snapshot_name + "-cow";
 }
 
-bool SnapshotManager::DeleteSnapshot(const std::string& name) {
-    (void)name;
-    return false;
+bool SnapshotManager::BeginUpdate() {
+    auto file = LockExclusive();
+    if (!file) return false;
+
+    auto state = ReadUpdateState(file.get());
+    if (state != UpdateState::None) {
+        LOG(ERROR) << "An update is already in progress, cannot begin a new update";
+        return false;
+    }
+    return WriteUpdateState(file.get(), UpdateState::Initiated);
+}
+
+bool SnapshotManager::CancelUpdate() {
+    auto file = LockExclusive();
+    if (!file) return false;
+
+    UpdateState state = ReadUpdateState(file.get());
+    if (state == UpdateState::None) return true;
+    if (state != UpdateState::Initiated) {
+        LOG(ERROR) << "Cannot cancel update after it has completed or started merging";
+        return false;
+    }
+
+    if (!RemoveAllSnapshots(file.get())) {
+        LOG(ERROR) << "Could not remove all snapshots";
+        return false;
+    }
+
+    if (!WriteUpdateState(file.get(), UpdateState::None)) {
+        LOG(ERROR) << "Could not write new update state";
+        return false;
+    }
+    return true;
+}
+
+bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name,
+                                     uint64_t device_size, uint64_t snapshot_size,
+                                     uint64_t cow_size) {
+    CHECK(lock);
+    if (!EnsureImageManager()) return false;
+
+    // Sanity check these sizes. Like liblp, we guarantee the partition size
+    // is respected, which means it has to be sector-aligned. (This guarantee
+    // is useful for locating avb footers correctly). The COW size, however,
+    // can be arbitrarily larger than specified, so we can safely round it up.
+    if (device_size % kSectorSize != 0) {
+        LOG(ERROR) << "Snapshot " << name
+                   << " device size is not a multiple of the sector size: " << device_size;
+        return false;
+    }
+    if (snapshot_size % kSectorSize != 0) {
+        LOG(ERROR) << "Snapshot " << name
+                   << " snapshot size is not a multiple of the sector size: " << snapshot_size;
+        return false;
+    }
+
+    // Round the COW size up to the nearest sector.
+    cow_size += kSectorSize - 1;
+    cow_size &= ~(kSectorSize - 1);
+
+    LOG(INFO) << "Snapshot " << name << " will have COW size " << cow_size;
+
+    auto status_file = OpenSnapshotStatusFile(name, O_RDWR | O_CREAT, LOCK_EX);
+    if (!status_file) return false;
+
+    // Note, we leave the status file hanging around if we fail to create the
+    // actual backing image. This is harmless, since it'll get removed when
+    // CancelUpdate is called.
+    SnapshotStatus status = {
+            .state = "created",
+            .device_size = device_size,
+            .snapshot_size = snapshot_size,
+    };
+    if (!WriteSnapshotStatus(status_file.get(), status)) {
+        PLOG(ERROR) << "Could not write snapshot status: " << name;
+        return false;
+    }
+
+    auto cow_name = GetCowName(name);
+    int cow_flags = IImageManager::CREATE_IMAGE_ZERO_FILL;
+    return images_->CreateBackingImage(cow_name, cow_size, cow_flags);
+}
+
+bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
+                                  const std::string& base_device,
+                                  const std::chrono::milliseconds& timeout_ms,
+                                  std::string* dev_path) {
+    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)) {
+        return false;
+    }
+
+    // Validate the block device size, as well as the requested snapshot size.
+    // During this we also compute the linear sector region if any.
+    {
+        unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC));
+        if (fd < 0) {
+            PLOG(ERROR) << "open failed: " << base_device;
+            return false;
+        }
+        auto dev_size = get_block_device_size(fd);
+        if (!dev_size) {
+            PLOG(ERROR) << "Could not determine block device size: " << base_device;
+            return false;
+        }
+        if (status.device_size != dev_size) {
+            LOG(ERROR) << "Block device size for " << base_device << " does not match"
+                       << "(expected " << status.device_size << ", got " << dev_size << ")";
+            return false;
+        }
+    }
+    if (status.device_size % kSectorSize != 0) {
+        LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size;
+        return false;
+    }
+    if (status.snapshot_size % kSectorSize != 0 || status.snapshot_size > status.device_size) {
+        LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size;
+        return false;
+    }
+    uint64_t snapshot_sectors = status.snapshot_size / kSectorSize;
+    uint64_t linear_sectors = (status.device_size - status.snapshot_size) / kSectorSize;
+
+    auto cow_name = GetCowName(name);
+
+    std::string cow_dev;
+    if (!images_->MapImageDevice(cow_name, timeout_ms, &cow_dev)) {
+        return false;
+    }
+
+    auto& dm = DeviceMapper::Instance();
+
+    // Merging is a global state, not per-snapshot. We do however track the
+    // progress of individual snapshots' merges.
+    SnapshotStorageMode mode;
+    UpdateState update_state = ReadUpdateState(lock);
+    if (update_state == UpdateState::Merging || update_state == UpdateState::MergeCompleted) {
+        mode = SnapshotStorageMode::Merge;
+    } else {
+        mode = SnapshotStorageMode::Persistent;
+    }
+
+    // The kernel (tested on 4.19) crashes horribly if a device has both a snapshot
+    // and a linear target in the same table. Instead, we stack them, and give the
+    // snapshot device a different name. It is not exposed to the caller in this
+    // case.
+    auto snap_name = (linear_sectors > 0) ? name + "-inner" : name;
+
+    DmTable table;
+    table.Emplace<DmTargetSnapshot>(0, snapshot_sectors, base_device, cow_dev, mode,
+                                    kSnapshotChunkSize);
+    if (!dm.CreateDevice(snap_name, table, dev_path, timeout_ms)) {
+        LOG(ERROR) << "Could not create snapshot device: " << snap_name;
+        images_->UnmapImageDevice(cow_name);
+        return false;
+    }
+
+    if (linear_sectors) {
+        // Our stacking will looks like this:
+        //     [linear, linear] ; to snapshot, and non-snapshot region of base device
+        //     [snapshot-inner]
+        //     [base device]   [cow]
+        DmTable table;
+        table.Emplace<DmTargetLinear>(0, snapshot_sectors, *dev_path, 0);
+        table.Emplace<DmTargetLinear>(snapshot_sectors, linear_sectors, base_device,
+                                      snapshot_sectors);
+        if (!dm.CreateDevice(name, table, dev_path, timeout_ms)) {
+            LOG(ERROR) << "Could not create outer snapshot device: " << name;
+            dm.DeleteDevice(snap_name);
+            images_->UnmapImageDevice(cow_name);
+            return false;
+        }
+    }
+
+    // :TODO: when merging is implemented, we need to add an argument to the
+    // status indicating how much progress is left to merge. (device-mapper
+    // does not retain the initial values, so we can't derive them.)
+    return true;
+}
+
+bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
+    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)) {
+        return false;
+    }
+
+    auto& dm = DeviceMapper::Instance();
+    if (dm.GetState(name) != DmDeviceState::INVALID && !dm.DeleteDevice(name)) {
+        LOG(ERROR) << "Could not delete snapshot device: " << name;
+        return false;
+    }
+
+    // There may be an extra device, since the kernel doesn't let us have a
+    // snapshot and linear target in the same table.
+    auto dm_name = GetSnapshotDeviceName(name, status);
+    if (name != dm_name && !dm.DeleteDevice(dm_name)) {
+        LOG(ERROR) << "Could not delete inner snapshot device: " << dm_name;
+        return false;
+    }
+
+    auto cow_name = GetCowName(name);
+    if (images_->IsImageMapped(cow_name) && !images_->UnmapImageDevice(cow_name)) {
+        return false;
+    }
+    return true;
+}
+
+bool SnapshotManager::DeleteSnapshot(LockedFile* lock, const std::string& name) {
+    CHECK(lock);
+    if (!EnsureImageManager()) return false;
+
+    // Take the snapshot's lock after Unmap, since it will also try to lock.
+    auto status_file = OpenSnapshotStatusFile(name, O_RDONLY, LOCK_EX);
+    if (!status_file) return false;
+
+    auto cow_name = GetCowName(name);
+    if (!images_->BackingImageExists(cow_name)) {
+        return true;
+    }
+    if (!images_->DeleteBackingImage(cow_name)) {
+        return false;
+    }
+
+    if (!android::base::RemoveFileIfExists(status_file->path())) {
+        LOG(ERROR) << "Failed to remove status file: " << status_file->path();
+        return false;
+    }
+    return true;
 }
 
 bool SnapshotManager::InitiateMerge() {
@@ -63,9 +327,242 @@
     return false;
 }
 
-UpdateStatus SnapshotManager::GetUpdateStatus(double* progress) {
-    *progress = 0.0f;
-    return UpdateStatus::None;
+bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) {
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock, &snapshots)) {
+        LOG(ERROR) << "Could not list snapshots";
+        return false;
+    }
+
+    bool ok = true;
+    for (const auto& name : snapshots) {
+        ok &= DeleteSnapshot(lock, name);
+    }
+    return ok;
+}
+
+UpdateState SnapshotManager::GetUpdateState(double* progress) {
+    auto file = LockShared();
+    if (!file) {
+        return UpdateState::None;
+    }
+
+    auto state = ReadUpdateState(file.get());
+    if (progress) {
+        *progress = 0.0;
+        if (state == UpdateState::Merging) {
+            // :TODO: When merging is implemented, set progress_val.
+        } else if (state == UpdateState::MergeCompleted) {
+            *progress = 100.0;
+        }
+    }
+    return state;
+}
+
+bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots) {
+    CHECK(lock);
+
+    auto dir_path = metadata_dir_ + "/snapshots"s;
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(dir_path.c_str()), closedir);
+    if (!dir) {
+        PLOG(ERROR) << "opendir failed: " << dir_path;
+        return false;
+    }
+
+    struct dirent* dp;
+    while ((dp = readdir(dir.get())) != nullptr) {
+        if (dp->d_type != DT_REG) continue;
+        snapshots->emplace_back(dp->d_name);
+    }
+    return true;
+}
+
+auto SnapshotManager::OpenFile(const std::string& file, int open_flags, int lock_flags)
+        -> std::unique_ptr<LockedFile> {
+    unique_fd fd(open(file.c_str(), open_flags | O_CLOEXEC | O_NOFOLLOW | O_SYNC, 0660));
+    if (fd < 0) {
+        PLOG(ERROR) << "Open failed: " << file;
+        return nullptr;
+    }
+    if (flock(fd, lock_flags) < 0) {
+        PLOG(ERROR) << "Acquire flock failed: " << file;
+        return nullptr;
+    }
+    return std::make_unique<LockedFile>(file, std::move(fd));
+}
+
+SnapshotManager::LockedFile::~LockedFile() {
+    if (flock(fd_, LOCK_UN) < 0) {
+        PLOG(ERROR) << "Failed to unlock file: " << path_;
+    }
+}
+
+std::unique_ptr<SnapshotManager::LockedFile> SnapshotManager::OpenStateFile(int open_flags,
+                                                                            int lock_flags) {
+    auto state_file = metadata_dir_ + "/state"s;
+    return OpenFile(state_file, open_flags, lock_flags);
+}
+
+std::unique_ptr<SnapshotManager::LockedFile> SnapshotManager::LockShared() {
+    return OpenStateFile(O_RDONLY, LOCK_SH);
+}
+
+std::unique_ptr<SnapshotManager::LockedFile> SnapshotManager::LockExclusive() {
+    return OpenStateFile(O_RDWR | O_CREAT, LOCK_EX);
+}
+
+UpdateState SnapshotManager::ReadUpdateState(LockedFile* file) {
+    // Reset position since some calls read+write.
+    if (lseek(file->fd(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek state file failed";
+        return UpdateState::None;
+    }
+
+    std::string contents;
+    if (!android::base::ReadFdToString(file->fd(), &contents)) {
+        PLOG(ERROR) << "Read state file failed";
+        return UpdateState::None;
+    }
+
+    if (contents.empty() || contents == "none") {
+        return UpdateState::None;
+    } else if (contents == "initiated") {
+        return UpdateState::Initiated;
+    } else if (contents == "unverified") {
+        return UpdateState::Unverified;
+    } else if (contents == "merging") {
+        return UpdateState::Merging;
+    } else if (contents == "merge-completed") {
+        return UpdateState::MergeCompleted;
+    } else {
+        LOG(ERROR) << "Unknown merge state in update state file";
+        return UpdateState::None;
+    }
+}
+
+bool SnapshotManager::WriteUpdateState(LockedFile* file, UpdateState state) {
+    std::string contents;
+    switch (state) {
+        case UpdateState::None:
+            contents = "none";
+            break;
+        case UpdateState::Initiated:
+            contents = "initiated";
+            break;
+        case UpdateState::Unverified:
+            contents = "unverified";
+            break;
+        case UpdateState::Merging:
+            contents = "merging";
+            break;
+        case UpdateState::MergeCompleted:
+            contents = "merge-completed";
+            break;
+        default:
+            LOG(ERROR) << "Unknown update state";
+            return false;
+    }
+
+    if (!Truncate(file)) return false;
+    if (!android::base::WriteStringToFd(contents, file->fd())) {
+        PLOG(ERROR) << "Could not write to state file";
+        return false;
+    }
+    return true;
+}
+
+auto SnapshotManager::OpenSnapshotStatusFile(const std::string& name, int open_flags,
+                                             int lock_flags) -> std::unique_ptr<LockedFile> {
+    auto file = metadata_dir_ + "/snapshots/"s + name;
+    return OpenFile(file, open_flags, lock_flags);
+}
+
+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";
+        return false;
+    }
+
+    std::string contents;
+    if (!android::base::ReadFdToString(file->fd(), &contents)) {
+        PLOG(ERROR) << "read status file failed";
+        return false;
+    }
+    auto pieces = android::base::Split(contents, " ");
+    if (pieces.size() != 5) {
+        LOG(ERROR) << "Invalid status line for snapshot: " << file->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();
+        return false;
+    }
+    if (!android::base::ParseUint(pieces[2], &status->snapshot_size)) {
+        LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path();
+        return false;
+    }
+    if (!android::base::ParseUint(pieces[3], &status->sectors_allocated)) {
+        LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path();
+        return false;
+    }
+    if (!android::base::ParseUint(pieces[4], &status->metadata_sectors)) {
+        LOG(ERROR) << "Invalid snapshot size in status line for: " << file->path();
+        return false;
+    }
+    return true;
+}
+
+bool SnapshotManager::WriteSnapshotStatus(LockedFile* file, const SnapshotStatus& status) {
+    std::vector<std::string> pieces = {
+            status.state,
+            std::to_string(status.device_size),
+            std::to_string(status.snapshot_size),
+            std::to_string(status.sectors_allocated),
+            std::to_string(status.metadata_sectors),
+    };
+    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();
+        return false;
+    }
+    return true;
+}
+
+bool SnapshotManager::Truncate(LockedFile* file) {
+    if (lseek(file->fd(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek file failed: " << file->path();
+        return false;
+    }
+    if (ftruncate(file->fd(), 0) < 0) {
+        PLOG(ERROR) << "truncate failed: " << file->path();
+        return false;
+    }
+    return true;
+}
+
+std::string SnapshotManager::GetSnapshotDeviceName(const std::string& snapshot_name,
+                                                   const SnapshotStatus& status) {
+    if (status.device_size != status.snapshot_size) {
+        return snapshot_name + "-inner";
+    }
+    return snapshot_name;
+}
+
+bool SnapshotManager::EnsureImageManager() {
+    if (images_) return true;
+
+    // For now, use a preset timeout.
+    images_ = android::fiemap::IImageManager::Open(gsid_dir_, 15000ms);
+    if (!images_) {
+        LOG(ERROR) << "Could not open ImageManager";
+        return false;
+    }
+    return true;
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
new file mode 100644
index 0000000..9cc9bd7
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -0,0 +1,191 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <libsnapshot/snapshot.h>
+
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <chrono>
+#include <iostream>
+
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+#include <libfiemap/image_manager.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace std::chrono_literals;
+using namespace std::string_literals;
+
+class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
+  public:
+    std::string GetGsidDir() const override { return "ota/test"s; }
+    std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
+    bool IsRunningSnapshot() const override { return is_running_snapshot_; }
+
+    void set_is_running_snapshot(bool value) { is_running_snapshot_ = value; }
+
+  private:
+    bool is_running_snapshot_;
+};
+
+std::unique_ptr<SnapshotManager> sm;
+TestDeviceInfo* test_device = nullptr;
+
+class SnapshotTest : public ::testing::Test {
+  protected:
+    void SetUp() override {
+        test_device->set_is_running_snapshot(false);
+
+        if (sm->GetUpdateState() != UpdateState::None) {
+            ASSERT_TRUE(sm->CancelUpdate());
+        }
+        ASSERT_TRUE(sm->BeginUpdate());
+        ASSERT_TRUE(sm->EnsureImageManager());
+
+        image_manager_ = sm->image_manager();
+        ASSERT_NE(image_manager_, nullptr);
+    }
+
+    void TearDown() override {
+        lock_ = nullptr;
+
+        if (sm->GetUpdateState() != UpdateState::None) {
+            ASSERT_TRUE(sm->CancelUpdate());
+        }
+        for (const auto& temp_image : temp_images_) {
+            image_manager_->UnmapImageDevice(temp_image);
+            image_manager_->DeleteBackingImage(temp_image);
+        }
+    }
+
+    bool AcquireLock() {
+        lock_ = sm->OpenStateFile(O_RDWR, LOCK_EX);
+        return !!lock_;
+    }
+
+    bool CreateTempDevice(const std::string& name, uint64_t size, std::string* path) {
+        if (!image_manager_->CreateBackingImage(name, size, false)) {
+            return false;
+        }
+        temp_images_.emplace_back(name);
+        return image_manager_->MapImageDevice(name, 10s, path);
+    }
+
+    std::unique_ptr<SnapshotManager::LockedFile> lock_;
+    std::vector<std::string> temp_images_;
+    android::fiemap::IImageManager* image_manager_ = nullptr;
+};
+
+TEST_F(SnapshotTest, CreateSnapshot) {
+    ASSERT_TRUE(AcquireLock());
+
+    static const uint64_t kDeviceSize = 1024 * 1024;
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
+                                   kDeviceSize));
+
+    std::vector<std::string> snapshots;
+    ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots));
+    ASSERT_EQ(snapshots.size(), 1);
+    ASSERT_EQ(snapshots[0], "test-snapshot");
+
+    // Scope so delete can re-acquire the snapshot file lock.
+    {
+        auto file = sm->OpenSnapshotStatusFile("test-snapshot", O_RDONLY, LOCK_SH);
+        ASSERT_NE(file, nullptr);
+
+        SnapshotManager::SnapshotStatus status;
+        ASSERT_TRUE(sm->ReadSnapshotStatus(file.get(), &status));
+        ASSERT_EQ(status.state, "created");
+        ASSERT_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"));
+}
+
+TEST_F(SnapshotTest, MapSnapshot) {
+    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;
+    ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+
+    std::string snap_device;
+    ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+    ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
+}
+
+TEST_F(SnapshotTest, MapPartialSnapshot) {
+    ASSERT_TRUE(AcquireLock());
+
+    static const uint64_t kSnapshotSize = 1024 * 1024;
+    static const uint64_t kDeviceSize = 1024 * 1024 * 2;
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kSnapshotSize,
+                                   kSnapshotSize));
+
+    std::string base_device;
+    ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+
+    std::string snap_device;
+    ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+    ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
+}
+
+}  // namespace snapshot
+}  // namespace android
+
+using namespace android::snapshot;
+
+bool Mkdir(const std::string& path) {
+    if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
+        std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
+        return false;
+    }
+    return true;
+}
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    std::vector<std::string> paths = {
+            "/data/gsi/ota/test",
+            "/metadata/gsi/ota/test",
+            "/metadata/ota/test",
+            "/metadata/ota/test/snapshots",
+    };
+    for (const auto& path : paths) {
+        if (!Mkdir(path)) {
+            return 1;
+        }
+    }
+
+    // Create this once, otherwise, gsid will start/stop between each test.
+    test_device = new TestDeviceInfo();
+    sm = SnapshotManager::New(test_device);
+    if (!sm) {
+        std::cerr << "Could not create snapshot manager";
+        return 1;
+    }
+
+    return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index c2a0f33..642f2c1 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -15,10 +15,13 @@
 
 adb remount tests
 
---help        This help
---serial      Specify device (must if multiple are present)
---color       Dress output with highlighting colors
---print-time  Report the test duration
+--color                     Dress output with highlighting colors
+--help                      This help
+--no-wait-screen            Do not wait for display screen to settle
+--print-time                Report the test duration
+--serial                    Specify device (must if multiple are present)
+--wait-adb <duration>       adb wait timeout
+--wait-fastboot <duration>  fastboot wait timeout
 
 Conditions:
  - Must be a userdebug build.
@@ -53,6 +56,7 @@
 
 ADB_WAIT=4m
 FASTBOOT_WAIT=2m
+screen_wait=true
 
 ##
 ##  Helper Functions
@@ -185,7 +189,7 @@
 [ "USAGE: adb_cat <file> >stdout
 
 Returns: content of file to stdout with carriage returns skipped,
-         true of the file exists" ]
+         true if the file exists" ]
 adb_cat() {
     local OUTPUT="`adb_sh cat ${1} </dev/null 2>&1`"
     local ret=${?}
@@ -193,6 +197,17 @@
     return ${ret}
 }
 
+[ "USAGE: adb_ls <dirfile> >stdout
+
+Returns: filename or directoru content to stdout with carriage returns skipped,
+         true if the ls had no errors" ]
+adb_ls() {
+    local OUTPUT="`adb_sh ls ${1} </dev/null 2>/dev/null`"
+    local ret=${?}
+    echo "${OUTPUT}" | tr -d '\r'
+    return ${ret}
+}
+
 [ "USAGE: adb_reboot
 
 Returns: true if the reboot command succeeded" ]
@@ -436,6 +451,10 @@
 -n - echo newline at exit
 TIMEOUT - default `format_duration ${wait_for_screen_timeout}`" ]
 wait_for_screen() {
+  if ! ${screen_wait}; then
+    adb_wait
+    return
+  fi
   exit_function=true
   if [ X"-n" = X"${1}" ]; then
     exit_function=echo
@@ -743,6 +762,9 @@
 
 OPTIONS=`getopt --alternative --unquoted \
                 --longoptions help,serial:,colour,color,no-colour,no-color \
+                --longoptions wait-adb:,wait-fastboot: \
+                --longoptions wait-screen,wait-display \
+                --longoptions no-wait-screen,no-wait-display \
                 --longoptions gtest_print_time,print-time \
                 -- "?hs:" ${*}` ||
   ( echo "${USAGE}" >&2 ; false ) ||
@@ -766,9 +788,23 @@
     --no-color | --no-colour)
       color=false
       ;;
+    --no-wait-display | --no-wait-screen)
+      screen_wait=false
+      ;;
+    --wait-display | --wait-screen)
+      screen_wait=true
+      ;;
     --print-time | --gtest_print_time)
       print_time=true
       ;;
+    --wait-adb)
+      ADB_WAIT=${2}
+      shift
+      ;;
+    --wait-fastboot)
+      FASTBOOT_WAIT=${2}
+      shift
+      ;;
     --)
       shift
       break
@@ -1145,10 +1181,14 @@
 
 A="Hello World! $(date)"
 echo "${A}" | adb_sh cat - ">/system/hello"
+echo "${A}" | adb_sh cat - ">/system/priv-app/hello"
 echo "${A}" | adb_sh cat - ">/vendor/hello"
 B="`adb_cat /system/hello`" ||
-  die "sytem hello"
+  die "system hello"
 check_eq "${A}" "${B}" /system before reboot
+B="`adb_cat /system/priv-app/hello`" ||
+  die "system priv-app hello"
+check_eq "${A}" "${B}" /system/priv-app before reboot
 B="`adb_cat /vendor/hello`" ||
   die "vendor hello"
 check_eq "${A}" "${B}" /vendor before reboot
@@ -1230,6 +1270,13 @@
 fi
 B="`adb_cat /system/hello`"
 check_eq "${A}" "${B}" /system after reboot
+# If overlayfs has a nested security problem, this will fail.
+B="`adb_ls /system/`" ||
+  dir "adb ls /system"
+[ X"${B}" != X"${B#*priv-app}" ] ||
+  dir "adb ls /system/priv-app"
+B="`adb_cat /system/priv-app/hello`"
+check_eq "${A}" "${B}" /system/priv-app after reboot
 echo "${GREEN}[       OK ]${NORMAL} /system content remains after reboot" >&2
 # Only root can read vendor if sepolicy permissions are as expected.
 adb_root ||
@@ -1351,6 +1398,12 @@
   fi
   B="`adb_cat /system/hello`"
   check_eq "${A}" "${B}" system after flash vendor
+  B="`adb_ls /system/`" ||
+    dir "adb ls /system"
+  [ X"${B}" != X"${B#*priv-app}" ] ||
+    dir "adb ls /system/priv-app"
+  B="`adb_cat /system/priv-app/hello`"
+  check_eq "${A}" "${B}" system/priv-app after flash vendor
   adb_root ||
     die "adb root"
   B="`adb_cat /vendor/hello`"
@@ -1392,15 +1445,17 @@
 echo "${H}"
 [ ${err} = 0 ] &&
   ( adb_sh rm /vendor/hello </dev/null 2>/dev/null || true ) &&
-  adb_sh rm /system/hello </dev/null ||
+  adb_sh rm /system/hello /system/priv-app/hello </dev/null ||
   ( [ -n "${L}" ] && echo "${L}" && false ) ||
   die -t ${T} "cleanup hello"
 B="`adb_cat /system/hello`"
 check_eq "cat: /system/hello: No such file or directory" "${B}" after rm
+B="`adb_cat /system/priv-app/hello`"
+check_eq "cat: /system/priv-app/hello: No such file or directory" "${B}" after rm
 B="`adb_cat /vendor/hello`"
 check_eq "cat: /vendor/hello: No such file or directory" "${B}" after rm
 
-if [ -n "${scratch_partition}" ]; then
+if ${is_bootloader_fastboot} && [ -n "${scratch_partition}" ]; then
 
   echo "${GREEN}[ RUN      ]${NORMAL} test fastboot flash to ${scratch_partition} recovery" >&2
 
@@ -1542,7 +1597,9 @@
     adb_wait ${ADB_WAIT} ||
     die "adb remount -R"
   if [ "orange" != "`get_property ro.boot.verifiedbootstate`" -o \
-       "2" = "`get_property partition.system.verified`" ]; then
+       "2" = "`get_property partition.system.verified`" ] &&
+     [ -n "`get_property ro.boot.verifiedbootstate`" -o \
+       -n "`get_property partition.system.verified`" ]; then
     die "remount -R command failed to disable verity"
   fi
 
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index 7e6ad5b..2738457 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -49,7 +49,10 @@
     std::cerr << "  delete <dm-name>" << std::endl;
     std::cerr << "  list <devices | targets> [-v]" << std::endl;
     std::cerr << "  getpath <dm-name>" << std::endl;
+    std::cerr << "  info <dm-name>" << std::endl;
     std::cerr << "  status <dm-name>" << std::endl;
+    std::cerr << "  resume <dm-name>" << std::endl;
+    std::cerr << "  suspend <dm-name>" << std::endl;
     std::cerr << "  table <dm-name>" << std::endl;
     std::cerr << "  help" << std::endl;
     std::cerr << std::endl;
@@ -194,19 +197,12 @@
     char** argv_;
 };
 
-static int DmCreateCmdHandler(int argc, char** argv) {
-    if (argc < 1) {
-        std::cerr << "Usage: dmctl create <dm-name> [-ro] <targets...>" << std::endl;
-        return -EINVAL;
-    }
-    std::string name = argv[0];
-
+static bool parse_table_args(DmTable* table, int argc, char** argv) {
     // Parse extended options first.
-    DmTable table;
     int arg_index = 1;
     while (arg_index < argc && argv[arg_index][0] == '-') {
         if (strcmp(argv[arg_index], "-ro") == 0) {
-            table.set_readonly(true);
+            table->set_readonly(true);
             arg_index++;
         } else {
             std::cerr << "Unrecognized option: " << argv[arg_index] << std::endl;
@@ -218,15 +214,30 @@
     TargetParser parser(argc - arg_index, argv + arg_index);
     while (parser.More()) {
         std::unique_ptr<DmTarget> target = parser.Next();
-        if (!target || !table.AddTarget(std::move(target))) {
+        if (!target || !table->AddTarget(std::move(target))) {
             return -EINVAL;
         }
     }
 
-    if (table.num_targets() == 0) {
+    if (table->num_targets() == 0) {
         std::cerr << "Must define at least one target." << std::endl;
         return -EINVAL;
     }
+    return 0;
+}
+
+static int DmCreateCmdHandler(int argc, char** argv) {
+    if (argc < 1) {
+        std::cerr << "Usage: dmctl create <dm-name> [-ro] <targets...>" << std::endl;
+        return -EINVAL;
+    }
+    std::string name = argv[0];
+
+    DmTable table;
+    int ret = parse_table_args(&table, argc, argv);
+    if (ret) {
+        return ret;
+    }
 
     DeviceMapper& dm = DeviceMapper::Instance();
     if (!dm.CreateDevice(name, table)) {
@@ -252,6 +263,27 @@
     return 0;
 }
 
+static int DmReplaceCmdHandler(int argc, char** argv) {
+    if (argc < 1) {
+        std::cerr << "Usage: dmctl replace <dm-name> <targets...>" << std::endl;
+        return -EINVAL;
+    }
+    std::string name = argv[0];
+
+    DmTable table;
+    int ret = parse_table_args(&table, argc, argv);
+    if (ret) {
+        return ret;
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    if (!dm.LoadTableAndActivate(name, table)) {
+        std::cerr << "Failed to replace device-mapper table to: " << name << std::endl;
+        return -EIO;
+    }
+    return 0;
+}
+
 static int DmListTargets(DeviceMapper& dm, [[maybe_unused]] int argc,
                          [[maybe_unused]] char** argv) {
     std::vector<DmTargetTypeInfo> targets;
@@ -357,6 +389,41 @@
     return 0;
 }
 
+static int InfoCmdHandler(int argc, char** argv) {
+    if (argc != 1) {
+        std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+        return -EINVAL;
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    auto info = dm.GetDetailedInfo(argv[0]);
+    if (!info) {
+        std::cerr << "Invalid device \"" << argv[0] << "\"." << std::endl;
+        return -EINVAL;
+    }
+
+    constexpr int spacing = 14;
+    std::cout << std::left << std::setw(spacing) << "device"
+              << ": " << argv[0] << std::endl;
+    std::cout << std::left << std::setw(spacing) << "active"
+              << ": " << std::boolalpha << !info->IsSuspended() << std::endl;
+    std::cout << std::left << std::setw(spacing) << "access"
+              << ": ";
+    if (info->IsReadOnly()) {
+        std::cout << "ro ";
+    } else {
+        std::cout << "rw ";
+    }
+    std::cout << std::endl;
+    std::cout << std::left << std::setw(spacing) << "activeTable"
+              << ": " << std::boolalpha << info->IsActiveTablePresent() << std::endl;
+    std::cout << std::left << std::setw(spacing) << "inactiveTable"
+              << ": " << std::boolalpha << info->IsInactiveTablePresent() << std::endl;
+    std::cout << std::left << std::setw(spacing) << "bufferFull"
+              << ": " << std::boolalpha << info->IsBufferFull() << std::endl;
+    return 0;
+}
+
 static int DumpTable(const std::string& mode, int argc, char** argv) {
     if (argc != 1) {
         std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
@@ -399,15 +466,47 @@
     return DumpTable("status", argc, argv);
 }
 
+static int ResumeCmdHandler(int argc, char** argv) {
+    if (argc != 1) {
+        std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+        return -EINVAL;
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    if (!dm.ChangeState(argv[0], DmDeviceState::ACTIVE)) {
+        std::cerr << "Could not resume device \"" << argv[0] << "\"." << std::endl;
+        return -EINVAL;
+    }
+    return 0;
+}
+
+static int SuspendCmdHandler(int argc, char** argv) {
+    if (argc != 1) {
+        std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+        return -EINVAL;
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    if (!dm.ChangeState(argv[0], DmDeviceState::SUSPENDED)) {
+        std::cerr << "Could not suspend device \"" << argv[0] << "\"." << std::endl;
+        return -EINVAL;
+    }
+    return 0;
+}
+
 static std::map<std::string, std::function<int(int, char**)>> cmdmap = {
         // clang-format off
         {"create", DmCreateCmdHandler},
         {"delete", DmDeleteCmdHandler},
+        {"replace", DmReplaceCmdHandler},
         {"list", DmListCmdHandler},
         {"help", HelpCmdHandler},
         {"getpath", GetPathCmdHandler},
+        {"info", InfoCmdHandler},
         {"table", TableCmdHandler},
         {"status", StatusCmdHandler},
+        {"resume", ResumeCmdHandler},
+        {"suspend", SuspendCmdHandler},
         // clang-format on
 };
 
diff --git a/healthd/Android.bp b/healthd/Android.bp
index 2cf6be9..53be526 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -110,3 +110,55 @@
         "libutils",
     ],
 }
+
+sysprop_library {
+    name: "charger_sysprop",
+    srcs: ["charger.sysprop"],
+    property_owner: "Platform",
+    api_packages: ["android.sysprop"],
+}
+
+cc_library_static {
+    name: "libhealthd_draw",
+    export_include_dirs: ["."],
+    static_libs: [
+        "libcharger_sysprop",
+        "libminui",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+    header_libs: ["libbatteryservice_headers"],
+
+    srcs: ["healthd_draw.cpp"],
+}
+
+cc_library_static {
+    name: "libhealthd_charger",
+    local_include_dirs: ["include"],
+    export_include_dirs: [".", "include"],
+
+    static_libs: [
+        "android.hardware.health@2.0-impl",
+        "android.hardware.health@1.0-convert",
+        "libcharger_sysprop",
+        "libhealthstoragedefault",
+        "libhealthd_draw",
+        "libminui",
+    ],
+
+    shared_libs: [
+        "android.hardware.health@2.0",
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libpng",
+        "libsuspend",
+        "libutils",
+    ],
+
+    srcs: [
+        "healthd_mode_charger.cpp",
+        "AnimationParser.cpp",
+    ],
+}
diff --git a/healthd/Android.mk b/healthd/Android.mk
index d18f15a..b87f3c7 100644
--- a/healthd/Android.mk
+++ b/healthd/Android.mk
@@ -2,74 +2,6 @@
 
 LOCAL_PATH := $(call my-dir)
 
-### libhealthd_draw ###
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libhealthd_draw
-
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
-LOCAL_STATIC_LIBRARIES := libminui
-LOCAL_SHARED_LIBRARIES := libbase
-LOCAL_SRC_FILES := healthd_draw.cpp
-
-ifneq ($(TARGET_HEALTHD_DRAW_SPLIT_SCREEN),)
-LOCAL_CFLAGS += -DHEALTHD_DRAW_SPLIT_SCREEN=$(TARGET_HEALTHD_DRAW_SPLIT_SCREEN)
-else
-LOCAL_CFLAGS += -DHEALTHD_DRAW_SPLIT_SCREEN=0
-endif
-
-ifneq ($(TARGET_HEALTHD_DRAW_SPLIT_OFFSET),)
-LOCAL_CFLAGS += -DHEALTHD_DRAW_SPLIT_OFFSET=$(TARGET_HEALTHD_DRAW_SPLIT_OFFSET)
-else
-LOCAL_CFLAGS += -DHEALTHD_DRAW_SPLIT_OFFSET=0
-endif
-
-LOCAL_HEADER_LIBRARIES := libbatteryservice_headers
-
-include $(BUILD_STATIC_LIBRARY)
-
-### libhealthd_charger ###
-include $(CLEAR_VARS)
-
-LOCAL_CFLAGS := -Werror
-ifeq ($(strip $(BOARD_CHARGER_DISABLE_INIT_BLANK)),true)
-LOCAL_CFLAGS += -DCHARGER_DISABLE_INIT_BLANK
-endif
-ifeq ($(strip $(BOARD_CHARGER_ENABLE_SUSPEND)),true)
-LOCAL_CFLAGS += -DCHARGER_ENABLE_SUSPEND
-endif
-
-LOCAL_SRC_FILES := \
-    healthd_mode_charger.cpp \
-    AnimationParser.cpp
-
-LOCAL_MODULE := libhealthd_charger
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_EXPORT_C_INCLUDE_DIRS := \
-    $(LOCAL_PATH) \
-    $(LOCAL_PATH)/include
-
-LOCAL_STATIC_LIBRARIES := \
-    android.hardware.health@2.0-impl \
-    android.hardware.health@1.0-convert \
-    libhealthstoragedefault \
-    libhealthd_draw \
-    libminui \
-
-LOCAL_SHARED_LIBRARIES := \
-    android.hardware.health@2.0 \
-    libbase \
-    libcutils \
-    liblog \
-    libpng \
-    libutils \
-
-ifeq ($(strip $(BOARD_CHARGER_ENABLE_SUSPEND)),true)
-LOCAL_SHARED_LIBRARIES += libsuspend
-endif
-
-include $(BUILD_STATIC_LIBRARY)
-
 ### charger ###
 include $(CLEAR_VARS)
 ifeq ($(strip $(BOARD_CHARGER_NO_UI)),true)
@@ -83,18 +15,17 @@
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
 
 LOCAL_CFLAGS := -Werror
-ifeq ($(strip $(LOCAL_CHARGER_NO_UI)),true)
-LOCAL_CFLAGS += -DCHARGER_NO_UI
-endif
 
 CHARGER_STATIC_LIBRARIES := \
     android.hardware.health@2.0-impl \
     android.hardware.health@1.0-convert \
     libbinderthreadstate \
+    libcharger_sysprop \
     libhidltransport \
     libhidlbase \
     libhwbinder_noltopgo \
     libhealthstoragedefault \
+    libminui \
     libvndksupport \
     libhealthd_charger \
     libhealthd_charger_nops \
@@ -106,18 +37,12 @@
     libbase \
     libcutils \
     libjsoncpp \
+    libpng \
     libprocessgroup \
     liblog \
     libutils \
 
-ifneq ($(strip $(LOCAL_CHARGER_NO_UI)),true)
-CHARGER_STATIC_LIBRARIES += libminui
-CHARGER_SHARED_LIBRARIES += libpng
-endif
-
-ifeq ($(strip $(BOARD_CHARGER_ENABLE_SUSPEND)),true)
 CHARGER_SHARED_LIBRARIES += libsuspend
-endif
 
 LOCAL_STATIC_LIBRARIES := $(CHARGER_STATIC_LIBRARIES)
 LOCAL_SHARED_LIBRARIES := $(CHARGER_SHARED_LIBRARIES)
@@ -141,8 +66,7 @@
 LOCAL_MODULE_STEM := charger
 
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CFLAGS += -DCHARGER_NO_UI
+LOCAL_CFLAGS := -Wall -Werror -DCHARGER_FORCE_NO_UI=1
 
 # charger.recovery doesn't link against libhealthd_{charger,draw} or libminui, since it doesn't need
 # any UI support.
@@ -150,6 +74,7 @@
     android.hardware.health@2.0-impl \
     android.hardware.health@1.0-convert \
     libbinderthreadstate \
+    libcharger_sysprop \
     libhidltransport \
     libhidlbase \
     libhwbinder_noltopgo \
@@ -176,7 +101,7 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := charger_test
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_CFLAGS := -Wall -Werror -DCHARGER_NO_UI
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_STATIC_LIBRARIES := $(CHARGER_STATIC_LIBRARIES)
 LOCAL_SHARED_LIBRARIES := $(CHARGER_SHARED_LIBRARIES)
 LOCAL_SRC_FILES := \
diff --git a/healthd/AnimationParser.cpp b/healthd/AnimationParser.cpp
index 864038b..fde3b95 100644
--- a/healthd/AnimationParser.cpp
+++ b/healthd/AnimationParser.cpp
@@ -84,7 +84,6 @@
     static constexpr const char* fail_prefix = "fail: ";
     static constexpr const char* clock_prefix = "clock_display: ";
     static constexpr const char* percent_prefix = "percent_display: ";
-    static constexpr const char* frame_prefix = "frame: ";
 
     std::vector<animation::frame> frames;
 
diff --git a/healthd/api/current.txt b/healthd/api/current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/healthd/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/healthd/api/removed.txt b/healthd/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/healthd/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/healthd/api/system-current.txt b/healthd/api/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/healthd/api/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/healthd/api/system-removed.txt b/healthd/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/healthd/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/healthd/api/test-current.txt b/healthd/api/test-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/healthd/api/test-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/healthd/api/test-removed.txt b/healthd/api/test-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/healthd/api/test-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/healthd/charger.cpp b/healthd/charger.cpp
index 085cceb..58ed416 100644
--- a/healthd/charger.cpp
+++ b/healthd/charger.cpp
@@ -14,13 +14,18 @@
  * limitations under the License.
  */
 
+#include "charger.sysprop.h"
 #include "healthd_mode_charger.h"
 #include "healthd_mode_charger_nops.h"
 
-int main(int argc, char** argv) {
-#ifdef CHARGER_NO_UI
-    return healthd_charger_nops(argc, argv);
-#else
-    return healthd_charger_main(argc, argv);
+#ifndef CHARGER_FORCE_NO_UI
+#define CHARGER_FORCE_NO_UI 0
 #endif
+
+int main(int argc, char** argv) {
+    if (CHARGER_FORCE_NO_UI || android::sysprop::ChargerProperties::no_ui().value_or(false)) {
+        return healthd_charger_nops(argc, argv);
+    } else {
+        return healthd_charger_main(argc, argv);
+    }
 }
diff --git a/healthd/charger.sysprop b/healthd/charger.sysprop
new file mode 100644
index 0000000..b3f47a1
--- /dev/null
+++ b/healthd/charger.sysprop
@@ -0,0 +1,38 @@
+owner: Platform
+module: "android.sysprop.ChargerProperties"
+
+prop {
+    api_name: "draw_split_screen"
+    type: Boolean
+    prop_name: "ro.charger.draw_split_screen"
+    scope: Internal
+    access: Readonly
+}
+prop {
+    api_name: "draw_split_offset"
+    type: Long
+    prop_name: "ro.charger.draw_split_offset"
+    scope: Internal
+    access: Readonly
+}
+prop {
+    api_name: "disable_init_blank"
+    type: Boolean
+    prop_name: "ro.charger.disable_init_blank"
+    scope: Internal
+    access: Readonly
+}
+prop {
+    api_name: "enable_suspend"
+    type: Boolean
+    prop_name: "ro.charger.enable_suspend"
+    scope: Internal
+    access: Readonly
+}
+prop {
+    api_name: "no_ui"
+    type: Boolean
+    prop_name: "ro.charger.no_ui"
+    scope: Internal
+    access: Readonly
+}
diff --git a/healthd/healthd_draw.cpp b/healthd/healthd_draw.cpp
index 3da8bda..50eee19 100644
--- a/healthd/healthd_draw.cpp
+++ b/healthd/healthd_draw.cpp
@@ -18,15 +18,34 @@
 #include <batteryservice/BatteryService.h>
 #include <cutils/klog.h>
 
+#include "charger.sysprop.h"
 #include "healthd_draw.h"
 
 #define LOGE(x...) KLOG_ERROR("charger", x);
 #define LOGW(x...) KLOG_WARNING("charger", x);
 #define LOGV(x...) KLOG_DEBUG("charger", x);
 
+static bool get_split_screen() {
+    return android::sysprop::ChargerProperties::draw_split_screen().value_or(false);
+}
+
+static int get_split_offset() {
+    int64_t value = android::sysprop::ChargerProperties::draw_split_offset().value_or(0);
+    if (value < static_cast<int64_t>(std::numeric_limits<int>::min())) {
+        LOGW("draw_split_offset = %" PRId64 " overflow for an int; resetting to %d.\n", value,
+             std::numeric_limits<int>::min());
+        value = std::numeric_limits<int>::min();
+    }
+    if (value > static_cast<int64_t>(std::numeric_limits<int>::max())) {
+        LOGW("draw_split_offset = %" PRId64 " overflow for an int; resetting to %d.\n", value,
+             std::numeric_limits<int>::max());
+        value = std::numeric_limits<int>::max();
+    }
+    return static_cast<int>(value);
+}
+
 HealthdDraw::HealthdDraw(animation* anim)
-  : kSplitScreen(HEALTHD_DRAW_SPLIT_SCREEN),
-    kSplitOffset(HEALTHD_DRAW_SPLIT_OFFSET) {
+    : kSplitScreen(get_split_screen()), kSplitOffset(get_split_offset()) {
     int ret = gr_init();
 
     if (ret < 0) {
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index edf34f7..d676083 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -43,11 +43,10 @@
 #include <cutils/uevent.h>
 #include <sys/reboot.h>
 
-#ifdef CHARGER_ENABLE_SUSPEND
 #include <suspend/autosuspend.h>
-#endif
 
 #include "AnimationParser.h"
+#include "charger.sysprop.h"
 #include "healthd_draw.h"
 
 #include <health2/Health.h>
@@ -264,18 +263,16 @@
     LOGW("\n");
 }
 
-#ifdef CHARGER_ENABLE_SUSPEND
 static int request_suspend(bool enable) {
+    if (!android::sysprop::ChargerProperties::enable_suspend().value_or(false)) {
+        return 0;
+    }
+
     if (enable)
         return autosuspend_enable();
     else
         return autosuspend_disable();
 }
-#else
-static int request_suspend(bool /*enable*/) {
-    return 0;
-}
-#endif
 
 static void kick_animation(animation* anim) {
     anim->run = true;
@@ -321,10 +318,10 @@
 
         healthd_draw.reset(new HealthdDraw(batt_anim));
 
-#ifndef CHARGER_DISABLE_INIT_BLANK
-        healthd_draw->blank_screen(true);
-        charger->screen_blanked = true;
-#endif
+        if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) {
+            healthd_draw->blank_screen(true);
+            charger->screen_blanked = true;
+        }
     }
 
     /* animation is over, blank screen and leave */
diff --git a/init/Android.bp b/init/Android.bp
index ba60085..3233cc3 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -83,6 +83,7 @@
         "libfscrypt",
         "libgsi",
         "libhidl-gen-utils",
+        "libjsoncpp",
         "libkeyutils",
         "liblog",
         "liblogwrap",
@@ -93,12 +94,16 @@
         "libutils",
     ],
     bootstrap: true,
+    visibility: [":__subpackages__"],
 }
 
 cc_library_static {
     name: "libinit",
     recovery_available: true,
-    defaults: ["init_defaults", "selinux_policy_version"],
+    defaults: [
+        "init_defaults",
+        "selinux_policy_version",
+    ],
     srcs: [
         "action.cpp",
         "action_manager.cpp",
@@ -114,6 +119,7 @@
         "first_stage_mount.cpp",
         "import_parser.cpp",
         "init.cpp",
+        "interface_utils.cpp",
         "keychords.cpp",
         "modalias_handler.cpp",
         "mount_handler.cpp",
@@ -143,7 +149,10 @@
         "ueventd_parser.cpp",
         "util.cpp",
     ],
-    whole_static_libs: ["libcap", "com.android.sysprop.apex"],
+    whole_static_libs: [
+        "libcap",
+        "com.android.sysprop.apex",
+    ],
     header_libs: ["bootimg_headers"],
     proto: {
         type: "lite",
@@ -153,7 +162,10 @@
     target: {
         recovery: {
             cflags: ["-DRECOVERY"],
-            exclude_shared_libs: ["libbinder", "libutils"],
+            exclude_shared_libs: [
+                "libbinder",
+                "libutils",
+            ],
         },
     },
 }
@@ -182,7 +194,10 @@
     target: {
         recovery: {
             cflags: ["-DRECOVERY"],
-            exclude_shared_libs: ["libbinder", "libutils"],
+            exclude_shared_libs: [
+                "libbinder",
+                "libutils",
+            ],
         },
     },
 }
@@ -227,9 +242,13 @@
 
 genrule {
     name: "generated_stub_builtin_function_map",
+    tool_files: ["host_builtin_map.py"],
     out: ["generated_stub_builtin_function_map.h"],
-    srcs: ["builtins.cpp"],
-    cmd: "sed -n '/Builtin-function-map start/{:a;n;/Builtin-function-map end/q;p;ba}' $(in) | sed -e 's/do_[^}]*/do_stub/g' > $(out)",
+    srcs: [
+        "builtins.cpp",
+        "check_builtins.cpp",
+    ],
+    cmd: "$(location host_builtin_map.py) --builtins $(location builtins.cpp) --check_builtins $(location check_builtins.cpp) > $(out)",
 }
 
 cc_binary {
@@ -260,9 +279,11 @@
         "action_manager.cpp",
         "action_parser.cpp",
         "capabilities.cpp",
+        "check_builtins.cpp",
         "epoll.cpp",
         "keychords.cpp",
         "import_parser.cpp",
+        "interface_utils.cpp",
         "host_import_parser.cpp",
         "host_init_verifier.cpp",
         "parser.cpp",
@@ -281,7 +302,7 @@
     },
     generated_headers: [
         "generated_stub_builtin_function_map",
-        "generated_android_ids"
+        "generated_android_ids",
     ],
     target: {
         android: {
diff --git a/init/action.cpp b/init/action.cpp
index 8cf7645..1a66eee 100644
--- a/init/action.cpp
+++ b/init/action.cpp
@@ -35,9 +35,11 @@
     builtin_arguments.args.resize(args.size());
     builtin_arguments.args[0] = args[0];
     for (std::size_t i = 1; i < args.size(); ++i) {
-        if (!expand_props(args[i], &builtin_arguments.args[i])) {
-            return Error() << "cannot expand '" << args[i] << "'";
+        auto expanded_arg = ExpandProps(args[i]);
+        if (!expanded_arg) {
+            return expanded_arg.error();
         }
+        builtin_arguments.args[i] = std::move(*expanded_arg);
     }
 
     return function(builtin_arguments);
@@ -66,6 +68,30 @@
     return RunBuiltinFunction(func_, args_, kInitContext);
 }
 
+Result<void> Command::CheckCommand() const {
+    auto builtin_arguments = BuiltinArguments("host_init_verifier");
+
+    builtin_arguments.args.resize(args_.size());
+    builtin_arguments.args[0] = args_[0];
+    for (size_t i = 1; i < args_.size(); ++i) {
+        auto expanded_arg = ExpandProps(args_[i]);
+        if (!expanded_arg) {
+            if (expanded_arg.error().message().find("doesn't exist while expanding") !=
+                std::string::npos) {
+                // If we failed because we won't have a property, use an empty string, which is
+                // never returned from the parser, to indicate that this field cannot be checked.
+                builtin_arguments.args[i] = "";
+            } else {
+                return expanded_arg.error();
+            }
+        } else {
+            builtin_arguments.args[i] = std::move(*expanded_arg);
+        }
+    }
+
+    return func_(builtin_arguments);
+}
+
 std::string Command::BuildCommandString() const {
     return Join(args_, ' ');
 }
@@ -80,17 +106,20 @@
       filename_(filename),
       line_(line) {}
 
-const KeywordFunctionMap* Action::function_map_ = nullptr;
+const BuiltinFunctionMap* Action::function_map_ = nullptr;
 
 Result<void> Action::AddCommand(std::vector<std::string>&& args, int line) {
     if (!function_map_) {
         return Error() << "no function map available";
     }
 
-    auto function = function_map_->FindFunction(args);
-    if (!function) return Error() << function.error();
+    auto map_result = function_map_->Find(args);
+    if (!map_result) {
+        return Error() << map_result.error();
+    }
 
-    commands_.emplace_back(function->second, function->first, std::move(args), line);
+    commands_.emplace_back(map_result->function, map_result->run_in_subcontext, std::move(args),
+                           line);
     return {};
 }
 
@@ -102,6 +131,18 @@
     return commands_.size();
 }
 
+size_t Action::CheckAllCommands() const {
+    size_t failures = 0;
+    for (const auto& command : commands_) {
+        if (auto result = command.CheckCommand(); !result) {
+            LOG(ERROR) << "Command '" << command.BuildCommandString() << "' (" << filename_ << ":"
+                       << command.line() << ") failed: " << result.error();
+            ++failures;
+        }
+    }
+    return failures;
+}
+
 void Action::ExecuteOneCommand(std::size_t command) const {
     // We need a copy here since some Command execution may result in
     // changing commands_ vector by importing .rc files through parser
@@ -120,18 +161,8 @@
     auto result = command.InvokeFunc(subcontext_);
     auto duration = t.duration();
 
-    // There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
-    // device, such as '/sys/class/leds/jogball-backlight/brightness'.  As of this writing, there
-    // are 198 such failures on bullhead.  Instead of spamming the log reporting them, we do not
-    // report such failures unless we're running at the DEBUG log level.
-    bool report_failure = !result.has_value();
-    if (report_failure && android::base::GetMinimumLogSeverity() > android::base::DEBUG &&
-        result.error().code() == ENOENT) {
-        report_failure = false;
-    }
-
     // Any action longer than 50ms will be warned to user as slow operation
-    if (report_failure || duration > 50ms ||
+    if (!result.has_value() || duration > 50ms ||
         android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
         std::string trigger_name = BuildTriggersString();
         std::string cmd_str = command.BuildCommandString();
diff --git a/init/action.h b/init/action.h
index 13b250a..1534bf9 100644
--- a/init/action.h
+++ b/init/action.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef _INIT_ACTION_H
-#define _INIT_ACTION_H
+#pragma once
 
 #include <map>
 #include <queue>
@@ -41,6 +40,7 @@
 
     Result<void> InvokeFunc(Subcontext* subcontext) const;
     std::string BuildCommandString() const;
+    Result<void> CheckCommand() const;
 
     int line() const { return line_; }
 
@@ -63,7 +63,7 @@
 
     Result<void> AddCommand(std::vector<std::string>&& args, int line);
     void AddCommand(BuiltinFunction f, std::vector<std::string>&& args, int line);
-    std::size_t NumCommands() const;
+    size_t NumCommands() const;
     void ExecuteOneCommand(std::size_t command) const;
     void ExecuteAllCommands() const;
     bool CheckEvent(const EventTrigger& event_trigger) const;
@@ -71,11 +71,12 @@
     bool CheckEvent(const BuiltinAction& builtin_action) const;
     std::string BuildTriggersString() const;
     void DumpState() const;
+    size_t CheckAllCommands() const;
 
     bool oneshot() const { return oneshot_; }
     const std::string& filename() const { return filename_; }
     int line() const { return line_; }
-    static void set_function_map(const KeywordFunctionMap* function_map) {
+    static void set_function_map(const BuiltinFunctionMap* function_map) {
         function_map_ = function_map;
     }
 
@@ -91,10 +92,8 @@
     Subcontext* subcontext_;
     std::string filename_;
     int line_;
-    static const KeywordFunctionMap* function_map_;
+    static const BuiltinFunctionMap* function_map_;
 };
 
 }  // namespace init
 }  // namespace android
-
-#endif
diff --git a/init/action_manager.cpp b/init/action_manager.cpp
index 541c8f2..ebca762 100644
--- a/init/action_manager.cpp
+++ b/init/action_manager.cpp
@@ -23,6 +23,14 @@
 
 ActionManager::ActionManager() : current_command_(0) {}
 
+size_t ActionManager::CheckAllCommands() {
+    size_t failures = 0;
+    for (const auto& action : actions_) {
+        failures += action->CheckAllCommands();
+    }
+    return failures;
+}
+
 ActionManager& ActionManager::GetInstance() {
     static ActionManager instance;
     return instance;
diff --git a/init/action_manager.h b/init/action_manager.h
index 5f47a6d..a2b95ac 100644
--- a/init/action_manager.h
+++ b/init/action_manager.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef _INIT_ACTION_MANAGER_H
-#define _INIT_ACTION_MANAGER_H
+#pragma once
 
 #include <string>
 #include <vector>
@@ -32,6 +31,7 @@
 
     // Exposed for testing
     ActionManager();
+    size_t CheckAllCommands();
 
     void AddAction(std::unique_ptr<Action> action);
     void QueueEventTrigger(const std::string& trigger);
@@ -55,5 +55,3 @@
 
 }  // namespace init
 }  // namespace android
-
-#endif
diff --git a/init/builtins.cpp b/init/builtins.cpp
index ceab568..a2d782b 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -89,6 +89,43 @@
 namespace android {
 namespace init {
 
+// There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
+// device, such as '/sys/class/leds/jogball-backlight/brightness'.  As of this writing, there
+// are 81 such failures on cuttlefish.  Instead of spamming the log reporting them, we do not
+// report such failures unless we're running at the DEBUG log level.
+class ErrorIgnoreEnoent {
+  public:
+    ErrorIgnoreEnoent()
+        : ignore_error_(errno == ENOENT &&
+                        android::base::GetMinimumLogSeverity() > android::base::DEBUG) {}
+    explicit ErrorIgnoreEnoent(int errno_to_append)
+        : error_(errno_to_append),
+          ignore_error_(errno_to_append == ENOENT &&
+                        android::base::GetMinimumLogSeverity() > android::base::DEBUG) {}
+
+    template <typename T>
+    operator android::base::expected<T, ResultError>() {
+        if (ignore_error_) {
+            return {};
+        }
+        return error_;
+    }
+
+    template <typename T>
+    ErrorIgnoreEnoent& operator<<(T&& t) {
+        error_ << t;
+        return *this;
+    }
+
+  private:
+    Error error_;
+    bool ignore_error_;
+};
+
+inline ErrorIgnoreEnoent ErrnoErrorIgnoreEnoent() {
+    return ErrorIgnoreEnoent(errno);
+}
+
 std::vector<std::string> late_import_paths;
 
 static constexpr std::chrono::nanoseconds kCommandRetryTimeout = 5s;
@@ -201,26 +238,26 @@
 static Result<void> do_exec(const BuiltinArguments& args) {
     auto service = Service::MakeTemporaryOneshotService(args.args);
     if (!service) {
-        return Error() << "Could not create exec service";
+        return Error() << "Could not create exec service: " << service.error();
     }
-    if (auto result = service->ExecStart(); !result) {
+    if (auto result = (*service)->ExecStart(); !result) {
         return Error() << "Could not start exec service: " << result.error();
     }
 
-    ServiceList::GetInstance().AddService(std::move(service));
+    ServiceList::GetInstance().AddService(std::move(*service));
     return {};
 }
 
 static Result<void> do_exec_background(const BuiltinArguments& args) {
     auto service = Service::MakeTemporaryOneshotService(args.args);
     if (!service) {
-        return Error() << "Could not create exec background service";
+        return Error() << "Could not create exec background service: " << service.error();
     }
-    if (auto result = service->Start(); !result) {
+    if (auto result = (*service)->Start(); !result) {
         return Error() << "Could not start exec background service: " << result.error();
     }
 
-    ServiceList::GetInstance().AddService(std::move(service));
+    ServiceList::GetInstance().AddService(std::move(*service));
     return {};
 }
 
@@ -330,7 +367,7 @@
                 return ErrnoError() << "fchmodat() failed";
             }
         } else {
-            return ErrnoError() << "mkdir() failed";
+            return ErrnoErrorIgnoreEnoent() << "mkdir() failed";
         }
     }
 
@@ -344,7 +381,7 @@
         if (args.size() == 5) {
             gid = DecodeUid(args[4]);
             if (!gid) {
-                return Error() << "Unable to decode GID for '" << args[3] << "': " << gid.error();
+                return Error() << "Unable to decode GID for '" << args[4] << "': " << gid.error();
             }
         }
 
@@ -459,7 +496,7 @@
         if (wait)
             wait_for_file(source, kCommandRetryTimeout);
         if (mount(source, target, system, flags, options) < 0) {
-            return ErrnoError() << "mount() failed";
+            return ErrnoErrorIgnoreEnoent() << "mount() failed";
         }
 
     }
@@ -683,7 +720,7 @@
     Service* svc = ServiceList::GetInstance().FindService(args[1]);
     if (!svc) return Error() << "service " << args[1] << " not found";
     if (auto result = svc->Start(); !result) {
-        return Error() << "Could not start service: " << result.error();
+        return ErrorIgnoreEnoent() << "Could not start service: " << result.error();
     }
     return {};
 }
@@ -729,10 +766,7 @@
     if (MakeSymlink(args[1], args[2]) < 0) {
         // The symlink builtin is often used to create symlinks for older devices to be backwards
         // compatible with new paths, therefore we skip reporting this error.
-        if (errno == EEXIST && android::base::GetMinimumLogSeverity() > android::base::DEBUG) {
-            return {};
-        }
-        return ErrnoError() << "symlink() failed";
+        return ErrnoErrorIgnoreEnoent() << "symlink() failed";
     }
     return {};
 }
@@ -790,7 +824,8 @@
 
 static Result<void> do_write(const BuiltinArguments& args) {
     if (auto result = WriteFile(args[1], args[2]); !result) {
-        return Error() << "Unable to write to file '" << args[1] << "': " << result.error();
+        return ErrorIgnoreEnoent()
+               << "Unable to write to file '" << args[1] << "': " << result.error();
     }
 
     return {};
@@ -908,7 +943,7 @@
     }
 
     if (lchown(path.c_str(), *uid, *gid) == -1) {
-        return ErrnoError() << "lchown() failed";
+        return ErrnoErrorIgnoreEnoent() << "lchown() failed";
     }
 
     return {};
@@ -930,50 +965,27 @@
 static Result<void> do_chmod(const BuiltinArguments& args) {
     mode_t mode = get_mode(args[1].c_str());
     if (fchmodat(AT_FDCWD, args[2].c_str(), mode, AT_SYMLINK_NOFOLLOW) < 0) {
-        return ErrnoError() << "fchmodat() failed";
+        return ErrnoErrorIgnoreEnoent() << "fchmodat() failed";
     }
     return {};
 }
 
 static Result<void> do_restorecon(const BuiltinArguments& args) {
+    auto restorecon_info = ParseRestorecon(args.args);
+    if (!restorecon_info) {
+        return restorecon_info.error();
+    }
+
+    const auto& [flag, paths] = *restorecon_info;
+
     int ret = 0;
-
-    struct flag_type {const char* name; int value;};
-    static const flag_type flags[] = {
-        {"--recursive", SELINUX_ANDROID_RESTORECON_RECURSE},
-        {"--skip-ce", SELINUX_ANDROID_RESTORECON_SKIPCE},
-        {"--cross-filesystems", SELINUX_ANDROID_RESTORECON_CROSS_FILESYSTEMS},
-        {0, 0}
-    };
-
-    int flag = 0;
-
-    bool in_flags = true;
-    for (size_t i = 1; i < args.size(); ++i) {
-        if (android::base::StartsWith(args[i], "--")) {
-            if (!in_flags) {
-                return Error() << "flags must precede paths";
-            }
-            bool found = false;
-            for (size_t j = 0; flags[j].name; ++j) {
-                if (args[i] == flags[j].name) {
-                    flag |= flags[j].value;
-                    found = true;
-                    break;
-                }
-            }
-            if (!found) {
-                return Error() << "bad flag " << args[i];
-            }
-        } else {
-            in_flags = false;
-            if (selinux_android_restorecon(args[i].c_str(), flag) < 0) {
-                ret = errno;
-            }
+    for (const auto& path : paths) {
+        if (selinux_android_restorecon(path.c_str(), flag) < 0) {
+            ret = errno;
         }
     }
 
-    if (ret) return ErrnoError() << "selinux_android_restorecon() failed";
+    if (ret) return ErrnoErrorIgnoreEnoent() << "selinux_android_restorecon() failed";
     return {};
 }
 
@@ -1056,9 +1068,9 @@
                                             const BuiltinArguments& args) {
     auto service = Service::MakeTemporaryOneshotService(args.args);
     if (!service) {
-        return Error() << "Could not create exec service";
+        return Error() << "Could not create exec service: " << service.error();
     }
-    service->AddReapCallback([reboot_reason](const siginfo_t& siginfo) {
+    (*service)->AddReapCallback([reboot_reason](const siginfo_t& siginfo) {
         if (siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) {
             // TODO (b/122850122): support this in gsi
             if (fscrypt_is_native() && !android::gsi::IsGsiRunning()) {
@@ -1073,10 +1085,10 @@
             }
         }
     });
-    if (auto result = service->ExecStart(); !result) {
+    if (auto result = (*service)->ExecStart(); !result) {
         return Error() << "Could not start exec service: " << result.error();
     }
-    ServiceList::GetInstance().AddService(std::move(service));
+    ServiceList::GetInstance().AddService(std::move(*service));
     return {};
 }
 
@@ -1152,10 +1164,10 @@
 }
 
 // Builtin-function-map start
-const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
+const BuiltinFunctionMap& GetBuiltinFunctionMap() {
     constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
     // clang-format off
-    static const Map builtin_functions = {
+    static const BuiltinFunctionMap builtin_functions = {
         {"bootchart",               {1,     1,    {false,  do_bootchart}}},
         {"chmod",                   {2,     2,    {true,   do_chmod}}},
         {"chown",                   {2,     3,    {true,   do_chown}}},
diff --git a/init/builtins.h b/init/builtins.h
index 7bbf6aa..f0ff1eb 100644
--- a/init/builtins.h
+++ b/init/builtins.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef _INIT_BUILTINS_H
-#define _INIT_BUILTINS_H
+#pragma once
 
 #include <functional>
 #include <map>
@@ -31,18 +30,16 @@
 
 using BuiltinFunction = std::function<Result<void>(const BuiltinArguments&)>;
 
-using KeywordFunctionMap = KeywordMap<std::pair<bool, BuiltinFunction>>;
-class BuiltinFunctionMap : public KeywordFunctionMap {
-  public:
-    BuiltinFunctionMap() {}
-
-  private:
-    const Map& map() const override;
+struct BuiltinFunctionMapValue {
+    bool run_in_subcontext;
+    BuiltinFunction function;
 };
 
+using BuiltinFunctionMap = KeywordMap<BuiltinFunctionMapValue>;
+
+const BuiltinFunctionMap& GetBuiltinFunctionMap();
+
 extern std::vector<std::string> late_import_paths;
 
 }  // namespace init
 }  // namespace android
-
-#endif
diff --git a/init/check_builtins.cpp b/init/check_builtins.cpp
new file mode 100644
index 0000000..771f1d7
--- /dev/null
+++ b/init/check_builtins.cpp
@@ -0,0 +1,213 @@
+/*
+ * 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.
+ */
+
+// Note that these check functions cannot check expanded arguments from properties, since they will
+// not know what those properties would be at runtime.  They will be passed an empty string in the
+// situation that the input line had a property expansion without a default value, since an empty
+// string is otherwise an impossible value.  They should therefore disregard checking empty
+// arguments.
+
+#include "check_builtins.h"
+
+#include <sys/time.h>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+
+#include "builtin_arguments.h"
+#include "interface_utils.h"
+#include "rlimit_parser.h"
+#include "service.h"
+#include "util.h"
+
+using android::base::ParseInt;
+using android::base::StartsWith;
+
+#define ReturnIfAnyArgsEmpty()     \
+    for (const auto& arg : args) { \
+        if (arg.empty()) {         \
+            return {};             \
+        }                          \
+    }
+
+namespace android {
+namespace init {
+
+Result<void> check_chown(const BuiltinArguments& args) {
+    if (!args[1].empty()) {
+        auto uid = DecodeUid(args[1]);
+        if (!uid) {
+            return Error() << "Unable to decode UID for '" << args[1] << "': " << uid.error();
+        }
+    }
+
+    // GID is optional and pushes the index of path out by one if specified.
+    if (args.size() == 4 && !args[2].empty()) {
+        auto gid = DecodeUid(args[2]);
+        if (!gid) {
+            return Error() << "Unable to decode GID for '" << args[2] << "': " << gid.error();
+        }
+    }
+
+    return {};
+}
+
+Result<void> check_exec(const BuiltinArguments& args) {
+    ReturnIfAnyArgsEmpty();
+
+    auto result = Service::MakeTemporaryOneshotService(args.args);
+    if (!result) {
+        return result.error();
+    }
+
+    return {};
+}
+
+Result<void> check_exec_background(const BuiltinArguments& args) {
+    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";
+}
+
+Result<void> check_loglevel(const BuiltinArguments& args) {
+    ReturnIfAnyArgsEmpty();
+
+    int log_level = -1;
+    ParseInt(args[1], &log_level);
+    if (log_level < 0 || log_level > 7) {
+        return Error() << "loglevel must be in the range of 0-7";
+    }
+    return {};
+}
+
+Result<void> check_mkdir(const BuiltinArguments& args) {
+    if (args.size() >= 4) {
+        if (!args[3].empty()) {
+            auto uid = DecodeUid(args[3]);
+            if (!uid) {
+                return Error() << "Unable to decode UID for '" << args[3] << "': " << uid.error();
+            }
+        }
+
+        if (args.size() == 5 && !args[4].empty()) {
+            auto gid = DecodeUid(args[4]);
+            if (!gid) {
+                return Error() << "Unable to decode GID for '" << args[4] << "': " << gid.error();
+            }
+        }
+    }
+
+    return {};
+}
+
+Result<void> check_restorecon(const BuiltinArguments& args) {
+    ReturnIfAnyArgsEmpty();
+
+    auto restorecon_info = ParseRestorecon(args.args);
+    if (!restorecon_info) {
+        return restorecon_info.error();
+    }
+
+    return {};
+}
+
+Result<void> check_restorecon_recursive(const BuiltinArguments& args) {
+    return check_restorecon(std::move(args));
+}
+
+Result<void> check_setprop(const BuiltinArguments& args) {
+    const std::string& name = args[1];
+    if (name.empty()) {
+        return {};
+    }
+    const std::string& value = args[2];
+
+    if (!IsLegalPropertyName(name)) {
+        return Error() << "'" << name << "' is not a legal property name";
+    }
+
+    if (!value.empty()) {
+        if (auto result = IsLegalPropertyValue(name, value); !result) {
+            return result.error();
+        }
+    }
+
+    if (StartsWith(name, "ctl.")) {
+        return Error()
+               << "Do not set ctl. properties from init; call the Service functions directly";
+    }
+
+    static constexpr const char kRestoreconProperty[] = "selinux.restorecon_recursive";
+    if (name == kRestoreconProperty) {
+        return Error() << "Do not set '" << kRestoreconProperty
+                       << "' from init; use the restorecon builtin directly";
+    }
+
+    return {};
+}
+
+Result<void> check_setrlimit(const BuiltinArguments& args) {
+    ReturnIfAnyArgsEmpty();
+
+    auto rlimit = ParseRlimit(args.args);
+    if (!rlimit) return rlimit.error();
+    return {};
+}
+
+Result<void> check_sysclktz(const BuiltinArguments& args) {
+    ReturnIfAnyArgsEmpty();
+
+    struct timezone tz = {};
+    if (!android::base::ParseInt(args[1], &tz.tz_minuteswest)) {
+        return Error() << "Unable to parse mins_west_of_gmt";
+    }
+    return {};
+}
+
+Result<void> check_wait(const BuiltinArguments& args) {
+    if (args.size() == 3 && !args[2].empty()) {
+        int timeout_int;
+        if (!android::base::ParseInt(args[2], &timeout_int)) {
+            return Error() << "failed to parse timeout";
+        }
+    }
+    return {};
+}
+
+Result<void> check_wait_for_prop(const BuiltinArguments& args) {
+    return check_setprop(std::move(args));
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/check_builtins.h b/init/check_builtins.h
new file mode 100644
index 0000000..4ff0d0c
--- /dev/null
+++ b/init/check_builtins.h
@@ -0,0 +1,43 @@
+/*
+ * 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 "builtin_arguments.h"
+#include "result.h"
+
+namespace android {
+namespace init {
+
+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);
+Result<void> check_restorecon(const BuiltinArguments& args);
+Result<void> check_restorecon_recursive(const BuiltinArguments& args);
+Result<void> check_setprop(const BuiltinArguments& args);
+Result<void> check_setrlimit(const BuiltinArguments& args);
+Result<void> check_sysclktz(const BuiltinArguments& args);
+Result<void> check_wait(const BuiltinArguments& args);
+Result<void> check_wait_for_prop(const BuiltinArguments& args);
+
+}  // namespace init
+}  // namespace android
diff --git a/init/host_builtin_map.py b/init/host_builtin_map.py
new file mode 100755
index 0000000..6afcb17
--- /dev/null
+++ b/init/host_builtin_map.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+"""Generates the builtins map to be used by host_init_verifier.
+
+It copies the builtin function map from builtins.cpp, then replaces do_xxx() functions with the
+equivalent check_xxx() if found in check_builtins.cpp.
+
+"""
+
+import re
+import argparse
+
+parser = argparse.ArgumentParser('host_builtin_map.py')
+parser.add_argument('--builtins', required=True, help='Path to builtins.cpp')
+parser.add_argument('--check_builtins', required=True, help='Path to check_builtins.cpp')
+args = parser.parse_args()
+
+CHECK_REGEX = re.compile(r'.+check_(\S+)\(.+')
+check_functions = []
+with open(args.check_builtins) as check_file:
+  for line in check_file:
+    match = CHECK_REGEX.match(line)
+    if match:
+      check_functions.append(match.group(1))
+
+function_map = []
+with open(args.builtins) as builtins_file:
+  in_function_map = False
+  for line in builtins_file:
+    if '// Builtin-function-map start' in line:
+      in_function_map = True
+    elif '// Builtin-function-map end' in line:
+      in_function_map = False
+    elif in_function_map:
+      function_map.append(line)
+
+DO_REGEX = re.compile(r'.+do_([^\}]+).+')
+FUNCTION_REGEX = re.compile(r'(do_[^\}]+)')
+for line in function_map:
+  match = DO_REGEX.match(line)
+  if match:
+    if match.group(1) in check_functions:
+      print line.replace('do_', 'check_'),
+    else:
+      print FUNCTION_REGEX.sub('check_stub', line),
+  else:
+    print line,
diff --git a/init/host_init_stubs.h b/init/host_init_stubs.h
index 7c0544a..f9a08a5 100644
--- a/init/host_init_stubs.h
+++ b/init/host_init_stubs.h
@@ -26,6 +26,7 @@
 
 // android/api-level.h
 #define __ANDROID_API_P__ 28
+#define __ANDROID_API_R__ 30
 
 // sys/system_properties.h
 #define PROP_VALUE_MAX 92
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index 92c2aa5..b2402b3 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -30,13 +30,14 @@
 #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"
 #include "action_parser.h"
+#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"
@@ -131,51 +132,22 @@
     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 {
 
-static Result<void> do_stub(const BuiltinArguments& args) {
+static Result<void> check_stub(const BuiltinArguments& args) {
     return {};
 }
 
 #include "generated_stub_builtin_function_map.h"
 
 void PrintUsage() {
-    std::cout << "usage: host_init_verifier [-p FILE] -k FILE <init rc file>\n"
+    std::cout << "usage: host_init_verifier [-p FILE] -i FILE <init rc file>\n"
                  "\n"
                  "Tests an init script for correctness\n"
                  "\n"
                  "-p FILE\tSearch this passwd file for users and groups\n"
-                 "-k FILE\tUse this file as a space-separated list of known interfaces\n"
+                 "-i FILE\tParse this JSON file for the HIDL interface inheritance hierarchy\n"
               << std::endl;
 }
 
@@ -216,21 +188,26 @@
     argc -= optind;
     argv += optind;
 
-    if (argc != 1) {
+    if (argc != 1 || interface_inheritance_hierarchy_file.empty()) {
         PrintUsage();
         return EXIT_FAILURE;
     }
 
-    const BuiltinFunctionMap function_map;
+    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>());
 
@@ -238,9 +215,10 @@
         LOG(ERROR) << "Failed to open init rc script '" << *argv << "'";
         return EXIT_FAILURE;
     }
-    if (parser.parse_error_count() > 0) {
-        LOG(ERROR) << "Failed to parse init script '" << *argv << "' with "
-                   << parser.parse_error_count() << " errors";
+    size_t failures = parser.parse_error_count() + am.CheckAllCommands() + sl.CheckAllCommands();
+    if (failures > 0) {
+        LOG(ERROR) << "Failed to parse init script '" << *argv << "' with " << failures
+                   << " errors";
         return EXIT_FAILURE;
     }
     return EXIT_SUCCESS;
diff --git a/init/import_parser.cpp b/init/import_parser.cpp
index c72b7d6..1a43508 100644
--- a/init/import_parser.cpp
+++ b/init/import_parser.cpp
@@ -29,15 +29,14 @@
         return Error() << "single argument needed for import\n";
     }
 
-    std::string conf_file;
-    bool ret = expand_props(args[1], &conf_file);
-    if (!ret) {
-        return Error() << "error while expanding import";
+    auto conf_file = ExpandProps(args[1]);
+    if (!conf_file) {
+        return Error() << "Could not expand import: " << conf_file.error();
     }
 
-    LOG(INFO) << "Added '" << conf_file << "' to import list";
+    LOG(INFO) << "Added '" << *conf_file << "' to import list";
     if (filename_.empty()) filename_ = filename;
-    imports_.emplace_back(std::move(conf_file), line);
+    imports_.emplace_back(std::move(*conf_file), line);
     return {};
 }
 
diff --git a/init/init.cpp b/init/init.cpp
index 5dba54d..18fb0c3 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -688,7 +688,7 @@
     MountHandler mount_handler(&epoll);
     set_usb_controller();
 
-    const BuiltinFunctionMap function_map;
+    const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
     Action::set_function_map(&function_map);
 
     if (!SetupMountNamespaces()) {
diff --git a/init/init_test.cpp b/init/init_test.cpp
index a09db18..0411214 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -22,6 +22,7 @@
 #include "action.h"
 #include "action_manager.h"
 #include "action_parser.h"
+#include "builtin_arguments.h"
 #include "builtins.h"
 #include "import_parser.h"
 #include "keyword_map.h"
@@ -29,7 +30,6 @@
 #include "service.h"
 #include "service_list.h"
 #include "service_parser.h"
-#include "test_function_map.h"
 #include "util.h"
 
 namespace android {
@@ -37,7 +37,7 @@
 
 using ActionManagerCommand = std::function<void(ActionManager&)>;
 
-void TestInit(const std::string& init_script_file, const TestFunctionMap& test_function_map,
+void TestInit(const std::string& init_script_file, const BuiltinFunctionMap& test_function_map,
               const std::vector<ActionManagerCommand>& commands, ServiceList* service_list) {
     ActionManager am;
 
@@ -60,7 +60,7 @@
     }
 }
 
-void TestInitText(const std::string& init_script, const TestFunctionMap& test_function_map,
+void TestInitText(const std::string& init_script, const BuiltinFunctionMap& test_function_map,
                   const std::vector<ActionManagerCommand>& commands, ServiceList* service_list) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
@@ -76,8 +76,13 @@
 pass_test
 )init";
 
-    TestFunctionMap test_function_map;
-    test_function_map.Add("pass_test", [&expect_true]() { expect_true = true; });
+    auto do_pass_test = [&expect_true](const BuiltinArguments&) {
+        expect_true = true;
+        return Result<void>{};
+    };
+    BuiltinFunctionMap test_function_map = {
+            {"pass_test", {0, 0, {false, do_pass_test}}},
+    };
 
     ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
     std::vector<ActionManagerCommand> commands{trigger_boot};
@@ -103,10 +108,24 @@
 )init";
 
     int num_executed = 0;
-    TestFunctionMap test_function_map;
-    test_function_map.Add("execute_first", [&num_executed]() { EXPECT_EQ(0, num_executed++); });
-    test_function_map.Add("execute_second", [&num_executed]() { EXPECT_EQ(1, num_executed++); });
-    test_function_map.Add("execute_third", [&num_executed]() { EXPECT_EQ(2, num_executed++); });
+    auto do_execute_first = [&num_executed](const BuiltinArguments&) {
+        EXPECT_EQ(0, num_executed++);
+        return Result<void>{};
+    };
+    auto do_execute_second = [&num_executed](const BuiltinArguments&) {
+        EXPECT_EQ(1, num_executed++);
+        return Result<void>{};
+    };
+    auto do_execute_third = [&num_executed](const BuiltinArguments&) {
+        EXPECT_EQ(2, num_executed++);
+        return Result<void>{};
+    };
+
+    BuiltinFunctionMap test_function_map = {
+            {"execute_first", {0, 0, {false, do_execute_first}}},
+            {"execute_second", {0, 0, {false, do_execute_second}}},
+            {"execute_third", {0, 0, {false, do_execute_third}}},
+    };
 
     ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
     std::vector<ActionManagerCommand> commands{trigger_boot};
@@ -127,7 +146,7 @@
 )init";
 
     ServiceList service_list;
-    TestInitText(init_script, TestFunctionMap(), {}, &service_list);
+    TestInitText(init_script, BuiltinFunctionMap(), {}, &service_list);
     ASSERT_EQ(1, std::distance(service_list.begin(), service_list.end()));
 
     auto service = service_list.begin()->get();
@@ -186,8 +205,9 @@
         return Result<void>{};
     };
 
-    TestFunctionMap test_function_map;
-    test_function_map.Add("execute", 1, 1, false, execute_command);
+    BuiltinFunctionMap test_function_map = {
+            {"execute", {1, 1, {false, execute_command}}},
+    };
 
     ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); };
     std::vector<ActionManagerCommand> commands{trigger_boot};
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/keyword_map.h b/init/keyword_map.h
index 7837bb3..d92678f 100644
--- a/init/keyword_map.h
+++ b/init/keyword_map.h
@@ -18,36 +18,49 @@
 
 #include <map>
 #include <string>
+#include <vector>
 
 #include "result.h"
 
 namespace android {
 namespace init {
 
-template <typename Function>
+// Every init builtin, init service option, and ueventd option has a minimum and maximum number of
+// arguments.  These must be checked both at run time for safety and also at build time for
+// correctness in host_init_verifier.  Instead of copying and pasting the boiler plate code that
+// does this check into each function, it is abstracted in KeywordMap<>.  This class maps keywords
+// to functions and checks that the number of arguments provided falls in the correct range or
+// returns an error otherwise.
+
+// Value is the return value of Find(), which is typically either a single function or a struct with
+// additional information.
+template <typename Value>
 class KeywordMap {
   public:
-    using FunctionInfo = std::tuple<std::size_t, std::size_t, Function>;
-    using Map = std::map<std::string, FunctionInfo>;
+    struct MapValue {
+        size_t min_args;
+        size_t max_args;
+        Value value;
+    };
 
-    virtual ~KeywordMap() {
-    }
+    KeywordMap() {}
+    KeywordMap(std::initializer_list<std::pair<const std::string, MapValue>> init) : map_(init) {}
 
-    const Result<Function> FindFunction(const std::vector<std::string>& args) const {
+    Result<Value> Find(const std::vector<std::string>& args) const {
         if (args.empty()) return Error() << "Keyword needed, but not provided";
 
         auto& keyword = args[0];
         auto num_args = args.size() - 1;
 
-        auto function_info_it = map().find(keyword);
-        if (function_info_it == map().end()) {
+        auto result_it = map_.find(keyword);
+        if (result_it == map_.end()) {
             return Errorf("Invalid keyword '{}'", keyword);
         }
 
-        auto function_info = function_info_it->second;
+        auto result = result_it->second;
 
-        auto min_args = std::get<0>(function_info);
-        auto max_args = std::get<1>(function_info);
+        auto min_args = result.min_args;
+        auto max_args = result.max_args;
         if (min_args == max_args && num_args != min_args) {
             return Errorf("{} requires {} argument{}", keyword, min_args,
                           (min_args > 1 || min_args == 0) ? "s" : "");
@@ -63,13 +76,11 @@
             }
         }
 
-        return std::get<Function>(function_info);
+        return result.value;
     }
 
   private:
-    // Map of keyword ->
-    // (minimum number of arguments, maximum number of arguments, function pointer)
-    virtual const Map& map() const = 0;
+    std::map<std::string, MapValue> map_;
 };
 
 }  // namespace init
diff --git a/init/main.cpp b/init/main.cpp
index 2ce46ef..38bc74b 100644
--- a/init/main.cpp
+++ b/init/main.cpp
@@ -60,7 +60,7 @@
     if (argc > 1) {
         if (!strcmp(argv[1], "subcontext")) {
             android::base::InitLogging(argv, &android::base::KernelLogger);
-            const BuiltinFunctionMap function_map;
+            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
 
             return SubcontextMain(argc, argv, &function_map);
         }
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 3761750..17622a3 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -174,13 +174,8 @@
         return PROP_ERROR_INVALID_NAME;
     }
 
-    if (valuelen >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
-        *error = "Property value too long";
-        return PROP_ERROR_INVALID_VALUE;
-    }
-
-    if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) {
-        *error = "Value is not a UTF8 encoded string";
+    if (auto result = IsLegalPropertyValue(name, value); !result) {
+        *error = result.error().message();
         return PROP_ERROR_INVALID_VALUE;
     }
 
@@ -648,13 +643,14 @@
             }
 
             std::string raw_filename(fn);
-            std::string expanded_filename;
-            if (!expand_props(raw_filename, &expanded_filename)) {
-                LOG(ERROR) << "Could not expand filename '" << raw_filename << "'";
+            auto expanded_filename = ExpandProps(raw_filename);
+
+            if (!expanded_filename) {
+                LOG(ERROR) << "Could not expand filename ': " << expanded_filename.error();
                 continue;
             }
 
-            load_properties_from_file(expanded_filename.c_str(), key, properties);
+            load_properties_from_file(expanded_filename->c_str(), key, properties);
         } else {
             value = strchr(key, '=');
             if (!value) continue;
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 54be086..143cdfd 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -497,24 +497,28 @@
 // This function returns the Android version with which the vendor SEPolicy was compiled.
 // It is used for version checks such as whether or not vendor_init should be used
 int SelinuxGetVendorAndroidVersion() {
-    if (!IsSplitPolicyDevice()) {
-        // If this device does not split sepolicy files, it's not a Treble device and therefore,
-        // we assume it's always on the latest platform.
-        return __ANDROID_API_FUTURE__;
-    }
+    static int vendor_android_version = [] {
+        if (!IsSplitPolicyDevice()) {
+            // If this device does not split sepolicy files, it's not a Treble device and therefore,
+            // we assume it's always on the latest platform.
+            return __ANDROID_API_FUTURE__;
+        }
 
-    std::string version;
-    if (!GetVendorMappingVersion(&version)) {
-        LOG(FATAL) << "Could not read vendor SELinux version";
-    }
+        std::string version;
+        if (!GetVendorMappingVersion(&version)) {
+            LOG(FATAL) << "Could not read vendor SELinux version";
+        }
 
-    int major_version;
-    std::string major_version_str(version, 0, version.find('.'));
-    if (!ParseInt(major_version_str, &major_version)) {
-        PLOG(FATAL) << "Failed to parse the vendor sepolicy major version " << major_version_str;
-    }
+        int major_version;
+        std::string major_version_str(version, 0, version.find('.'));
+        if (!ParseInt(major_version_str, &major_version)) {
+            PLOG(FATAL) << "Failed to parse the vendor sepolicy major version "
+                        << major_version_str;
+        }
 
-    return major_version;
+        return major_version;
+    }();
+    return vendor_android_version;
 }
 
 // This function initializes SELinux then execs init to run in the init SELinux context.
diff --git a/init/service.cpp b/init/service.cpp
index 47f4db9..9537843 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -100,9 +100,11 @@
     expanded_args.resize(args.size());
     c_strings.push_back(const_cast<char*>(args[0].data()));
     for (std::size_t i = 1; i < args.size(); ++i) {
-        if (!expand_props(args[i], &expanded_args[i])) {
-            LOG(FATAL) << args[0] << ": cannot expand '" << args[i] << "'";
+        auto expanded_arg = ExpandProps(args[i]);
+        if (!expanded_arg) {
+            LOG(FATAL) << args[0] << ": cannot expand arguments': " << expanded_arg.error();
         }
+        expanded_args[i] = *expanded_arg;
         c_strings.push_back(expanded_args[i].data());
     }
     c_strings.push_back(nullptr);
@@ -165,6 +167,15 @@
             property_set(boottime_property, std::to_string(start_ns));
         }
     }
+
+    // init.svc_debug_pid.* properties are only for tests, and should not be used
+    // on device for security checks.
+    std::string pid_property = "init.svc_debug_pid." + name_;
+    if (new_state == "running") {
+        property_set(pid_property, std::to_string(pid_));
+    } else if (new_state == "stopped") {
+        property_set(pid_property, "");
+    }
 }
 
 void Service::KillProcessGroup(int signal) {
@@ -633,7 +644,8 @@
     }
 }
 
-std::unique_ptr<Service> Service::MakeTemporaryOneshotService(const std::vector<std::string>& args) {
+Result<std::unique_ptr<Service>> Service::MakeTemporaryOneshotService(
+        const std::vector<std::string>& args) {
     // Parse the arguments: exec [SECLABEL [UID [GID]*] --] COMMAND ARGS...
     // SECLABEL can be a - to denote default
     std::size_t command_arg = 1;
@@ -644,13 +656,11 @@
         }
     }
     if (command_arg > 4 + NR_SVC_SUPP_GIDS) {
-        LOG(ERROR) << "exec called with too many supplementary group ids";
-        return nullptr;
+        return Error() << "exec called with too many supplementary group ids";
     }
 
     if (command_arg >= args.size()) {
-        LOG(ERROR) << "exec called without command";
-        return nullptr;
+        return Error() << "exec called without command";
     }
     std::vector<std::string> str_args(args.begin() + command_arg, args.end());
 
@@ -669,8 +679,7 @@
     if (command_arg > 3) {
         uid = DecodeUid(args[2]);
         if (!uid) {
-            LOG(ERROR) << "Unable to decode UID for '" << args[2] << "': " << uid.error();
-            return nullptr;
+            return Error() << "Unable to decode UID for '" << args[2] << "': " << uid.error();
         }
     }
     Result<gid_t> gid = 0;
@@ -678,16 +687,14 @@
     if (command_arg > 4) {
         gid = DecodeUid(args[3]);
         if (!gid) {
-            LOG(ERROR) << "Unable to decode GID for '" << args[3] << "': " << gid.error();
-            return nullptr;
+            return Error() << "Unable to decode GID for '" << args[3] << "': " << gid.error();
         }
         std::size_t nr_supp_gids = command_arg - 1 /* -- */ - 4 /* exec SECLABEL UID GID */;
         for (size_t i = 0; i < nr_supp_gids; ++i) {
             auto supp_gid = DecodeUid(args[4 + i]);
             if (!supp_gid) {
-                LOG(ERROR) << "Unable to decode GID for '" << args[4 + i]
-                           << "': " << supp_gid.error();
-                return nullptr;
+                return Error() << "Unable to decode GID for '" << args[4 + i]
+                               << "': " << supp_gid.error();
             }
             supp_gids.push_back(*supp_gid);
         }
diff --git a/init/service.h b/init/service.h
index cdf31bb..ccefc8e 100644
--- a/init/service.h
+++ b/init/service.h
@@ -71,7 +71,8 @@
             const std::vector<gid_t>& supp_gids, int namespace_flags, const std::string& seclabel,
             Subcontext* subcontext_for_restart_commands, const std::vector<std::string>& args);
 
-    static std::unique_ptr<Service> MakeTemporaryOneshotService(const std::vector<std::string>& args);
+    static Result<std::unique_ptr<Service>> MakeTemporaryOneshotService(
+            const std::vector<std::string>& args);
 
     bool IsRunning() { return (flags_ & SVC_RUNNING) != 0; }
     Result<void> ExecStart();
@@ -96,6 +97,7 @@
     void AddReapCallback(std::function<void(const siginfo_t& siginfo)> callback) {
         reap_callbacks_.emplace_back(std::move(callback));
     }
+    size_t CheckAllCommands() const { return onrestart_.CheckAllCommands(); }
 
     static bool is_exec_service_running() { return is_exec_service_running_; }
 
diff --git a/init/service_list.cpp b/init/service_list.cpp
index 3a48183..c51a9cf 100644
--- a/init/service_list.cpp
+++ b/init/service_list.cpp
@@ -28,6 +28,14 @@
     return instance;
 }
 
+size_t ServiceList::CheckAllCommands() {
+    size_t failures = 0;
+    for (const auto& service : services_) {
+        failures += service->CheckAllCommands();
+    }
+    return failures;
+}
+
 void ServiceList::AddService(std::unique_ptr<Service> service) {
     services_.emplace_back(std::move(service));
 }
diff --git a/init/service_list.h b/init/service_list.h
index 2136a21..ee2c702 100644
--- a/init/service_list.h
+++ b/init/service_list.h
@@ -30,6 +30,7 @@
 
     // Exposed for testing
     ServiceList();
+    size_t CheckAllCommands();
 
     void AddService(std::unique_ptr<Service> service);
     void RemoveService(const Service& svc);
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 0fbbeb8..dd552fb 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -193,9 +193,9 @@
 Result<void> ServiceParser::ParseKeycodes(std::vector<std::string>&& args) {
     auto it = args.begin() + 1;
     if (args.size() == 2 && StartsWith(args[1], "$")) {
-        std::string expanded;
-        if (!expand_props(args[1], &expanded)) {
-            return Error() << "Could not expand property '" << args[1] << "'";
+        auto expanded = ExpandProps(args[1]);
+        if (!expanded) {
+            return expanded.error();
         }
 
         // If the property is not set, it defaults to none, in which case there are no keycodes
@@ -204,7 +204,7 @@
             return {};
         }
 
-        args = Split(expanded, ",");
+        args = Split(*expanded, ",");
         it = args.begin();
     }
 
@@ -422,9 +422,11 @@
     FileDescriptor file;
     file.type = args[2];
 
-    if (!expand_props(args[1], &file.name)) {
-        return Error() << "Could not expand property in file path '" << args[1] << "'";
+    auto file_name = ExpandProps(args[1]);
+    if (!file_name) {
+        return Error() << "Could not expand file path ': " << file_name.error();
     }
+    file.name = *file_name;
     if (file.name[0] != '/' || file.name.find("../") != std::string::npos) {
         return Error() << "file name must not be relative";
     }
@@ -461,18 +463,10 @@
     return {};
 }
 
-class ServiceParser::OptionParserMap : public KeywordMap<OptionParser> {
-  public:
-    OptionParserMap() {}
-
-  private:
-    const Map& map() const override;
-};
-
-const ServiceParser::OptionParserMap::Map& ServiceParser::OptionParserMap::map() const {
+const KeywordMap<ServiceParser::OptionParser>& ServiceParser::GetParserMap() const {
     constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
     // clang-format off
-    static const Map option_parsers = {
+    static const KeywordMap<ServiceParser::OptionParser> parser_map = {
         {"capabilities",
                         {0,     kMax, &ServiceParser::ParseCapabilities}},
         {"class",       {1,     kMax, &ServiceParser::ParseClass}},
@@ -518,7 +512,7 @@
         {"writepid",    {1,     kMax, &ServiceParser::ParseWritepid}},
     };
     // clang-format on
-    return option_parsers;
+    return parser_map;
 }
 
 Result<void> ServiceParser::ParseSection(std::vector<std::string>&& args,
@@ -561,8 +555,7 @@
         return {};
     }
 
-    static const OptionParserMap parser_map;
-    auto parser = parser_map.FindFunction(args);
+    auto parser = GetParserMap().Find(args);
 
     if (!parser) return parser.error();
 
@@ -575,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 bca0739..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(
@@ -45,7 +44,7 @@
 
   private:
     using OptionParser = Result<void> (ServiceParser::*)(std::vector<std::string>&& args);
-    class OptionParserMap;
+    const KeywordMap<ServiceParser::OptionParser>& GetParserMap() const;
 
     Result<void> ParseCapabilities(std::vector<std::string>&& args);
     Result<void> ParseClass(std::vector<std::string>&& args);
diff --git a/init/service_test.cpp b/init/service_test.cpp
index 6a34acc..c9cc7bd 100644
--- a/init/service_test.cpp
+++ b/init/service_test.cpp
@@ -75,15 +75,15 @@
 TEST(service, make_temporary_oneshot_service_invalid_syntax) {
     std::vector<std::string> args;
     // Nothing.
-    ASSERT_EQ(nullptr, Service::MakeTemporaryOneshotService(args));
+    ASSERT_FALSE(Service::MakeTemporaryOneshotService(args));
 
     // No arguments to 'exec'.
     args.push_back("exec");
-    ASSERT_EQ(nullptr, Service::MakeTemporaryOneshotService(args));
+    ASSERT_FALSE(Service::MakeTemporaryOneshotService(args));
 
     // No command in "exec --".
     args.push_back("--");
-    ASSERT_EQ(nullptr, Service::MakeTemporaryOneshotService(args));
+    ASSERT_FALSE(Service::MakeTemporaryOneshotService(args));
 }
 
 TEST(service, make_temporary_oneshot_service_too_many_supplementary_gids) {
@@ -97,7 +97,7 @@
     }
     args.push_back("--");
     args.push_back("/system/bin/id");
-    ASSERT_EQ(nullptr, Service::MakeTemporaryOneshotService(args));
+    ASSERT_FALSE(Service::MakeTemporaryOneshotService(args));
 }
 
 static void Test_make_temporary_oneshot_service(bool dash_dash, bool seclabel, bool uid, bool gid,
@@ -122,8 +122,9 @@
     }
     args.push_back("/system/bin/toybox");
     args.push_back("id");
-    auto svc = Service::MakeTemporaryOneshotService(args);
-    ASSERT_NE(nullptr, svc);
+    auto service_ret = Service::MakeTemporaryOneshotService(args);
+    ASSERT_TRUE(service_ret);
+    auto svc = std::move(*service_ret);
 
     if (seclabel) {
         ASSERT_EQ("u:r:su:s0", svc->seclabel());
diff --git a/init/sigchld_handler.cpp b/init/sigchld_handler.cpp
index c9a09cd..984235d 100644
--- a/init/sigchld_handler.cpp
+++ b/init/sigchld_handler.cpp
@@ -73,6 +73,13 @@
                 auto exec_duration_ms =
                     std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();
                 wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f);
+            } else if (service->flags() & SVC_ONESHOT) {
+                auto exec_duration = boot_clock::now() - service->time_started();
+                auto exec_duration_ms =
+                        std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration)
+                                .count();
+                wait_string = StringPrintf(" oneshot service took %f seconds in background",
+                                           exec_duration_ms / 1000.0f);
             }
         } else {
             name = StringPrintf("Untracked pid %d", pid);
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index 2f9541b..00f91d8 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -27,6 +27,7 @@
 #include <selinux/android.h>
 
 #include "action.h"
+#include "builtins.h"
 #include "util.h"
 
 #if defined(__ANDROID__)
@@ -99,7 +100,7 @@
 
 class SubcontextProcess {
   public:
-    SubcontextProcess(const KeywordFunctionMap* function_map, std::string context, int init_fd)
+    SubcontextProcess(const BuiltinFunctionMap* function_map, std::string context, int init_fd)
         : function_map_(function_map), context_(std::move(context)), init_fd_(init_fd){};
     void MainLoop();
 
@@ -109,7 +110,7 @@
     void ExpandArgs(const SubcontextCommand::ExpandArgsCommand& expand_args_command,
                     SubcontextReply* reply) const;
 
-    const KeywordFunctionMap* function_map_;
+    const BuiltinFunctionMap* function_map_;
     const std::string context_;
     const int init_fd_;
 };
@@ -122,12 +123,12 @@
         args.emplace_back(string);
     }
 
-    auto map_result = function_map_->FindFunction(args);
+    auto map_result = function_map_->Find(args);
     Result<void> result;
     if (!map_result) {
         result = Error() << "Cannot find command: " << map_result.error();
     } else {
-        result = RunBuiltinFunction(map_result->second, args, context_);
+        result = RunBuiltinFunction(map_result->function, args, context_);
     }
 
     for (const auto& [name, value] : properties_to_set) {
@@ -150,15 +151,15 @@
 void SubcontextProcess::ExpandArgs(const SubcontextCommand::ExpandArgsCommand& expand_args_command,
                                    SubcontextReply* reply) const {
     for (const auto& arg : expand_args_command.args()) {
-        auto expanded_prop = std::string{};
-        if (!expand_props(arg, &expanded_prop)) {
+        auto expanded_arg = ExpandProps(arg);
+        if (!expanded_arg) {
             auto* failure = reply->mutable_failure();
-            failure->set_error_string("Failed to expand '" + arg + "'");
+            failure->set_error_string(expanded_arg.error().message());
             failure->set_error_errno(0);
             return;
         } else {
             auto* expand_args_reply = reply->mutable_expand_args_reply();
-            expand_args_reply->add_expanded_args(expanded_prop);
+            expand_args_reply->add_expanded_args(*expanded_arg);
         }
     }
 }
@@ -215,7 +216,7 @@
 
 }  // namespace
 
-int SubcontextMain(int argc, char** argv, const KeywordFunctionMap* function_map) {
+int SubcontextMain(int argc, char** argv, const BuiltinFunctionMap* function_map) {
     if (argc < 4) LOG(FATAL) << "Fewer than 4 args specified to subcontext (" << argc << ")";
 
     auto context = std::string(argv[2]);
diff --git a/init/subcontext.h b/init/subcontext.h
index 16bd870..591521f 100644
--- a/init/subcontext.h
+++ b/init/subcontext.h
@@ -60,7 +60,7 @@
     android::base::unique_fd socket_;
 };
 
-int SubcontextMain(int argc, char** argv, const KeywordFunctionMap* function_map);
+int SubcontextMain(int argc, char** argv, const BuiltinFunctionMap* function_map);
 std::vector<Subcontext>* InitializeSubcontexts();
 bool SubcontextChildReap(pid_t pid);
 void SubcontextTerminate();
diff --git a/init/subcontext_benchmark.cpp b/init/subcontext_benchmark.cpp
index fdbbc41..f6fee8a 100644
--- a/init/subcontext_benchmark.cpp
+++ b/init/subcontext_benchmark.cpp
@@ -19,8 +19,6 @@
 #include <benchmark/benchmark.h>
 #include <selinux/selinux.h>
 
-#include "test_function_map.h"
-
 namespace android {
 namespace init {
 
@@ -50,11 +48,11 @@
 
 BENCHMARK(BenchmarkSuccess);
 
-TestFunctionMap BuildTestFunctionMap() {
-    TestFunctionMap test_function_map;
-    test_function_map.Add("return_success", 0, 0, true,
-                          [](const BuiltinArguments& args) { return Result<void>{}; });
-
+BuiltinFunctionMap BuildTestFunctionMap() {
+    auto function = [](const BuiltinArguments& args) { return Result<void>{}; };
+    BuiltinFunctionMap test_function_map = {
+            {"return_success", {0, 0, {true, function}}},
+    };
     return test_function_map;
 }
 
diff --git a/init/subcontext_test.cpp b/init/subcontext_test.cpp
index 55912d6..ae89c38 100644
--- a/init/subcontext_test.cpp
+++ b/init/subcontext_test.cpp
@@ -26,7 +26,6 @@
 #include <selinux/selinux.h>
 
 #include "builtin_arguments.h"
-#include "test_function_map.h"
 
 using namespace std::literals;
 
@@ -167,50 +166,58 @@
         };
         auto result = subcontext.ExpandArgs(args);
         ASSERT_FALSE(result);
-        EXPECT_EQ("Failed to expand '" + args[1] + "'", result.error().message());
+        EXPECT_EQ("unexpected end of string in '" + args[1] + "', looking for }",
+                  result.error().message());
     });
 }
 
-TestFunctionMap BuildTestFunctionMap() {
-    TestFunctionMap test_function_map;
+BuiltinFunctionMap BuildTestFunctionMap() {
     // For CheckDifferentPid
-    test_function_map.Add("return_pids_as_error", 0, 0, true,
-                          [](const BuiltinArguments& args) -> Result<void> {
-                              return Error() << getpid() << " " << getppid();
-                          });
+    auto do_return_pids_as_error = [](const BuiltinArguments& args) -> Result<void> {
+        return Error() << getpid() << " " << getppid();
+    };
 
     // For SetProp
-    test_function_map.Add("setprop", 2, 2, true, [](const BuiltinArguments& args) {
+    auto do_setprop = [](const BuiltinArguments& args) {
         android::base::SetProperty(args[1], args[2]);
         return Result<void>{};
-    });
+    };
 
     // For MultipleCommands
     // Using a shared_ptr to extend lifetime of words to both lambdas
     auto words = std::make_shared<std::vector<std::string>>();
-    test_function_map.Add("add_word", 1, 1, true, [words](const BuiltinArguments& args) {
+    auto do_add_word = [words](const BuiltinArguments& args) {
         words->emplace_back(args[1]);
         return Result<void>{};
-    });
-    test_function_map.Add("return_words_as_error", 0, 0, true,
-                          [words](const BuiltinArguments& args) -> Result<void> {
-                              return Error() << Join(*words, " ");
-                          });
+    };
+    auto do_return_words_as_error = [words](const BuiltinArguments& args) -> Result<void> {
+        return Error() << Join(*words, " ");
+    };
 
     // For RecoverAfterAbort
-    test_function_map.Add("cause_log_fatal", 0, 0, true,
-                          [](const BuiltinArguments& args) -> Result<void> {
-                              return Error() << std::string(4097, 'f');
-                          });
-    test_function_map.Add(
-            "generate_sane_error", 0, 0, true,
-            [](const BuiltinArguments& args) -> Result<void> { return Error() << "Sane error!"; });
+    auto do_cause_log_fatal = [](const BuiltinArguments& args) -> Result<void> {
+        return Error() << std::string(4097, 'f');
+    };
+    auto do_generate_sane_error = [](const BuiltinArguments& args) -> Result<void> {
+        return Error() << "Sane error!";
+    };
 
     // For ContextString
-    test_function_map.Add(
-            "return_context_as_error", 0, 0, true,
-            [](const BuiltinArguments& args) -> Result<void> { return Error() << args.context; });
+    auto do_return_context_as_error = [](const BuiltinArguments& args) -> Result<void> {
+        return Error() << args.context;
+    };
 
+    // clang-format off
+    BuiltinFunctionMap test_function_map = {
+        {"return_pids_as_error",        {0,     0,      {true,  do_return_pids_as_error}}},
+        {"setprop",                     {2,     2,      {true,  do_setprop}}},
+        {"add_word",                    {1,     1,      {true,  do_add_word}}},
+        {"return_words_as_error",       {0,     0,      {true,  do_return_words_as_error}}},
+        {"cause_log_fatal",             {0,     0,      {true,  do_cause_log_fatal}}},
+        {"generate_sane_error",         {0,     0,      {true,  do_generate_sane_error}}},
+        {"return_context_as_error",     {0,     0,      {true,  do_return_context_as_error}}},
+    };
+    // clang-format on
     return test_function_map;
 }
 
diff --git a/init/test_function_map.h b/init/test_function_map.h
deleted file mode 100644
index 466836c..0000000
--- a/init/test_function_map.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-#include "builtin_arguments.h"
-#include "builtins.h"
-#include "keyword_map.h"
-
-namespace android {
-namespace init {
-
-class TestFunctionMap : public KeywordFunctionMap {
-  public:
-    // Helper for argument-less functions
-    using BuiltinFunctionNoArgs = std::function<void(void)>;
-    void Add(const std::string& name, BuiltinFunctionNoArgs function) {
-        Add(name, 0, 0, false, [f = std::move(function)](const BuiltinArguments&) {
-            f();
-            return Result<void>{};
-        });
-    }
-
-    void Add(const std::string& name, std::size_t min_parameters, std::size_t max_parameters,
-             bool run_in_subcontext, BuiltinFunction function) {
-        builtin_functions_[name] = make_tuple(min_parameters, max_parameters,
-                                              make_pair(run_in_subcontext, std::move(function)));
-    }
-
-  private:
-    Map builtin_functions_ = {};
-
-    const Map& map() const override { return builtin_functions_; }
-};
-
-}  // namespace init
-}  // namespace android
diff --git a/init/test_utils/Android.bp b/init/test_utils/Android.bp
new file mode 100644
index 0000000..1cb05b6
--- /dev/null
+++ b/init/test_utils/Android.bp
@@ -0,0 +1,27 @@
+cc_library_static {
+    name: "libinit_test_utils",
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Wno-unused-parameter",
+        "-Werror",
+    ],
+    srcs: [
+        "service_utils.cpp",
+    ],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libjsoncpp",
+        "libprotobuf-cpp-lite",
+        "libhidl-gen-utils",
+    ],
+    whole_static_libs: [
+        "libinit",
+        "libpropertyinfoparser",
+    ],
+    static_libs: [
+        "libbase",
+    ],
+    export_include_dirs: ["include"], // for tests
+}
diff --git a/init/test_utils/include/init-test-utils/service_utils.h b/init/test_utils/include/init-test-utils/service_utils.h
new file mode 100644
index 0000000..3ec61d4
--- /dev/null
+++ b/init/test_utils/include/init-test-utils/service_utils.h
@@ -0,0 +1,32 @@
+//
+// 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 <android-base/result.h>
+#include <hidl-util/FqInstance.h>
+
+namespace android {
+namespace init {
+
+using ServiceInterfacesMap = std::map<std::string, std::set<android::FqInstance>>;
+android::base::Result<ServiceInterfacesMap> GetOnDeviceServiceInterfacesMap();
+
+}  // namespace init
+}  // namespace android
diff --git a/init/test_utils/service_utils.cpp b/init/test_utils/service_utils.cpp
new file mode 100644
index 0000000..bc00702
--- /dev/null
+++ b/init/test_utils/service_utils.cpp
@@ -0,0 +1,63 @@
+//
+// 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 <string>
+
+#include <android-base/logging.h>
+
+#include "../parser.h"
+#include "../service.h"
+#include "../service_list.h"
+#include "../service_parser.h"
+#include "include/init-test-utils/service_utils.h"
+
+namespace android {
+namespace init {
+
+android::base::Result<ServiceInterfacesMap> GetOnDeviceServiceInterfacesMap() {
+    ServiceList& service_list = ServiceList::GetInstance();
+    Parser parser;
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+    for (const auto& location : {
+                 "/init.rc",
+                 "/system/etc/init",
+                 "/system_ext/etc/init",
+                 "/product/etc/init",
+                 "/odm/etc/init",
+                 "/vendor/etc/init",
+         }) {
+        parser.ParseConfig(location);
+    }
+
+    ServiceInterfacesMap result;
+    for (const auto& service : service_list.services()) {
+        // Create an entry for all services, including services that may not
+        // have any declared interfaces.
+        result[service->name()] = std::set<android::FqInstance>();
+        for (const auto& intf : service->interfaces()) {
+            android::FqInstance fqInstance;
+            if (!fqInstance.setTo(intf)) {
+                return android::base::Error() << "Unable to parse interface: '" << intf << "'";
+            }
+            result[service->name()].insert(fqInstance);
+        }
+    }
+    return result;
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp
index 25bab93..8ee0cce 100644
--- a/init/ueventd_parser.cpp
+++ b/init/ueventd_parser.cpp
@@ -176,21 +176,14 @@
 
 Result<void> SubsystemParser::ParseLineSection(std::vector<std::string>&& args, int line) {
     using OptionParser = Result<void> (SubsystemParser::*)(std::vector<std::string> && args);
+    // clang-format off
+    static const KeywordMap<OptionParser> parser_map = {
+        {"devname",     {1,     1,      &SubsystemParser::ParseDevName}},
+        {"dirname",     {1,     1,      &SubsystemParser::ParseDirName}},
+    };
+    // clang-format on
 
-    static class OptionParserMap : public KeywordMap<OptionParser> {
-      private:
-        const Map& map() const override {
-            // clang-format off
-            static const Map option_parsers = {
-                {"devname",     {1,     1,      &SubsystemParser::ParseDevName}},
-                {"dirname",     {1,     1,      &SubsystemParser::ParseDirName}},
-            };
-            // clang-format on
-            return option_parsers;
-        }
-    } parser_map;
-
-    auto parser = parser_map.FindFunction(args);
+    auto parser = parser_map.Find(args);
 
     if (!parser) return Error() << parser.error();
 
diff --git a/init/util.cpp b/init/util.cpp
index 8bfb755..0532375 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -41,13 +41,18 @@
 #include <selinux/android.h>
 
 #if defined(__ANDROID__)
+#include <android/api-level.h>
+#include <sys/system_properties.h>
+
 #include "reboot_utils.h"
 #include "selabel.h"
+#include "selinux.h"
 #else
 #include "host_init_stubs.h"
 #endif
 
 using android::base::boot_clock;
+using android::base::StartsWith;
 using namespace std::literals::string_literals;
 
 namespace android {
@@ -267,12 +272,10 @@
     return S_ISDIR(info.st_mode);
 }
 
-bool expand_props(const std::string& src, std::string* dst) {
+Result<std::string> ExpandProps(const std::string& src) {
     const char* src_ptr = src.c_str();
 
-    if (!dst) {
-        return false;
-    }
+    std::string dst;
 
     /* - variables can either be $x.y or ${x.y}, in case they are only part
      *   of the string.
@@ -286,19 +289,19 @@
 
         c = strchr(src_ptr, '$');
         if (!c) {
-            dst->append(src_ptr);
-            return true;
+            dst.append(src_ptr);
+            return dst;
         }
 
-        dst->append(src_ptr, c);
+        dst.append(src_ptr, c);
         c++;
 
         if (*c == '$') {
-            dst->push_back(*(c++));
+            dst.push_back(*(c++));
             src_ptr = c;
             continue;
         } else if (*c == '\0') {
-            return true;
+            return dst;
         }
 
         std::string prop_name;
@@ -308,8 +311,7 @@
             const char* end = strchr(c, '}');
             if (!end) {
                 // failed to find closing brace, abort.
-                LOG(ERROR) << "unexpected end of string in '" << src << "', looking for }";
-                return false;
+                return Error() << "unexpected end of string in '" << src << "', looking for }";
             }
             prop_name = std::string(c, end);
             c = end + 1;
@@ -320,29 +322,34 @@
             }
         } else {
             prop_name = c;
-            LOG(ERROR) << "using deprecated syntax for specifying property '" << c << "', use ${name} instead";
+            if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__) {
+                return Error() << "using deprecated syntax for specifying property '" << c
+                               << "', use ${name} instead";
+            } else {
+                LOG(ERROR) << "using deprecated syntax for specifying property '" << c
+                           << "', use ${name} instead";
+            }
             c += prop_name.size();
         }
 
         if (prop_name.empty()) {
-            LOG(ERROR) << "invalid zero-length property name in '" << src << "'";
-            return false;
+            return Error() << "invalid zero-length property name in '" << src << "'";
         }
 
         std::string prop_val = android::base::GetProperty(prop_name, "");
         if (prop_val.empty()) {
             if (def_val.empty()) {
-                LOG(ERROR) << "property '" << prop_name << "' doesn't exist while expanding '" << src << "'";
-                return false;
+                return Error() << "property '" << prop_name << "' doesn't exist while expanding '"
+                               << src << "'";
             }
             prop_val = def_val;
         }
 
-        dst->append(prop_val);
+        dst.append(prop_val);
         src_ptr = c;
     }
 
-    return true;
+    return dst;
 }
 
 static std::string init_android_dt_dir() {
@@ -414,6 +421,58 @@
     return true;
 }
 
+Result<void> IsLegalPropertyValue(const std::string& name, const std::string& value) {
+    if (value.size() >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
+        return Error() << "Property value too long";
+    }
+
+    if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) {
+        return Error() << "Value is not a UTF8 encoded string";
+    }
+
+    return {};
+}
+
+Result<std::pair<int, std::vector<std::string>>> ParseRestorecon(
+        const std::vector<std::string>& args) {
+    struct flag_type {
+        const char* name;
+        int value;
+    };
+    static const flag_type flags[] = {
+            {"--recursive", SELINUX_ANDROID_RESTORECON_RECURSE},
+            {"--skip-ce", SELINUX_ANDROID_RESTORECON_SKIPCE},
+            {"--cross-filesystems", SELINUX_ANDROID_RESTORECON_CROSS_FILESYSTEMS},
+            {0, 0}};
+
+    int flag = 0;
+    std::vector<std::string> paths;
+
+    bool in_flags = true;
+    for (size_t i = 1; i < args.size(); ++i) {
+        if (android::base::StartsWith(args[i], "--")) {
+            if (!in_flags) {
+                return Error() << "flags must precede paths";
+            }
+            bool found = false;
+            for (size_t j = 0; flags[j].name; ++j) {
+                if (args[i] == flags[j].name) {
+                    flag |= flags[j].value;
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return Error() << "bad flag " << args[i];
+            }
+        } else {
+            in_flags = false;
+            paths.emplace_back(args[i]);
+        }
+    }
+    return std::pair(flag, paths);
+}
+
 static void InitAborter(const char* abort_message) {
     // When init forks, it continues to use this aborter for LOG(FATAL), but we want children to
     // simply abort instead of trying to reboot the system.
diff --git a/init/util.h b/init/util.h
index 6a12fb6..4cccefe 100644
--- a/init/util.h
+++ b/init/util.h
@@ -14,24 +14,20 @@
  * limitations under the License.
  */
 
-#ifndef _INIT_UTIL_H_
-#define _INIT_UTIL_H_
+#pragma once
 
 #include <sys/stat.h>
 #include <sys/types.h>
 
 #include <chrono>
 #include <functional>
-#include <ostream>
 #include <string>
 
 #include <android-base/chrono_utils.h>
-#include <selinux/label.h>
 
 #include "result.h"
 
 using android::base::boot_clock;
-using namespace std::chrono_literals;
 
 namespace android {
 namespace init {
@@ -52,7 +48,7 @@
                            const std::function<void(const std::string&, const std::string&, bool)>&);
 bool make_dir(const std::string& path, mode_t mode);
 bool is_dir(const char* pathname);
-bool expand_props(const std::string& src, std::string* dst);
+Result<std::string> ExpandProps(const std::string& src);
 
 // Returns the platform's Android DT directory as specified in the kernel cmdline.
 // If the platform does not configure a custom DT path, returns the standard one (based in procfs).
@@ -62,11 +58,13 @@
 bool is_android_dt_value_expected(const std::string& sub_path, const std::string& expected_content);
 
 bool IsLegalPropertyName(const std::string& name);
+Result<void> IsLegalPropertyValue(const std::string& name, const std::string& value);
+
+Result<std::pair<int, std::vector<std::string>>> ParseRestorecon(
+        const std::vector<std::string>& args);
 
 void SetStdioToDevNull(char** argv);
 void InitKernelLogging(char** argv);
 bool IsRecoveryMode();
 }  // namespace init
 }  // namespace android
-
-#endif
diff --git a/libion/OWNERS b/libion/OWNERS
new file mode 100644
index 0000000..143ad2d
--- /dev/null
+++ b/libion/OWNERS
@@ -0,0 +1,2 @@
+sspatil@google.com
+hridya@google.com
diff --git a/libion/ion.c b/libion/ion.c
index b8de5a4..1ecfc78 100644
--- a/libion/ion.c
+++ b/libion/ion.c
@@ -152,6 +152,8 @@
     ion_user_handle_t handle;
     int ret;
 
+    if (!handle_fd) return -EINVAL;
+
     if (!ion_is_legacy(fd)) {
         struct ion_new_allocation_data data = {
             .len = len,
@@ -201,6 +203,7 @@
     int ret;
     struct ion_heap_query query;
 
+    if (!cnt) return -EINVAL;
     memset(&query, 0, sizeof(query));
 
     ret = ion_ioctl(fd, ION_IOC_HEAP_QUERY, &query);
diff --git a/libion/tests/Android.bp b/libion/tests/Android.bp
index b3fcb3b..d3b4688 100644
--- a/libion/tests/Android.bp
+++ b/libion/tests/Android.bp
@@ -18,18 +18,15 @@
     name: "ion-unit-tests",
     cflags: [
         "-g",
-        "-Wall",
-        "-Werror",
         "-Wno-missing-field-initializers",
     ],
     shared_libs: ["libion"],
     srcs: [
-        "ion_test_fixture.cpp",
         "allocate_test.cpp",
-        "formerly_valid_handle_test.cpp",
-        "invalid_values_test.cpp",
-        "map_test.cpp",
-        "device_test.cpp",
         "exit_test.cpp",
+    	"heap_query.cpp",
+        "invalid_values_test.cpp",
+        "ion_test_fixture.cpp",
+        "map_test.cpp",
     ],
 }
diff --git a/libion/tests/allocate_test.cpp b/libion/tests/allocate_test.cpp
index 3c4524e..5ed01bb 100644
--- a/libion/tests/allocate_test.cpp
+++ b/libion/tests/allocate_test.cpp
@@ -14,95 +14,106 @@
  * limitations under the License.
  */
 
-#include <memory>
 #include <sys/mman.h>
+#include <memory>
 
 #include <gtest/gtest.h>
 
 #include <ion/ion.h>
 #include "ion_test_fixture.h"
 
-class Allocate : public IonAllHeapsTest {
-};
+class Allocate : public IonTest {};
 
-TEST_F(Allocate, Allocate)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Allocate, Allocate) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
-            ion_user_handle_t handle = 0;
-            ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, 0, &handle));
-            ASSERT_TRUE(handle != 0);
-            ASSERT_EQ(0, ion_free(m_ionFd, handle));
+            int fd;
+            ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &fd));
+            ASSERT_TRUE(fd != 0);
+            ASSERT_EQ(close(fd), 0);  // free the buffer
         }
     }
 }
 
-TEST_F(Allocate, AllocateCached)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Allocate, AllocateCached) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
-            ion_user_handle_t handle = 0;
-            ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED, &handle));
-            ASSERT_TRUE(handle != 0);
-            ASSERT_EQ(0, ion_free(m_ionFd, handle));
+            int fd;
+            ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), ION_FLAG_CACHED, &fd));
+            ASSERT_TRUE(fd != 0);
+            ASSERT_EQ(close(fd), 0);  // free the buffer
         }
     }
 }
 
-TEST_F(Allocate, AllocateCachedNeedsSync)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Allocate, AllocateCachedNeedsSync) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
-            ion_user_handle_t handle = 0;
-            ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED_NEEDS_SYNC, &handle));
-            ASSERT_TRUE(handle != 0);
-            ASSERT_EQ(0, ion_free(m_ionFd, handle));
+            int fd;
+            ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id),
+                                      ION_FLAG_CACHED_NEEDS_SYNC, &fd));
+            ASSERT_TRUE(fd != 0);
+            ASSERT_EQ(close(fd), 0);  // free the buffer
         }
     }
 }
 
-TEST_F(Allocate, RepeatedAllocate)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Allocate, RepeatedAllocate) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
-            ion_user_handle_t handle = 0;
+            int fd;
 
             for (unsigned int i = 0; i < 1024; i++) {
                 SCOPED_TRACE(::testing::Message() << "iteration " << i);
-                ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, 0, &handle));
-                ASSERT_TRUE(handle != 0);
-                ASSERT_EQ(0, ion_free(m_ionFd, handle));
+                ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &fd));
+                ASSERT_TRUE(fd != 0);
+                ASSERT_EQ(close(fd), 0);  // free the buffer
             }
         }
     }
 }
 
-TEST_F(Allocate, Zeroed)
-{
+TEST_F(Allocate, Large) {
+    for (const auto& heap : ion_heaps) {
+        SCOPED_TRACE(::testing::Message()
+                     << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
+        int fd;
+        ASSERT_EQ(-ENOMEM,
+                  ion_alloc_fd(ionfd, 3UL * 1024 * 1024 * 1024, 0, (1 << heap.heap_id), 0, &fd));
+    }
+}
+
+// Make sure all heaps always return zeroed pages
+TEST_F(Allocate, Zeroed) {
     auto zeroes_ptr = std::make_unique<char[]>(4096);
 
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+    for (const auto& heap : ion_heaps) {
+        SCOPED_TRACE(::testing::Message()
+                     << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
         int fds[16];
         for (unsigned int i = 0; i < 16; i++) {
             int map_fd = -1;
 
-            ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, 0, &map_fd));
+            ASSERT_EQ(0, ion_alloc_fd(ionfd, 4096, 0, (1 << heap.heap_id), 0, &map_fd));
             ASSERT_GE(map_fd, 0);
 
-            void *ptr = NULL;
+            void* ptr = NULL;
             ptr = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED, map_fd, 0);
             ASSERT_TRUE(ptr != NULL);
 
@@ -116,13 +127,13 @@
             ASSERT_EQ(0, close(fds[i]));
         }
 
-        int newIonFd = ion_open();
+        int new_ionfd = ion_open();
         int map_fd = -1;
 
-        ASSERT_EQ(0, ion_alloc_fd(newIonFd, 4096, 0, heapMask, 0, &map_fd));
+        ASSERT_EQ(0, ion_alloc_fd(new_ionfd, 4096, 0, (1 << heap.heap_id), 0, &map_fd));
         ASSERT_GE(map_fd, 0);
 
-        void *ptr = NULL;
+        void* ptr = NULL;
         ptr = mmap(NULL, 4096, PROT_READ, MAP_SHARED, map_fd, 0);
         ASSERT_TRUE(ptr != NULL);
 
@@ -130,14 +141,6 @@
 
         ASSERT_EQ(0, munmap(ptr, 4096));
         ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Allocate, Large)
-{
-    for (unsigned int heapMask : m_allHeaps) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        ion_user_handle_t handle = 0;
-        ASSERT_EQ(-ENOMEM, ion_alloc(m_ionFd, 3UL*1024*1024*1024, 0, heapMask, 0, &handle));
+        ASSERT_EQ(0, ion_close(new_ionfd));
     }
 }
diff --git a/libion/tests/device_test.cpp b/libion/tests/device_test.cpp
deleted file mode 100644
index eb3f7b6..0000000
--- a/libion/tests/device_test.cpp
+++ /dev/null
@@ -1,546 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <fcntl.h>
-#include <memory>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <linux/ion_test.h>
-
-#include <gtest/gtest.h>
-
-#include <ion/ion.h>
-
-#include "ion_test_fixture.h"
-
-#define ALIGN(x,y) (((x) + ((y) - 1)) & ~((y) - 1))
-
-class Device : public IonAllHeapsTest {
- public:
-    virtual void SetUp();
-    virtual void TearDown();
-    int m_deviceFd;
-    void readDMA(int fd, void *buf, size_t size);
-    void writeDMA(int fd, void *buf, size_t size);
-    void readKernel(int fd, void *buf, size_t size);
-    void writeKernel(int fd, void *buf, size_t size);
-    void blowCache();
-    void dirtyCache(void *ptr, size_t size);
-};
-
-void Device::SetUp()
-{
-    IonAllHeapsTest::SetUp();
-    m_deviceFd = open("/dev/ion-test", O_RDONLY);
-    ASSERT_GE(m_deviceFd, 0);
-}
-
-void Device::TearDown()
-{
-    ASSERT_EQ(0, close(m_deviceFd));
-    IonAllHeapsTest::TearDown();
-}
-
-void Device::readDMA(int fd, void *buf, size_t size)
-{
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, fd));
-    struct ion_test_rw_data ion_test_rw_data = {
-            .ptr = (uint64_t)buf,
-            .offset = 0,
-            .size = size,
-            .write = 0,
-    };
-
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_DMA_MAPPING, &ion_test_rw_data));
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, -1));
-}
-
-void Device::writeDMA(int fd, void *buf, size_t size)
-{
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, fd));
-    struct ion_test_rw_data ion_test_rw_data = {
-            .ptr = (uint64_t)buf,
-            .offset = 0,
-            .size = size,
-            .write = 1,
-    };
-
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_DMA_MAPPING, &ion_test_rw_data));
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, -1));
-}
-
-void Device::readKernel(int fd, void *buf, size_t size)
-{
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, fd));
-    struct ion_test_rw_data ion_test_rw_data = {
-            .ptr = (uint64_t)buf,
-            .offset = 0,
-            .size = size,
-            .write = 0,
-    };
-
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_KERNEL_MAPPING, &ion_test_rw_data));
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, -1));
-}
-
-void Device::writeKernel(int fd, void *buf, size_t size)
-{
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, fd));
-    struct ion_test_rw_data ion_test_rw_data = {
-            .ptr = (uint64_t)buf,
-            .offset = 0,
-            .size = size,
-            .write = 1,
-    };
-
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_KERNEL_MAPPING, &ion_test_rw_data));
-    ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, -1));
-}
-
-void Device::blowCache()
-{
-    const size_t bigger_than_cache = 8*1024*1024;
-    void *buf1 = malloc(bigger_than_cache);
-    void *buf2 = malloc(bigger_than_cache);
-    memset(buf1, 0xaa, bigger_than_cache);
-    memcpy(buf2, buf1, bigger_than_cache);
-    free(buf1);
-    free(buf2);
-}
-
-void Device::dirtyCache(void *ptr, size_t size)
-{
-    /* try to dirty cache lines */
-    for (size_t i = size-1; i > 0; i--) {
-        ((volatile char *)ptr)[i];
-        ((char *)ptr)[i] = i;
-    }
-}
-
-TEST_F(Device, KernelReadCached)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = ION_FLAG_CACHED;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        for (int i = 0; i < 4096; i++)
-            ((char *)ptr)[i] = i;
-
-        ((char*)buf)[4096] = 0x12;
-        readKernel(map_fd, buf, 4096);
-        ASSERT_EQ(((char*)buf)[4096], 0x12);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)buf)[i]);
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, KernelWriteCached)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (int i = 0; i < 4096; i++)
-        ((char *)buf)[i] = i;
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = ION_FLAG_CACHED;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        dirtyCache(ptr, 4096);
-
-        writeKernel(map_fd, buf, 4096);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, DMAReadCached)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = ION_FLAG_CACHED;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        for (int i = 0; i < 4096; i++)
-            ((char *)ptr)[i] = i;
-
-        readDMA(map_fd, buf, 4096);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)buf)[i]);
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, DMAWriteCached)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (int i = 0; i < 4096; i++)
-        ((char *)buf)[i] = i;
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = ION_FLAG_CACHED;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        dirtyCache(ptr, 4096);
-
-        writeDMA(map_fd, buf, 4096);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, KernelReadCachedNeedsSync)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        for (int i = 0; i < 4096; i++)
-            ((char *)ptr)[i] = i;
-
-        ((char*)buf)[4096] = 0x12;
-        readKernel(map_fd, buf, 4096);
-        ASSERT_EQ(((char*)buf)[4096], 0x12);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)buf)[i]);
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, KernelWriteCachedNeedsSync)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (int i = 0; i < 4096; i++)
-        ((char *)buf)[i] = i;
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        dirtyCache(ptr, 4096);
-
-        writeKernel(map_fd, buf, 4096);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, DMAReadCachedNeedsSync)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        for (int i = 0; i < 4096; i++)
-            ((char *)ptr)[i] = i;
-
-        ion_sync_fd(m_ionFd, map_fd);
-
-        readDMA(map_fd, buf, 4096);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)buf)[i]);
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, DMAWriteCachedNeedsSync)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (int i = 0; i < 4096; i++)
-        ((char *)buf)[i] = i;
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        dirtyCache(ptr, 4096);
-
-        writeDMA(map_fd, buf, 4096);
-
-        ion_sync_fd(m_ionFd, map_fd);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-TEST_F(Device, KernelRead)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = 0;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        for (int i = 0; i < 4096; i++)
-            ((char *)ptr)[i] = i;
-
-        ((char*)buf)[4096] = 0x12;
-        readKernel(map_fd, buf, 4096);
-        ASSERT_EQ(((char*)buf)[4096], 0x12);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)buf)[i]);
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, KernelWrite)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (int i = 0; i < 4096; i++)
-        ((char *)buf)[i] = i;
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = 0;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        dirtyCache(ptr, 4096);
-
-        writeKernel(map_fd, buf, 4096);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, DMARead)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = 0;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        for (int i = 0; i < 4096; i++)
-            ((char *)ptr)[i] = i;
-
-        readDMA(map_fd, buf, 4096);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)buf)[i]);
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, DMAWrite)
-{
-    auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
-    void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
-    for (int i = 0; i < 4096; i++)
-        ((char *)buf)[i] = i;
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = 0;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        dirtyCache(ptr, 4096);
-
-        writeDMA(map_fd, buf, 4096);
-
-        for (int i = 0; i < 4096; i++)
-            ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
-
-TEST_F(Device, IsCached)
-{
-    auto buf_ptr = std::make_unique<char[]>(4096);
-    void *buf = buf_ptr.get();
-
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        int map_fd = -1;
-        unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
-
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
-        ASSERT_GE(map_fd, 0);
-
-        void *ptr;
-        ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-        ASSERT_TRUE(ptr != NULL);
-
-        dirtyCache(ptr, 4096);
-
-        readDMA(map_fd, buf, 4096);
-
-        bool same = true;
-        for (int i = 4096-16; i >= 0; i -= 16)
-            if (((char *)buf)[i] != i)
-                same = false;
-        ASSERT_FALSE(same);
-
-        ASSERT_EQ(0, munmap(ptr, 4096));
-        ASSERT_EQ(0, close(map_fd));
-    }
-}
diff --git a/libion/tests/exit_test.cpp b/libion/tests/exit_test.cpp
index cdd3e27..f312389 100644
--- a/libion/tests/exit_test.cpp
+++ b/libion/tests/exit_test.cpp
@@ -22,206 +22,206 @@
 
 #include "ion_test_fixture.h"
 
-class Exit : public IonAllHeapsTest {
-};
+class Exit : public IonTest {};
 
-TEST_F(Exit, WithAlloc)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithAllocFd) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
-            EXPECT_EXIT({
-                ion_user_handle_t handle = 0;
+            EXPECT_EXIT(
+                    {
+                        int handle_fd = -1;
 
-                ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, 0, &handle));
-                ASSERT_TRUE(handle != 0);
-                exit(0);
-            }, ::testing::ExitedWithCode(0), "");
+                        ASSERT_EQ(0,
+                                  ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &handle_fd));
+                        ASSERT_NE(-1, handle_fd);
+                        exit(0);
+                    },
+                    ::testing::ExitedWithCode(0), "");
         }
     }
 }
 
-TEST_F(Exit, WithAllocFd)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
-        for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-            SCOPED_TRACE(::testing::Message() << "size " << size);
-            EXPECT_EXIT({
-                int handle_fd = -1;
-
-                ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, 0, &handle_fd));
-                ASSERT_NE(-1, handle_fd);
-                exit(0);
-            }, ::testing::ExitedWithCode(0), "");
-        }
-    }
-}
-
-TEST_F(Exit, WithRepeatedAllocFd)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithRepeatedAllocFd) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
             for (unsigned int i = 0; i < 1024; i++) {
-                SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+                SCOPED_TRACE(::testing::Message()
+                             << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
                 SCOPED_TRACE(::testing::Message() << "size " << size);
-                ASSERT_EXIT({
-                    int handle_fd = -1;
+                ASSERT_EXIT(
+                        {
+                            int handle_fd = -1;
 
-                    ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, 0, &handle_fd));
-                    ASSERT_NE(-1, handle_fd);
-                    exit(0);
-                }, ::testing::ExitedWithCode(0), "")
-                        << "failed on heap " << heapMask
-                        << " and size " << size
-                        << " on iteration " << i;
+                            ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0,
+                                                      &handle_fd));
+                            ASSERT_NE(-1, handle_fd);
+                            exit(0);
+                        },
+                        ::testing::ExitedWithCode(0), "")
+                        << "failed on heap " << heap.name << ":" << heap.type << ":" << heap.heap_id
+                        << " and size " << size << " on iteration " << i;
             }
         }
     }
 }
 
-
-TEST_F(Exit, WithMapping)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithMapping) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
-            EXPECT_EXIT({
-                int map_fd = -1;
+            EXPECT_EXIT(
+                    {
+                        int map_fd = -1;
 
-                ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, 0, &map_fd));
-                ASSERT_GE(map_fd, 0);
+                        ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &map_fd));
+                        ASSERT_GE(map_fd, 0);
 
-                void *ptr;
-                ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-                ASSERT_TRUE(ptr != NULL);
-                exit(0);
-            }, ::testing::ExitedWithCode(0), "");
-        }
-    }
-
-}
-
-TEST_F(Exit, WithPartialMapping)
-{
-    static const size_t allocationSizes[] = {64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
-        for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-            SCOPED_TRACE(::testing::Message() << "size " << size);
-            EXPECT_EXIT({
-                int map_fd = -1;
-
-                ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, 0, &map_fd));
-                ASSERT_GE(map_fd, 0);
-
-                void *ptr;
-                ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-                ASSERT_TRUE(ptr != NULL);
-
-                ASSERT_EQ(0, munmap(ptr, size / 2));
-                exit(0);
-            }, ::testing::ExitedWithCode(0), "");
+                        void* ptr;
+                        ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+                        ASSERT_TRUE(ptr != NULL);
+                        exit(0);
+                    },
+                    ::testing::ExitedWithCode(0), "");
         }
     }
 }
 
-TEST_F(Exit, WithMappingCached)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithPartialMapping) {
+    static const size_t allocationSizes[] = {64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
-            EXPECT_EXIT({
-                int map_fd = -1;
+            EXPECT_EXIT(
+                    {
+                        int map_fd = -1;
 
-                ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED, &map_fd));
-                ASSERT_GE(map_fd, 0);
+                        ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &map_fd));
+                        ASSERT_GE(map_fd, 0);
 
-                void *ptr;
-                ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-                ASSERT_TRUE(ptr != NULL);
-                exit(0);
-            }, ::testing::ExitedWithCode(0), "");
-        }
-    }
+                        void* ptr;
+                        ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+                        ASSERT_TRUE(ptr != NULL);
 
-}
-
-TEST_F(Exit, WithPartialMappingCached)
-{
-    static const size_t allocationSizes[] = {64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
-        for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-            SCOPED_TRACE(::testing::Message() << "size " << size);
-            EXPECT_EXIT({
-                int map_fd = -1;
-
-                ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED, &map_fd));
-                ASSERT_GE(map_fd, 0);
-
-                void *ptr;
-                ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-                ASSERT_TRUE(ptr != NULL);
-
-                ASSERT_EQ(0, munmap(ptr, size / 2));
-                exit(0);
-            }, ::testing::ExitedWithCode(0), "");
+                        ASSERT_EQ(0, munmap(ptr, size / 2));
+                        exit(0);
+                    },
+                    ::testing::ExitedWithCode(0), "");
         }
     }
 }
 
-TEST_F(Exit, WithMappingNeedsSync)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithMappingCached) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
-            EXPECT_EXIT({
-                int map_fd = -1;
+            EXPECT_EXIT(
+                    {
+                        int map_fd = -1;
 
-                ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC, &map_fd));
-                ASSERT_GE(map_fd, 0);
+                        ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id),
+                                                  ION_FLAG_CACHED, &map_fd));
+                        ASSERT_GE(map_fd, 0);
 
-                void *ptr;
-                ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-                ASSERT_TRUE(ptr != NULL);
-                exit(0);
-            }, ::testing::ExitedWithCode(0), "");
+                        void* ptr;
+                        ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+                        ASSERT_TRUE(ptr != NULL);
+                        exit(0);
+                    },
+                    ::testing::ExitedWithCode(0), "");
         }
     }
-
 }
 
-TEST_F(Exit, WithPartialMappingNeedsSync)
-{
-    static const size_t allocationSizes[] = {64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithPartialMappingCached) {
+    static const size_t allocationSizes[] = {64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
-            EXPECT_EXIT({
-                int map_fd = -1;
+            EXPECT_EXIT(
+                    {
+                        int map_fd = -1;
 
-                ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC, &map_fd));
-                ASSERT_GE(map_fd, 0);
+                        ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id),
+                                                  ION_FLAG_CACHED, &map_fd));
+                        ASSERT_GE(map_fd, 0);
 
-                void *ptr;
-                ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
-                ASSERT_TRUE(ptr != NULL);
+                        void* ptr;
+                        ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+                        ASSERT_TRUE(ptr != NULL);
 
-                ASSERT_EQ(0, munmap(ptr, size / 2));
-                exit(0);
-            }, ::testing::ExitedWithCode(0), "");
+                        ASSERT_EQ(0, munmap(ptr, size / 2));
+                        exit(0);
+                    },
+                    ::testing::ExitedWithCode(0), "");
+        }
+    }
+}
+
+TEST_F(Exit, WithMappingNeedsSync) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
+        for (size_t size : allocationSizes) {
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
+            SCOPED_TRACE(::testing::Message() << "size " << size);
+            EXPECT_EXIT(
+                    {
+                        int map_fd = -1;
+
+                        ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id),
+                                                  ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC,
+                                                  &map_fd));
+                        ASSERT_GE(map_fd, 0);
+
+                        void* ptr;
+                        ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+                        ASSERT_TRUE(ptr != NULL);
+                        exit(0);
+                    },
+                    ::testing::ExitedWithCode(0), "");
+        }
+    }
+}
+
+TEST_F(Exit, WithPartialMappingNeedsSync) {
+    static const size_t allocationSizes[] = {64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
+        for (size_t size : allocationSizes) {
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
+            SCOPED_TRACE(::testing::Message() << "size " << size);
+            EXPECT_EXIT(
+                    {
+                        int map_fd = -1;
+
+                        ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id),
+                                                  ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC,
+                                                  &map_fd));
+                        ASSERT_GE(map_fd, 0);
+
+                        void* ptr;
+                        ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+                        ASSERT_TRUE(ptr != NULL);
+
+                        ASSERT_EQ(0, munmap(ptr, size / 2));
+                        exit(0);
+                    },
+                    ::testing::ExitedWithCode(0), "");
         }
     }
 }
diff --git a/libion/tests/formerly_valid_handle_test.cpp b/libion/tests/formerly_valid_handle_test.cpp
deleted file mode 100644
index 01ab8f3..0000000
--- a/libion/tests/formerly_valid_handle_test.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2013 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 <sys/mman.h>
-
-#include <gtest/gtest.h>
-
-#include <ion/ion.h>
-
-#include "ion_test_fixture.h"
-
-class FormerlyValidHandle : public IonTest {
- public:
-    virtual void SetUp();
-    virtual void TearDown();
-    ion_user_handle_t m_handle;
-};
-
-void FormerlyValidHandle::SetUp()
-{
-    IonTest::SetUp();
-    ASSERT_EQ(0, ion_alloc(m_ionFd, 4096, 0, 1/* ion_env->m_firstHeap */, 0, &m_handle));
-    ASSERT_TRUE(m_handle != 0);
-    ASSERT_EQ(0, ion_free(m_ionFd, m_handle));
-}
-
-void FormerlyValidHandle::TearDown()
-{
-    m_handle = 0;
-}
-
-TEST_F(FormerlyValidHandle, free)
-{
-	ASSERT_EQ(-EINVAL, ion_free(m_ionFd, m_handle));
-}
-
-TEST_F(FormerlyValidHandle, map)
-{
-    int map_fd;
-    unsigned char *ptr;
-
-    ASSERT_EQ(-EINVAL, ion_map(m_ionFd, m_handle, 4096, PROT_READ, 0, 0, &ptr, &map_fd));
-}
-
-TEST_F(FormerlyValidHandle, share)
-{
-    int share_fd;
-
-    ASSERT_EQ(-EINVAL, ion_share(m_ionFd, m_handle, &share_fd));
-}
diff --git a/libion/tests/heap_query.cpp b/libion/tests/heap_query.cpp
new file mode 100644
index 0000000..bad3bbf
--- /dev/null
+++ b/libion/tests/heap_query.cpp
@@ -0,0 +1,27 @@
+/*
+ * 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 <gtest/gtest.h>
+#include "ion_test_fixture.h"
+
+class HeapQuery : public IonTest {};
+
+TEST_F(HeapQuery, AtleastOneHeap) {
+    ASSERT_GT(ion_heaps.size(), 0);
+}
+
+// TODO: Check if we expect some of the default
+// heap types to be present on all devices.
diff --git a/libion/tests/invalid_values_test.cpp b/libion/tests/invalid_values_test.cpp
index 77fea17..48fcd72 100644
--- a/libion/tests/invalid_values_test.cpp
+++ b/libion/tests/invalid_values_test.cpp
@@ -16,171 +16,71 @@
 
 #include <sys/mman.h>
 
+#include <memory>
+#include <vector>
+
 #include <gtest/gtest.h>
 
 #include <ion/ion.h>
-
 #include "ion_test_fixture.h"
 
-class InvalidValues : public IonAllHeapsTest {
- public:
-    virtual void SetUp();
-    virtual void TearDown();
-    ion_user_handle_t m_validHandle;
-    int m_validShareFd;
-    ion_user_handle_t const m_badHandle = -1;
-};
+class InvalidValues : public IonTest {};
 
-void InvalidValues::SetUp()
-{
-    IonAllHeapsTest::SetUp();
-    ASSERT_EQ(0, ion_alloc(m_ionFd, 4096, 0, m_firstHeap, 0, &m_validHandle))
-      << m_ionFd << " " << m_firstHeap;
-    ASSERT_TRUE(m_validHandle != 0);
-    ASSERT_EQ(0, ion_share(m_ionFd, m_validHandle, &m_validShareFd));
-}
-
-void InvalidValues::TearDown()
-{
-    ASSERT_EQ(0, ion_free(m_ionFd, m_validHandle));
-    ASSERT_EQ(0, close(m_validShareFd));
-    m_validHandle = 0;
-    IonAllHeapsTest::TearDown();
-}
-
-TEST_F(InvalidValues, ion_close)
-{
+TEST_F(InvalidValues, ion_close) {
     EXPECT_EQ(-EBADF, ion_close(-1));
 }
 
-TEST_F(InvalidValues, ion_alloc)
-{
-    ion_user_handle_t handle;
-    /* invalid ion_fd */
-    int ret = ion_alloc(0, 4096, 0, m_firstHeap, 0, &handle);
-    EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
-    /* invalid ion_fd */
-    EXPECT_EQ(-EBADF, ion_alloc(-1, 4096, 0, m_firstHeap, 0, &handle));
-    /* no heaps */
-    EXPECT_EQ(-ENODEV, ion_alloc(m_ionFd, 4096, 0, 0, 0, &handle));
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        /* zero size */
-        EXPECT_EQ(-EINVAL, ion_alloc(m_ionFd, 0, 0, heapMask, 0, &handle));
-        /* too large size */
-        EXPECT_EQ(-EINVAL, ion_alloc(m_ionFd, -1, 0, heapMask, 0, &handle));
-        /* bad alignment */
-        EXPECT_EQ(-EINVAL, ion_alloc(m_ionFd, 4096, -1, heapMask, 0, &handle));
-        /* NULL handle */
-        EXPECT_EQ(-EINVAL, ion_alloc(m_ionFd, 4096, 0, heapMask, 0, NULL));
-    }
-}
-
-TEST_F(InvalidValues, ion_alloc_fd)
-{
+TEST_F(InvalidValues, ion_alloc_fd) {
     int fd;
-    /* invalid ion_fd */
-    int ret = ion_alloc_fd(0, 4096, 0, m_firstHeap, 0, &fd);
-    EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
-    /* invalid ion_fd */
-    EXPECT_EQ(-EBADF, ion_alloc_fd(-1, 4096, 0, m_firstHeap, 0, &fd));
-    /* no heaps */
-    EXPECT_EQ(-ENODEV, ion_alloc_fd(m_ionFd, 4096, 0, 0, 0, &fd));
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-        /* zero size */
-        EXPECT_EQ(-EINVAL, ion_alloc_fd(m_ionFd, 0, 0, heapMask, 0, &fd));
-        /* too large size */
-        EXPECT_EQ(-EINVAL, ion_alloc_fd(m_ionFd, -1, 0, heapMask, 0, &fd));
-        /* bad alignment */
-        EXPECT_EQ(-EINVAL, ion_alloc_fd(m_ionFd, 4096, -1, heapMask, 0, &fd));
-        /* NULL handle */
-        EXPECT_EQ(-EINVAL, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, 0, NULL));
+    // no heaps
+    EXPECT_EQ(-ENODEV, ion_alloc_fd(ionfd, 4096, 0, 0, 0, &fd));
+    for (const auto& heap : ion_heaps) {
+        // invalid ion_fd
+        int ret = ion_alloc_fd(0, 4096, 0, (1 << heap.heap_id), 0, &fd);
+        EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
+        // invalid ion_fd
+        EXPECT_EQ(-EBADF, ion_alloc_fd(-1, 4096, 0, (1 << heap.heap_id), 0, &fd));
+        SCOPED_TRACE(::testing::Message()
+                     << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
+        // zero size
+        EXPECT_EQ(-EINVAL, ion_alloc_fd(ionfd, 0, 0, (1 << heap.heap_id), 0, &fd));
+        // too large size
+        EXPECT_EQ(-EINVAL, ion_alloc_fd(ionfd, -1, 0, (1 << heap.heap_id), 0, &fd));
+        // bad alignment
+        // TODO: Current userspace and kernel code completely ignores alignment. So this
+        // test is going to fail. We need to completely remove alignment from the API.
+        // All memory by default is always page aligned. OR actually pass the alignment
+        // down into the kernel and make kernel respect the alignment.
+        // EXPECT_EQ(-EINVAL, ion_alloc_fd(ionfd, 4096, -1, (1 << heap.heap_id), 0, &fd));
+
+        // NULL fd
+        EXPECT_EQ(-EINVAL, ion_alloc_fd(ionfd, 4096, 0, (1 << heap.heap_id), 0, nullptr));
     }
 }
 
-TEST_F(InvalidValues, ion_free)
-{
-    /* invalid ion fd */
-    int ret = ion_free(0, m_validHandle);
-    EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
-    /* invalid ion fd */
-    EXPECT_EQ(-EBADF, ion_free(-1, m_validHandle));
-    /* zero handle */
-    EXPECT_EQ(-EINVAL, ion_free(m_ionFd, 0));
-    /* bad handle */
-    EXPECT_EQ(-EINVAL, ion_free(m_ionFd, m_badHandle));
+TEST_F(InvalidValues, ion_query_heap_cnt) {
+    // NULL count
+    EXPECT_EQ(-EINVAL, ion_query_heap_cnt(ionfd, nullptr));
+
+    int heap_count;
+    // bad fd
+    EXPECT_EQ(-EBADF, ion_query_heap_cnt(-1, &heap_count));
 }
 
-TEST_F(InvalidValues, ion_map)
-{
-    int map_fd;
-    unsigned char *ptr;
+TEST_F(InvalidValues, ion_query_get_heaps) {
+    int heap_count;
+    ASSERT_EQ(0, ion_query_heap_cnt(ionfd, &heap_count));
+    ASSERT_GT(heap_count, 0);
 
-    /* invalid ion fd */
-    int ret = ion_map(0, m_validHandle, 4096, PROT_READ, 0, 0, &ptr, &map_fd);
-    EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
-    /* invalid ion fd */
-    EXPECT_EQ(-EBADF, ion_map(-1, m_validHandle, 4096, PROT_READ, 0, 0, &ptr, &map_fd));
-    /* zero handle */
-    EXPECT_EQ(-EINVAL, ion_map(m_ionFd, 0, 4096, PROT_READ, 0, 0, &ptr, &map_fd));
-    /* bad handle */
-    EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_badHandle, 4096, PROT_READ, 0, 0, &ptr, &map_fd));
-    /* zero length */
-    EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_validHandle, 0, PROT_READ, 0, 0, &ptr, &map_fd));
-    /* bad prot */
-    EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_validHandle, 4096, -1, 0, 0, &ptr, &map_fd));
-    /* bad offset */
-    EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_validHandle, 4096, PROT_READ, 0, -1, &ptr, &map_fd));
-    /* NULL ptr */
-    EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_validHandle, 4096, PROT_READ, 0, 0, NULL, &map_fd));
-    /* NULL map_fd */
-    EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_validHandle, 4096, PROT_READ, 0, 0, &ptr, NULL));
-}
+    // nullptr buffers, still returns success but without
+    // the ion_heap_data.
+    EXPECT_EQ(0, ion_query_get_heaps(ionfd, heap_count, nullptr));
 
-TEST_F(InvalidValues, ion_share)
-{
-    int share_fd;
+    std::unique_ptr<struct ion_heap_data[]> heaps =
+            std::make_unique<struct ion_heap_data[]>(heap_count);
+    // bad fd
+    EXPECT_EQ(-EBADF, ion_query_get_heaps(-1, heap_count, heaps.get()));
 
-    /* invalid ion fd */
-    int ret = ion_share(0, m_validHandle, &share_fd);
-    EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
-    /* invalid ion fd */
-    EXPECT_EQ(-EBADF, ion_share(-1, m_validHandle, &share_fd));
-    /* zero handle */
-    EXPECT_EQ(-EINVAL, ion_share(m_ionFd, 0, &share_fd));
-    /* bad handle */
-    EXPECT_EQ(-EINVAL, ion_share(m_ionFd, m_badHandle, &share_fd));
-    /* NULL share_fd */
-    EXPECT_EQ(-EINVAL, ion_share(m_ionFd, m_validHandle, NULL));
-}
-
-TEST_F(InvalidValues, ion_import)
-{
-    ion_user_handle_t handle;
-
-    /* invalid ion fd */
-    int ret = ion_import(0, m_validShareFd, &handle);
-    EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
-    /* invalid ion fd */
-    EXPECT_EQ(-EBADF, ion_import(-1, m_validShareFd, &handle));
-    /* bad share_fd */
-    EXPECT_EQ(-EINVAL, ion_import(m_ionFd, 0, &handle));
-    /* invalid share_fd */
-    EXPECT_EQ(-EBADF, ion_import(m_ionFd, -1, &handle));
-    /* NULL handle */
-    EXPECT_EQ(-EINVAL, ion_import(m_ionFd, m_validShareFd, NULL));
-}
-
-TEST_F(InvalidValues, ion_sync_fd)
-{
-    /* invalid ion fd */
-    int ret = ion_sync_fd(0, m_validShareFd);
-    EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
-    /* invalid ion fd */
-    EXPECT_EQ(-EBADF, ion_sync_fd(-1, m_validShareFd));
-    /* bad share_fd */
-    EXPECT_EQ(-EINVAL, ion_sync_fd(m_ionFd, 0));
-    /* invalid share_fd */
-    EXPECT_EQ(-EBADF, ion_sync_fd(m_ionFd, -1));
+    // invalid heap data pointer
+    EXPECT_EQ(-EFAULT, ion_query_get_heaps(ionfd, heap_count, reinterpret_cast<void*>(0xdeadf00d)));
 }
diff --git a/libion/tests/ion_4.12.h b/libion/tests/ion_4.12.h
new file mode 100644
index 0000000..614510c
--- /dev/null
+++ b/libion/tests/ion_4.12.h
@@ -0,0 +1,50 @@
+/****************************************************************************
+ ****************************************************************************
+ ***
+ ***   This header was automatically generated from a Linux kernel header
+ ***   of the same name, to make information necessary for userspace to
+ ***   call into the kernel available to libc.  It contains only constants,
+ ***   structures, and macros generated from the original header, and thus,
+ ***   contains no copyrightable information.
+ ***
+ ***   To edit the content of this header, modify the corresponding
+ ***   source file (e.g. under external/kernel-headers/original/) then
+ ***   run bionic/libc/kernel/tools/update_all.py
+ ***
+ ***   Any manual change here will be lost the next time this script will
+ ***   be run. You've been warned!
+ ***
+ ****************************************************************************
+ ****************************************************************************/
+#ifndef _UAPI_LINUX_ION_NEW_H
+#define _UAPI_LINUX_ION_NEW_H
+#include <linux/ioctl.h>
+#include <linux/types.h>
+#define ION_NUM_HEAP_IDS (sizeof(unsigned int) * 8)
+struct ion_new_allocation_data {
+  __u64 len;
+  __u32 heap_id_mask;
+  __u32 flags;
+  __u32 fd;
+  __u32 unused;
+};
+#define MAX_HEAP_NAME 32
+struct ion_heap_data {
+  char name[MAX_HEAP_NAME];
+  __u32 type;
+  __u32 heap_id;
+  __u32 reserved0;
+  __u32 reserved1;
+  __u32 reserved2;
+};
+struct ion_heap_query {
+  __u32 cnt;
+  __u32 reserved0;
+  __u64 heaps;
+  __u32 reserved1;
+  __u32 reserved2;
+};
+#define ION_IOC_MAGIC 'I'
+#define ION_IOC_NEW_ALLOC _IOWR(ION_IOC_MAGIC, 0, struct ion_new_allocation_data)
+#define ION_IOC_HEAP_QUERY _IOWR(ION_IOC_MAGIC, 8, struct ion_heap_query)
+#endif
diff --git a/libion/tests/ion_test_fixture.cpp b/libion/tests/ion_test_fixture.cpp
index e20c730..935fe5c 100644
--- a/libion/tests/ion_test_fixture.cpp
+++ b/libion/tests/ion_test_fixture.cpp
@@ -15,59 +15,26 @@
  */
 
 #include <gtest/gtest.h>
-
 #include <ion/ion.h>
 
 #include "ion_test_fixture.h"
 
-IonTest::IonTest() : m_ionFd(-1)
-{
-}
+IonTest::IonTest() : ionfd(-1), ion_heaps() {}
 
 void IonTest::SetUp() {
-    m_ionFd = ion_open();
-    ASSERT_GE(m_ionFd, 0);
+    ionfd = ion_open();
+    ASSERT_GE(ionfd, 0);
+
+    int heap_count;
+    int ret = ion_query_heap_cnt(ionfd, &heap_count);
+    ASSERT_EQ(ret, 0);
+    ASSERT_GT(heap_count, 0);
+
+    ion_heaps.resize(heap_count, {});
+    ret = ion_query_get_heaps(ionfd, heap_count, ion_heaps.data());
+    ASSERT_EQ(ret, 0);
 }
 
 void IonTest::TearDown() {
-    ion_close(m_ionFd);
-}
-
-IonAllHeapsTest::IonAllHeapsTest() :
-        m_firstHeap(0),
-        m_lastHeap(0),
-        m_allHeaps()
-{
-}
-
-void IonAllHeapsTest::SetUp() {
-    int fd = ion_open();
-    ASSERT_GE(fd, 0);
-
-    for (int i = 1; i != 0; i <<= 1) {
-        ion_user_handle_t handle = 0;
-        int ret;
-        ret = ion_alloc(fd, 4096, 0, i, 0, &handle);
-        if (ret == 0 && handle != 0) {
-            ion_free(fd, handle);
-            if (!m_firstHeap) {
-                m_firstHeap = i;
-            }
-            m_lastHeap = i;
-            m_allHeaps.push_back(i);
-        } else {
-            ASSERT_EQ(-ENODEV, ret);
-        }
-    }
-    ion_close(fd);
-
-    EXPECT_NE(0U, m_firstHeap);
-    EXPECT_NE(0U, m_lastHeap);
-
-    RecordProperty("Heaps", m_allHeaps.size());
-    IonTest::SetUp();
-}
-
-void IonAllHeapsTest::TearDown() {
-    IonTest::TearDown();
+    ion_close(ionfd);
 }
diff --git a/libion/tests/ion_test_fixture.h b/libion/tests/ion_test_fixture.h
index 4098214..4f254b8 100644
--- a/libion/tests/ion_test_fixture.h
+++ b/libion/tests/ion_test_fixture.h
@@ -18,29 +18,19 @@
 #define ION_TEST_FIXTURE_H_
 
 #include <gtest/gtest.h>
+#include <vector>
+#include "ion_4.12.h"
 
 using ::testing::Test;
 
 class IonTest : public virtual Test {
- public:
+  public:
     IonTest();
-	virtual ~IonTest() {};
-	virtual void SetUp();
-	virtual void TearDown();
-	int m_ionFd;
-};
-
-class IonAllHeapsTest : public IonTest {
- public:
-    IonAllHeapsTest();
-    virtual ~IonAllHeapsTest() {};
+    virtual ~IonTest(){};
     virtual void SetUp();
     virtual void TearDown();
-
-    unsigned int m_firstHeap;
-    unsigned int m_lastHeap;
-
-    std::vector<unsigned int> m_allHeaps;
+    int ionfd;
+    std::vector<struct ion_heap_data> ion_heaps;
 };
 
 #endif /* ION_TEST_FIXTURE_H_ */
diff --git a/libion/tests/map_test.cpp b/libion/tests/map_test.cpp
index c006dc8..f1b47b7 100644
--- a/libion/tests/map_test.cpp
+++ b/libion/tests/map_test.cpp
@@ -15,61 +15,30 @@
  */
 
 #include <sys/mman.h>
+#include <unistd.h>
 
 #include <gtest/gtest.h>
 
 #include <ion/ion.h>
-
 #include "ion_test_fixture.h"
 
-class Map : public IonAllHeapsTest {
-};
+class Map : public IonTest {};
 
-TEST_F(Map, MapHandle)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Map, MapFd) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
-            SCOPED_TRACE(::testing::Message() << "size " << size);
-            ion_user_handle_t handle = 0;
-
-            ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, 0, &handle));
-            ASSERT_TRUE(handle != 0);
-
-            int map_fd = -1;
-            unsigned char *ptr = NULL;
-            ASSERT_EQ(0, ion_map(m_ionFd, handle, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0, &ptr, &map_fd));
-            ASSERT_TRUE(ptr != NULL);
-            ASSERT_GE(map_fd, 0);
-
-            ASSERT_EQ(0, close(map_fd));
-
-            ASSERT_EQ(0, ion_free(m_ionFd, handle));
-
-            memset(ptr, 0xaa, size);
-
-            ASSERT_EQ(0, munmap(ptr, size));
-        }
-    }
-}
-
-TEST_F(Map, MapFd)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
-        for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
             int map_fd = -1;
 
-            ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, 0, &map_fd));
+            ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &map_fd));
             ASSERT_GE(map_fd, 0);
 
-            void *ptr;
+            void* ptr;
             ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
             ASSERT_TRUE(ptr != NULL);
-
             ASSERT_EQ(0, close(map_fd));
 
             memset(ptr, 0xaa, size);
@@ -79,53 +48,51 @@
     }
 }
 
-TEST_F(Map, MapOffset)
-{
-    for (unsigned int heapMask : m_allHeaps) {
-        SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+TEST_F(Map, MapOffset) {
+    for (const auto& heap : ion_heaps) {
+        SCOPED_TRACE(::testing::Message()
+                     << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
         int map_fd = -1;
 
-        ASSERT_EQ(0, ion_alloc_fd(m_ionFd, PAGE_SIZE * 2, 0, heapMask, 0, &map_fd));
+        ASSERT_EQ(0, ion_alloc_fd(ionfd, getpagesize() * 2, 0, (1 << heap.heap_id), 0, &map_fd));
         ASSERT_GE(map_fd, 0);
 
-        unsigned char *ptr;
-        ptr = (unsigned char *)mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+        unsigned char* ptr;
+        ptr = (unsigned char*)mmap(NULL, getpagesize() * 2, PROT_READ | PROT_WRITE, MAP_SHARED,
+                                   map_fd, 0);
         ASSERT_TRUE(ptr != NULL);
 
-        memset(ptr, 0, PAGE_SIZE);
-        memset(ptr + PAGE_SIZE, 0xaa, PAGE_SIZE);
+        memset(ptr, 0, getpagesize());
+        memset(ptr + getpagesize(), 0xaa, getpagesize());
 
-        ASSERT_EQ(0, munmap(ptr, PAGE_SIZE * 2));
+        ASSERT_EQ(0, munmap(ptr, getpagesize() * 2));
 
-        ptr = (unsigned char *)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, PAGE_SIZE);
+        ptr = (unsigned char*)mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, map_fd,
+                                   getpagesize());
         ASSERT_TRUE(ptr != NULL);
-
         ASSERT_EQ(ptr[0], 0xaa);
-        ASSERT_EQ(ptr[PAGE_SIZE - 1], 0xaa);
-
-        ASSERT_EQ(0, munmap(ptr, PAGE_SIZE));
-
+        ASSERT_EQ(ptr[getpagesize() - 1], 0xaa);
+        ASSERT_EQ(0, munmap(ptr, getpagesize()));
         ASSERT_EQ(0, close(map_fd));
     }
 }
 
-TEST_F(Map, MapCached)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Map, MapCached) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
             int map_fd = -1;
             unsigned int flags = ION_FLAG_CACHED;
 
-            ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, flags, &map_fd));
+            ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), flags, &map_fd));
             ASSERT_GE(map_fd, 0);
 
-            void *ptr;
+            void* ptr;
             ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
             ASSERT_TRUE(ptr != NULL);
-
             ASSERT_EQ(0, close(map_fd));
 
             memset(ptr, 0xaa, size);
@@ -135,23 +102,22 @@
     }
 }
 
-TEST_F(Map, MapCachedNeedsSync)
-{
-    static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
-    for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Map, MapCachedNeedsSync) {
+    static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+    for (const auto& heap : ion_heaps) {
         for (size_t size : allocationSizes) {
-            SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+            SCOPED_TRACE(::testing::Message()
+                         << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
             SCOPED_TRACE(::testing::Message() << "size " << size);
             int map_fd = -1;
             unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
 
-            ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, flags, &map_fd));
+            ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), flags, &map_fd));
             ASSERT_GE(map_fd, 0);
 
-            void *ptr;
+            void* ptr;
             ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
             ASSERT_TRUE(ptr != NULL);
-
             ASSERT_EQ(0, close(map_fd));
 
             memset(ptr, 0xaa, size);
diff --git a/libmeminfo/vts/AndroidTest.xml b/libmeminfo/vts/AndroidTest.xml
index 530d16e..9614025 100644
--- a/libmeminfo/vts/AndroidTest.xml
+++ b/libmeminfo/vts/AndroidTest.xml
@@ -24,6 +24,7 @@
         <option name="binary-test-source" value="_32bit::DATA/nativetest/vts_meminfo_test/vts_meminfo_test" />
         <option name="binary-test-source" value="_64bit::DATA/nativetest64/vts_meminfo_test/vts_meminfo_test" />
         <option name="binary-test-type" value="gtest"/>
+        <option name="precondition-first-api-level" value="29" />
         <option name="test-timeout" value="10m"/>
     </test>
 </configuration>
diff --git a/libmodprobe/Android.bp b/libmodprobe/Android.bp
index a2824d1..78da46c 100644
--- a/libmodprobe/Android.bp
+++ b/libmodprobe/Android.bp
@@ -3,6 +3,7 @@
     cflags: [
         "-Werror",
     ],
+    vendor_available: true,
     recovery_available: true,
     srcs: [
         "libmodprobe.cpp",
diff --git a/libmodprobe/OWNERS b/libmodprobe/OWNERS
new file mode 100644
index 0000000..4b770b1
--- /dev/null
+++ b/libmodprobe/OWNERS
@@ -0,0 +1,2 @@
+tomcherry@google.com
+smuckle@google.com
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index 0ec766a..dcb4ffb 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <set>
 #include <string>
 #include <unordered_map>
 #include <vector>
@@ -25,12 +26,21 @@
     Modprobe(const std::vector<std::string>&);
 
     bool LoadListedModules();
-    bool LoadWithAliases(const std::string& module_name, bool strict);
+    bool LoadWithAliases(const std::string& module_name, bool strict,
+                         const std::string& parameters = "");
+    bool Remove(const std::string& module_name);
+    std::vector<std::string> ListModules(const std::string& pattern);
+    bool GetAllDependencies(const std::string& module, std::vector<std::string>* pre_dependencies,
+                            std::vector<std::string>* dependencies,
+                            std::vector<std::string>* post_dependencies);
+    void EnableBlacklist(bool enable);
+    void EnableVerbose(bool enable);
 
   private:
     std::string MakeCanonical(const std::string& module_path);
-    bool InsmodWithDeps(const std::string& module_name);
-    bool Insmod(const std::string& path_name);
+    bool InsmodWithDeps(const std::string& module_name, const std::string& parameters);
+    bool Insmod(const std::string& path_name, const std::string& parameters);
+    bool Rmmod(const std::string& module_name);
     std::vector<std::string> GetDependencies(const std::string& module);
     bool ModuleExists(const std::string& module_name);
 
@@ -39,6 +49,7 @@
     bool ParseSoftdepCallback(const std::vector<std::string>& args);
     bool ParseLoadCallback(const std::vector<std::string>& args);
     bool ParseOptionsCallback(const std::vector<std::string>& args);
+    bool ParseBlacklistCallback(const std::vector<std::string>& args);
     void ParseCfg(const std::string& cfg, std::function<bool(const std::vector<std::string>&)> f);
 
     std::vector<std::pair<std::string, std::string>> module_aliases_;
@@ -47,4 +58,6 @@
     std::vector<std::pair<std::string, std::string>> module_post_softdep_;
     std::vector<std::string> module_load_;
     std::unordered_map<std::string, std::string> module_options_;
+    std::set<std::string> module_blacklist_;
+    bool blacklist_enabled = false;
 };
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index 01cf2e3..73ae15b 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -194,6 +194,31 @@
     return true;
 }
 
+bool Modprobe::ParseBlacklistCallback(const std::vector<std::string>& args) {
+    auto it = args.begin();
+    const std::string& type = *it++;
+
+    if (type != "blacklist") {
+        LOG(ERROR) << "non-blacklist line encountered in modules.blacklist";
+        return false;
+    }
+
+    if (args.size() != 2) {
+        LOG(ERROR) << "lines in modules.blacklist must have exactly 2 entries, not " << args.size();
+        return false;
+    }
+
+    const std::string& module = *it++;
+
+    const std::string& canonical_name = MakeCanonical(module);
+    if (canonical_name.empty()) {
+        return false;
+    }
+    this->module_blacklist_.emplace(canonical_name);
+
+    return true;
+}
+
 void Modprobe::ParseCfg(const std::string& cfg,
                         std::function<bool(const std::vector<std::string>&)> f) {
     std::string cfg_contents;
@@ -231,6 +256,23 @@
 
         auto options_callback = std::bind(&Modprobe::ParseOptionsCallback, this, _1);
         ParseCfg(base_path + "/modules.options", options_callback);
+
+        auto blacklist_callback = std::bind(&Modprobe::ParseBlacklistCallback, this, _1);
+        ParseCfg(base_path + "/modules.blacklist", blacklist_callback);
+    }
+
+    android::base::SetMinimumLogSeverity(android::base::INFO);
+}
+
+void Modprobe::EnableBlacklist(bool enable) {
+    blacklist_enabled = enable;
+}
+
+void Modprobe::EnableVerbose(bool enable) {
+    if (enable) {
+        android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+    } else {
+        android::base::SetMinimumLogSeverity(android::base::INFO);
     }
 }
 
@@ -242,7 +284,7 @@
     return it->second;
 }
 
-bool Modprobe::InsmodWithDeps(const std::string& module_name) {
+bool Modprobe::InsmodWithDeps(const std::string& module_name, const std::string& parameters) {
     if (module_name.empty()) {
         LOG(ERROR) << "Need valid module name, given: " << module_name;
         return false;
@@ -256,11 +298,8 @@
 
     // load module dependencies in reverse order
     for (auto dep = dependencies.rbegin(); dep != dependencies.rend() - 1; ++dep) {
-        const std::string& canonical_name = MakeCanonical(*dep);
-        if (canonical_name.empty()) {
-            return false;
-        }
-        if (!LoadWithAliases(canonical_name, true)) {
+        LOG(VERBOSE) << "Loading hard dep for '" << module_name << "': " << *dep;
+        if (!LoadWithAliases(*dep, true)) {
             return false;
         }
     }
@@ -268,18 +307,20 @@
     // try to load soft pre-dependencies
     for (const auto& [module, softdep] : module_pre_softdep_) {
         if (module_name == module) {
+            LOG(VERBOSE) << "Loading soft pre-dep for '" << module << "': " << softdep;
             LoadWithAliases(softdep, false);
         }
     }
 
     // load target module itself with args
-    if (!Insmod(dependencies[0])) {
+    if (!Insmod(dependencies[0], parameters)) {
         return false;
     }
 
     // try to load soft post-dependencies
     for (const auto& [module, softdep] : module_post_softdep_) {
         if (module_name == module) {
+            LOG(VERBOSE) << "Loading soft post-dep for '" << module << "': " << softdep;
             LoadWithAliases(softdep, false);
         }
     }
@@ -287,25 +328,27 @@
     return true;
 }
 
-bool Modprobe::LoadWithAliases(const std::string& module_name, bool strict) {
-    std::set<std::string> modules_to_load = {module_name};
+bool Modprobe::LoadWithAliases(const std::string& module_name, bool strict,
+                               const std::string& parameters) {
+    std::set<std::string> modules_to_load = {MakeCanonical(module_name)};
     bool module_loaded = false;
 
     // use aliases to expand list of modules to load (multiple modules
     // may alias themselves to the requested name)
     for (const auto& [alias, aliased_module] : module_aliases_) {
         if (fnmatch(alias.c_str(), module_name.c_str(), 0) != 0) continue;
+        LOG(VERBOSE) << "Found alias for '" << module_name << "': '" << aliased_module;
         modules_to_load.emplace(aliased_module);
     }
 
     // attempt to load all modules aliased to this name
     for (const auto& module : modules_to_load) {
         if (!ModuleExists(module)) continue;
-        if (InsmodWithDeps(module)) module_loaded = true;
+        if (InsmodWithDeps(module, parameters)) module_loaded = true;
     }
 
     if (strict && !module_loaded) {
-        LOG(ERROR) << "LoadWithAliases did not find a module for " << module_name;
+        LOG(ERROR) << "LoadWithAliases was unable to load " << module_name;
         return false;
     }
     return true;
@@ -319,3 +362,64 @@
     }
     return true;
 }
+
+bool Modprobe::Remove(const std::string& module_name) {
+    auto dependencies = GetDependencies(MakeCanonical(module_name));
+    if (dependencies.empty()) {
+        LOG(ERROR) << "Empty dependencies for module " << module_name;
+        return false;
+    }
+    if (!Rmmod(dependencies[0])) {
+        return false;
+    }
+    for (auto dep = dependencies.begin() + 1; dep != dependencies.end(); ++dep) {
+        Rmmod(*dep);
+    }
+    return true;
+}
+
+std::vector<std::string> Modprobe::ListModules(const std::string& pattern) {
+    std::vector<std::string> rv;
+    for (const auto& [module, deps] : module_deps_) {
+        // Attempt to match both the canonical module name and the module filename.
+        if (!fnmatch(pattern.c_str(), module.c_str(), 0)) {
+            rv.emplace_back(module);
+        } else if (!fnmatch(pattern.c_str(), basename(deps[0].c_str()), 0)) {
+            rv.emplace_back(deps[0]);
+        }
+    }
+    return rv;
+}
+
+bool Modprobe::GetAllDependencies(const std::string& module,
+                                  std::vector<std::string>* pre_dependencies,
+                                  std::vector<std::string>* dependencies,
+                                  std::vector<std::string>* post_dependencies) {
+    std::string canonical_name = MakeCanonical(module);
+    if (pre_dependencies) {
+        pre_dependencies->clear();
+        for (const auto& [it_module, it_softdep] : module_pre_softdep_) {
+            if (canonical_name == it_module) {
+                pre_dependencies->emplace_back(it_softdep);
+            }
+        }
+    }
+    if (dependencies) {
+        dependencies->clear();
+        auto hard_deps = GetDependencies(canonical_name);
+        if (hard_deps.empty()) {
+            return false;
+        }
+        for (auto dep = hard_deps.rbegin(); dep != hard_deps.rend(); dep++) {
+            dependencies->emplace_back(*dep);
+        }
+    }
+    if (post_dependencies) {
+        for (const auto& [it_module, it_softdep] : module_post_softdep_) {
+            if (canonical_name == it_module) {
+                post_dependencies->emplace_back(it_softdep);
+            }
+        }
+    }
+    return true;
+}
diff --git a/libmodprobe/libmodprobe_ext.cpp b/libmodprobe/libmodprobe_ext.cpp
index 5f3a04d..2efcac2 100644
--- a/libmodprobe/libmodprobe_ext.cpp
+++ b/libmodprobe/libmodprobe_ext.cpp
@@ -22,7 +22,7 @@
 
 #include <modprobe/modprobe.h>
 
-bool Modprobe::Insmod(const std::string& path_name) {
+bool Modprobe::Insmod(const std::string& path_name, const std::string& parameters) {
     android::base::unique_fd fd(
             TEMP_FAILURE_RETRY(open(path_name.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
     if (fd == -1) {
@@ -35,6 +35,9 @@
     if (options_iter != module_options_.end()) {
         options = options_iter->second;
     }
+    if (!parameters.empty()) {
+        options = options + " " + parameters;
+    }
 
     LOG(INFO) << "Loading module " << path_name << " with args \"" << options << "\"";
     int ret = syscall(__NR_finit_module, fd.get(), options.c_str(), 0);
@@ -51,17 +54,32 @@
     return true;
 }
 
+bool Modprobe::Rmmod(const std::string& module_name) {
+    int ret = syscall(__NR_delete_module, MakeCanonical(module_name).c_str(), O_NONBLOCK);
+    if (ret != 0) {
+        PLOG(ERROR) << "Failed to remove module '" << module_name << "'";
+        return false;
+    }
+    return true;
+}
+
 bool Modprobe::ModuleExists(const std::string& module_name) {
     struct stat fileStat;
+    if (blacklist_enabled && module_blacklist_.count(module_name)) {
+        LOG(INFO) << "module " << module_name << " is blacklisted";
+        return false;
+    }
     auto deps = GetDependencies(module_name);
     if (deps.empty()) {
         // missing deps can happen in the case of an alias
         return false;
     }
     if (stat(deps.front().c_str(), &fileStat)) {
+        LOG(INFO) << "module " << module_name << " does not exist";
         return false;
     }
     if (!S_ISREG(fileStat.st_mode)) {
+        LOG(INFO) << "module " << module_name << " is not a regular file";
         return false;
     }
     return true;
diff --git a/libmodprobe/libmodprobe_ext_test.cpp b/libmodprobe/libmodprobe_ext_test.cpp
index 0f073cb..7d817b1 100644
--- a/libmodprobe/libmodprobe_ext_test.cpp
+++ b/libmodprobe/libmodprobe_ext_test.cpp
@@ -29,7 +29,7 @@
 
 #include "libmodprobe_test.h"
 
-bool Modprobe::Insmod(const std::string& path_name) {
+bool Modprobe::Insmod(const std::string& path_name, const std::string& parameters) {
     auto deps = GetDependencies(MakeCanonical(path_name));
     if (deps.empty()) {
         return false;
@@ -47,12 +47,29 @@
     if (options_iter != module_options_.end()) {
         options = " " + options_iter->second;
     }
+    if (!parameters.empty()) {
+        options = options + " " + parameters;
+    }
+
     modules_loaded.emplace_back(path_name + options);
     return true;
 }
 
+bool Modprobe::Rmmod(const std::string& module_name) {
+    for (auto it = modules_loaded.begin(); it != modules_loaded.end(); it++) {
+        if (*it == module_name) {
+            modules_loaded.erase(it);
+            return true;
+        }
+    }
+    return false;
+}
+
 bool Modprobe::ModuleExists(const std::string& module_name) {
     auto deps = GetDependencies(module_name);
+    if (blacklist_enabled && module_blacklist_.count(module_name)) {
+        return false;
+    }
     if (deps.empty()) {
         // missing deps can happen in the case of an alias
         return false;
diff --git a/libmodprobe/libmodprobe_test.cpp b/libmodprobe/libmodprobe_test.cpp
index 481658d..a711631 100644
--- a/libmodprobe/libmodprobe_test.cpp
+++ b/libmodprobe/libmodprobe_test.cpp
@@ -56,6 +56,14 @@
             "/test13.ko",
     };
 
+    std::vector<std::string> expected_after_remove = {
+            "/test14.ko", "/test15.ko",         "/test1.ko",
+            "/test6.ko",  "/test2.ko",          "/test5.ko",
+            "/test8.ko",  "/test7.ko param1=4", "/test9.ko param_x=1 param_y=2 param_z=3",
+            "/test10.ko", "/test12.ko",         "/test11.ko",
+            "/test13.ko",
+    };
+
     const std::string modules_dep =
             "test1.ko:\n"
             "test2.ko:\n"
@@ -91,6 +99,10 @@
             "options test9.ko param_x=1 param_y=2 param_z=3\n"
             "options test100.ko param_1=1\n";
 
+    const std::string modules_blacklist =
+            "blacklist test9.ko\n"
+            "blacklist test3.ko\n";
+
     const std::string modules_load =
             "test4.ko\n"
             "test1.ko\n"
@@ -101,17 +113,20 @@
             "test11.ko\n";
 
     TemporaryDir dir;
-    ASSERT_TRUE(android::base::WriteStringToFile(
-            modules_alias, std::string(dir.path) + "/modules.alias", 0600, getuid(), getgid()));
+    auto dir_path = std::string(dir.path);
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_alias, dir_path + "/modules.alias", 0600,
+                                                 getuid(), getgid()));
 
-    ASSERT_TRUE(android::base::WriteStringToFile(
-            modules_dep, std::string(dir.path) + "/modules.dep", 0600, getuid(), getgid()));
-    ASSERT_TRUE(android::base::WriteStringToFile(
-            modules_softdep, std::string(dir.path) + "/modules.softdep", 0600, getuid(), getgid()));
-    ASSERT_TRUE(android::base::WriteStringToFile(
-            modules_options, std::string(dir.path) + "/modules.options", 0600, getuid(), getgid()));
-    ASSERT_TRUE(android::base::WriteStringToFile(
-            modules_load, std::string(dir.path) + "/modules.load", 0600, getuid(), getgid()));
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_dep, dir_path + "/modules.dep", 0600,
+                                                 getuid(), getgid()));
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_softdep, dir_path + "/modules.softdep",
+                                                 0600, getuid(), getgid()));
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_options, dir_path + "/modules.options",
+                                                 0600, getuid(), getgid()));
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_load, dir_path + "/modules.load", 0600,
+                                                 getuid(), getgid()));
+    ASSERT_TRUE(android::base::WriteStringToFile(modules_blacklist, dir_path + "/modules.blacklist",
+                                                 0600, getuid(), getgid()));
 
     for (auto i = test_modules.begin(); i != test_modules.end(); ++i) {
         *i = dir.path + *i;
@@ -131,4 +146,21 @@
     }
 
     EXPECT_TRUE(modules_loaded == expected_modules_loaded);
+
+    EXPECT_TRUE(m.Remove("test4"));
+
+    GTEST_LOG_(INFO) << "Expected modules loaded after removing test4 (in order):";
+    for (auto i = expected_after_remove.begin(); i != expected_after_remove.end(); ++i) {
+        *i = dir.path + *i;
+        GTEST_LOG_(INFO) << "\"" << *i << "\"";
+    }
+    GTEST_LOG_(INFO) << "Actual modules loaded after removing test4 (in order):";
+    for (auto i = modules_loaded.begin(); i != modules_loaded.end(); ++i) {
+        GTEST_LOG_(INFO) << "\"" << *i << "\"";
+    }
+
+    EXPECT_TRUE(modules_loaded == expected_after_remove);
+
+    m.EnableBlacklist(true);
+    EXPECT_FALSE(m.LoadWithAliases("test4", true));
 }
diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp
index c22ce8a..a9eea8c 100644
--- a/libnativeloader/library_namespaces.cpp
+++ b/libnativeloader/library_namespaces.cpp
@@ -33,6 +33,8 @@
 #include "public_libraries.h"
 #include "utils.h"
 
+using android::base::Error;
+
 namespace android::nativeloader {
 
 namespace {
@@ -128,11 +130,11 @@
   }
 }
 
-NativeLoaderNamespace* LibraryNamespaces::Create(JNIEnv* env, uint32_t target_sdk_version,
-                                                 jobject class_loader, bool is_shared,
-                                                 jstring dex_path, jstring java_library_path,
-                                                 jstring java_permitted_path,
-                                                 std::string* error_msg) {
+Result<NativeLoaderNamespace*> LibraryNamespaces::Create(JNIEnv* env, uint32_t target_sdk_version,
+                                                         jobject class_loader, bool is_shared,
+                                                         jstring dex_path,
+                                                         jstring java_library_path,
+                                                         jstring java_permitted_path) {
   std::string library_path;  // empty string by default.
 
   if (java_library_path != nullptr) {
@@ -158,14 +160,14 @@
   }
 
   // Initialize the anonymous namespace with the first non-empty library path.
+  Result<void> ret;
   if (!library_path.empty() && !initialized_ &&
-      !InitPublicNamespace(library_path.c_str(), error_msg)) {
-    return nullptr;
+      !(ret = InitPublicNamespace(library_path.c_str()))) {
+    return ret.error();
   }
 
-  bool found = FindNamespaceByClassLoader(env, class_loader);
-
-  LOG_ALWAYS_FATAL_IF(found, "There is already a namespace associated with this classloader");
+  LOG_ALWAYS_FATAL_IF(FindNamespaceByClassLoader(env, class_loader) != nullptr,
+                      "There is already a namespace associated with this classloader");
 
   std::string system_exposed_libraries = default_public_libraries();
   const char* namespace_name = kClassloaderNamespaceName;
@@ -216,58 +218,66 @@
   auto app_ns =
       NativeLoaderNamespace::Create(namespace_name, library_path, permitted_path, parent_ns,
                                     is_shared, target_sdk_version < 24 /* is_greylist_enabled */);
-  if (app_ns.IsNil()) {
-    *error_msg = app_ns.GetError();
-    return nullptr;
+  if (!app_ns) {
+    return app_ns.error();
   }
 
   // ... and link to other namespaces to allow access to some public libraries
-  bool is_bridged = app_ns.IsBridged();
+  bool is_bridged = app_ns->IsBridged();
 
   auto platform_ns = NativeLoaderNamespace::GetPlatformNamespace(is_bridged);
-  if (!app_ns.Link(platform_ns, system_exposed_libraries)) {
-    *error_msg = app_ns.GetError();
-    return nullptr;
+  if (!platform_ns) {
+    return platform_ns.error();
+  }
+
+  auto linked = app_ns->Link(*platform_ns, system_exposed_libraries);
+  if (!linked) {
+    return linked.error();
   }
 
   auto runtime_ns = NativeLoaderNamespace::GetExportedNamespace(kRuntimeNamespaceName, is_bridged);
   // Runtime apex does not exist in host, and under certain build conditions.
-  if (!runtime_ns.IsNil()) {
-    if (!app_ns.Link(runtime_ns, runtime_public_libraries())) {
-      *error_msg = app_ns.GetError();
-      return nullptr;
+  if (runtime_ns) {
+    linked = app_ns->Link(*runtime_ns, runtime_public_libraries());
+    if (!linked) {
+      return linked.error();
     }
   }
 
   // Give access to NNAPI libraries (apex-updated LLNDK library).
   auto nnapi_ns =
       NativeLoaderNamespace::GetExportedNamespace(kNeuralNetworksNamespaceName, is_bridged);
-  if (!app_ns.Link(nnapi_ns, neuralnetworks_public_libraries())) {
-    *error_msg = app_ns.GetError();
-    return nullptr;
+  if (nnapi_ns) {
+    linked = app_ns->Link(*nnapi_ns, neuralnetworks_public_libraries());
+    if (!linked) {
+      return linked.error();
+    }
   }
 
   // Give access to VNDK-SP libraries from the 'vndk' namespace.
   if (unbundled_vendor_or_product_app && !vndksp_libraries().empty()) {
     auto vndk_ns = NativeLoaderNamespace::GetExportedNamespace(kVndkNamespaceName, is_bridged);
-    if (!vndk_ns.IsNil() && !app_ns.Link(vndk_ns, vndksp_libraries())) {
-      *error_msg = app_ns.GetError();
-      return nullptr;
+    if (vndk_ns) {
+      linked = app_ns->Link(*vndk_ns, vndksp_libraries());
+      if (!linked) {
+        return linked.error();
+      }
     }
   }
 
-  // Note that when vendor_ns is not configured, vendor_ns.IsNil() will be true
-  // and it will result in linking to the default namespace which is expected
-  // behavior in this case.
   if (!vendor_public_libraries().empty()) {
     auto vendor_ns = NativeLoaderNamespace::GetExportedNamespace(kVendorNamespaceName, is_bridged);
-    if (!app_ns.Link(vendor_ns, vendor_public_libraries())) {
-      *error_msg = dlerror();
-      return nullptr;
+    // when vendor_ns is not configured, link to the platform namespace
+    auto target_ns = vendor_ns ? vendor_ns : platform_ns;
+    if (target_ns) {
+      linked = app_ns->Link(*target_ns, vendor_public_libraries());
+      if (!linked) {
+        return linked.error();
+      }
     }
   }
 
-  namespaces_.push_back(std::make_pair(env->NewWeakGlobalRef(class_loader), app_ns));
+  namespaces_.push_back(std::make_pair(env->NewWeakGlobalRef(class_loader), *app_ns));
 
   return &(namespaces_.back().second);
 }
@@ -285,7 +295,7 @@
   return nullptr;
 }
 
-bool LibraryNamespaces::InitPublicNamespace(const char* library_path, std::string* error_msg) {
+Result<void> LibraryNamespaces::InitPublicNamespace(const char* library_path) {
   // Ask native bride if this apps library path should be handled by it
   bool is_native_bridge = NativeBridgeIsPathSupported(library_path);
 
@@ -296,8 +306,7 @@
   initialized_ = android_init_anonymous_namespace(default_public_libraries().c_str(),
                                                   is_native_bridge ? nullptr : library_path);
   if (!initialized_) {
-    *error_msg = dlerror();
-    return false;
+    return Error() << dlerror();
   }
 
   // and now initialize native bridge namespaces if necessary.
@@ -305,11 +314,11 @@
     initialized_ = NativeBridgeInitAnonymousNamespace(default_public_libraries().c_str(),
                                                       is_native_bridge ? library_path : nullptr);
     if (!initialized_) {
-      *error_msg = NativeBridgeGetError();
+      return Error() << NativeBridgeGetError();
     }
   }
 
-  return initialized_;
+  return {};
 }
 
 NativeLoaderNamespace* LibraryNamespaces::FindParentNamespaceByClassLoader(JNIEnv* env,
diff --git a/libnativeloader/library_namespaces.h b/libnativeloader/library_namespaces.h
index 6e9a190..e54bc0a 100644
--- a/libnativeloader/library_namespaces.h
+++ b/libnativeloader/library_namespaces.h
@@ -25,10 +25,13 @@
 #include <list>
 #include <string>
 
+#include <android-base/result.h>
 #include <jni.h>
 
 namespace android::nativeloader {
 
+using android::base::Result;
+
 // LibraryNamespaces is a singleton object that manages NativeLoaderNamespace
 // objects for an app process. Its main job is to create (and configure) a new
 // NativeLoaderNamespace object for a Java ClassLoader, and to find an existing
@@ -46,13 +49,13 @@
     namespaces_.clear();
     initialized_ = false;
   }
-  NativeLoaderNamespace* Create(JNIEnv* env, uint32_t target_sdk_version, jobject class_loader,
-                                bool is_shared, jstring dex_path, jstring java_library_path,
-                                jstring java_permitted_path, std::string* error_msg);
+  Result<NativeLoaderNamespace*> Create(JNIEnv* env, uint32_t target_sdk_version,
+                                        jobject class_loader, bool is_shared, jstring dex_path,
+                                        jstring java_library_path, jstring java_permitted_path);
   NativeLoaderNamespace* FindNamespaceByClassLoader(JNIEnv* env, jobject class_loader);
 
  private:
-  bool InitPublicNamespace(const char* library_path, std::string* error_msg);
+  Result<void> InitPublicNamespace(const char* library_path);
   NativeLoaderNamespace* FindParentNamespaceByClassLoader(JNIEnv* env, jobject class_loader);
 
   bool initialized_;
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index 0c29324..6d3c057 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -92,12 +92,10 @@
                                    jstring permitted_path) {
 #if defined(__ANDROID__)
   std::lock_guard<std::mutex> guard(g_namespaces_mutex);
-
-  std::string error_msg;
-  bool success = g_namespaces->Create(env, target_sdk_version, class_loader, is_shared, dex_path,
-                                      library_path, permitted_path, &error_msg) != nullptr;
-  if (!success) {
-    return env->NewStringUTF(error_msg.c_str());
+  auto ns = g_namespaces->Create(env, target_sdk_version, class_loader, is_shared, dex_path,
+                                 library_path, permitted_path);
+  if (!ns) {
+    return env->NewStringUTF(ns.error().message().c_str());
   }
 #else
   UNUSED(env, target_sdk_version, class_loader, is_shared, dex_path, library_path, permitted_path);
@@ -139,11 +137,14 @@
   if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
     // This is the case where the classloader was not created by ApplicationLoaders
     // In this case we create an isolated not-shared namespace for it.
-    std::string create_error_msg;
-    if ((ns = g_namespaces->Create(env, target_sdk_version, class_loader, false /* is_shared */,
-                                   nullptr, library_path, nullptr, &create_error_msg)) == nullptr) {
-      *error_msg = strdup(create_error_msg.c_str());
+    Result<NativeLoaderNamespace*> isolated_ns =
+        g_namespaces->Create(env, target_sdk_version, class_loader, false /* is_shared */, nullptr,
+                             library_path, nullptr);
+    if (!isolated_ns) {
+      *error_msg = strdup(isolated_ns.error().message().c_str());
       return nullptr;
+    } else {
+      ns = *isolated_ns;
     }
   }
 
@@ -222,12 +223,14 @@
 #if defined(__ANDROID__)
 void* OpenNativeLibraryInNamespace(NativeLoaderNamespace* ns, const char* path,
                                    bool* needs_native_bridge, char** error_msg) {
-  void* handle = ns->Load(path);
-  if (handle == nullptr) {
-    *error_msg = ns->GetError();
+  auto handle = ns->Load(path);
+  if (!handle && error_msg != nullptr) {
+    *error_msg = strdup(handle.error().message().c_str());
   }
-  *needs_native_bridge = ns->IsBridged();
-  return handle;
+  if (needs_native_bridge != nullptr) {
+    *needs_native_bridge = ns->IsBridged();
+  }
+  return handle ? *handle : nullptr;
 }
 
 // native_bridge_namespaces are not supported for callers of this function.
diff --git a/libnativeloader/native_loader_namespace.cpp b/libnativeloader/native_loader_namespace.cpp
index 58ac686..4add6e6 100644
--- a/libnativeloader/native_loader_namespace.cpp
+++ b/libnativeloader/native_loader_namespace.cpp
@@ -28,6 +28,9 @@
 
 #include "nativeloader/dlext_namespaces.h"
 
+using android::base::Error;
+using android::base::Errorf;
+
 namespace android {
 
 namespace {
@@ -35,41 +38,54 @@
 constexpr const char* kDefaultNamespaceName = "default";
 constexpr const char* kPlatformNamespaceName = "platform";
 
-}  // namespace
-
-NativeLoaderNamespace NativeLoaderNamespace::GetExportedNamespace(const std::string& name,
-                                                                  bool is_bridged) {
-  if (!is_bridged) {
-    return NativeLoaderNamespace(name, android_get_exported_namespace(name.c_str()));
-  } else {
-    return NativeLoaderNamespace(name, NativeBridgeGetExportedNamespace(name.c_str()));
+std::string GetLinkerError(bool is_bridged) {
+  const char* msg = is_bridged ? NativeBridgeGetError() : dlerror();
+  if (msg == nullptr) {
+    return "no error";
   }
+  return std::string(msg);
 }
 
-char* NativeLoaderNamespace::GetError() const {
-  if (!IsBridged()) {
-    return strdup(dlerror());
+}  // namespace
+
+Result<NativeLoaderNamespace> NativeLoaderNamespace::GetExportedNamespace(const std::string& name,
+                                                                          bool is_bridged) {
+  if (!is_bridged) {
+    auto raw = android_get_exported_namespace(name.c_str());
+    if (raw != nullptr) {
+      return NativeLoaderNamespace(name, raw);
+    }
   } else {
-    return strdup(NativeBridgeGetError());
+    auto raw = NativeBridgeGetExportedNamespace(name.c_str());
+    if (raw != nullptr) {
+      return NativeLoaderNamespace(name, raw);
+    }
   }
+  return Errorf("namespace {} does not exist or exported", name);
 }
 
 // The platform namespace is called "default" for binaries in /system and
 // "platform" for those in the Runtime APEX. Try "platform" first since
 // "default" always exists.
-NativeLoaderNamespace NativeLoaderNamespace::GetPlatformNamespace(bool is_bridged) {
-  NativeLoaderNamespace ns = GetExportedNamespace(kPlatformNamespaceName, is_bridged);
-  if (ns.IsNil()) {
-    ns = GetExportedNamespace(kDefaultNamespaceName, is_bridged);
+Result<NativeLoaderNamespace> NativeLoaderNamespace::GetPlatformNamespace(bool is_bridged) {
+  auto ns = GetExportedNamespace(kPlatformNamespaceName, is_bridged);
+  if (ns) return ns;
+  ns = GetExportedNamespace(kDefaultNamespaceName, is_bridged);
+  if (ns) return ns;
+
+  // If nothing is found, return NativeLoaderNamespace constructed from nullptr.
+  // nullptr also means default namespace to the linker.
+  if (!is_bridged) {
+    return NativeLoaderNamespace(kDefaultNamespaceName, static_cast<android_namespace_t*>(nullptr));
+  } else {
+    return NativeLoaderNamespace(kDefaultNamespaceName,
+                                 static_cast<native_bridge_namespace_t*>(nullptr));
   }
-  return ns;
 }
 
-NativeLoaderNamespace NativeLoaderNamespace::Create(const std::string& name,
-                                                    const std::string& search_paths,
-                                                    const std::string& permitted_paths,
-                                                    const NativeLoaderNamespace* parent,
-                                                    bool is_shared, bool is_greylist_enabled) {
+Result<NativeLoaderNamespace> NativeLoaderNamespace::Create(
+    const std::string& name, const std::string& search_paths, const std::string& permitted_paths,
+    const NativeLoaderNamespace* parent, bool is_shared, bool is_greylist_enabled) {
   bool is_bridged = false;
   if (parent != nullptr) {
     is_bridged = parent->IsBridged();
@@ -78,8 +94,11 @@
   }
 
   // Fall back to the platform namespace if no parent is set.
-  const NativeLoaderNamespace& effective_parent =
-      parent != nullptr ? *parent : GetPlatformNamespace(is_bridged);
+  auto platform_ns = GetPlatformNamespace(is_bridged);
+  if (!platform_ns) {
+    return platform_ns.error();
+  }
+  const NativeLoaderNamespace& effective_parent = parent != nullptr ? *parent : *platform_ns;
 
   uint64_t type = ANDROID_NAMESPACE_TYPE_ISOLATED;
   if (is_shared) {
@@ -93,37 +112,56 @@
     android_namespace_t* raw =
         android_create_namespace(name.c_str(), nullptr, search_paths.c_str(), type,
                                  permitted_paths.c_str(), effective_parent.ToRawAndroidNamespace());
-    return NativeLoaderNamespace(name, raw);
+    if (raw != nullptr) {
+      return NativeLoaderNamespace(name, raw);
+    }
   } else {
     native_bridge_namespace_t* raw = NativeBridgeCreateNamespace(
         name.c_str(), nullptr, search_paths.c_str(), type, permitted_paths.c_str(),
         effective_parent.ToRawNativeBridgeNamespace());
-    return NativeLoaderNamespace(name, raw);
+    if (raw != nullptr) {
+      return NativeLoaderNamespace(name, raw);
+    }
   }
+  return Errorf("failed to create {} namespace name:{}, search_paths:{}, permitted_paths:{}",
+                is_bridged ? "bridged" : "native", name, search_paths, permitted_paths);
 }
 
-bool NativeLoaderNamespace::Link(const NativeLoaderNamespace& target,
-                                 const std::string& shared_libs) const {
+Result<void> NativeLoaderNamespace::Link(const NativeLoaderNamespace& target,
+                                         const std::string& shared_libs) const {
   LOG_ALWAYS_FATAL_IF(shared_libs.empty(), "empty share lib when linking %s to %s",
                       this->name().c_str(), target.name().c_str());
   if (!IsBridged()) {
-    return android_link_namespaces(this->ToRawAndroidNamespace(), target.ToRawAndroidNamespace(),
-                                   shared_libs.c_str());
+    if (android_link_namespaces(this->ToRawAndroidNamespace(), target.ToRawAndroidNamespace(),
+                                shared_libs.c_str())) {
+      return {};
+    }
   } else {
-    return NativeBridgeLinkNamespaces(this->ToRawNativeBridgeNamespace(),
-                                      target.ToRawNativeBridgeNamespace(), shared_libs.c_str());
+    if (NativeBridgeLinkNamespaces(this->ToRawNativeBridgeNamespace(),
+                                   target.ToRawNativeBridgeNamespace(), shared_libs.c_str())) {
+      return {};
+    }
   }
+  return Error() << GetLinkerError(IsBridged());
 }
 
-void* NativeLoaderNamespace::Load(const char* lib_name) const {
+Result<void*> NativeLoaderNamespace::Load(const char* lib_name) const {
   if (!IsBridged()) {
     android_dlextinfo extinfo;
     extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
     extinfo.library_namespace = this->ToRawAndroidNamespace();
-    return android_dlopen_ext(lib_name, RTLD_NOW, &extinfo);
+    void* handle = android_dlopen_ext(lib_name, RTLD_NOW, &extinfo);
+    if (handle != nullptr) {
+      return handle;
+    }
   } else {
-    return NativeBridgeLoadLibraryExt(lib_name, RTLD_NOW, this->ToRawNativeBridgeNamespace());
+    void* handle =
+        NativeBridgeLoadLibraryExt(lib_name, RTLD_NOW, this->ToRawNativeBridgeNamespace());
+    if (handle != nullptr) {
+      return handle;
+    }
   }
+  return Error() << GetLinkerError(IsBridged());
 }
 
 }  // namespace android
diff --git a/libnativeloader/native_loader_namespace.h b/libnativeloader/native_loader_namespace.h
index 71e4247..29b759c 100644
--- a/libnativeloader/native_loader_namespace.h
+++ b/libnativeloader/native_loader_namespace.h
@@ -21,22 +21,25 @@
 #include <vector>
 
 #include <android-base/logging.h>
+#include <android-base/result.h>
 #include <android/dlext.h>
 #include <log/log.h>
 #include <nativebridge/native_bridge.h>
 
 namespace android {
 
+using android::base::Result;
+
 // NativeLoaderNamespace abstracts a linker namespace for the native
 // architecture (ex: arm on arm) or the translated architecture (ex: arm on
 // x86). Instances of this class are managed by LibraryNamespaces object.
 struct NativeLoaderNamespace {
  public:
-  // TODO(return with errors)
-  static NativeLoaderNamespace Create(const std::string& name, const std::string& search_paths,
-                                      const std::string& permitted_paths,
-                                      const NativeLoaderNamespace* parent, bool is_shared,
-                                      bool is_greylist_enabled);
+  static Result<NativeLoaderNamespace> Create(const std::string& name,
+                                              const std::string& search_paths,
+                                              const std::string& permitted_paths,
+                                              const NativeLoaderNamespace* parent, bool is_shared,
+                                              bool is_greylist_enabled);
 
   NativeLoaderNamespace(NativeLoaderNamespace&&) = default;
   NativeLoaderNamespace(const NativeLoaderNamespace&) = default;
@@ -47,16 +50,13 @@
 
   std::string name() const { return name_; }
   bool IsBridged() const { return raw_.index() == 1; }
-  bool IsNil() const {
-    return IsBridged() ? std::get<1>(raw_) == nullptr : std::get<0>(raw_) == nullptr;
-  }
 
-  bool Link(const NativeLoaderNamespace& target, const std::string& shared_libs) const;
-  void* Load(const char* lib_name) const;
-  char* GetError() const;
+  Result<void> Link(const NativeLoaderNamespace& target, const std::string& shared_libs) const;
+  Result<void*> Load(const char* lib_name) const;
 
-  static NativeLoaderNamespace GetExportedNamespace(const std::string& name, bool is_bridged);
-  static NativeLoaderNamespace GetPlatformNamespace(bool is_bridged);
+  static Result<NativeLoaderNamespace> GetExportedNamespace(const std::string& name,
+                                                            bool is_bridged);
+  static Result<NativeLoaderNamespace> GetPlatformNamespace(bool is_bridged);
 
  private:
   explicit NativeLoaderNamespace(const std::string& name, android_namespace_t* ns)
diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp
index 614188b..b939eee 100644
--- a/libnativeloader/native_loader_test.cpp
+++ b/libnativeloader/native_loader_test.cpp
@@ -85,6 +85,7 @@
     {"runtime", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("runtime"))},
     {"sphal", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("sphal"))},
     {"vndk", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("vndk"))},
+    {"neuralnetworks", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("neuralnetworks"))},
 };
 
 // The actual gmock object
@@ -98,7 +99,7 @@
           if (namespaces.find(name) != namespaces.end()) {
             return namespaces[name];
           }
-          return nullptr;
+          return TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("(namespace not found"));
         }));
   }
 
@@ -339,11 +340,13 @@
   bool expected_link_with_sphal_ns = !vendor_public_libraries().empty();
   bool expected_link_with_vndk_ns = false;
   bool expected_link_with_default_ns = false;
+  bool expected_link_with_neuralnetworks_ns = true;
   std::string expected_shared_libs_to_platform_ns = default_public_libraries();
   std::string expected_shared_libs_to_runtime_ns = runtime_public_libraries();
   std::string expected_shared_libs_to_sphal_ns = vendor_public_libraries();
   std::string expected_shared_libs_to_vndk_ns = vndksp_libraries();
   std::string expected_shared_libs_to_default_ns = default_public_libraries();
+  std::string expected_shared_libs_to_neuralnetworks_ns = neuralnetworks_public_libraries();
 
   void SetExpectations() {
     NativeLoaderTest::SetExpectations();
@@ -394,6 +397,11 @@
                                               StrEq(expected_shared_libs_to_default_ns)))
           .WillOnce(Return(true));
     }
+    if (expected_link_with_neuralnetworks_ns) {
+      EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), _, NsEq("neuralnetworks"),
+                                              StrEq(expected_shared_libs_to_neuralnetworks_ns)))
+          .WillOnce(Return(true));
+    }
   }
 
   void RunTest() {
diff --git a/libnativeloader/public_libraries.cpp b/libnativeloader/public_libraries.cpp
index 10e23fd..6cee668 100644
--- a/libnativeloader/public_libraries.cpp
+++ b/libnativeloader/public_libraries.cpp
@@ -26,6 +26,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <android-base/result.h>
 #include <android-base/strings.h>
 #include <log/log.h>
 
@@ -34,6 +35,9 @@
 namespace android::nativeloader {
 
 using namespace std::string_literals;
+using android::base::ErrnoError;
+using android::base::Errorf;
+using android::base::Result;
 
 namespace {
 
@@ -91,22 +95,21 @@
   file_name->insert(insert_pos, vndk_version_str());
 }
 
-const std::function<bool(const std::string&, std::string*)> always_true =
-    [](const std::string&, std::string*) { return true; };
+const std::function<Result<void>(const std::string&)> always_true =
+    [](const std::string&) -> Result<void> { return {}; };
 
-bool ReadConfig(const std::string& configFile, std::vector<std::string>* sonames,
-                const std::function<bool(const std::string& /* soname */,
-                                         std::string* /* error_msg */)>& check_soname,
-                std::string* error_msg = nullptr) {
+Result<std::vector<std::string>> ReadConfig(
+    const std::string& configFile,
+    const std::function<Result<void>(const std::string& /* soname */)>& check_soname) {
   // Read list of public native libraries from the config file.
   std::string file_content;
   if (!base::ReadFileToString(configFile, &file_content)) {
-    if (error_msg) *error_msg = strerror(errno);
-    return false;
+    return ErrnoError();
   }
 
   std::vector<std::string> lines = base::Split(file_content, "\n");
 
+  std::vector<std::string> sonames;
   for (auto& line : lines) {
     auto trimmed_line = base::Trim(line);
     if (trimmed_line[0] == '#' || trimmed_line.empty()) {
@@ -116,8 +119,7 @@
     if (space_pos != std::string::npos) {
       std::string type = trimmed_line.substr(space_pos + 1);
       if (type != "32" && type != "64") {
-        if (error_msg) *error_msg = "Malformed line: " + line;
-        return false;
+        return Errorf("Malformed line: {}", line);
       }
 #if defined(__LP64__)
       // Skip 32 bit public library.
@@ -133,13 +135,13 @@
       trimmed_line.resize(space_pos);
     }
 
-    if (check_soname(trimmed_line, error_msg)) {
-      sonames->push_back(trimmed_line);
-    } else {
-      return false;
+    auto ret = check_soname(trimmed_line);
+    if (!ret) {
+      return ret.error();
     }
+    sonames.push_back(trimmed_line);
   }
-  return true;
+  return sonames;
 }
 
 void ReadExtensionLibraries(const char* dirname, std::vector<std::string>* sonames) {
@@ -162,24 +164,22 @@
             "Error extracting company name from public native library list file path \"%s\"",
             config_file_path.c_str());
 
-        std::string error_msg;
-
-        LOG_ALWAYS_FATAL_IF(
-            !ReadConfig(config_file_path, sonames,
-                        [&company_name](const std::string& soname, std::string* error_msg) {
-                          if (android::base::StartsWith(soname, "lib") &&
-                              android::base::EndsWith(soname, "." + company_name + ".so")) {
-                            return true;
-                          } else {
-                            *error_msg = "Library name \"" + soname +
-                                         "\" does not end with the company name: " + company_name +
-                                         ".";
-                            return false;
-                          }
-                        },
-                        &error_msg),
-            "Error reading public native library list from \"%s\": %s", config_file_path.c_str(),
-            error_msg.c_str());
+        auto ret = ReadConfig(
+            config_file_path, [&company_name](const std::string& soname) -> Result<void> {
+              if (android::base::StartsWith(soname, "lib") &&
+                  android::base::EndsWith(soname, "." + company_name + ".so")) {
+                return {};
+              } else {
+                return Errorf("Library name \"{}\" does not end with the company name {}.", soname,
+                              company_name);
+              }
+            });
+        if (ret) {
+          sonames->insert(sonames->end(), ret->begin(), ret->end());
+        } else {
+          LOG_ALWAYS_FATAL("Error reading public native library list from \"%s\": %s",
+                           config_file_path.c_str(), ret.error().message().c_str());
+        }
       }
     }
   }
@@ -187,16 +187,17 @@
 
 static std::string InitDefaultPublicLibraries() {
   std::string config_file = root_dir() + kDefaultPublicLibrariesFile;
-  std::vector<std::string> sonames;
-  std::string error_msg;
-  LOG_ALWAYS_FATAL_IF(!ReadConfig(config_file, &sonames, always_true, &error_msg),
-                      "Error reading public native library list from \"%s\": %s",
-                      config_file.c_str(), error_msg.c_str());
+  auto sonames = ReadConfig(config_file, always_true);
+  if (!sonames) {
+    LOG_ALWAYS_FATAL("Error reading public native library list from \"%s\": %s",
+                     config_file.c_str(), sonames.error().message().c_str());
+    return "";
+  }
 
   std::string additional_libs = additional_public_libraries();
   if (!additional_libs.empty()) {
     auto vec = base::Split(additional_libs, ":");
-    std::copy(vec.begin(), vec.end(), std::back_inserter(sonames));
+    std::copy(vec.begin(), vec.end(), std::back_inserter(*sonames));
   }
 
   // Remove the public libs in the runtime namespace.
@@ -216,18 +217,18 @@
       continue;
     }
 
-    auto it = std::find(sonames.begin(), sonames.end(), lib_name);
-    if (it != sonames.end()) {
-      sonames.erase(it);
+    auto it = std::find(sonames->begin(), sonames->end(), lib_name);
+    if (it != sonames->end()) {
+      sonames->erase(it);
     }
   }
 
   // Remove the public libs in the nnapi namespace.
-  auto it = std::find(sonames.begin(), sonames.end(), kNeuralNetworksApexPublicLibrary);
-  if (it != sonames.end()) {
-    sonames.erase(it);
+  auto it = std::find(sonames->begin(), sonames->end(), kNeuralNetworksApexPublicLibrary);
+  if (it != sonames->end()) {
+    sonames->erase(it);
   }
-  return android::base::Join(sonames, ':');
+  return android::base::Join(*sonames, ':');
 }
 
 static std::string InitRuntimePublicLibraries() {
@@ -243,9 +244,11 @@
 
 static std::string InitVendorPublicLibraries() {
   // This file is optional, quietly ignore if the file does not exist.
-  std::vector<std::string> sonames;
-  ReadConfig(kVendorPublicLibrariesFile, &sonames, always_true, nullptr);
-  return android::base::Join(sonames, ':');
+  auto sonames = ReadConfig(kVendorPublicLibrariesFile, always_true);
+  if (!sonames) {
+    return "";
+  }
+  return android::base::Join(*sonames, ':');
 }
 
 // read /system/etc/public.libraries-<companyname>.txt and
@@ -262,17 +265,23 @@
 static std::string InitLlndkLibraries() {
   std::string config_file = kLlndkLibrariesFile;
   InsertVndkVersionStr(&config_file);
-  std::vector<std::string> sonames;
-  ReadConfig(config_file, &sonames, always_true, nullptr);
-  return android::base::Join(sonames, ':');
+  auto sonames = ReadConfig(config_file, always_true);
+  if (!sonames) {
+    LOG_ALWAYS_FATAL("%s", sonames.error().message().c_str());
+    return "";
+  }
+  return android::base::Join(*sonames, ':');
 }
 
 static std::string InitVndkspLibraries() {
   std::string config_file = kVndkLibrariesFile;
   InsertVndkVersionStr(&config_file);
-  std::vector<std::string> sonames;
-  ReadConfig(config_file, &sonames, always_true, nullptr);
-  return android::base::Join(sonames, ':');
+  auto sonames = ReadConfig(config_file, always_true);
+  if (!sonames) {
+    LOG_ALWAYS_FATAL("%s", sonames.error().message().c_str());
+    return "";
+  }
+  return android::base::Join(*sonames, ':');
 }
 
 static std::string InitNeuralNetworksPublicLibraries() {
diff --git a/libstats/include/stats_event_list.h b/libstats/include/stats_event_list.h
index 845a197..b7ada0c 100644
--- a/libstats/include/stats_event_list.h
+++ b/libstats/include/stats_event_list.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_STATS_LOG_STATS_EVENT_LIST_H
-#define ANDROID_STATS_LOG_STATS_EVENT_LIST_H
+#pragma once
 
 #include <log/log_event_list.h>
 #include <sys/uio.h>
@@ -133,7 +132,6 @@
         return *this;
     }
 
-#if defined(_USING_LIBCXX)
     stats_event_list& operator<<(const std::string& value) {
         int retval = android_log_write_string8_len(ctx, value.data(), value.length());
         if (retval < 0) {
@@ -141,7 +139,6 @@
         }
         return *this;
     }
-#endif
 
     stats_event_list& operator<<(float value) {
         int retval = android_log_write_float32(ctx, value);
@@ -203,7 +200,6 @@
         return ret >= 0;
     }
 
-#if defined(_USING_LIBCXX)
     bool AppendString(const std::string& value) {
         int retval = android_log_write_string8_len(ctx, value.data(), value.length());
         if (retval < 0) {
@@ -219,7 +215,6 @@
         }
         return ret;
     }
-#endif
 
     bool AppendFloat(float value) {
         int retval = android_log_write_float32(ctx, value);
@@ -253,4 +248,3 @@
 };
 
 #endif
-#endif  // ANDROID_STATS_LOG_STATS_EVENT_LIST_H
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index 24a745a..b08e061 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -452,48 +452,6 @@
     *codePoint |= 0x3F & byte;
 }
 
-size_t utf8_to_utf32_length(const char *src, size_t src_len)
-{
-    if (src == nullptr || src_len == 0) {
-        return 0;
-    }
-    size_t ret = 0;
-    const char* cur;
-    const char* end;
-    size_t num_to_skip;
-    for (cur = src, end = src + src_len, num_to_skip = 1;
-         cur < end;
-         cur += num_to_skip, ret++) {
-        const char first_char = *cur;
-        num_to_skip = 1;
-        if ((first_char & 0x80) == 0) {  // ASCII
-            continue;
-        }
-        int32_t mask;
-
-        for (mask = 0x40; (first_char & mask); num_to_skip++, mask >>= 1) {
-        }
-    }
-    return ret;
-}
-
-void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst)
-{
-    if (src == nullptr || src_len == 0 || dst == nullptr) {
-        return;
-    }
-
-    const char* cur = src;
-    const char* const end = src + src_len;
-    char32_t* cur_utf32 = dst;
-    while (cur < end) {
-        size_t num_read;
-        *cur_utf32++ = static_cast<char32_t>(utf32_at_internal(cur, &num_read));
-        cur += num_read;
-    }
-    *cur_utf32 = 0;
-}
-
 static inline uint32_t utf8_to_utf32_codepoint(const uint8_t *src, size_t length)
 {
     uint32_t unicode;
diff --git a/libutils/include/utils/Unicode.h b/libutils/include/utils/Unicode.h
index a2aaa47..fc6712d 100644
--- a/libutils/include/utils/Unicode.h
+++ b/libutils/include/utils/Unicode.h
@@ -129,18 +129,6 @@
 ssize_t utf8_length(const char *src);
 
 /**
- * Measure the length of a UTF-32 string.
- */
-size_t utf8_to_utf32_length(const char *src, size_t src_len);
-
-/**
- * Stores a UTF-32 string converted from "src" in "dst". "dst" must be large
- * enough to store the entire converted string as measured by
- * utf8_to_utf32_length plus space for a NUL terminator.
- */
-void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst);
-
-/**
  * Returns the UTF-16 length of UTF-8 string "src". Returns -1 in case
  * it's invalid utf8. No buffer over-read occurs because of bound checks. Using overreadIsFatal you
  * can ask to log a message and fail in case the invalid utf8 could have caused an override if no
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 6e38d95..6507711 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -899,6 +899,11 @@
             case 0:
                 // only long options
                 if (long_options[option_index].name == pid_str) {
+                    if (pid != 0) {
+                        logcat_panic(context, HELP_TRUE, "Only supports one PID argument.\n");
+                        goto exit;
+                    }
+
                     // ToDo: determine runtime PID_MAX?
                     if (!getSizeTArg(optarg, &pid, 1)) {
                         logcat_panic(context, HELP_TRUE, "%s %s out of range\n",
diff --git a/logcat/logcatd.rc b/logcat/logcatd.rc
index 25104eb..26c9de3 100644
--- a/logcat/logcatd.rc
+++ b/logcat/logcatd.rc
@@ -13,28 +13,13 @@
     # expect /init to report failure if property empty (default)
     setprop persist.logd.logpersistd.size ${persist.logd.logpersistd.count}
 
-on property:persist.logd.logpersistd.size=256
-    setprop persist.logd.logpersistd.size ""
-    setprop logd.logpersistd.size ""
-
 on property:persist.logd.logpersistd.size=*
-    # expect /init to report failure if property empty (default)
     setprop logd.logpersistd.size ${persist.logd.logpersistd.size}
 
-on property:persist.logd.logpersistd.rotate_kbytes=1024
-    setprop persist.logd.logpersistd.rotate_kbytes ""
-    setprop logd.logpersistd.rotate_kbytes ""
-
 on property:persist.logd.logpersistd.rotate_kbytes=*
-   # expect /init to report failure if property empty (default)
-   setprop logd.logpersistd.rotate_kbytes ${persist.logd.logpersistd.rotate_kbytes}
-
-on property:persist.logd.logpersistd.buffer=all
-    setprop persist.logd.logpersistd.buffer ""
-    setprop logd.logpersistd.buffer ""
+    setprop logd.logpersistd.rotate_kbytes ${persist.logd.logpersistd.rotate_kbytes}
 
 on property:persist.logd.logpersistd.buffer=*
-    # expect /init to report failure if property empty (default)
     setprop logd.logpersistd.buffer ${persist.logd.logpersistd.buffer}
 
 on property:persist.logd.logpersistd=logcatd
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 912bbdc..4559050 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -378,6 +378,62 @@
 		echo $(lib).so >> $@;)
 
 #######################################
+# vndkcore.libraries.txt
+include $(CLEAR_VARS)
+LOCAL_MODULE := vndkcore.libraries.txt
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
+LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_CORE_LIBRARIES := $(VNDK_CORE_LIBRARIES)
+$(LOCAL_BUILT_MODULE):
+	@echo "Generate: $@"
+	@mkdir -p $(dir $@)
+	$(hide) echo -n > $@
+	$(hide) $(foreach lib,$(PRIVATE_VNDK_CORE_LIBRARIES), \
+		echo $(lib).so >> $@;)
+
+#######################################
+# vndkprivate.libraries.txt
+include $(CLEAR_VARS)
+LOCAL_MODULE := vndkprivate.libraries.txt
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
+LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_PRIVATE_LIBRARIES := $(VNDK_PRIVATE_LIBRARIES)
+$(LOCAL_BUILT_MODULE):
+	@echo "Generate: $@"
+	@mkdir -p $(dir $@)
+	$(hide) echo -n > $@
+	$(hide) $(foreach lib,$(PRIVATE_VNDK_PRIVATE_LIBRARIES), \
+		echo $(lib).so >> $@;)
+
+#######################################
+# sanitizer.libraries.txt
+include $(CLEAR_VARS)
+LOCAL_MODULE := sanitizer.libraries.txt
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
+LOCAL_MODULE_STEM := $(LOCAL_MODULE)
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): PRIVATE_SANITIZER_RUNTIME_LIBRARIES := $(addsuffix .so,\
+  $(ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
+  $(HWADDRESS_SANITIZER_RUNTIME_LIBRARY) \
+  $(UBSAN_RUNTIME_LIBRARY) \
+  $(TSAN_RUNTIME_LIBRARY) \
+  $(2ND_ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
+  $(2ND_HWADDRESS_SANITIZER_RUNTIME_LIBRARY) \
+  $(2ND_UBSAN_RUNTIME_LIBRARY) \
+  $(2ND_TSAN_RUNTIME_LIBRARY))
+$(LOCAL_BUILT_MODULE):
+	@echo "Generate: $@"
+	@mkdir -p $(dir $@)
+	$(hide) echo -n > $@
+	$(hide) $(foreach lib,$(PRIVATE_SANITIZER_RUNTIME_LIBRARIES), \
+		echo $(lib) >> $@;)
+
+#######################################
 # adb_debug.prop in debug ramdisk
 include $(CLEAR_VARS)
 LOCAL_MODULE := adb_debug.prop
diff --git a/rootdir/init.rc b/rootdir/init.rc
index d22e9a7..86d8042 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -415,6 +415,7 @@
     chmod 0700 /metadata/vold
     mkdir /metadata/password_slots 0771 root system
     mkdir /metadata/ota 0700 root system
+    mkdir /metadata/ota/snapshots 0700 root system
 
     mkdir /metadata/apex 0700 root system
     mkdir /metadata/apex/sessions 0700 root system
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index bac3dc3..694b50e 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -13,6 +13,7 @@
         "auditctl",
         "awk",
         "bzip2",
+        "ldd",
         "logwrapper",
         "mini-keyctl",
         "mkshrc",
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
index 6897663..1d934a2 100644
--- a/storaged/storaged.cpp
+++ b/storaged/storaged.cpp
@@ -164,8 +164,10 @@
 }
 
 void storaged_t::add_user_ce(userid_t user_id) {
-    load_proto(user_id);
-    proto_loaded[user_id] = true;
+    if (!proto_loaded[user_id]) {
+        load_proto(user_id);
+        proto_loaded[user_id] = true;
+    }
 }
 
 void storaged_t::remove_user_ce(userid_t user_id) {
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)