init: Reland "Terminate gracefully when CAP_SYS_BOOT is absent"

This change makes it possible for Android running in a container to
terminate cleanly instead of calling abort() when requested to shut
down.

Bug: 62388055
Test: `adb reboot` on bullhead causes no kernel panics
Test: `adb reboot` on a system without CAP_SYS_BOOT makes init terminate
       nicely

Change-Id: I36b2298610f5b4a2bf8b05103d04804883df2c88
diff --git a/init/reboot.cpp b/init/reboot.cpp
index df7912f..c8bb98b 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -21,6 +21,7 @@
 #include <linux/fs.h>
 #include <mntent.h>
 #include <selinux/selinux.h>
+#include <sys/capability.h>
 #include <sys/cdefs.h>
 #include <sys/ioctl.h>
 #include <sys/mount.h>
@@ -48,6 +49,7 @@
 #include <logwrap/logwrap.h>
 #include <private/android_filesystem_config.h>
 
+#include "capabilities.h"
 #include "property_service.h"
 #include "service.h"
 
@@ -162,9 +164,38 @@
     LOG(WARNING) << "powerctl_shutdown_time_ms:" << std::to_string(t->duration_ms()) << ":" << stat;
 }
 
+// Determines whether the system is capable of rebooting. This is conservative,
+// so if any of the attempts to determine this fail, it will still return true.
+static bool IsRebootCapable() {
+    if (!CAP_IS_SUPPORTED(CAP_SYS_BOOT)) {
+        PLOG(WARNING) << "CAP_SYS_BOOT is not supported";
+        return true;
+    }
+
+    ScopedCaps caps(cap_get_proc());
+    if (!caps) {
+        PLOG(WARNING) << "cap_get_proc() failed";
+        return true;
+    }
+
+    cap_flag_value_t value = CAP_SET;
+    if (cap_get_flag(caps.get(), CAP_SYS_BOOT, CAP_EFFECTIVE, &value) != 0) {
+        PLOG(WARNING) << "cap_get_flag(CAP_SYS_BOOT, EFFECTIVE) failed";
+        return true;
+    }
+    return value == CAP_SET;
+}
+
 static void __attribute__((noreturn))
 RebootSystem(unsigned int cmd, const std::string& rebootTarget) {
     LOG(INFO) << "Reboot ending, jumping to kernel";
+
+    if (!IsRebootCapable()) {
+        // On systems where init does not have the capability of rebooting the
+        // device, just exit cleanly.
+        exit(0);
+    }
+
     switch (cmd) {
         case ANDROID_RB_POWEROFF:
             reboot(RB_POWER_OFF);