init: make triggering shutdown from vendor_init better

Previously, we assumed that TriggerShutdown() should never be called
from vendor_init and used property service as a back up in case it
ever did.  We have since then found out that vendor_init may indeed
call TriggerShutdown() and we want to make it just as strict as it is
in init, wherein it will immediately start the shutdown sequence
without executing any further commands.

Test: init unit tests, trigger shuttdown from init and vendor_init
Change-Id: I1f44dae801a28269eb8127879a8b7d6adff6f353
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 5ee928e..a55514b 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -140,14 +140,7 @@
     if (!write_bootloader_message(options, &err)) {
         return Error() << "Failed to set bootloader message: " << err;
     }
-    // This function should only be reached from init and not from vendor_init, and we want to
-    // immediately trigger reboot instead of relaying through property_service.  Older devices may
-    // still have paths that reach here from vendor_init, so we keep the property_set as a fallback.
-    if (getpid() == 1) {
-        TriggerShutdown("reboot,recovery");
-    } else {
-        property_set("sys.powerctl", "reboot,recovery");
-    }
+    trigger_shutdown("reboot,recovery");
     return {};
 }
 
@@ -554,7 +547,7 @@
             // support userdata remount on FDE devices, this should never been triggered. Time to
             // panic!
             LOG(ERROR) << "Userdata remount is not supported on FDE devices. How did you get here?";
-            TriggerShutdown("reboot,requested-userdata-remount-on-fde-device");
+            trigger_shutdown("reboot,requested-userdata-remount-on-fde-device");
         }
         ActionManager::GetInstance().QueueEventTrigger("encrypt");
         return {};
@@ -564,7 +557,7 @@
             // don't support userdata remount on FDE devices, this should never been triggered.
             // Time to panic!
             LOG(ERROR) << "Userdata remount is not supported on FDE devices. How did you get here?";
-            TriggerShutdown("reboot,requested-userdata-remount-on-fde-device");
+            trigger_shutdown("reboot,requested-userdata-remount-on-fde-device");
         }
         property_set("ro.crypto.state", "encrypted");
         property_set("ro.crypto.type", "block");
@@ -1148,7 +1141,7 @@
     }
     // TODO(b/135984674): check that fstab contains /data.
     if (auto rc = fs_mgr_remount_userdata_into_checkpointing(&fstab); rc < 0) {
-        TriggerShutdown("reboot,mount-userdata-failed");
+        trigger_shutdown("reboot,mount-userdata-failed");
     }
     if (auto result = queue_fs_event(initial_mount_fstab_return_code, true); !result) {
         return Error() << "queue_fs_event() failed: " << result.error();
diff --git a/init/host_init_stubs.h b/init/host_init_stubs.h
index 9b33a1c..30d3129 100644
--- a/init/host_init_stubs.h
+++ b/init/host_init_stubs.h
@@ -35,11 +35,6 @@
 namespace android {
 namespace init {
 
-// init.h
-inline void TriggerShutdown(const std::string&) {
-    abort();
-}
-
 // property_service.h
 inline bool CanReadProperty(const std::string&, const std::string&) {
     return true;
diff --git a/init/init.cpp b/init/init.cpp
index ff86f8d..8e2da59 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -180,7 +180,7 @@
     waiting_for_prop.reset();
 }
 
-void TriggerShutdown(const std::string& command) {
+static void TriggerShutdown(const std::string& command) {
     // We can't call HandlePowerctlMessage() directly in this function,
     // because it modifies the contents of the action queue, which can cause the action queue
     // to get into a bad state if this function is called from a command being executed by the
@@ -681,6 +681,8 @@
 
     boot_clock::time_point start_time = boot_clock::now();
 
+    trigger_shutdown = TriggerShutdown;
+
     SetStdioToDevNull(argv);
     InitKernelLogging(argv);
     LOG(INFO) << "init second stage started!";
diff --git a/init/init.h b/init/init.h
index d884a94..0805940 100644
--- a/init/init.h
+++ b/init/init.h
@@ -31,8 +31,6 @@
 Parser CreateParser(ActionManager& action_manager, ServiceList& service_list);
 Parser CreateServiceOnlyParser(ServiceList& service_list);
 
-void TriggerShutdown(const std::string& command);
-
 bool start_waiting_for_property(const char *name, const char *value);
 
 void DumpState();
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 4a16969..64ec1fb 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -731,7 +731,7 @@
     auto guard = android::base::make_scope_guard([] {
         // Leave shutdown so that we can handle a full reboot.
         LeaveShutdown();
-        TriggerShutdown("reboot,abort-userspace-reboot");
+        trigger_shutdown("reboot,abort-userspace-reboot");
     });
     // Triggering userspace-reboot-requested will result in a bunch of set_prop
     // actions. We should make sure, that all of them are propagated before
diff --git a/init/service.cpp b/init/service.cpp
index f8e98a2..574ff52 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -43,7 +43,6 @@
 #if defined(__ANDROID__)
 #include <ApexProperties.sysprop.h>
 
-#include "init.h"
 #include "mount_namespace.h"
 #include "property_service.h"
 #else
@@ -260,7 +259,7 @@
 
     if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_) {
         LOG(ERROR) << "Service with 'reboot_on_failure' option failed, shutting down system.";
-        TriggerShutdown(*on_failure_reboot_target_);
+        trigger_shutdown(*on_failure_reboot_target_);
     }
 
     if (flags_ & SVC_EXEC) UnSetExec();
@@ -340,7 +339,7 @@
 Result<void> Service::ExecStart() {
     auto reboot_on_failure = make_scope_guard([this] {
         if (on_failure_reboot_target_) {
-            TriggerShutdown(*on_failure_reboot_target_);
+            trigger_shutdown(*on_failure_reboot_target_);
         }
     });
 
@@ -371,7 +370,7 @@
 Result<void> Service::Start() {
     auto reboot_on_failure = make_scope_guard([this] {
         if (on_failure_reboot_target_) {
-            TriggerShutdown(*on_failure_reboot_target_);
+            trigger_shutdown(*on_failure_reboot_target_);
         }
     });
 
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index 79fc372..e55265b 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -51,6 +51,8 @@
 namespace init {
 namespace {
 
+std::string shutdown_command;
+
 class SubcontextProcess {
   public:
     SubcontextProcess(const BuiltinFunctionMap* function_map, std::string context, int init_fd)
@@ -153,6 +155,11 @@
                            << subcontext_command.command_case();
         }
 
+        if (!shutdown_command.empty()) {
+            reply.set_trigger_shutdown(shutdown_command);
+            shutdown_command.clear();
+        }
+
         if (auto result = SendMessage(init_fd_, reply); !result) {
             LOG(FATAL) << "Failed to send message to init: " << result.error();
         }
@@ -174,6 +181,8 @@
         return 0;
     };
 
+    trigger_shutdown = [](const std::string& command) { shutdown_command = command; };
+
     auto subcontext_process = SubcontextProcess(function_map, context, init_fd);
     subcontext_process.MainLoop();
     return 0;
@@ -254,6 +263,11 @@
         Restart();
         return Error() << "Unable to parse message from subcontext";
     }
+
+    if (subcontext_reply.has_trigger_shutdown()) {
+        trigger_shutdown(subcontext_reply.trigger_shutdown());
+    }
+
     return subcontext_reply;
 }
 
diff --git a/init/subcontext.proto b/init/subcontext.proto
index e68115e..068c7ce 100644
--- a/init/subcontext.proto
+++ b/init/subcontext.proto
@@ -38,4 +38,6 @@
         Failure failure = 2;
         ExpandArgsReply expand_args_reply = 3;
     }
+
+    optional string trigger_shutdown = 4;
 }
\ No newline at end of file
diff --git a/init/subcontext_test.cpp b/init/subcontext_test.cpp
index 9cac35e..9c1a788 100644
--- a/init/subcontext_test.cpp
+++ b/init/subcontext_test.cpp
@@ -26,6 +26,7 @@
 #include <selinux/selinux.h>
 
 #include "builtin_arguments.h"
+#include "util.h"
 
 using namespace std::literals;
 
@@ -142,6 +143,18 @@
     });
 }
 
+TEST(subcontext, TriggerShutdown) {
+    static constexpr const char kTestShutdownCommand[] = "reboot,test-shutdown-command";
+    static std::string trigger_shutdown_command;
+    trigger_shutdown = [](const std::string& command) { trigger_shutdown_command = command; };
+    RunTest([](auto& subcontext, auto& context_string) {
+        auto result = subcontext.Execute(
+                std::vector<std::string>{"trigger_shutdown", kTestShutdownCommand});
+        ASSERT_TRUE(result);
+    });
+    EXPECT_EQ(kTestShutdownCommand, trigger_shutdown_command);
+}
+
 TEST(subcontext, ExpandArgs) {
     RunTest([](auto& subcontext, auto& context_string) {
         auto args = std::vector<std::string>{
@@ -207,6 +220,11 @@
         return Error() << args.context;
     };
 
+    auto do_trigger_shutdown = [](const BuiltinArguments& args) -> Result<void> {
+        trigger_shutdown(args[1]);
+        return {};
+    };
+
     // clang-format off
     BuiltinFunctionMap test_function_map = {
         {"return_pids_as_error",        {0,     0,      {true,  do_return_pids_as_error}}},
@@ -216,6 +234,7 @@
         {"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}}},
+        {"trigger_shutdown",            {1,     1,      {true,  do_trigger_shutdown}}},
     };
     // clang-format on
     return test_function_map;
diff --git a/init/util.cpp b/init/util.cpp
index ada9e78..e5254dd 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -61,6 +61,8 @@
 
 const std::string kDefaultAndroidDtDir("/proc/device-tree/firmware/android/");
 
+void (*trigger_shutdown)(const std::string& command) = nullptr;
+
 // DecodeUid() - decodes and returns the given string, which can be either the
 // numeric or name representation, into the integer uid or gid.
 Result<uid_t> DecodeUid(const std::string& name) {
diff --git a/init/util.h b/init/util.h
index 3d81d72..9d89ed7 100644
--- a/init/util.h
+++ b/init/util.h
@@ -35,6 +35,8 @@
 
 static const char kColdBootDoneProp[] = "ro.cold_boot_done";
 
+extern void (*trigger_shutdown)(const std::string& command);
+
 Result<int> CreateSocket(const std::string& name, int type, bool passcred, mode_t perm, uid_t uid,
                          gid_t gid, const std::string& socketcon);