1072 lines
42 KiB
C++
1072 lines
42 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include "reboot.h"
|
|
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/loop.h>
|
|
#include <mntent.h>
|
|
#include <semaphore.h>
|
|
#include <stdlib.h>
|
|
#include <sys/cdefs.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/swap.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <chrono>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <InitProperties.sysprop.h>
|
|
#include <android-base/chrono_utils.h>
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/macros.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/scopeguard.h>
|
|
#include <android-base/strings.h>
|
|
#include <android-base/unique_fd.h>
|
|
#include <bootloader_message/bootloader_message.h>
|
|
#include <cutils/android_reboot.h>
|
|
#include <fs_mgr.h>
|
|
#include <logwrap/logwrap.h>
|
|
#include <private/android_filesystem_config.h>
|
|
#include <selinux/selinux.h>
|
|
|
|
#include "action.h"
|
|
#include "action_manager.h"
|
|
#include "builtin_arguments.h"
|
|
#include "init.h"
|
|
#include "mount_namespace.h"
|
|
#include "property_service.h"
|
|
#include "reboot_utils.h"
|
|
#include "service.h"
|
|
#include "service_list.h"
|
|
#include "sigchld_handler.h"
|
|
#include "util.h"
|
|
|
|
using namespace std::literals;
|
|
|
|
using android::base::boot_clock;
|
|
using android::base::GetBoolProperty;
|
|
using android::base::GetUintProperty;
|
|
using android::base::SetProperty;
|
|
using android::base::Split;
|
|
using android::base::Timer;
|
|
using android::base::unique_fd;
|
|
using android::base::WaitForProperty;
|
|
using android::base::WriteStringToFile;
|
|
|
|
namespace android {
|
|
namespace init {
|
|
|
|
static bool shutting_down = false;
|
|
|
|
static const std::set<std::string> kDebuggingServices{"tombstoned", "logd", "adbd", "console"};
|
|
|
|
static std::vector<Service*> GetDebuggingServices(bool only_post_data) {
|
|
std::vector<Service*> ret;
|
|
ret.reserve(kDebuggingServices.size());
|
|
for (const auto& s : ServiceList::GetInstance()) {
|
|
if (kDebuggingServices.count(s->name()) && (!only_post_data || s->is_post_data())) {
|
|
ret.push_back(s.get());
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void PersistRebootReason(const char* reason, bool write_to_property) {
|
|
if (write_to_property) {
|
|
SetProperty(LAST_REBOOT_REASON_PROPERTY, reason);
|
|
}
|
|
auto fd = unique_fd(TEMP_FAILURE_RETRY(open(
|
|
LAST_REBOOT_REASON_FILE, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY, 0666)));
|
|
if (!fd.ok()) {
|
|
PLOG(ERROR) << "Could not open '" << LAST_REBOOT_REASON_FILE
|
|
<< "' to persist reboot reason";
|
|
return;
|
|
}
|
|
WriteStringToFd(reason, fd);
|
|
fsync(fd.get());
|
|
}
|
|
|
|
// represents umount status during reboot / shutdown.
|
|
enum UmountStat {
|
|
/* umount succeeded. */
|
|
UMOUNT_STAT_SUCCESS = 0,
|
|
/* umount was not run. */
|
|
UMOUNT_STAT_SKIPPED = 1,
|
|
/* umount failed with timeout. */
|
|
UMOUNT_STAT_TIMEOUT = 2,
|
|
/* could not run due to error */
|
|
UMOUNT_STAT_ERROR = 3,
|
|
/* not used by init but reserved for other part to use this to represent the
|
|
the state where umount status before reboot is not found / available. */
|
|
UMOUNT_STAT_NOT_AVAILABLE = 4,
|
|
};
|
|
|
|
// Utility for struct mntent
|
|
class MountEntry {
|
|
public:
|
|
explicit MountEntry(const mntent& entry)
|
|
: mnt_fsname_(entry.mnt_fsname),
|
|
mnt_dir_(entry.mnt_dir),
|
|
mnt_type_(entry.mnt_type),
|
|
mnt_opts_(entry.mnt_opts) {}
|
|
|
|
bool Umount(bool force) {
|
|
LOG(INFO) << "Unmounting " << mnt_fsname_ << ":" << mnt_dir_ << " opts " << mnt_opts_;
|
|
int r = umount2(mnt_dir_.c_str(), force ? MNT_FORCE : 0);
|
|
if (r == 0) {
|
|
LOG(INFO) << "Umounted " << mnt_fsname_ << ":" << mnt_dir_ << " opts " << mnt_opts_;
|
|
return true;
|
|
} else {
|
|
PLOG(WARNING) << "Cannot umount " << mnt_fsname_ << ":" << mnt_dir_ << " opts "
|
|
<< mnt_opts_;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void DoFsck() {
|
|
int st;
|
|
if (IsF2Fs()) {
|
|
const char* f2fs_argv[] = {
|
|
"/system/bin/fsck.f2fs",
|
|
"-a",
|
|
mnt_fsname_.c_str(),
|
|
};
|
|
logwrap_fork_execvp(arraysize(f2fs_argv), f2fs_argv, &st, false, LOG_KLOG, true,
|
|
nullptr);
|
|
} else if (IsExt4()) {
|
|
const char* ext4_argv[] = {
|
|
"/system/bin/e2fsck",
|
|
"-y",
|
|
mnt_fsname_.c_str(),
|
|
};
|
|
logwrap_fork_execvp(arraysize(ext4_argv), ext4_argv, &st, false, LOG_KLOG, true,
|
|
nullptr);
|
|
}
|
|
}
|
|
|
|
static bool IsBlockDevice(const struct mntent& mntent) {
|
|
return android::base::StartsWith(mntent.mnt_fsname, "/dev/block");
|
|
}
|
|
|
|
static bool IsEmulatedDevice(const struct mntent& mntent) {
|
|
return android::base::StartsWith(mntent.mnt_fsname, "/data/");
|
|
}
|
|
|
|
private:
|
|
bool IsF2Fs() const { return mnt_type_ == "f2fs"; }
|
|
|
|
bool IsExt4() const { return mnt_type_ == "ext4"; }
|
|
|
|
std::string mnt_fsname_;
|
|
std::string mnt_dir_;
|
|
std::string mnt_type_;
|
|
std::string mnt_opts_;
|
|
};
|
|
|
|
// Turn off backlight while we are performing power down cleanup activities.
|
|
static void TurnOffBacklight() {
|
|
Service* service = ServiceList::GetInstance().FindService("blank_screen");
|
|
if (service == nullptr) {
|
|
LOG(WARNING) << "cannot find blank_screen in TurnOffBacklight";
|
|
return;
|
|
}
|
|
if (auto result = service->Start(); !result.ok()) {
|
|
LOG(WARNING) << "Could not start blank_screen service: " << result.error();
|
|
}
|
|
}
|
|
|
|
static Result<void> CallVdc(const std::string& system, const std::string& cmd) {
|
|
LOG(INFO) << "Calling /system/bin/vdc " << system << " " << cmd;
|
|
const char* vdc_argv[] = {"/system/bin/vdc", system.c_str(), cmd.c_str()};
|
|
int status;
|
|
if (logwrap_fork_execvp(arraysize(vdc_argv), vdc_argv, &status, false, LOG_KLOG, true,
|
|
nullptr) != 0) {
|
|
return ErrnoError() << "Failed to call '/system/bin/vdc " << system << " " << cmd << "'";
|
|
}
|
|
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
|
|
return {};
|
|
}
|
|
return Error() << "'/system/bin/vdc " << system << " " << cmd << "' failed : " << status;
|
|
}
|
|
|
|
static void LogShutdownTime(UmountStat stat, Timer* t) {
|
|
LOG(WARNING) << "powerctl_shutdown_time_ms:" << std::to_string(t->duration().count()) << ":"
|
|
<< stat;
|
|
}
|
|
|
|
static bool IsDataMounted() {
|
|
std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(setmntent("/proc/mounts", "re"), endmntent);
|
|
if (fp == nullptr) {
|
|
PLOG(ERROR) << "Failed to open /proc/mounts";
|
|
return false;
|
|
}
|
|
mntent* mentry;
|
|
while ((mentry = getmntent(fp.get())) != nullptr) {
|
|
if (mentry->mnt_dir == "/data"s) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Find all read+write block devices and emulated devices in /proc/mounts and add them to
|
|
// the correpsponding list.
|
|
static bool FindPartitionsToUmount(std::vector<MountEntry>* block_dev_partitions,
|
|
std::vector<MountEntry>* emulated_partitions, bool dump) {
|
|
std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(setmntent("/proc/mounts", "re"), endmntent);
|
|
if (fp == nullptr) {
|
|
PLOG(ERROR) << "Failed to open /proc/mounts";
|
|
return false;
|
|
}
|
|
mntent* mentry;
|
|
while ((mentry = getmntent(fp.get())) != nullptr) {
|
|
if (dump) {
|
|
LOG(INFO) << "mount entry " << mentry->mnt_fsname << ":" << mentry->mnt_dir << " opts "
|
|
<< mentry->mnt_opts << " type " << mentry->mnt_type;
|
|
} else if (MountEntry::IsBlockDevice(*mentry) && hasmntopt(mentry, "rw")) {
|
|
std::string mount_dir(mentry->mnt_dir);
|
|
// These are R/O partitions changed to R/W after adb remount.
|
|
// Do not umount them as shutdown critical services may rely on them.
|
|
if (mount_dir != "/" && mount_dir != "/system" && mount_dir != "/vendor" &&
|
|
mount_dir != "/oem") {
|
|
block_dev_partitions->emplace(block_dev_partitions->begin(), *mentry);
|
|
}
|
|
} else if (MountEntry::IsEmulatedDevice(*mentry)) {
|
|
emulated_partitions->emplace(emulated_partitions->begin(), *mentry);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void DumpUmountDebuggingInfo() {
|
|
int status;
|
|
if (!security_getenforce()) {
|
|
LOG(INFO) << "Run lsof";
|
|
const char* lsof_argv[] = {"/system/bin/lsof"};
|
|
logwrap_fork_execvp(arraysize(lsof_argv), lsof_argv, &status, false, LOG_KLOG, true,
|
|
nullptr);
|
|
}
|
|
FindPartitionsToUmount(nullptr, nullptr, true);
|
|
// dump current CPU stack traces and uninterruptible tasks
|
|
WriteStringToFile("l", PROC_SYSRQ);
|
|
WriteStringToFile("w", PROC_SYSRQ);
|
|
}
|
|
|
|
static UmountStat UmountPartitions(std::chrono::milliseconds timeout) {
|
|
Timer t;
|
|
/* data partition needs all pending writes to be completed and all emulated partitions
|
|
* umounted.If the current waiting is not good enough, give
|
|
* up and leave it to e2fsck after reboot to fix it.
|
|
*/
|
|
while (true) {
|
|
std::vector<MountEntry> block_devices;
|
|
std::vector<MountEntry> emulated_devices;
|
|
if (!FindPartitionsToUmount(&block_devices, &emulated_devices, false)) {
|
|
return UMOUNT_STAT_ERROR;
|
|
}
|
|
if (block_devices.size() == 0) {
|
|
return UMOUNT_STAT_SUCCESS;
|
|
}
|
|
bool unmount_done = true;
|
|
if (emulated_devices.size() > 0) {
|
|
for (auto& entry : emulated_devices) {
|
|
if (!entry.Umount(false)) unmount_done = false;
|
|
}
|
|
if (unmount_done) {
|
|
sync();
|
|
}
|
|
}
|
|
for (auto& entry : block_devices) {
|
|
if (!entry.Umount(timeout == 0ms)) unmount_done = false;
|
|
}
|
|
if (unmount_done) {
|
|
return UMOUNT_STAT_SUCCESS;
|
|
}
|
|
if ((timeout < t.duration())) { // try umount at least once
|
|
return UMOUNT_STAT_TIMEOUT;
|
|
}
|
|
std::this_thread::sleep_for(100ms);
|
|
}
|
|
}
|
|
|
|
static void KillAllProcesses() {
|
|
WriteStringToFile("i", PROC_SYSRQ);
|
|
}
|
|
|
|
// Create reboot/shutdwon monitor thread
|
|
void RebootMonitorThread(unsigned int cmd, const std::string& reboot_target,
|
|
sem_t* reboot_semaphore, std::chrono::milliseconds shutdown_timeout,
|
|
bool* reboot_monitor_run) {
|
|
unsigned int remaining_shutdown_time = 0;
|
|
|
|
// 300 seconds more than the timeout passed to the thread as there is a final Umount pass
|
|
// after the timeout is reached.
|
|
constexpr unsigned int shutdown_watchdog_timeout_default = 300;
|
|
auto shutdown_watchdog_timeout = android::base::GetUintProperty(
|
|
"ro.build.shutdown.watchdog.timeout", shutdown_watchdog_timeout_default);
|
|
remaining_shutdown_time = shutdown_watchdog_timeout + shutdown_timeout.count() / 1000;
|
|
|
|
while (*reboot_monitor_run == true) {
|
|
if (TEMP_FAILURE_RETRY(sem_wait(reboot_semaphore)) == -1) {
|
|
LOG(ERROR) << "sem_wait failed and exit RebootMonitorThread()";
|
|
return;
|
|
}
|
|
|
|
timespec shutdown_timeout_timespec;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &shutdown_timeout_timespec) == -1) {
|
|
LOG(ERROR) << "clock_gettime() fail! exit RebootMonitorThread()";
|
|
return;
|
|
}
|
|
|
|
// If there are some remaining shutdown time left from previous round, we use
|
|
// remaining time here.
|
|
shutdown_timeout_timespec.tv_sec += remaining_shutdown_time;
|
|
|
|
LOG(INFO) << "shutdown_timeout_timespec.tv_sec: " << shutdown_timeout_timespec.tv_sec;
|
|
|
|
int sem_return = 0;
|
|
while ((sem_return = sem_timedwait_monotonic_np(reboot_semaphore,
|
|
&shutdown_timeout_timespec)) == -1 &&
|
|
errno == EINTR) {
|
|
}
|
|
|
|
if (sem_return == -1) {
|
|
LOG(ERROR) << "Reboot thread timed out";
|
|
|
|
if (android::base::GetBoolProperty("ro.debuggable", false) == true) {
|
|
if (false) {
|
|
// SEPolicy will block debuggerd from running and this is intentional.
|
|
// But these lines are left to be enabled during debugging.
|
|
LOG(INFO) << "Try to dump init process call trace:";
|
|
const char* vdc_argv[] = {"/system/bin/debuggerd", "-b", "1"};
|
|
int status;
|
|
logwrap_fork_execvp(arraysize(vdc_argv), vdc_argv, &status, false, LOG_KLOG,
|
|
true, nullptr);
|
|
}
|
|
LOG(INFO) << "Show stack for all active CPU:";
|
|
WriteStringToFile("l", PROC_SYSRQ);
|
|
|
|
LOG(INFO) << "Show tasks that are in disk sleep(uninterruptable sleep), which are "
|
|
"like "
|
|
"blocked in mutex or hardware register access:";
|
|
WriteStringToFile("w", PROC_SYSRQ);
|
|
}
|
|
|
|
// In shutdown case,notify kernel to sync and umount fs to read-only before shutdown.
|
|
if (cmd == ANDROID_RB_POWEROFF || cmd == ANDROID_RB_THERMOFF) {
|
|
WriteStringToFile("s", PROC_SYSRQ);
|
|
|
|
WriteStringToFile("u", PROC_SYSRQ);
|
|
|
|
RebootSystem(cmd, reboot_target);
|
|
}
|
|
|
|
LOG(ERROR) << "Trigger crash at last!";
|
|
WriteStringToFile("c", PROC_SYSRQ);
|
|
} else {
|
|
timespec current_time_timespec;
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, ¤t_time_timespec) == -1) {
|
|
LOG(ERROR) << "clock_gettime() fail! exit RebootMonitorThread()";
|
|
return;
|
|
}
|
|
|
|
remaining_shutdown_time =
|
|
shutdown_timeout_timespec.tv_sec - current_time_timespec.tv_sec;
|
|
|
|
LOG(INFO) << "remaining_shutdown_time: " << remaining_shutdown_time;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Try umounting all emulated file systems R/W block device cfile systems.
|
|
* This will just try umount and give it up if it fails.
|
|
* For fs like ext4, this is ok as file system will be marked as unclean shutdown
|
|
* and necessary check can be done at the next reboot.
|
|
* For safer shutdown, caller needs to make sure that
|
|
* all processes / emulated partition for the target fs are all cleaned-up.
|
|
*
|
|
* return true when umount was successful. false when timed out.
|
|
*/
|
|
static UmountStat TryUmountAndFsck(unsigned int cmd, bool run_fsck,
|
|
std::chrono::milliseconds timeout, sem_t* reboot_semaphore) {
|
|
Timer t;
|
|
std::vector<MountEntry> block_devices;
|
|
std::vector<MountEntry> emulated_devices;
|
|
|
|
if (run_fsck && !FindPartitionsToUmount(&block_devices, &emulated_devices, false)) {
|
|
return UMOUNT_STAT_ERROR;
|
|
}
|
|
|
|
UmountStat stat = UmountPartitions(timeout - t.duration());
|
|
if (stat != UMOUNT_STAT_SUCCESS) {
|
|
LOG(INFO) << "umount timeout, last resort, kill all and try";
|
|
if (DUMP_ON_UMOUNT_FAILURE) DumpUmountDebuggingInfo();
|
|
KillAllProcesses();
|
|
// even if it succeeds, still it is timeout and do not run fsck with all processes killed
|
|
UmountStat st = UmountPartitions(0ms);
|
|
if ((st != UMOUNT_STAT_SUCCESS) && DUMP_ON_UMOUNT_FAILURE) DumpUmountDebuggingInfo();
|
|
}
|
|
|
|
if (stat == UMOUNT_STAT_SUCCESS && run_fsck) {
|
|
LOG(INFO) << "Pause reboot monitor thread before fsck";
|
|
sem_post(reboot_semaphore);
|
|
|
|
// fsck part is excluded from timeout check. It only runs for user initiated shutdown
|
|
// and should not affect reboot time.
|
|
for (auto& entry : block_devices) {
|
|
entry.DoFsck();
|
|
}
|
|
|
|
LOG(INFO) << "Resume reboot monitor thread after fsck";
|
|
sem_post(reboot_semaphore);
|
|
}
|
|
return stat;
|
|
}
|
|
|
|
// zram is able to use backing device on top of a loopback device.
|
|
// In order to unmount /data successfully, we have to kill the loopback device first
|
|
#define ZRAM_DEVICE "/dev/block/zram0"
|
|
#define ZRAM_RESET "/sys/block/zram0/reset"
|
|
#define ZRAM_BACK_DEV "/sys/block/zram0/backing_dev"
|
|
static Result<void> KillZramBackingDevice() {
|
|
if (access(ZRAM_BACK_DEV, F_OK) != 0 && errno == ENOENT) {
|
|
LOG(INFO) << "No zram backing device configured";
|
|
return {};
|
|
}
|
|
std::string backing_dev;
|
|
if (!android::base::ReadFileToString(ZRAM_BACK_DEV, &backing_dev)) {
|
|
return ErrnoError() << "Failed to read " << ZRAM_BACK_DEV;
|
|
}
|
|
|
|
// cut the last "\n"
|
|
backing_dev.erase(backing_dev.length() - 1);
|
|
|
|
// shutdown zram handle
|
|
Timer swap_timer;
|
|
LOG(INFO) << "swapoff() start...";
|
|
if (swapoff(ZRAM_DEVICE) == -1) {
|
|
return ErrnoError() << "zram_backing_dev: swapoff (" << backing_dev << ")"
|
|
<< " failed";
|
|
}
|
|
LOG(INFO) << "swapoff() took " << swap_timer;;
|
|
|
|
if (!WriteStringToFile("1", ZRAM_RESET)) {
|
|
return Error() << "zram_backing_dev: reset (" << backing_dev << ")"
|
|
<< " failed";
|
|
}
|
|
|
|
if (!android::base::StartsWith(backing_dev, "/dev/block/loop")) {
|
|
LOG(INFO) << backing_dev << " is not a loop device. Exiting early";
|
|
return {};
|
|
}
|
|
|
|
// clear loopback device
|
|
unique_fd loop(TEMP_FAILURE_RETRY(open(backing_dev.c_str(), O_RDWR | O_CLOEXEC)));
|
|
if (loop.get() < 0) {
|
|
return ErrnoError() << "zram_backing_dev: open(" << backing_dev << ")"
|
|
<< " failed";
|
|
}
|
|
|
|
if (ioctl(loop.get(), LOOP_CLR_FD, 0) < 0) {
|
|
return ErrnoError() << "zram_backing_dev: loop_clear (" << backing_dev << ")"
|
|
<< " failed";
|
|
}
|
|
LOG(INFO) << "zram_backing_dev: `" << backing_dev << "` is cleared successfully.";
|
|
return {};
|
|
}
|
|
|
|
// Stops given services, waits for them to be stopped for |timeout| ms.
|
|
// If terminate is true, then SIGTERM is sent to services, otherwise SIGKILL is sent.
|
|
static void StopServices(const std::vector<Service*>& services, std::chrono::milliseconds timeout,
|
|
bool terminate) {
|
|
LOG(INFO) << "Stopping " << services.size() << " services by sending "
|
|
<< (terminate ? "SIGTERM" : "SIGKILL");
|
|
std::vector<pid_t> pids;
|
|
pids.reserve(services.size());
|
|
for (const auto& s : services) {
|
|
if (s->pid() > 0) {
|
|
pids.push_back(s->pid());
|
|
}
|
|
if (terminate) {
|
|
s->Terminate();
|
|
} else {
|
|
s->Stop();
|
|
}
|
|
}
|
|
if (timeout > 0ms) {
|
|
WaitToBeReaped(pids, timeout);
|
|
} else {
|
|
// Even if we don't to wait for services to stop, we still optimistically reap zombies.
|
|
ReapAnyOutstandingChildren();
|
|
}
|
|
}
|
|
|
|
// Like StopServices, but also logs all the services that failed to stop after the provided timeout.
|
|
// Returns number of violators.
|
|
static int StopServicesAndLogViolations(const std::vector<Service*>& services,
|
|
std::chrono::milliseconds timeout, bool terminate) {
|
|
StopServices(services, timeout, terminate);
|
|
int still_running = 0;
|
|
for (const auto& s : services) {
|
|
if (s->IsRunning()) {
|
|
LOG(ERROR) << "[service-misbehaving] : service '" << s->name() << "' is still running "
|
|
<< timeout.count() << "ms after receiving "
|
|
<< (terminate ? "SIGTERM" : "SIGKILL");
|
|
still_running++;
|
|
}
|
|
}
|
|
return still_running;
|
|
}
|
|
|
|
static Result<void> UnmountAllApexes() {
|
|
const char* args[] = {"/system/bin/apexd", "--unmount-all"};
|
|
int status;
|
|
if (logwrap_fork_execvp(arraysize(args), args, &status, false, LOG_KLOG, true, nullptr) != 0) {
|
|
return ErrnoError() << "Failed to call '/system/bin/apexd --unmount-all'";
|
|
}
|
|
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
|
|
return {};
|
|
}
|
|
return Error() << "'/system/bin/apexd --unmount-all' failed : " << status;
|
|
}
|
|
|
|
//* Reboot / shutdown the system.
|
|
// cmd ANDROID_RB_* as defined in android_reboot.h
|
|
// reason Reason string like "reboot", "shutdown,userrequested"
|
|
// reboot_target Reboot target string like "bootloader". Otherwise, it should be an empty string.
|
|
// run_fsck Whether to run fsck after umount is done.
|
|
//
|
|
static void DoReboot(unsigned int cmd, const std::string& reason, const std::string& reboot_target,
|
|
bool run_fsck) {
|
|
Timer t;
|
|
LOG(INFO) << "Reboot start, reason: " << reason << ", reboot_target: " << reboot_target;
|
|
|
|
bool is_thermal_shutdown = cmd == ANDROID_RB_THERMOFF;
|
|
|
|
auto shutdown_timeout = 0ms;
|
|
if (!SHUTDOWN_ZERO_TIMEOUT) {
|
|
constexpr unsigned int shutdown_timeout_default = 6;
|
|
constexpr unsigned int max_thermal_shutdown_timeout = 3;
|
|
auto shutdown_timeout_final = android::base::GetUintProperty("ro.build.shutdown_timeout",
|
|
shutdown_timeout_default);
|
|
if (is_thermal_shutdown && shutdown_timeout_final > max_thermal_shutdown_timeout) {
|
|
shutdown_timeout_final = max_thermal_shutdown_timeout;
|
|
}
|
|
shutdown_timeout = std::chrono::seconds(shutdown_timeout_final);
|
|
}
|
|
LOG(INFO) << "Shutdown timeout: " << shutdown_timeout.count() << " ms";
|
|
|
|
sem_t reboot_semaphore;
|
|
if (sem_init(&reboot_semaphore, false, 0) == -1) {
|
|
// These should never fail, but if they do, skip the graceful reboot and reboot immediately.
|
|
LOG(ERROR) << "sem_init() fail and RebootSystem() return!";
|
|
RebootSystem(cmd, reboot_target);
|
|
}
|
|
|
|
// Start a thread to monitor init shutdown process
|
|
LOG(INFO) << "Create reboot monitor thread.";
|
|
bool reboot_monitor_run = true;
|
|
std::thread reboot_monitor_thread(&RebootMonitorThread, cmd, reboot_target, &reboot_semaphore,
|
|
shutdown_timeout, &reboot_monitor_run);
|
|
reboot_monitor_thread.detach();
|
|
|
|
// Start reboot monitor thread
|
|
sem_post(&reboot_semaphore);
|
|
|
|
// Ensure last reboot reason is reduced to canonical
|
|
// alias reported in bootloader or system boot reason.
|
|
size_t skip = 0;
|
|
std::vector<std::string> reasons = Split(reason, ",");
|
|
if (reasons.size() >= 2 && reasons[0] == "reboot" &&
|
|
(reasons[1] == "recovery" || reasons[1] == "bootloader" || reasons[1] == "cold" ||
|
|
reasons[1] == "hard" || reasons[1] == "warm")) {
|
|
skip = strlen("reboot,");
|
|
}
|
|
PersistRebootReason(reason.c_str() + skip, true);
|
|
|
|
// If /data isn't mounted then we can skip the extra reboot steps below, since we don't need to
|
|
// worry about unmounting it.
|
|
if (!IsDataMounted()) {
|
|
sync();
|
|
RebootSystem(cmd, reboot_target);
|
|
abort();
|
|
}
|
|
|
|
// watchdogd is a vendor specific component but should be alive to complete shutdown safely.
|
|
const std::set<std::string> to_starts{"watchdogd"};
|
|
std::vector<Service*> stop_first;
|
|
stop_first.reserve(ServiceList::GetInstance().services().size());
|
|
for (const auto& s : ServiceList::GetInstance()) {
|
|
if (kDebuggingServices.count(s->name())) {
|
|
// keep debugging tools until non critical ones are all gone.
|
|
s->SetShutdownCritical();
|
|
} else if (to_starts.count(s->name())) {
|
|
if (auto result = s->Start(); !result.ok()) {
|
|
LOG(ERROR) << "Could not start shutdown 'to_start' service '" << s->name()
|
|
<< "': " << result.error();
|
|
}
|
|
s->SetShutdownCritical();
|
|
} else if (s->IsShutdownCritical()) {
|
|
// Start shutdown critical service if not started.
|
|
if (auto result = s->Start(); !result.ok()) {
|
|
LOG(ERROR) << "Could not start shutdown critical service '" << s->name()
|
|
<< "': " << result.error();
|
|
}
|
|
} else {
|
|
stop_first.push_back(s.get());
|
|
}
|
|
}
|
|
|
|
// remaining operations (specifically fsck) may take a substantial duration
|
|
if (cmd == ANDROID_RB_POWEROFF || is_thermal_shutdown) {
|
|
TurnOffBacklight();
|
|
}
|
|
|
|
Service* boot_anim = ServiceList::GetInstance().FindService("bootanim");
|
|
Service* surface_flinger = ServiceList::GetInstance().FindService("surfaceflinger");
|
|
if (boot_anim != nullptr && surface_flinger != nullptr && surface_flinger->IsRunning()) {
|
|
bool do_shutdown_animation = GetBoolProperty("ro.init.shutdown_animation", false);
|
|
|
|
if (do_shutdown_animation) {
|
|
SetProperty("service.bootanim.exit", "0");
|
|
SetProperty("service.bootanim.progress", "0");
|
|
// Could be in the middle of animation. Stop and start so that it can pick
|
|
// up the right mode.
|
|
boot_anim->Stop();
|
|
}
|
|
|
|
for (const auto& service : ServiceList::GetInstance()) {
|
|
if (service->classnames().count("animation") == 0) {
|
|
continue;
|
|
}
|
|
|
|
// start all animation classes if stopped.
|
|
if (do_shutdown_animation) {
|
|
service->Start();
|
|
}
|
|
service->SetShutdownCritical(); // will not check animation class separately
|
|
}
|
|
|
|
if (do_shutdown_animation) {
|
|
boot_anim->Start();
|
|
surface_flinger->SetShutdownCritical();
|
|
boot_anim->SetShutdownCritical();
|
|
}
|
|
}
|
|
|
|
// optional shutdown step
|
|
// 1. terminate all services except shutdown critical ones. wait for delay to finish
|
|
if (shutdown_timeout > 0ms) {
|
|
StopServicesAndLogViolations(stop_first, shutdown_timeout / 2, true /* SIGTERM */);
|
|
}
|
|
// Send SIGKILL to ones that didn't terminate cleanly.
|
|
StopServicesAndLogViolations(stop_first, 0ms, false /* SIGKILL */);
|
|
SubcontextTerminate();
|
|
// Reap subcontext pids.
|
|
ReapAnyOutstandingChildren();
|
|
|
|
// 3. send volume abort_fuse and volume shutdown to vold
|
|
Service* vold_service = ServiceList::GetInstance().FindService("vold");
|
|
if (vold_service != nullptr && vold_service->IsRunning()) {
|
|
// Manually abort FUSE connections, since the FUSE daemon is already dead
|
|
// at this point, and unmounting it might hang.
|
|
CallVdc("volume", "abort_fuse");
|
|
CallVdc("volume", "shutdown");
|
|
vold_service->Stop();
|
|
} else {
|
|
LOG(INFO) << "vold not running, skipping vold shutdown";
|
|
}
|
|
// logcat stopped here
|
|
StopServices(GetDebuggingServices(false /* only_post_data */), 0ms, false /* SIGKILL */);
|
|
// 4. sync, try umount, and optionally run fsck for user shutdown
|
|
{
|
|
Timer sync_timer;
|
|
LOG(INFO) << "sync() before umount...";
|
|
sync();
|
|
LOG(INFO) << "sync() before umount took" << sync_timer;
|
|
}
|
|
// 5. drop caches and disable zram backing device, if exist
|
|
KillZramBackingDevice();
|
|
|
|
LOG(INFO) << "Ready to unmount apexes. So far shutdown sequence took " << t;
|
|
// 6. unmount active apexes, otherwise they might prevent clean unmount of /data.
|
|
if (auto ret = UnmountAllApexes(); !ret.ok()) {
|
|
LOG(ERROR) << ret.error();
|
|
}
|
|
UmountStat stat =
|
|
TryUmountAndFsck(cmd, run_fsck, shutdown_timeout - t.duration(), &reboot_semaphore);
|
|
// Follow what linux shutdown is doing: one more sync with little bit delay
|
|
{
|
|
Timer sync_timer;
|
|
LOG(INFO) << "sync() after umount...";
|
|
sync();
|
|
LOG(INFO) << "sync() after umount took" << sync_timer;
|
|
}
|
|
if (!is_thermal_shutdown) std::this_thread::sleep_for(100ms);
|
|
LogShutdownTime(stat, &t);
|
|
|
|
// Send signal to terminate reboot monitor thread.
|
|
reboot_monitor_run = false;
|
|
sem_post(&reboot_semaphore);
|
|
|
|
// Reboot regardless of umount status. If umount fails, fsck after reboot will fix it.
|
|
RebootSystem(cmd, reboot_target);
|
|
abort();
|
|
}
|
|
|
|
static void EnterShutdown() {
|
|
LOG(INFO) << "Entering shutdown mode";
|
|
shutting_down = true;
|
|
// Skip wait for prop if it is in progress
|
|
ResetWaitForProp();
|
|
// Clear EXEC flag if there is one pending
|
|
for (const auto& s : ServiceList::GetInstance()) {
|
|
s->UnSetExec();
|
|
}
|
|
}
|
|
|
|
static void LeaveShutdown() {
|
|
LOG(INFO) << "Leaving shutdown mode";
|
|
shutting_down = false;
|
|
StartSendingMessages();
|
|
}
|
|
|
|
static std::chrono::milliseconds GetMillisProperty(const std::string& name,
|
|
std::chrono::milliseconds default_value) {
|
|
auto value = GetUintProperty(name, static_cast<uint64_t>(default_value.count()));
|
|
return std::chrono::milliseconds(std::move(value));
|
|
}
|
|
|
|
static Result<void> DoUserspaceReboot() {
|
|
LOG(INFO) << "Userspace reboot initiated";
|
|
// An ugly way to pass a more precise reason on why fallback to hard reboot was triggered.
|
|
std::string sub_reason = "";
|
|
auto guard = android::base::make_scope_guard([&sub_reason] {
|
|
// Leave shutdown so that we can handle a full reboot.
|
|
LeaveShutdown();
|
|
trigger_shutdown("reboot,userspace_failed,shutdown_aborted," + sub_reason);
|
|
});
|
|
// Triggering userspace-reboot-requested will result in a bunch of setprop
|
|
// actions. We should make sure, that all of them are propagated before
|
|
// proceeding with userspace reboot. Synchronously setting sys.init.userspace_reboot.in_progress
|
|
// property is not perfect, but it should do the trick.
|
|
if (!android::sysprop::InitProperties::userspace_reboot_in_progress(true)) {
|
|
sub_reason = "setprop";
|
|
return Error() << "Failed to set sys.init.userspace_reboot.in_progress property";
|
|
}
|
|
EnterShutdown();
|
|
if (!SetProperty("sys.powerctl", "")) {
|
|
sub_reason = "resetprop";
|
|
return Error() << "Failed to reset sys.powerctl property";
|
|
}
|
|
std::vector<Service*> stop_first;
|
|
// Remember the services that were enabled. We will need to manually enable them again otherwise
|
|
// triggers like class_start won't restart them.
|
|
std::vector<Service*> were_enabled;
|
|
stop_first.reserve(ServiceList::GetInstance().services().size());
|
|
for (const auto& s : ServiceList::GetInstance().services_in_shutdown_order()) {
|
|
if (s->is_post_data() && !kDebuggingServices.count(s->name())) {
|
|
stop_first.push_back(s);
|
|
}
|
|
if (s->is_post_data() && s->IsEnabled()) {
|
|
were_enabled.push_back(s);
|
|
}
|
|
}
|
|
{
|
|
Timer sync_timer;
|
|
LOG(INFO) << "sync() before terminating services...";
|
|
sync();
|
|
LOG(INFO) << "sync() took " << sync_timer;
|
|
}
|
|
auto sigterm_timeout = GetMillisProperty("init.userspace_reboot.sigterm.timeoutmillis", 5s);
|
|
auto sigkill_timeout = GetMillisProperty("init.userspace_reboot.sigkill.timeoutmillis", 10s);
|
|
LOG(INFO) << "Timeout to terminate services: " << sigterm_timeout.count() << "ms "
|
|
<< "Timeout to kill services: " << sigkill_timeout.count() << "ms";
|
|
std::string services_file_name = "/metadata/userspacereboot/services.txt";
|
|
const int flags = O_RDWR | O_CREAT | O_SYNC | O_APPEND | O_CLOEXEC;
|
|
StopServicesAndLogViolations(stop_first, sigterm_timeout, true /* SIGTERM */);
|
|
if (int r = StopServicesAndLogViolations(stop_first, sigkill_timeout, false /* SIGKILL */);
|
|
r > 0) {
|
|
auto fd = unique_fd(TEMP_FAILURE_RETRY(open(services_file_name.c_str(), flags, 0666)));
|
|
android::base::WriteStringToFd("Post-data services still running: \n", fd);
|
|
for (const auto& s : stop_first) {
|
|
if (s->IsRunning()) {
|
|
android::base::WriteStringToFd(s->name() + "\n", fd);
|
|
}
|
|
}
|
|
sub_reason = "sigkill";
|
|
return Error() << r << " post-data services are still running";
|
|
}
|
|
if (auto result = KillZramBackingDevice(); !result.ok()) {
|
|
sub_reason = "zram";
|
|
return result;
|
|
}
|
|
if (auto result = CallVdc("volume", "reset"); !result.ok()) {
|
|
sub_reason = "vold_reset";
|
|
return result;
|
|
}
|
|
if (int r = StopServicesAndLogViolations(GetDebuggingServices(true /* only_post_data */),
|
|
sigkill_timeout, false /* SIGKILL */);
|
|
r > 0) {
|
|
auto fd = unique_fd(TEMP_FAILURE_RETRY(open(services_file_name.c_str(), flags, 0666)));
|
|
android::base::WriteStringToFd("Debugging services still running: \n", fd);
|
|
for (const auto& s : GetDebuggingServices(true)) {
|
|
if (s->IsRunning()) {
|
|
android::base::WriteStringToFd(s->name() + "\n", fd);
|
|
}
|
|
}
|
|
sub_reason = "sigkill_debug";
|
|
return Error() << r << " debugging services are still running";
|
|
}
|
|
{
|
|
Timer sync_timer;
|
|
LOG(INFO) << "sync() after stopping services...";
|
|
sync();
|
|
LOG(INFO) << "sync() took " << sync_timer;
|
|
}
|
|
if (auto result = UnmountAllApexes(); !result.ok()) {
|
|
sub_reason = "apex";
|
|
return result;
|
|
}
|
|
if (!SwitchToMountNamespaceIfNeeded(NS_BOOTSTRAP).ok()) {
|
|
sub_reason = "ns_switch";
|
|
return Error() << "Failed to switch to bootstrap namespace";
|
|
}
|
|
// Remove services that were defined in an APEX.
|
|
ServiceList::GetInstance().RemoveServiceIf([](const std::unique_ptr<Service>& s) -> bool {
|
|
if (s->is_from_apex()) {
|
|
LOG(INFO) << "Removing service '" << s->name() << "' because it's defined in an APEX";
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
// Re-enable services
|
|
for (const auto& s : were_enabled) {
|
|
LOG(INFO) << "Re-enabling service '" << s->name() << "'";
|
|
s->Enable();
|
|
}
|
|
ServiceList::GetInstance().ResetState();
|
|
LeaveShutdown();
|
|
ActionManager::GetInstance().QueueEventTrigger("userspace-reboot-resume");
|
|
guard.Disable(); // Go on with userspace reboot.
|
|
return {};
|
|
}
|
|
|
|
static void UserspaceRebootWatchdogThread() {
|
|
auto started_timeout = GetMillisProperty("init.userspace_reboot.started.timeoutmillis", 10s);
|
|
if (!WaitForProperty("sys.init.userspace_reboot.in_progress", "1", started_timeout)) {
|
|
LOG(ERROR) << "Userspace reboot didn't start in " << started_timeout.count()
|
|
<< "ms. Switching to full reboot";
|
|
// Init might be wedged, don't try to write reboot reason into a persistent property and do
|
|
// a dirty reboot.
|
|
PersistRebootReason("userspace_failed,watchdog_triggered,failed_to_start", false);
|
|
RebootSystem(ANDROID_RB_RESTART2, "userspace_failed,watchdog_triggered,failed_to_start");
|
|
}
|
|
LOG(INFO) << "Starting userspace reboot watchdog";
|
|
auto watchdog_timeout = GetMillisProperty("init.userspace_reboot.watchdog.timeoutmillis", 5min);
|
|
LOG(INFO) << "UserspaceRebootWatchdog timeout: " << watchdog_timeout.count() << "ms";
|
|
if (!WaitForProperty("sys.boot_completed", "1", watchdog_timeout)) {
|
|
LOG(ERROR) << "Failed to boot in " << watchdog_timeout.count()
|
|
<< "ms. Switching to full reboot";
|
|
// In this case device is in a boot loop. Only way to recover is to do dirty reboot.
|
|
// Since init might be wedged, don't try to write reboot reason into a persistent property.
|
|
PersistRebootReason("userspace_failed,watchdog_triggered,failed_to_boot", false);
|
|
RebootSystem(ANDROID_RB_RESTART2, "userspace_failed,watchdog_triggered,failed_to_boot");
|
|
}
|
|
LOG(INFO) << "Device booted, stopping userspace reboot watchdog";
|
|
}
|
|
|
|
static void HandleUserspaceReboot() {
|
|
if (!android::sysprop::InitProperties::is_userspace_reboot_supported().value_or(false)) {
|
|
LOG(ERROR) << "Attempted a userspace reboot on a device that doesn't support it";
|
|
return;
|
|
}
|
|
// Spinnig up a separate thread will fail the setns call later in the boot sequence.
|
|
// Fork a new process to monitor userspace reboot while we are investigating a better solution.
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
PLOG(ERROR) << "Failed to fork process for userspace reboot watchdog. Switching to full "
|
|
<< "reboot";
|
|
trigger_shutdown("reboot,userspace_failed,watchdog_fork");
|
|
return;
|
|
}
|
|
if (pid == 0) {
|
|
// Child
|
|
UserspaceRebootWatchdogThread();
|
|
_exit(EXIT_SUCCESS);
|
|
}
|
|
LOG(INFO) << "Clearing queue and starting userspace-reboot-requested trigger";
|
|
auto& am = ActionManager::GetInstance();
|
|
am.ClearQueue();
|
|
am.QueueEventTrigger("userspace-reboot-requested");
|
|
auto handler = [](const BuiltinArguments&) { return DoUserspaceReboot(); };
|
|
am.QueueBuiltinAction(handler, "userspace-reboot");
|
|
}
|
|
|
|
/**
|
|
* Check if "command" field is set in bootloader message.
|
|
*
|
|
* If "command" field is broken (contains non-printable characters prior to
|
|
* terminating zero), it will be zeroed.
|
|
*
|
|
* @param[in,out] boot Bootloader message (BCB) structure
|
|
* @return true if "command" field is already set, and false if it's empty
|
|
*/
|
|
static bool CommandIsPresent(bootloader_message* boot) {
|
|
if (boot->command[0] == '\0')
|
|
return false;
|
|
|
|
for (size_t i = 0; i < arraysize(boot->command); ++i) {
|
|
if (boot->command[i] == '\0')
|
|
return true;
|
|
if (!isprint(boot->command[i]))
|
|
break;
|
|
}
|
|
|
|
memset(boot->command, 0, sizeof(boot->command));
|
|
return false;
|
|
}
|
|
|
|
void HandlePowerctlMessage(const std::string& command) {
|
|
unsigned int cmd = 0;
|
|
std::vector<std::string> cmd_params = Split(command, ",");
|
|
std::string reboot_target = "";
|
|
bool run_fsck = false;
|
|
bool command_invalid = false;
|
|
bool userspace_reboot = false;
|
|
|
|
if (cmd_params[0] == "shutdown") {
|
|
cmd = ANDROID_RB_POWEROFF;
|
|
if (cmd_params.size() >= 2) {
|
|
if (cmd_params[1] == "userrequested") {
|
|
// The shutdown reason is PowerManager.SHUTDOWN_USER_REQUESTED.
|
|
// Run fsck once the file system is remounted in read-only mode.
|
|
run_fsck = true;
|
|
} else if (cmd_params[1] == "thermal") {
|
|
// Turn off sources of heat immediately.
|
|
TurnOffBacklight();
|
|
// run_fsck is false to avoid delay
|
|
cmd = ANDROID_RB_THERMOFF;
|
|
}
|
|
}
|
|
} else if (cmd_params[0] == "reboot") {
|
|
cmd = ANDROID_RB_RESTART2;
|
|
if (cmd_params.size() >= 2) {
|
|
reboot_target = cmd_params[1];
|
|
if (reboot_target == "userspace") {
|
|
LOG(INFO) << "Userspace reboot requested";
|
|
userspace_reboot = true;
|
|
}
|
|
// adb reboot fastboot should boot into bootloader for devices not
|
|
// supporting logical partitions.
|
|
if (reboot_target == "fastboot" &&
|
|
!android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
|
|
reboot_target = "bootloader";
|
|
}
|
|
// When rebooting to the bootloader notify the bootloader writing
|
|
// also the BCB.
|
|
if (reboot_target == "bootloader") {
|
|
std::string err;
|
|
if (!write_reboot_bootloader(&err)) {
|
|
LOG(ERROR) << "reboot-bootloader: Error writing "
|
|
"bootloader_message: "
|
|
<< err;
|
|
}
|
|
} else if (reboot_target == "recovery") {
|
|
bootloader_message boot = {};
|
|
if (std::string err; !read_bootloader_message(&boot, &err)) {
|
|
LOG(ERROR) << "Failed to read bootloader message: " << err;
|
|
}
|
|
// Update the boot command field if it's empty, and preserve
|
|
// the other arguments in the bootloader message.
|
|
if (!CommandIsPresent(&boot)) {
|
|
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
|
|
if (std::string err; !write_bootloader_message(boot, &err)) {
|
|
LOG(ERROR) << "Failed to set bootloader message: " << err;
|
|
return;
|
|
}
|
|
}
|
|
} else if (reboot_target == "sideload" || reboot_target == "sideload-auto-reboot" ||
|
|
reboot_target == "fastboot") {
|
|
std::string arg = reboot_target == "sideload-auto-reboot" ? "sideload_auto_reboot"
|
|
: reboot_target;
|
|
const std::vector<std::string> options = {
|
|
"--" + arg,
|
|
};
|
|
std::string err;
|
|
if (!write_bootloader_message(options, &err)) {
|
|
LOG(ERROR) << "Failed to set bootloader message: " << err;
|
|
return;
|
|
}
|
|
reboot_target = "recovery";
|
|
}
|
|
|
|
// If there are additional parameter, pass them along
|
|
for (size_t i = 2; (cmd_params.size() > i) && cmd_params[i].size(); ++i) {
|
|
reboot_target += "," + cmd_params[i];
|
|
}
|
|
}
|
|
} else {
|
|
command_invalid = true;
|
|
}
|
|
if (command_invalid) {
|
|
LOG(ERROR) << "powerctl: unrecognized command '" << command << "'";
|
|
return;
|
|
}
|
|
|
|
// We do not want to process any messages (queue'ing triggers, shutdown messages, control
|
|
// messages, etc) from properties during reboot.
|
|
StopSendingMessages();
|
|
|
|
if (userspace_reboot) {
|
|
HandleUserspaceReboot();
|
|
return;
|
|
}
|
|
|
|
LOG(INFO) << "Clear action queue and start shutdown trigger";
|
|
ActionManager::GetInstance().ClearQueue();
|
|
// Queue shutdown trigger first
|
|
ActionManager::GetInstance().QueueEventTrigger("shutdown");
|
|
// Queue built-in shutdown_done
|
|
auto shutdown_handler = [cmd, command, reboot_target, run_fsck](const BuiltinArguments&) {
|
|
DoReboot(cmd, command, reboot_target, run_fsck);
|
|
return Result<void>{};
|
|
};
|
|
ActionManager::GetInstance().QueueBuiltinAction(shutdown_handler, "shutdown_done");
|
|
|
|
EnterShutdown();
|
|
}
|
|
|
|
bool IsShuttingDown() {
|
|
return shutting_down;
|
|
}
|
|
|
|
} // namespace init
|
|
} // namespace android
|