Merge "libbase: fix libbase_test.logging.StdioLogger."
diff --git a/OWNERS b/OWNERS
index 1d319af..682a067 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,5 +1 @@
 enh@google.com
-per-file libsysutils/src/Netlink* = ek@google.com
-per-file libsysutils/src/Netlink* = lorenzo@google.com
-per-file libsysutils/include/sysutils/Netlink* = ek@google.com
-per-file libsysutils/include/sysutils/Netlink* = lorenzo@google.com
diff --git a/adb/daemon/remount_service.cpp b/adb/daemon/remount_service.cpp
index eb46903..830b35d 100644
--- a/adb/daemon/remount_service.cpp
+++ b/adb/daemon/remount_service.cpp
@@ -29,10 +29,13 @@
 #include <sys/vfs.h>
 #include <unistd.h>
 
+#include <set>
 #include <string>
 #include <vector>
 
 #include <android-base/properties.h>
+#include <bootloader_message/bootloader_message.h>
+#include <cutils/android_reboot.h>
 #include <ext4_utils/ext4_utils.h>
 
 #include "adb.h"
@@ -142,7 +145,7 @@
     return true;
 }
 
-static bool remount_partition(int fd, const char* dir, std::vector<std::string>& dedup) {
+static bool remount_partition(int fd, const char* dir) {
     if (!directory_exists(dir)) {
         return true;
     }
@@ -168,32 +171,83 @@
         return false;
     }
     if (mount(dev.c_str(), dir, "none", MS_REMOUNT, nullptr) == -1) {
-        if (errno == EROFS && fs_has_shared_blocks(dev.c_str())) {
-            if (!can_unshare_blocks(fd, dev.c_str())) {
-                return false;
-            }
-            // We return true so remount_service() can detect that the only
-            // failure was deduplicated filesystems.
-            dedup.push_back(dev);
-            return true;
-        }
         WriteFdFmt(fd, "remount of the %s superblock failed: %s\n", dir, strerror(errno));
         return false;
     }
     return true;
 }
 
+static void reboot_for_remount(int fd, bool need_fsck) {
+    std::string reboot_cmd = "reboot";
+    if (need_fsck) {
+        const std::vector<std::string> options = {"--fsck_unshare_blocks"};
+        std::string err;
+        if (!write_bootloader_message(options, &err)) {
+            WriteFdFmt(fd, "Failed to set bootloader message: %s\n", err.c_str());
+            return;
+        }
+
+        WriteFdExactly(fd,
+                       "The device will now reboot to recovery and attempt "
+                       "un-deduplication.\n");
+        reboot_cmd = "reboot,recovery";
+    }
+
+    sync();
+    android::base::SetProperty(ANDROID_RB_PROPERTY, reboot_cmd.c_str());
+}
+
 void remount_service(int fd, void* cookie) {
+    unique_fd close_fd(fd);
+
+    const char* cmd = reinterpret_cast<const char*>(cookie);
+    bool user_requested_reboot = cmd && !strcmp(cmd, "-R");
+
     if (getuid() != 0) {
         WriteFdExactly(fd, "Not running as root. Try \"adb root\" first.\n");
-        adb_close(fd);
         return;
     }
 
     bool system_verified = !(android::base::GetProperty("partition.system.verified", "").empty());
     bool vendor_verified = !(android::base::GetProperty("partition.vendor.verified", "").empty());
 
-    if (system_verified || vendor_verified) {
+    std::vector<std::string> partitions = {"/odm", "/oem", "/product", "/vendor"};
+    if (android::base::GetBoolProperty("ro.build.system_root_image", false)) {
+        partitions.push_back("/");
+    } else {
+        partitions.push_back("/system");
+    }
+
+    // Find partitions that are deduplicated, and can be un-deduplicated.
+    std::set<std::string> dedup;
+    for (const auto& partition : partitions) {
+        std::string dev = find_mount(partition.c_str(), partition == "/");
+        if (dev.empty() || !fs_has_shared_blocks(dev.c_str())) {
+            continue;
+        }
+        if (can_unshare_blocks(fd, dev.c_str())) {
+            dedup.emplace(partition);
+        }
+    }
+
+    bool verity_enabled = (system_verified || vendor_verified);
+
+    // Reboot now if the user requested it (and an operation needs a reboot).
+    if (user_requested_reboot) {
+        if (!dedup.empty() || verity_enabled) {
+            if (verity_enabled) {
+                set_verity_enabled_state_service(fd, nullptr);
+            }
+            reboot_for_remount(fd, !dedup.empty());
+            return;
+        }
+        WriteFdExactly(fd, "No reboot needed, skipping -R.\n");
+    }
+
+    // If we need to disable-verity, but we also need to perform a recovery
+    // fsck for deduplicated partitions, hold off on warning about verity. We
+    // can handle both verity and the recovery fsck in the same reboot cycle.
+    if (verity_enabled && dedup.empty()) {
         // Allow remount but warn of likely bad effects
         bool both = system_verified && vendor_verified;
         WriteFdFmt(fd,
@@ -206,32 +260,40 @@
                        "Use \"adb disable-verity\" to disable verity.\n"
                        "If you do not, remount may succeed, however, you will still "
                        "not be able to write to these volumes.\n");
+        WriteFdExactly(fd,
+                       "Alternately, use \"adb remount -R\" to disable verity "
+                       "and automatically reboot.\n");
     }
 
     bool success = true;
-    std::vector<std::string> dedup;
-    if (android::base::GetBoolProperty("ro.build.system_root_image", false)) {
-        success &= remount_partition(fd, "/", dedup);
-    } else {
-        success &= remount_partition(fd, "/system", dedup);
+    for (const auto& partition : partitions) {
+        // Don't try to remount partitions that need an fsck in recovery.
+        if (dedup.count(partition)) {
+            continue;
+        }
+        success &= remount_partition(fd, partition.c_str());
     }
-    success &= remount_partition(fd, "/odm", dedup);
-    success &= remount_partition(fd, "/oem", dedup);
-    success &= remount_partition(fd, "/product", dedup);
-    success &= remount_partition(fd, "/vendor", dedup);
 
-    if (!success) {
-        WriteFdExactly(fd, "remount failed\n");
-    } else if (dedup.empty()) {
-        WriteFdExactly(fd, "remount succeeded\n");
-    } else {
+    if (!dedup.empty()) {
         WriteFdExactly(fd,
-                       "The following partitions are deduplicated and could "
-                       "not be remounted:\n");
+                       "The following partitions are deduplicated and cannot "
+                       "yet be remounted:\n");
         for (const std::string& name : dedup) {
             WriteFdFmt(fd, "  %s\n", name.c_str());
         }
+
+        WriteFdExactly(fd,
+                       "To reboot and un-deduplicate the listed partitions, "
+                       "please retry with adb remount -R.\n");
+        if (system_verified || vendor_verified) {
+            WriteFdExactly(fd, "Note: verity will be automatically disabled after reboot.\n");
+        }
+        return;
     }
 
-    adb_close(fd);
+    if (!success) {
+        WriteFdExactly(fd, "remount failed\n");
+    } else {
+        WriteFdExactly(fd, "remount succeeded\n");
+    }
 }
diff --git a/adb/services.cpp b/adb/services.cpp
index 0b0c161..a757d90 100644
--- a/adb/services.cpp
+++ b/adb/services.cpp
@@ -291,7 +291,9 @@
     } else if(!strncmp(name, "sync:", 5)) {
         ret = create_service_thread("sync", file_sync_service, nullptr);
     } else if(!strncmp(name, "remount:", 8)) {
-        ret = create_service_thread("remount", remount_service, nullptr);
+        const char* options = name + strlen("remount:");
+        void* cookie = const_cast<void*>(reinterpret_cast<const void*>(options));
+        ret = create_service_thread("remount", remount_service, cookie);
     } else if(!strncmp(name, "reboot:", 7)) {
         void* arg = strdup(name + 7);
         if (arg == NULL) return -1;
diff --git a/bootstat/boot_reason_test.sh b/bootstat/boot_reason_test.sh
index 01b8948..8ed92a6 100755
--- a/bootstat/boot_reason_test.sh
+++ b/bootstat/boot_reason_test.sh
@@ -445,6 +445,10 @@
     *hw_reset* )                            var="hard,hw_reset" ;;
     *usb* )                                 var="cold,charger" ;;
     *rtc* )                                 var="cold,rtc" ;;
+    *2sec_reboot* )                         var="cold,rtc,2sec" ;;
+    *wdt_by_pass_pwk* )                     var="warm" ;;
+    wdt )                                   var="reboot" ;;
+    *tool_by_pass_pwk* )                    var="reboot,tool" ;;
     *bootloader* )                          var="bootloader" ;;
     * )                                     var="reboot" ;;
   esac
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index 1b13b21..d5fb047 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -201,7 +201,7 @@
     {"cold", 56},
     {"hard", 57},
     {"warm", 58},
-    // {"recovery", 59},  // Duplicate of enum 3 above. Immediate reuse possible.
+    {"reboot,kernel_power_off_charging__reboot_system", 59},  // Can not happen
     {"thermal-shutdown", 60},
     {"shutdown,thermal", 61},
     {"shutdown,battery", 62},
@@ -228,7 +228,7 @@
     {"2sec_reboot", 83},
     {"reboot,by_key", 84},
     {"reboot,longkey", 85},
-    {"reboot,2sec", 86},
+    {"reboot,2sec", 86},  // Deprecate in two years, replaced with cold,rtc,2sec
     {"shutdown,thermal,battery", 87},
     {"reboot,its_just_so_hard", 88},  // produced by boot_reason_test
     {"reboot,Its Just So Hard", 89},  // produced by boot_reason_test
@@ -306,6 +306,10 @@
     {"kernel_panic,sysrq,livelock,alarm", 161},   // llkd
     {"kernel_panic,sysrq,livelock,driver", 162},  // llkd
     {"kernel_panic,sysrq,livelock,zombie", 163},  // llkd
+    {"kernel_panic,modem", 164},
+    {"kernel_panic,adsp", 165},
+    {"kernel_panic,dsps", 166},
+    {"kernel_panic,wcnss", 167},
 };
 
 // Converts a string value representing the reason the system booted to an
@@ -702,6 +706,10 @@
         {"Corrupt kernel stack", "stack"},
         {"low stack detected", "stack"},
         {"corrupted stack end", "stack"},
+        {"subsys-restart: Resetting the SoC - modem crashed.", "modem"},
+        {"subsys-restart: Resetting the SoC - adsp crashed.", "adsp"},
+        {"subsys-restart: Resetting the SoC - dsps crashed.", "dsps"},
+        {"subsys-restart: Resetting the SoC - wcnss crashed.", "wcnss"},
     };
 
     ret = "kernel_panic";
@@ -782,7 +790,10 @@
         {"hard,hw_reset", "hw_reset"},
         {"cold,charger", "usb"},
         {"cold,rtc", "rtc"},
-        {"reboot,2sec", "2sec_reboot"},
+        {"cold,rtc,2sec", "2sec_reboot"},
+        {"!warm", "wdt_by_pass_pwk"},  // change flavour of blunt
+        {"!reboot", "^wdt$"},          // change flavour of blunt
+        {"reboot,tool", "tool_by_pass_pwk"},
         {"bootloader", ""},
     };
 
@@ -842,6 +853,10 @@
             ret = "reboot," + subReason;  // legitimize unknown reasons
           }
         }
+        // Some bootloaders shutdown results record in last kernel message.
+        if (!strcmp(ret.c_str(), "reboot,kernel_power_off_charging__reboot_system")) {
+          ret = "shutdown";
+        }
       }
 
       // Check for kernel panics, allowed to override reboot command.
@@ -1114,6 +1129,13 @@
   return android::base::boot_clock::now().time_since_epoch() - GetBootTimeOffset();
 }
 
+void SetSystemBootReason() {
+  const std::string bootloader_boot_reason(GetProperty(bootloader_reboot_reason_property));
+  const std::string system_boot_reason(BootReasonStrToReason(bootloader_boot_reason));
+  // Record the scrubbed system_boot_reason to the property
+  SetProperty(system_reboot_reason_property, system_boot_reason);
+}
+
 // Records several metrics related to the time it takes to boot the device,
 // including disambiguating boot time on encrypted or non-encrypted devices.
 void RecordBootComplete() {
@@ -1193,12 +1215,10 @@
   boot_event_store.AddBootEventWithValue("boot_reason", boot_reason);
 
   // Log the scrubbed system_boot_reason.
-  const std::string system_reason(BootReasonStrToReason(reason));
+  const std::string system_reason(GetProperty(system_reboot_reason_property));
   int32_t system_boot_reason = BootReasonStrToEnum(system_reason);
   boot_event_store.AddBootEventWithValue("system_boot_reason", system_boot_reason);
 
-  // Record the scrubbed system_boot_reason to the property
-  SetProperty(system_reboot_reason_property, system_reason);
   if (reason == "") {
     SetProperty(bootloader_reboot_reason_property, system_reason);
   }
@@ -1264,20 +1284,22 @@
 
   int option_index = 0;
   static const char value_str[] = "value";
+  static const char system_boot_reason_str[] = "set_system_boot_reason";
   static const char boot_complete_str[] = "record_boot_complete";
   static const char boot_reason_str[] = "record_boot_reason";
   static const char factory_reset_str[] = "record_time_since_factory_reset";
   static const struct option long_options[] = {
       // clang-format off
-      { "help",            no_argument,       NULL,   'h' },
-      { "log",             no_argument,       NULL,   'l' },
-      { "print",           no_argument,       NULL,   'p' },
-      { "record",          required_argument, NULL,   'r' },
-      { value_str,         required_argument, NULL,   0 },
-      { boot_complete_str, no_argument,       NULL,   0 },
-      { boot_reason_str,   no_argument,       NULL,   0 },
-      { factory_reset_str, no_argument,       NULL,   0 },
-      { NULL,              0,                 NULL,   0 }
+      { "help",                 no_argument,       NULL,   'h' },
+      { "log",                  no_argument,       NULL,   'l' },
+      { "print",                no_argument,       NULL,   'p' },
+      { "record",               required_argument, NULL,   'r' },
+      { value_str,              required_argument, NULL,   0 },
+      { system_boot_reason_str, no_argument,       NULL,   0 },
+      { boot_complete_str,      no_argument,       NULL,   0 },
+      { boot_reason_str,        no_argument,       NULL,   0 },
+      { factory_reset_str,      no_argument,       NULL,   0 },
+      { NULL,                   0,                 NULL,   0 }
       // clang-format on
   };
 
@@ -1293,6 +1315,8 @@
           // |optarg| is an external variable set by getopt representing
           // the option argument.
           value = optarg;
+        } else if (option_name == system_boot_reason_str) {
+          SetSystemBootReason();
         } else if (option_name == boot_complete_str) {
           RecordBootComplete();
         } else if (option_name == boot_reason_str) {
diff --git a/bootstat/bootstat.rc b/bootstat/bootstat.rc
index f06a38f..1300a27 100644
--- a/bootstat/bootstat.rc
+++ b/bootstat/bootstat.rc
@@ -68,8 +68,9 @@
 
 # Record boot complete metrics.
 on property:sys.boot_completed=1 && property:sys.logbootcomplete=1
+    # Converts bootloader boot reason to system boot reason
     # Record boot_complete and related stats (decryption, etc).
     # Record the boot reason.
     # Record time since factory reset.
     # Log all boot events.
-    exec_background - system log -- /system/bin/bootstat --record_boot_complete --record_boot_reason --record_time_since_factory_reset -l
+    exec_background - system log -- /system/bin/bootstat --set_system_boot_reason --record_boot_complete --record_boot_reason --record_time_since_factory_reset -l
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index 4b32b9d..f31337d 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -197,6 +197,7 @@
     fprintf(stderr, "  LOG-FATAL             call libbase LOG(FATAL)\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "  SIGFPE                cause a SIGFPE\n");
+    fprintf(stderr, "  SIGILL                cause a SIGILL\n");
     fprintf(stderr, "  SIGSEGV               cause a SIGSEGV at address 0x0 (synonym: crash)\n");
     fprintf(stderr, "  SIGSEGV-non-null      cause a SIGSEGV at a non-zero address\n");
     fprintf(stderr, "  SIGSEGV-unmapped      mmap/munmap a region of memory and then attempt to access it\n");
@@ -268,6 +269,16 @@
     } else if (!strcasecmp(arg, "SIGFPE")) {
         raise(SIGFPE);
         return EXIT_SUCCESS;
+    } else if (!strcasecmp(arg, "SIGILL")) {
+#if defined(__aarch64__)
+      __asm__ volatile(".word 0\n");
+#elif defined(__arm__)
+      __asm__ volatile(".word 0xe7f0def0\n");
+#elif defined(__i386__) || defined(__x86_64__)
+      __asm__ volatile("ud2\n");
+#else
+#error
+#endif
     } else if (!strcasecmp(arg, "SIGTRAP")) {
         raise(SIGTRAP);
         return EXIT_SUCCESS;
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index e11be1e..433bb46 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -102,10 +102,17 @@
   if (!cause.empty()) _LOG(log, logtype::HEADER, "Cause: %s\n", cause.c_str());
 }
 
-static void dump_signal_info(log_t* log, const ThreadInfo& thread_info) {
-  char addr_desc[32]; // ", fault addr 0x1234"
+static void dump_signal_info(log_t* log, const ThreadInfo& thread_info, Memory* process_memory) {
+  char addr_desc[64];  // ", fault addr 0x1234"
   if (signal_has_si_addr(thread_info.siginfo)) {
-    snprintf(addr_desc, sizeof(addr_desc), "%p", thread_info.siginfo->si_addr);
+    void* addr = thread_info.siginfo->si_addr;
+    if (thread_info.siginfo->si_signo == SIGILL) {
+      uint32_t instruction = {};
+      process_memory->Read(reinterpret_cast<uint64_t>(addr), &instruction, sizeof(instruction));
+      snprintf(addr_desc, sizeof(addr_desc), "%p (*pc=%#08x)", addr, instruction);
+    } else {
+      snprintf(addr_desc, sizeof(addr_desc), "%p", addr);
+    }
   } else {
     snprintf(addr_desc, sizeof(addr_desc), "--------");
   }
@@ -418,7 +425,7 @@
   dump_thread_info(log, thread_info);
 
   if (thread_info.siginfo) {
-    dump_signal_info(log, thread_info);
+    dump_signal_info(log, thread_info, process_memory);
   }
 
   if (primary_thread) {
diff --git a/init/Android.bp b/init/Android.bp
index a3083c1..25877c0 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -170,6 +170,7 @@
     srcs: [
         "devices_test.cpp",
         "init_test.cpp",
+        "keychords_test.cpp",
         "persistent_properties_test.cpp",
         "property_service_test.cpp",
         "property_type_test.cpp",
@@ -235,7 +236,8 @@
         "epoll.cpp",
         "keychords.cpp",
         "import_parser.cpp",
-        "host_init_parser.cpp",
+        "host_import_parser.cpp",
+        "host_init_verifier.cpp",
         "host_init_stubs.cpp",
         "parser.cpp",
         "rlimit_parser.cpp",
@@ -248,7 +250,10 @@
     proto: {
         type: "lite",
     },
-    generated_headers: ["generated_stub_builtin_function_map"],
+    generated_headers: [
+        "generated_stub_builtin_function_map",
+        "generated_android_ids"
+    ],
     target: {
         android: {
             enabled: false,
diff --git a/init/action_parser.cpp b/init/action_parser.cpp
index f9b96eb..8a4b518 100644
--- a/init/action_parser.cpp
+++ b/init/action_parser.cpp
@@ -21,10 +21,6 @@
 
 #include "stable_properties.h"
 
-#if !defined(__ANDROID__)
-#include "host_init_stubs.h"
-#endif
-
 using android::base::GetBoolProperty;
 using android::base::StartsWith;
 
diff --git a/init/host_import_parser.cpp b/init/host_import_parser.cpp
new file mode 100644
index 0000000..faf6fc1
--- /dev/null
+++ b/init/host_import_parser.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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 "host_import_parser.h"
+
+#include <android-base/strings.h>
+
+using android::base::StartsWith;
+
+namespace android {
+namespace init {
+
+Result<Success> HostImportParser::ParseSection(std::vector<std::string>&& args,
+                                               const std::string& filename, int line) {
+    if (args.size() != 2) {
+        return Error() << "single argument needed for import\n";
+    }
+
+    auto import_path = args[1];
+
+    if (StartsWith(import_path, "/system") || StartsWith(import_path, "/product") ||
+        StartsWith(import_path, "/odm") || StartsWith(import_path, "/vendor")) {
+        import_path = out_dir_ + "/" + import_path;
+    } else {
+        import_path = out_dir_ + "/root/" + import_path;
+    }
+
+    return ImportParser::ParseSection({"import", import_path}, filename, line);
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/host_import_parser.h b/init/host_import_parser.h
new file mode 100644
index 0000000..e2980b2
--- /dev/null
+++ b/init/host_import_parser.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "import_parser.h"
+#include "parser.h"
+
+namespace android {
+namespace init {
+
+class HostImportParser : public ImportParser {
+  public:
+    HostImportParser(const std::string& out_dir, Parser* parser)
+        : ImportParser(parser), out_dir_(out_dir) {}
+    Result<Success> ParseSection(std::vector<std::string>&& args, const std::string& filename,
+                                 int line) override;
+
+  private:
+    std::string out_dir_;
+};
+
+}  // namespace init
+}  // namespace android
diff --git a/init/host_init_parser.cpp b/init/host_init_parser.cpp
deleted file mode 100644
index df497ea..0000000
--- a/init/host_init_parser.cpp
+++ /dev/null
@@ -1,82 +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.
-//
-
-#include <pwd.h>
-
-#include <android-base/logging.h>
-
-#include "action.h"
-#include "action_manager.h"
-#include "action_parser.h"
-#include "parser.h"
-#include "result.h"
-#include "service.h"
-
-// The host passwd file won't have the Android entries, so we fake success here.
-passwd* getpwnam(const char* login) {  // NOLINT: implementing bad function.
-    char dummy_buf[] = "dummy";
-    static passwd dummy_passwd = {
-        .pw_name = dummy_buf,
-        .pw_dir = dummy_buf,
-        .pw_shell = dummy_buf,
-        .pw_uid = 123,
-        .pw_gid = 123,
-    };
-    return &dummy_passwd;
-}
-
-namespace android {
-namespace init {
-
-static Result<Success> do_stub(const BuiltinArguments& args) {
-    return Success();
-}
-
-#include "generated_stub_builtin_function_map.h"
-
-int main(int argc, char** argv) {
-    android::base::InitLogging(argv, &android::base::StdioLogger);
-    if (argc != 2) {
-        LOG(ERROR) << "Usage: " << argv[0] << " <init file to parse>";
-        return -1;
-    }
-    const BuiltinFunctionMap function_map;
-    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));
-    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, nullptr));
-
-    size_t num_errors = 0;
-    if (!parser.ParseConfig(argv[1], &num_errors)) {
-        LOG(ERROR) << "Failed to find script";
-        return -1;
-    }
-    if (num_errors > 0) {
-        LOG(ERROR) << "Parse failed with " << num_errors << " errors";
-        return -1;
-    }
-    LOG(INFO) << "Parse success!";
-    return 0;
-}
-
-}  // namespace init
-}  // namespace android
-
-int main(int argc, char** argv) {
-    android::init::main(argc, argv);
-}
diff --git a/init/host_init_stubs.cpp b/init/host_init_stubs.cpp
index c40a254..2352fc7 100644
--- a/init/host_init_stubs.cpp
+++ b/init/host_init_stubs.cpp
@@ -16,6 +16,8 @@
 
 #include "host_init_stubs.h"
 
+#include <android-base/properties.h>
+
 // unistd.h
 int setgroups(size_t __size, const gid_t* __list) {
     return 0;
@@ -28,7 +30,11 @@
 std::string default_console = "/dev/console";
 
 // property_service.h
-uint32_t (*property_set)(const std::string& name, const std::string& value) = nullptr;
+uint32_t SetProperty(const std::string& key, const std::string& value) {
+    android::base::SetProperty(key, value);
+    return 0;
+}
+uint32_t (*property_set)(const std::string& name, const std::string& value) = SetProperty;
 uint32_t HandlePropertySet(const std::string&, const std::string&, const std::string&, const ucred&,
                            std::string*) {
     return 0;
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
new file mode 100644
index 0000000..d6884af
--- /dev/null
+++ b/init/host_init_verifier.cpp
@@ -0,0 +1,162 @@
+//
+// 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 <errno.h>
+#include <pwd.h>
+#include <stdio.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+
+#include "action.h"
+#include "action_manager.h"
+#include "action_parser.h"
+#include "host_import_parser.h"
+#include "host_init_stubs.h"
+#include "parser.h"
+#include "result.h"
+#include "service.h"
+
+#define EXCLUDE_FS_CONFIG_STRUCTURES
+#include "generated_android_ids.h"
+
+using namespace std::literals;
+
+using android::base::ParseInt;
+using android::base::ReadFileToString;
+using android::base::Split;
+
+static std::string out_dir;
+
+static std::vector<std::pair<std::string, int>> GetVendorPasswd() {
+    std::string passwd;
+    if (!ReadFileToString(out_dir + "/vendor/etc/passwd", &passwd)) {
+        return {};
+    }
+
+    std::vector<std::pair<std::string, int>> result;
+    auto passwd_lines = Split(passwd, "\n");
+    for (const auto& line : passwd_lines) {
+        auto split_line = Split(line, ":");
+        if (split_line.size() < 3) {
+            continue;
+        }
+        int uid = 0;
+        if (!ParseInt(split_line[2], &uid)) {
+            continue;
+        }
+        result.emplace_back(split_line[0], uid);
+    }
+    return result;
+}
+
+passwd* getpwnam(const char* login) {  // NOLINT: implementing bad function.
+    // This isn't thread safe, but that's okay for our purposes.
+    static char static_name[32] = "";
+    static char static_dir[32] = "/";
+    static char static_shell[32] = "/system/bin/sh";
+    static passwd static_passwd = {
+        .pw_name = static_name,
+        .pw_dir = static_dir,
+        .pw_shell = static_shell,
+        .pw_uid = 0,
+        .pw_gid = 0,
+    };
+
+    for (size_t n = 0; n < android_id_count; ++n) {
+        if (!strcmp(android_ids[n].name, login)) {
+            snprintf(static_name, sizeof(static_name), "%s", android_ids[n].name);
+            static_passwd.pw_uid = android_ids[n].aid;
+            static_passwd.pw_gid = android_ids[n].aid;
+            return &static_passwd;
+        }
+    }
+
+    static const auto vendor_passwd = GetVendorPasswd();
+
+    for (const auto& [name, uid] : vendor_passwd) {
+        if (name == login) {
+            snprintf(static_name, sizeof(static_name), "%s", name.c_str());
+            static_passwd.pw_uid = uid;
+            static_passwd.pw_gid = uid;
+            return &static_passwd;
+        }
+    }
+
+    errno = ENOENT;
+    return nullptr;
+}
+
+namespace android {
+namespace init {
+
+static Result<Success> do_stub(const BuiltinArguments& args) {
+    return Success();
+}
+
+#include "generated_stub_builtin_function_map.h"
+
+int main(int argc, char** argv) {
+    android::base::InitLogging(argv, &android::base::StdioLogger);
+    android::base::SetMinimumLogSeverity(android::base::ERROR);
+    if (argc != 3) {
+        LOG(ERROR) << "Usage: " << argv[0] << " <out directory> <properties>";
+        return -1;
+    }
+
+    out_dir = argv[1];
+
+    auto properties = Split(argv[2], ",");
+    for (const auto& property : properties) {
+        auto split_property = Split(property, "=");
+        if (split_property.size() != 2) {
+            continue;
+        }
+        property_set(split_property[0], split_property[1]);
+    }
+
+    const BuiltinFunctionMap function_map;
+    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));
+    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, nullptr));
+    parser.AddSectionParser("import", std::make_unique<HostImportParser>(out_dir, &parser));
+
+    if (!parser.ParseConfig(argv[1] + "/root/init.rc"s)) {
+        LOG(ERROR) << "Failed to find root init.rc script";
+        return -1;
+    }
+    if (parser.parse_error_count() > 0) {
+        LOG(ERROR) << "Init script parsing failed with " << parser.parse_error_count() << " errors";
+        return -1;
+    }
+    return 0;
+}
+
+}  // namespace init
+}  // namespace android
+
+int main(int argc, char** argv) {
+    android::init::main(argc, argv);
+}
diff --git a/init/import_parser.cpp b/init/import_parser.cpp
index e335fd1..fb3185e 100644
--- a/init/import_parser.cpp
+++ b/init/import_parser.cpp
@@ -41,14 +41,15 @@
     return Success();
 }
 
+Result<Success> ImportParser::ParseLineSection(std::vector<std::string>&&, int) {
+    return Error() << "Unexpected line found after import statement";
+}
+
 void ImportParser::EndFile() {
     auto current_imports = std::move(imports_);
     imports_.clear();
     for (const auto& [import, line_num] : current_imports) {
-        if (!parser_->ParseConfig(import)) {
-            PLOG(ERROR) << filename_ << ": " << line_num << ": Could not import file '" << import
-                        << "'";
-        }
+        parser_->ParseConfig(import);
     }
 }
 
diff --git a/init/import_parser.h b/init/import_parser.h
index 5a2f894..7bc72e6 100644
--- a/init/import_parser.h
+++ b/init/import_parser.h
@@ -30,6 +30,7 @@
     ImportParser(Parser* parser) : parser_(parser) {}
     Result<Success> ParseSection(std::vector<std::string>&& args, const std::string& filename,
                                  int line) override;
+    Result<Success> ParseLineSection(std::vector<std::string>&&, int) override;
     void EndFile() override;
 
   private:
diff --git a/init/init.cpp b/init/init.cpp
index 5652c5e..82648d9 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -553,22 +553,30 @@
     }
 }
 
-void HandleKeychord(int id) {
+void HandleKeychord(const std::vector<int>& keycodes) {
     // Only handle keychords if adb is enabled.
     std::string adb_enabled = android::base::GetProperty("init.svc.adbd", "");
-    if (adb_enabled == "running") {
-        Service* svc = ServiceList::GetInstance().FindService(id, &Service::keychord_id);
-        if (svc) {
-            LOG(INFO) << "Starting service '" << svc->name() << "' from keychord " << id;
+    if (adb_enabled != "running") {
+        LOG(WARNING) << "Not starting service for keychord " << android::base::Join(keycodes, ' ')
+                     << " because ADB is disabled";
+        return;
+    }
+
+    auto found = false;
+    for (const auto& service : ServiceList::GetInstance()) {
+        auto svc = service.get();
+        if (svc->keycodes() == keycodes) {
+            found = true;
+            LOG(INFO) << "Starting service '" << svc->name() << "' from keychord "
+                      << android::base::Join(keycodes, ' ');
             if (auto result = svc->Start(); !result) {
-                LOG(ERROR) << "Could not start service '" << svc->name() << "' from keychord " << id
-                           << ": " << result.error();
+                LOG(ERROR) << "Could not start service '" << svc->name() << "' from keychord "
+                           << android::base::Join(keycodes, ' ') << ": " << result.error();
             }
-        } else {
-            LOG(ERROR) << "Service for keychord " << id << " not found";
         }
-    } else {
-        LOG(WARNING) << "Not starting service for keychord " << id << " because ADB is disabled";
+    }
+    if (!found) {
+        LOG(ERROR) << "Service for keychord " << android::base::Join(keycodes, ' ') << " not found";
     }
 }
 
@@ -753,7 +761,7 @@
     am.QueueBuiltinAction(
         [&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {
             for (const auto& svc : ServiceList::GetInstance()) {
-                svc->set_keychord_id(keychords.GetId(svc->keycodes()));
+                keychords.Register(svc->keycodes());
             }
             keychords.Start(&epoll, HandleKeychord);
             return Success();
diff --git a/init/init_first_stage.cpp b/init/init_first_stage.cpp
index 34de640..db60ce1 100644
--- a/init/init_first_stage.cpp
+++ b/init/init_first_stage.cpp
@@ -123,7 +123,7 @@
     bool enabled = false;
     import_kernel_cmdline(
         false, [&enabled](const std::string& key, const std::string& value, bool in_qemu) {
-            if (key == "androidboot.lrap" && value == "1") {
+            if (key == "androidboot.logical_partitions" && value == "1") {
                 enabled = true;
             }
         });
diff --git a/init/keychords.cpp b/init/keychords.cpp
index 9aa8b2a..1af06dd 100644
--- a/init/keychords.cpp
+++ b/init/keychords.cpp
@@ -37,7 +37,7 @@
 namespace android {
 namespace init {
 
-Keychords::Keychords() : epoll_(nullptr), count_(0), inotify_fd_(-1) {}
+Keychords::Keychords() : epoll_(nullptr), inotify_fd_(-1) {}
 
 Keychords::~Keychords() noexcept {
     if (inotify_fd_ >= 0) {
@@ -108,23 +108,22 @@
     }
 }
 
-Keychords::Entry::Entry(const std::vector<int>& keycodes, int id)
-    : keycodes(keycodes), id(id), notified(false) {}
+Keychords::Entry::Entry() : notified(false) {}
 
 void Keychords::LambdaCheck() {
-    for (auto& e : entries_) {
+    for (auto& [keycodes, entry] : entries_) {
         auto found = true;
-        for (auto& code : e.keycodes) {
+        for (auto& code : keycodes) {
             if (!current_.GetBit(code)) {
-                e.notified = false;
+                entry.notified = false;
                 found = false;
                 break;
             }
         }
         if (!found) continue;
-        if (e.notified) continue;
-        e.notified = true;
-        handler_(e.id);
+        if (entry.notified) continue;
+        entry.notified = true;
+        handler_(keycodes);
     }
 }
 
@@ -158,8 +157,8 @@
 #endif
 
     Keychords::Mask mask;
-    for (auto& e : entries_) {
-        for (auto& code : e.keycodes) {
+    for (auto& [keycodes, entry] : entries_) {
+        for (auto& code : keycodes) {
             mask.resize(code);
             mask.SetBit(code);
         }
@@ -271,17 +270,15 @@
     }
 }
 
-int Keychords::GetId(const std::vector<int>& keycodes) {
-    if (keycodes.empty()) return 0;
-    ++count_;
-    entries_.emplace_back(Entry(keycodes, count_));
-    return count_;
+void Keychords::Register(const std::vector<int>& keycodes) {
+    if (keycodes.empty()) return;
+    entries_.try_emplace(keycodes, Entry());
 }
 
-void Keychords::Start(Epoll* epoll, std::function<void(int)> handler) {
+void Keychords::Start(Epoll* epoll, std::function<void(const std::vector<int>&)> handler) {
     epoll_ = epoll;
     handler_ = handler;
-    if (count_) GeteventOpenDevice();
+    if (entries_.size()) GeteventOpenDevice();
 }
 
 }  // namespace init
diff --git a/init/keychords.h b/init/keychords.h
index 74a9195..00ed205 100644
--- a/init/keychords.h
+++ b/init/keychords.h
@@ -36,8 +36,8 @@
     Keychords& operator=(Keychords&&) = delete;
     ~Keychords() noexcept;
 
-    int GetId(const std::vector<int>& keycodes);
-    void Start(Epoll* epoll, std::function<void(int)> handler);
+    void Register(const std::vector<int>& keycodes);
+    void Start(Epoll* epoll, std::function<void(const std::vector<int>&)> handler);
 
   private:
     // Bit management
@@ -65,10 +65,8 @@
     };
 
     struct Entry {
-        Entry(const std::vector<int>& keycodes, int id);
+        Entry();
 
-        const std::vector<int> keycodes;
-        const int id;
         bool notified;
     };
 
@@ -84,12 +82,11 @@
     void GeteventCloseDevice(const std::string& device);
 
     Epoll* epoll_;
-    std::function<void(int)> handler_;
+    std::function<void(const std::vector<int>&)> handler_;
 
     std::map<std::string, int> registration_;
 
-    int count_;
-    std::vector<Entry> entries_;
+    std::map<const std::vector<int>, Entry> entries_;
 
     Mask current_;
 
diff --git a/init/keychords_test.cpp b/init/keychords_test.cpp
new file mode 100644
index 0000000..c8c47a8
--- /dev/null
+++ b/init/keychords_test.cpp
@@ -0,0 +1,348 @@
+/*
+ * 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 "keychords.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <linux/uinput.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <chrono>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+
+#include "epoll.h"
+
+using namespace std::chrono_literals;
+
+namespace android {
+namespace init {
+
+namespace {
+
+// This class is used to inject keys.
+class EventHandler {
+  public:
+    EventHandler();
+    EventHandler(const EventHandler&) = delete;
+    EventHandler(EventHandler&&);
+    EventHandler& operator=(const EventHandler&) = delete;
+    EventHandler& operator=(EventHandler&&);
+    ~EventHandler() noexcept;
+
+    bool init();
+
+    bool send(struct input_event& e);
+    bool send(uint16_t type, uint16_t code, uint16_t value);
+    bool send(uint16_t code, bool value);
+
+  private:
+    int fd_;
+};
+
+EventHandler::EventHandler() : fd_(-1) {}
+
+EventHandler::EventHandler(EventHandler&& rval) : fd_(rval.fd_) {
+    rval.fd_ = -1;
+}
+
+EventHandler& EventHandler::operator=(EventHandler&& rval) {
+    fd_ = rval.fd_;
+    rval.fd_ = -1;
+    return *this;
+}
+
+EventHandler::~EventHandler() {
+    if (fd_ == -1) return;
+    ::ioctl(fd_, UI_DEV_DESTROY);
+    ::close(fd_);
+}
+
+bool EventHandler::init() {
+    if (fd_ != -1) return true;
+    auto fd = TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK | O_CLOEXEC));
+    if (fd == -1) return false;
+    if (::ioctl(fd, UI_SET_EVBIT, EV_KEY) == -1) {
+        ::close(fd);
+        return false;
+    }
+
+    static const struct uinput_user_dev u = {
+        .name = "com.google.android.init.test",
+        .id.bustype = BUS_VIRTUAL,
+        .id.vendor = 0x1AE0,   // Google
+        .id.product = 0x494E,  // IN
+        .id.version = 1,
+    };
+    if (TEMP_FAILURE_RETRY(::write(fd, &u, sizeof(u))) != sizeof(u)) {
+        ::close(fd);
+        return false;
+    }
+
+    // all keys
+    for (uint16_t i = 0; i < KEY_MAX; ++i) {
+        if (::ioctl(fd, UI_SET_KEYBIT, i) == -1) {
+            ::close(fd);
+            return false;
+        }
+    }
+    if (::ioctl(fd, UI_DEV_CREATE) == -1) {
+        ::close(fd);
+        return false;
+    }
+    fd_ = fd;
+    return true;
+}
+
+bool EventHandler::send(struct input_event& e) {
+    gettimeofday(&e.time, nullptr);
+    return TEMP_FAILURE_RETRY(::write(fd_, &e, sizeof(e))) == sizeof(e);
+}
+
+bool EventHandler::send(uint16_t type, uint16_t code, uint16_t value) {
+    struct input_event e = {.type = type, .code = code, .value = value};
+    return send(e);
+}
+
+bool EventHandler::send(uint16_t code, bool value) {
+    return (code < KEY_MAX) && init() && send(EV_KEY, code, value) && send(EV_SYN, SYN_REPORT, 0);
+}
+
+std::string InitFds(const char* prefix, pid_t pid = getpid()) {
+    std::string ret;
+
+    std::string init_fds("/proc/");
+    init_fds += std::to_string(pid) + "/fd";
+    std::unique_ptr<DIR, decltype(&closedir)> fds(opendir(init_fds.c_str()), closedir);
+    if (!fds) return ret;
+
+    dirent* entry;
+    while ((entry = readdir(fds.get()))) {
+        if (entry->d_name[0] == '.') continue;
+        std::string devname = init_fds + '/' + entry->d_name;
+        char buf[256];
+        auto retval = readlink(devname.c_str(), buf, sizeof(buf) - 1);
+        if ((retval < 0) || (size_t(retval) >= (sizeof(buf) - 1))) continue;
+        buf[retval] = '\0';
+        if (!android::base::StartsWith(buf, prefix)) continue;
+        if (ret.size() != 0) ret += ",";
+        ret += buf;
+    }
+    return ret;
+}
+
+std::string InitInputFds() {
+    return InitFds("/dev/input/");
+}
+
+std::string InitInotifyFds() {
+    return InitFds("anon_inode:inotify");
+}
+
+// NB: caller (this series of tests, or conversely the service parser in init)
+// is responsible for validation, sorting and uniqueness of the chords, so no
+// fuzzing is advised.
+
+const std::vector<int> escape_chord = {KEY_ESC};
+const std::vector<int> triple1_chord = {KEY_BACKSPACE, KEY_VOLUMEDOWN, KEY_VOLUMEUP};
+const std::vector<int> triple2_chord = {KEY_VOLUMEDOWN, KEY_VOLUMEUP, KEY_BACK};
+
+const std::vector<const std::vector<int>> empty_chords;
+const std::vector<const std::vector<int>> chords = {
+    escape_chord,
+    triple1_chord,
+    triple2_chord,
+};
+
+class TestFrame {
+  public:
+    TestFrame(const std::vector<const std::vector<int>>& chords, EventHandler* ev = nullptr);
+
+    void RelaxForMs(std::chrono::milliseconds wait = 1ms);
+
+    void SetChord(int key, bool value = true);
+    void SetChords(const std::vector<int>& chord, bool value = true);
+    void ClrChord(int key);
+    void ClrChords(const std::vector<int>& chord);
+
+    bool IsOnlyChord(const std::vector<int>& chord) const;
+    bool IsNoChord() const;
+    bool IsChord(const std::vector<int>& chord) const;
+    void WaitForChord(const std::vector<int>& chord);
+
+    std::string Format() const;
+
+  private:
+    static std::string Format(const std::vector<const std::vector<int>>& chords);
+
+    Epoll epoll_;
+    Keychords keychords_;
+    std::vector<const std::vector<int>> keycodes_;
+    EventHandler* ev_;
+};
+
+TestFrame::TestFrame(const std::vector<const std::vector<int>>& chords, EventHandler* ev)
+    : ev_(ev) {
+    if (!epoll_.Open()) return;
+    for (const auto& keycodes : chords) keychords_.Register(keycodes);
+    keychords_.Start(&epoll_, [this](const std::vector<int>& keycodes) {
+        this->keycodes_.emplace_back(keycodes);
+    });
+}
+
+void TestFrame::RelaxForMs(std::chrono::milliseconds wait) {
+    epoll_.Wait(wait);
+}
+
+void TestFrame::SetChord(int key, bool value) {
+    ASSERT_TRUE(!!ev_);
+    RelaxForMs();
+    EXPECT_TRUE(ev_->send(key, value));
+}
+
+void TestFrame::SetChords(const std::vector<int>& chord, bool value) {
+    ASSERT_TRUE(!!ev_);
+    for (auto& key : chord) SetChord(key, value);
+    RelaxForMs();
+}
+
+void TestFrame::ClrChord(int key) {
+    ASSERT_TRUE(!!ev_);
+    SetChord(key, false);
+}
+
+void TestFrame::ClrChords(const std::vector<int>& chord) {
+    ASSERT_TRUE(!!ev_);
+    SetChords(chord, false);
+}
+
+bool TestFrame::IsOnlyChord(const std::vector<int>& chord) const {
+    auto ret = false;
+    for (const auto& keycode : keycodes_) {
+        if (keycode != chord) return false;
+        ret = true;
+    }
+    return ret;
+}
+
+bool TestFrame::IsNoChord() const {
+    return keycodes_.empty();
+}
+
+bool TestFrame::IsChord(const std::vector<int>& chord) const {
+    for (const auto& keycode : keycodes_) {
+        if (keycode == chord) return true;
+    }
+    return false;
+}
+
+void TestFrame::WaitForChord(const std::vector<int>& chord) {
+    for (int retry = 1000; retry && !IsChord(chord); --retry) RelaxForMs();
+}
+
+std::string TestFrame::Format(const std::vector<const std::vector<int>>& chords) {
+    std::string ret("{");
+    if (!chords.empty()) {
+        ret += android::base::Join(chords.front(), ' ');
+        for (auto it = std::next(chords.begin()); it != chords.end(); ++it) {
+            ret += ',';
+            ret += android::base::Join(*it, ' ');
+        }
+    }
+    return ret + '}';
+}
+
+std::string TestFrame::Format() const {
+    return Format(keycodes_);
+}
+
+}  // namespace
+
+TEST(keychords, not_instantiated) {
+    TestFrame test_frame(empty_chords);
+    EXPECT_TRUE(InitInotifyFds().size() == 0);
+}
+
+TEST(keychords, instantiated) {
+    // Test if a valid set of chords results in proper instantiation of the
+    // underlying mechanisms for /dev/input/ attachment.
+    TestFrame test_frame(chords);
+    EXPECT_TRUE(InitInotifyFds().size() != 0);
+}
+
+TEST(keychords, init_inotify) {
+    std::string before(InitInputFds());
+
+    TestFrame test_frame(chords);
+
+    EventHandler ev;
+    EXPECT_TRUE(ev.init());
+
+    for (int retry = 1000; retry && before == InitInputFds(); --retry) test_frame.RelaxForMs();
+    std::string after(InitInputFds());
+    EXPECT_NE(before, after);
+}
+
+TEST(keychords, key) {
+    EventHandler ev;
+    EXPECT_TRUE(ev.init());
+    TestFrame test_frame(chords, &ev);
+
+    test_frame.SetChords(escape_chord);
+    test_frame.WaitForChord(escape_chord);
+    test_frame.ClrChords(escape_chord);
+    EXPECT_TRUE(test_frame.IsOnlyChord(escape_chord))
+        << "expected only " << android::base::Join(escape_chord, ' ') << " got "
+        << test_frame.Format();
+}
+
+TEST(keychords, keys_in_series) {
+    EventHandler ev;
+    EXPECT_TRUE(ev.init());
+    TestFrame test_frame(chords, &ev);
+
+    for (auto& key : triple1_chord) {
+        test_frame.SetChord(key);
+        test_frame.ClrChord(key);
+    }
+    test_frame.WaitForChord(triple1_chord);
+    EXPECT_TRUE(test_frame.IsNoChord()) << "expected nothing got " << test_frame.Format();
+}
+
+TEST(keychords, keys_in_parallel) {
+    EventHandler ev;
+    EXPECT_TRUE(ev.init());
+    TestFrame test_frame(chords, &ev);
+
+    test_frame.SetChords(triple2_chord);
+    test_frame.WaitForChord(triple2_chord);
+    test_frame.ClrChords(triple2_chord);
+    EXPECT_TRUE(test_frame.IsOnlyChord(triple2_chord))
+        << "expected only " << android::base::Join(triple2_chord, ' ') << " got "
+        << test_frame.Format();
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/parser.cpp b/init/parser.cpp
index 4453aaa..ee6ee06 100644
--- a/init/parser.cpp
+++ b/init/parser.cpp
@@ -39,7 +39,7 @@
     line_callbacks_.emplace_back(prefix, callback);
 }
 
-void Parser::ParseData(const std::string& filename, const std::string& data, size_t* parse_errors) {
+void Parser::ParseData(const std::string& filename, const std::string& data) {
     // TODO: Use a parser with const input and remove this copy
     std::vector<char> data_copy(data.begin(), data.end());
     data_copy.push_back('\0');
@@ -57,7 +57,7 @@
         if (section_parser == nullptr) return;
 
         if (auto result = section_parser->EndSection(); !result) {
-            (*parse_errors)++;
+            parse_error_count_++;
             LOG(ERROR) << filename << ": " << section_start_line << ": " << result.error();
         }
 
@@ -81,7 +81,7 @@
                         end_section();
 
                         if (auto result = callback(std::move(args)); !result) {
-                            (*parse_errors)++;
+                            parse_error_count_++;
                             LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                         }
                         break;
@@ -94,16 +94,20 @@
                     if (auto result =
                             section_parser->ParseSection(std::move(args), filename, state.line);
                         !result) {
-                        (*parse_errors)++;
+                        parse_error_count_++;
                         LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                         section_parser = nullptr;
                     }
                 } else if (section_parser) {
                     if (auto result = section_parser->ParseLineSection(std::move(args), state.line);
                         !result) {
-                        (*parse_errors)++;
+                        parse_error_count_++;
                         LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                     }
+                } else {
+                    parse_error_count_++;
+                    LOG(ERROR) << filename << ": " << state.line
+                               << ": Invalid section keyword found";
                 }
                 args.clear();
                 break;
@@ -114,17 +118,17 @@
     }
 }
 
-bool Parser::ParseConfigFile(const std::string& path, size_t* parse_errors) {
+bool Parser::ParseConfigFile(const std::string& path) {
     LOG(INFO) << "Parsing file " << path << "...";
     android::base::Timer t;
     auto config_contents = ReadFile(path);
     if (!config_contents) {
-        LOG(ERROR) << "Unable to read config file '" << path << "': " << config_contents.error();
+        LOG(INFO) << "Unable to read config file '" << path << "': " << config_contents.error();
         return false;
     }
 
     config_contents->push_back('\n');  // TODO: fix parse_config.
-    ParseData(path, *config_contents, parse_errors);
+    ParseData(path, *config_contents);
     for (const auto& [section_name, section_parser] : section_parsers_) {
         section_parser->EndFile();
     }
@@ -133,11 +137,11 @@
     return true;
 }
 
-bool Parser::ParseConfigDir(const std::string& path, size_t* parse_errors) {
+bool Parser::ParseConfigDir(const std::string& path) {
     LOG(INFO) << "Parsing directory " << path << "...";
     std::unique_ptr<DIR, decltype(&closedir)> config_dir(opendir(path.c_str()), closedir);
     if (!config_dir) {
-        PLOG(ERROR) << "Could not import directory '" << path << "'";
+        PLOG(INFO) << "Could not import directory '" << path << "'";
         return false;
     }
     dirent* current_file;
@@ -153,7 +157,7 @@
     // Sort first so we load files in a consistent order (bug 31996208)
     std::sort(files.begin(), files.end());
     for (const auto& file : files) {
-        if (!ParseConfigFile(file, parse_errors)) {
+        if (!ParseConfigFile(file)) {
             LOG(ERROR) << "could not import file '" << file << "'";
         }
     }
@@ -161,16 +165,10 @@
 }
 
 bool Parser::ParseConfig(const std::string& path) {
-    size_t parse_errors;
-    return ParseConfig(path, &parse_errors);
-}
-
-bool Parser::ParseConfig(const std::string& path, size_t* parse_errors) {
-    *parse_errors = 0;
     if (is_dir(path.c_str())) {
-        return ParseConfigDir(path, parse_errors);
+        return ParseConfigDir(path);
     }
-    return ParseConfigFile(path, parse_errors);
+    return ParseConfigFile(path);
 }
 
 }  // namespace init
diff --git a/init/parser.h b/init/parser.h
index f6e237f..3501d8c 100644
--- a/init/parser.h
+++ b/init/parser.h
@@ -72,17 +72,19 @@
     Parser();
 
     bool ParseConfig(const std::string& path);
-    bool ParseConfig(const std::string& path, size_t* parse_errors);
     void AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser);
     void AddSingleLineParser(const std::string& prefix, LineCallback callback);
 
+    size_t parse_error_count() const { return parse_error_count_; }
+
   private:
-    void ParseData(const std::string& filename, const std::string& data, size_t* parse_errors);
-    bool ParseConfigFile(const std::string& path, size_t* parse_errors);
-    bool ParseConfigDir(const std::string& path, size_t* parse_errors);
+    void ParseData(const std::string& filename, const std::string& data);
+    bool ParseConfigFile(const std::string& path);
+    bool ParseConfigDir(const std::string& path);
 
     std::map<std::string, std::unique_ptr<SectionParser>> section_parsers_;
     std::vector<std::pair<std::string, LineCallback>> line_callbacks_;
+    size_t parse_error_count_ = 0;
 };
 
 }  // namespace init
diff --git a/init/service.cpp b/init/service.cpp
index a698735..565cae7 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -228,7 +228,6 @@
       seclabel_(seclabel),
       onrestart_(false, subcontext_for_restart_commands, "<Service '" + name + "' onrestart>", 0,
                  "onrestart", {}),
-      keychord_id_(0),
       ioprio_class_(IoSchedClass_NONE),
       ioprio_pri_(0),
       priority_(0),
@@ -548,7 +547,7 @@
             for (auto& key : keycodes_) {
                 if (key == code) return Error() << "duplicate keycode: " << args[i];
             }
-            keycodes_.emplace_back(code);
+            keycodes_.insert(std::upper_bound(keycodes_.begin(), keycodes_.end(), code), code);
         } else {
             return Error() << "invalid keycode: " << args[i];
         }
diff --git a/init/service.h b/init/service.h
index cbfd52f..ea79a07 100644
--- a/init/service.h
+++ b/init/service.h
@@ -108,8 +108,6 @@
     const std::vector<gid_t>& supp_gids() const { return supp_gids_; }
     const std::string& seclabel() const { return seclabel_; }
     const std::vector<int>& keycodes() const { return keycodes_; }
-    int keychord_id() const { return keychord_id_; }
-    void set_keychord_id(int keychord_id) { keychord_id_ = keychord_id; }
     IoSchedClass ioprio_class() const { return ioprio_class_; }
     int ioprio_pri() const { return ioprio_pri_; }
     const std::set<std::string>& interfaces() const { return interfaces_; }
@@ -199,9 +197,8 @@
 
     std::set<std::string> interfaces_;  // e.g. some.package.foo@1.0::IBaz/instance-name
 
-    // keycodes for triggering this service via /dev/keychord
+    // keycodes for triggering this service via /dev/input/input*
     std::vector<int> keycodes_;
-    int keychord_id_;
 
     IoSchedClass ioprio_class_;
     int ioprio_pri_;
diff --git a/init/service_test.cpp b/init/service_test.cpp
index b43c2e9..194aa2b 100644
--- a/init/service_test.cpp
+++ b/init/service_test.cpp
@@ -46,7 +46,6 @@
     EXPECT_EQ(0U, service_in_old_memory->uid());
     EXPECT_EQ(0U, service_in_old_memory->gid());
     EXPECT_EQ(0U, service_in_old_memory->namespace_flags());
-    EXPECT_EQ(0, service_in_old_memory->keychord_id());
     EXPECT_EQ(IoSchedClass_NONE, service_in_old_memory->ioprio_class());
     EXPECT_EQ(0, service_in_old_memory->ioprio_pri());
     EXPECT_EQ(0, service_in_old_memory->priority());
@@ -66,7 +65,6 @@
     EXPECT_EQ(0U, service_in_old_memory2->uid());
     EXPECT_EQ(0U, service_in_old_memory2->gid());
     EXPECT_EQ(0U, service_in_old_memory2->namespace_flags());
-    EXPECT_EQ(0, service_in_old_memory2->keychord_id());
     EXPECT_EQ(IoSchedClass_NONE, service_in_old_memory2->ioprio_class());
     EXPECT_EQ(0, service_in_old_memory2->ioprio_pri());
     EXPECT_EQ(0, service_in_old_memory2->priority());
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 0f2b460..6169324 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -80,6 +80,7 @@
     { 00775, AID_ROOT,         AID_ROOT,         0, "data/preloads" },
     { 00771, AID_SYSTEM,       AID_SYSTEM,       0, "data" },
     { 00755, AID_ROOT,         AID_SYSTEM,       0, "mnt" },
+    { 00755, AID_ROOT,         AID_SHELL,        0, "product/bin" },
     { 00750, AID_ROOT,         AID_SHELL,        0, "sbin" },
     { 00777, AID_ROOT,         AID_ROOT,         0, "sdcard" },
     { 00751, AID_ROOT,         AID_SDCARD_R,     0, "storage" },
@@ -195,6 +196,7 @@
     { 00755, AID_ROOT,      AID_ROOT,      0, "bin/*" },
     { 00640, AID_ROOT,      AID_SHELL,     0, "fstab.*" },
     { 00750, AID_ROOT,      AID_SHELL,     0, "init*" },
+    { 00755, AID_ROOT,      AID_SHELL,     0, "product/bin/*" },
     { 00750, AID_ROOT,      AID_SHELL,     0, "sbin/*" },
     { 00755, AID_ROOT,      AID_SHELL,     0, "system/bin/*" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "system/lib/valgrind/*" },
@@ -237,9 +239,10 @@
     return fd;
 }
 
-// if path is "vendor/<stuff>", "oem/<stuff>" or "odm/<stuff>"
+// if path is "odm/<stuff>", "oem/<stuff>", "product/<stuff>" or
+// "vendor/<stuff>"
 static bool is_partition(const char* path, size_t len) {
-    static const char* partitions[] = {"vendor/", "oem/", "odm/"};
+    static const char* partitions[] = {"odm/", "oem/", "product/", "vendor/"};
     for (size_t i = 0; i < (sizeof(partitions) / sizeof(partitions[0])); ++i) {
         size_t plen = strlen(partitions[i]);
         if (len <= plen) continue;
diff --git a/liblog/tests/log_read_test.cpp b/liblog/tests/log_read_test.cpp
index 444a5ac..443c3ea 100644
--- a/liblog/tests/log_read_test.cpp
+++ b/liblog/tests/log_read_test.cpp
@@ -27,6 +27,7 @@
 // Test the APIs in this standalone include file
 #include <log/log_read.h>
 // Do not use anything in log/log_time.h despite side effects of the above.
+#include <private/android_logger.h>
 
 TEST(liblog, __android_log_write__android_logger_list_read) {
 #ifdef __ANDROID__
@@ -105,7 +106,10 @@
       // framework (b/68266385).
       EXPECT_LE(  // boolean 1 or 0 depending on expected content or empty
           !!((strcmp("crash", name) != 0) &&
-             ((strcmp("kernel", name) != 0) || __android_log_is_debuggable()) &&
+             ((strcmp("kernel", name) != 0) ||
+              __android_logger_property_get_bool(
+                  "ro.logd.kernel", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG |
+                                        BOOL_DEFAULT_FLAG_SVELTE)) &&
              (strcmp("stats", name) != 0)),
           android_logger_get_log_readable_size(logger));
     } else {
diff --git a/libmemunreachable/README.md b/libmemunreachable/README.md
index 6d9141a..9cc0c9b 100644
--- a/libmemunreachable/README.md
+++ b/libmemunreachable/README.md
@@ -44,7 +44,7 @@
 
 ### C++ interface ###
 
-####`bool GetUnreachableMemory(UnreachableMemoryInfo& info, size_t limit = 100)`####
+#### `bool GetUnreachableMemory(UnreachableMemoryInfo& info, size_t limit = 100)` ####
 Updates an `UnreachableMemoryInfo` object with information on leaks, including details on up to `limit` leaks.  Returns true if leak detection succeeded.
 
 #### `std::string GetUnreachableMemoryString(bool log_contents = false, size_t limit = 100)` ####
diff --git a/libnativeloader/OWNERS b/libnativeloader/OWNERS
index f2cc942..5c28a9f 100644
--- a/libnativeloader/OWNERS
+++ b/libnativeloader/OWNERS
@@ -1 +1,2 @@
 dimitry@google.com
+jiyong@google.com
diff --git a/libsync/Android.bp b/libsync/Android.bp
index 3fae5e6..c95563d 100644
--- a/libsync/Android.bp
+++ b/libsync/Android.bp
@@ -40,13 +40,6 @@
 }
 
 cc_test {
-    name: "sync_test",
-    defaults: ["libsync_defaults"],
-    gtest: false,
-    srcs: ["sync_test.c"],
-}
-
-cc_test {
     name: "sync-unit-tests",
     shared_libs: ["libsync"],
     srcs: ["tests/sync_test.cpp"],
diff --git a/libsync/OWNERS b/libsync/OWNERS
new file mode 100644
index 0000000..dc61733
--- /dev/null
+++ b/libsync/OWNERS
@@ -0,0 +1,3 @@
+ghackmann@google.com
+jessehall@google.com
+marissaw@google.com
diff --git a/libsync/sync_test.c b/libsync/sync_test.c
deleted file mode 100644
index f1ffdcf..0000000
--- a/libsync/sync_test.c
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- *  sync_test.c
- *
- *   Copyright 2012 Google, Inc
- *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-#include <errno.h>
-#include <pthread.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <android/sync.h>
-#include "sw_sync.h"
-
-pthread_mutex_t printf_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-struct sync_thread_data {
-    int thread_no;
-    int fd[2];
-};
-
-void *sync_thread(void *data)
-{
-    struct sync_thread_data *sync_data = data;
-    struct sync_fence_info_data *info;
-    int err;
-    int i;
-
-    for (i = 0; i < 2; i++) {
-        err = sync_wait(sync_data->fd[i], 10000);
-
-        pthread_mutex_lock(&printf_mutex);
-        if (err < 0) {
-            printf("thread %d wait %d failed: %s\n", sync_data->thread_no,
-                   i, strerror(errno));
-        } else {
-            printf("thread %d wait %d done\n", sync_data->thread_no, i);
-        }
-        info = sync_fence_info(sync_data->fd[i]);
-        if (info) {
-            struct sync_pt_info *pt_info = NULL;
-            printf("  fence %s %d\n", info->name, info->status);
-
-            while ((pt_info = sync_pt_info(info, pt_info))) {
-                int ts_sec = pt_info->timestamp_ns / 1000000000LL;
-                int ts_usec = (pt_info->timestamp_ns % 1000000000LL) / 1000LL;
-                printf("    pt %s %s %d %d.%06d", pt_info->obj_name,
-                       pt_info->driver_name, pt_info->status,
-                       ts_sec, ts_usec);
-                if (!strcmp(pt_info->driver_name, "sw_sync"))
-                    printf(" val=%d\n", *(uint32_t *)pt_info->driver_data);
-                else
-                    printf("\n");
-            }
-            sync_fence_info_free(info);
-        }
-        pthread_mutex_unlock(&printf_mutex);
-    }
-
-    return NULL;
-}
-
-int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused)))
-{
-    struct sync_thread_data sync_data[4];
-    pthread_t threads[4];
-    int sync_timeline_fd;
-    int i, j;
-    char str[256];
-
-    sync_timeline_fd = sw_sync_timeline_create();
-    if (sync_timeline_fd < 0) {
-        perror("can't create sw_sync_timeline:");
-        return 1;
-    }
-
-    for (i = 0; i < 3; i++) {
-        sync_data[i].thread_no = i;
-
-        for (j = 0; j < 2; j++) {
-            unsigned val = i + j * 3 + 1;
-            snprintf(str, sizeof(str), "test_fence%d-%d", i, j);
-            int fd = sw_sync_fence_create(sync_timeline_fd, str, val);
-            if (fd < 0) {
-                printf("can't create sync pt %d: %s", val, strerror(errno));
-                return 1;
-            }
-            sync_data[i].fd[j] = fd;
-            printf("sync_data[%d].fd[%d] = %d;\n", i, j, fd);
-
-        }
-    }
-
-    sync_data[3].thread_no = 3;
-    for (j = 0; j < 2; j++) {
-        snprintf(str, sizeof(str), "merged_fence%d", j);
-        sync_data[3].fd[j] = sync_merge(str, sync_data[0].fd[j], sync_data[1].fd[j]);
-        if (sync_data[3].fd[j] < 0) {
-            printf("can't merge sync pts %d and %d: %s\n",
-                   sync_data[0].fd[j], sync_data[1].fd[j], strerror(errno));
-            return 1;
-        }
-    }
-
-    for (i = 0; i < 4; i++)
-        pthread_create(&threads[i], NULL, sync_thread, &sync_data[i]);
-
-
-    for (i = 0; i < 3; i++) {
-        int err;
-        printf("press enter to inc to %d\n", i+1);
-        fgets(str, sizeof(str), stdin);
-        err = sw_sync_timeline_inc(sync_timeline_fd, 1);
-        if (err < 0) {
-            perror("can't increment sync obj:");
-            return 1;
-        }
-    }
-
-    printf("press enter to close sync_timeline\n");
-    fgets(str, sizeof(str), stdin);
-
-    close(sync_timeline_fd);
-
-    printf("press enter to end test\n");
-    fgets(str, sizeof(str), stdin);
-
-    for (i = 0; i < 3; i++) {
-        void *val;
-        pthread_join(threads[i], &val);
-    }
-
-    return 0;
-}
diff --git a/libsysutils/include/sysutils/OWNERS b/libsysutils/include/sysutils/OWNERS
new file mode 100644
index 0000000..b78918e
--- /dev/null
+++ b/libsysutils/include/sysutils/OWNERS
@@ -0,0 +1,2 @@
+per-file Netlink* = ek@google.com
+per-file Netlink* = lorenzo@google.com
diff --git a/libsysutils/src/OWNERS b/libsysutils/src/OWNERS
new file mode 100644
index 0000000..b78918e
--- /dev/null
+++ b/libsysutils/src/OWNERS
@@ -0,0 +1,2 @@
+per-file Netlink* = ek@google.com
+per-file Netlink* = lorenzo@google.com
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 26be64d..d4cef7c 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -232,6 +232,9 @@
         "tests/files/offline/straddle_arm/*",
         "tests/files/offline/straddle_arm64/*",
     ],
+    required: [
+        "libunwindstack_local",
+    ],
 }
 
 //-------------------------------------------------------------------------
diff --git a/mkbootimg/include/bootimg/bootimg.h b/mkbootimg/include/bootimg/bootimg.h
index 406e208..bce308b 100644
--- a/mkbootimg/include/bootimg/bootimg.h
+++ b/mkbootimg/include/bootimg/bootimg.h
@@ -111,7 +111,7 @@
 
 struct boot_img_hdr_v1 : public boot_img_hdr_v0 {
     uint32_t recovery_dtbo_size;   /* size in bytes for recovery DTBO image */
-    uint64_t recovery_dtbo_offset; /* physical load addr */
+    uint64_t recovery_dtbo_offset; /* offset to recovery dtbo in boot image */
     uint32_t header_size;
 } __attribute__((packed));
 
@@ -138,11 +138,14 @@
  * 1. kernel and ramdisk are required (size != 0)
  * 2. recovery_dtbo is required for recovery.img in non-A/B devices(recovery_dtbo_size != 0)
  * 3. second is optional (second_size == 0 -> no second)
- * 4. load each element (kernel, ramdisk, second, recovery_dtbo) at
+ * 4. load each element (kernel, ramdisk, second) at
  *    the specified physical address (kernel_addr, etc)
- * 5. prepare tags at tag_addr.  kernel_args[] is
+ * 5. If booting to recovery mode in a non-A/B device, extract recovery dtbo and
+ *    apply the correct set of overlays on the base device tree depending on the
+ *    hardware/product revision.
+ * 6. prepare tags at tag_addr.  kernel_args[] is
  *    appended to the kernel commandline in the tags.
- * 6. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
- * 7. if second_size != 0: jump to second_addr
+ * 7. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
+ * 8. if second_size != 0: jump to second_addr
  *    else: jump to kernel_addr
  */
diff --git a/mkbootimg/mkbootimg b/mkbootimg/mkbootimg
index ac20d05..fda9af0 100755
--- a/mkbootimg/mkbootimg
+++ b/mkbootimg/mkbootimg
@@ -45,6 +45,22 @@
     f.write(pack(str(pad) + 'x'))
 
 
+def get_number_of_pages(image_size, page_size):
+    """calculates the number of pages required for the image"""
+    return (image_size + page_size - 1) / page_size
+
+
+def get_recovery_dtbo_offset(args):
+    """calculates the offset of recovery_dtbo image in the boot image"""
+    num_header_pages = 1 # header occupies a page
+    num_kernel_pages = get_number_of_pages(filesize(args.kernel), args.pagesize)
+    num_ramdisk_pages = get_number_of_pages(filesize(args.ramdisk), args.pagesize)
+    num_second_pages = get_number_of_pages(filesize(args.second), args.pagesize)
+    dtbo_offset = args.pagesize * (num_header_pages + num_kernel_pages +
+                                   num_ramdisk_pages + num_second_pages)
+    return dtbo_offset
+
+
 def write_header(args):
     BOOT_MAGIC = 'ANDROID!'.encode()
     args.output.write(pack('8s', BOOT_MAGIC))
@@ -76,9 +92,12 @@
     args.output.write(pack('1024s', args.cmdline[512:].encode()))
 
     if args.header_version > 0:
-        args.output.write(pack('I', filesize(args.recovery_dtbo)))           # size in bytes
-        args.output.write(pack('Q', args.base + args.recovery_dtbo_offset))  # physical load addr
-        args.output.write(pack('I', args.output.tell() + 4))                 # size of boot header
+        args.output.write(pack('I', filesize(args.recovery_dtbo)))   # size in bytes
+        if args.recovery_dtbo:
+            args.output.write(pack('Q', get_recovery_dtbo_offset(args))) # recovery dtbo offset
+        else:
+            args.output.write(pack('Q', 0)) # Will be set to 0 for devices without a recovery dtbo
+        args.output.write(pack('I', args.output.tell() + 4))         # size of boot header
 
     pad_file(args.output, args.pagesize)
     return img_id
@@ -150,8 +169,6 @@
     parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000)
     parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
                         default=0x00f00000)
-    parser.add_argument('--recovery_dtbo_offset', help='recovery dtbo offset', type=parse_int,
-                        default=0x0f000000)
     parser.add_argument('--os_version', help='operating system version', type=parse_os_version,
                         default=0)
     parser.add_argument('--os_patch_level', help='operating system patch level',
diff --git a/mkbootimg/unpack_bootimg b/mkbootimg/unpack_bootimg
index 8e42ec0..c37acd5 100755
--- a/mkbootimg/unpack_bootimg
+++ b/mkbootimg/unpack_bootimg
@@ -76,8 +76,8 @@
     if version > 0:
         recovery_dtbo_size = unpack('I', args.boot_img.read(1 * 4))[0]
         print('recovery dtbo size: %s' % recovery_dtbo_size)
-        recovery_dtbo_address = unpack('Q', args.boot_img.read(8))[0]
-        print('recovery dtbo load address: %s' % recovery_dtbo_address)
+        recovery_dtbo_offset = unpack('Q', args.boot_img.read(8))[0]
+        print('recovery dtbo offset: %s' % recovery_dtbo_offset)
         boot_header_size = unpack('I', args.boot_img.read(4))[0]
         print('boot header size: %s' % boot_header_size)
     else:
@@ -95,16 +95,13 @@
                                  ) # header + kernel
     image_info_list.append((ramdisk_offset, ramdisk_size, 'ramdisk'))
 
-    num_second_pages = get_number_of_pages(second_size, page_size)
     second_offset = page_size * (
         num_header_pages + num_kernel_pages + num_ramdisk_pages
     )  # header + kernel + ramdisk
     image_info_list.append((second_offset, second_size, 'second'))
 
     if recovery_dtbo_size > 0:
-        dtbo_offset = page_size * (num_header_pages + num_kernel_pages +
-                                   num_ramdisk_pages + num_second_pages)
-        image_info_list.append((dtbo_offset, recovery_dtbo_size,
+        image_info_list.append((recovery_dtbo_offset, recovery_dtbo_size,
                                 'recovery_dtbo'))
 
     for image_info in image_info_list:
diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt
index 51e3f9e..a0b1996 100644
--- a/rootdir/etc/ld.config.txt
+++ b/rootdir/etc/ld.config.txt
@@ -7,6 +7,7 @@
 # absolute path of an executable is selected.
 dir.system = /system/bin/
 dir.system = /system/xbin/
+dir.system = /product/bin/
 
 dir.vendor = /odm/bin/
 dir.vendor = /vendor/bin/
diff --git a/rootdir/etc/ld.config.vndk_lite.txt b/rootdir/etc/ld.config.vndk_lite.txt
index ab03755..db65c14 100644
--- a/rootdir/etc/ld.config.vndk_lite.txt
+++ b/rootdir/etc/ld.config.vndk_lite.txt
@@ -7,6 +7,7 @@
 # absolute path of an executable is selected.
 dir.system = /system/bin/
 dir.system = /system/xbin/
+dir.system = /product/bin/
 
 dir.vendor = /odm/bin/
 dir.vendor = /vendor/bin/