From 8d01f63f50fb001f41835a0dab636981f2ba76dd Mon Sep 17 00:00:00 2001 From: Keun-young Park Date: Mon, 13 Mar 2017 11:54:47 -0700 Subject: [PATCH] remove emergency shutdown and improve init's reboot logic - Emergency shutdown just marks the fs as clean while leaving fs in the middle of any state. Do not use it anymore. - Changed android_reboot to set sys.powerctl property so that all shutdown can be done by init. - Normal reboot sequence changed to 1. Terminate processes (give time to clean up). And wait for completion based on ro.build.shutdown_timeout. Default value (when not set) is changed to 3 secs. If it is 0, do not terminate processes. 2. Kill all remaining services except critical services for shutdown. 3. Shutdown vold using "vdc volume shutdown" 4. umount all emulated partitions. If it fails, just detach. Wait in step 5 can handle it. 5. Try umounting R/W block devices for up to max timeout. If it fails, try DETACH. If umount fails to complete before reboot, it can be detected when system reboots. 6. Reboot - Log shutdown time and umount stat to log so that it can be collected after reboot - To umount emulated partitions, all pending writes inside kernel should be completed. - To umount /data partition, all emulated partitions on top of /data should be umounted and all pending writes should be completed. - umount retry will only wait up to timeout. If there are too many pending writes, reboot will discard them and e2fsck after reboot will fix any file system issues. bug: 36004738 bug: 32246772 Test: many reboots combining reboot from UI and adb reboot. Check last_kmsg and fs_stat after reboot. Change-Id: I6e74d6c68a21e76e08cc0438573d1586fd9aaee2 --- init/Android.mk | 1 + init/builtins.cpp | 211 ++--------- init/reboot.cpp | 413 ++++++++++++++++++++++ init/reboot.h | 30 ++ init/service.h | 26 +- init/util.cpp | 12 +- init/util.h | 1 - libcutils/android_reboot.c | 258 +------------- libcutils/include/cutils/android_reboot.h | 11 +- 9 files changed, 525 insertions(+), 438 deletions(-) create mode 100644 init/reboot.cpp create mode 100644 init/reboot.h diff --git a/init/Android.mk b/init/Android.mk index d48f15232..c82a19e26 100644 --- a/init/Android.mk +++ b/init/Android.mk @@ -77,6 +77,7 @@ LOCAL_SRC_FILES:= \ init.cpp \ keychords.cpp \ property_service.cpp \ + reboot.cpp \ signal_handler.cpp \ ueventd.cpp \ ueventd_parser.cpp \ diff --git a/init/builtins.cpp b/init/builtins.cpp index 43eb378d7..24875d5fa 100644 --- a/init/builtins.cpp +++ b/init/builtins.cpp @@ -64,6 +64,7 @@ #include "init_parser.h" #include "log.h" #include "property_service.h" +#include "reboot.h" #include "service.h" #include "signal_handler.h" #include "util.h" @@ -71,7 +72,6 @@ using namespace std::literals::string_literals; #define chmod DO_NOT_USE_CHMOD_USE_FCHMODAT_SYMLINK_NOFOLLOW -#define UNMOUNT_CHECK_TIMES 10 static constexpr std::chrono::nanoseconds kCommandRetryTimeout = 5s; @@ -116,114 +116,14 @@ done: return ret; } -// Turn off backlight while we are performing power down cleanup activities. -static void turnOffBacklight() { - static const char off[] = "0"; - - android::base::WriteStringToFile(off, "/sys/class/leds/lcd-backlight/brightness"); - - static const char backlightDir[] = "/sys/class/backlight"; - std::unique_ptr dir(opendir(backlightDir), closedir); - if (!dir) { - return; - } - - struct dirent *dp; - while ((dp = readdir(dir.get())) != NULL) { - if (((dp->d_type != DT_DIR) && (dp->d_type != DT_LNK)) || - (dp->d_name[0] == '.')) { - continue; - } - - std::string fileName = android::base::StringPrintf("%s/%s/brightness", - backlightDir, - dp->d_name); - android::base::WriteStringToFile(off, fileName); - } -} - static int reboot_into_recovery(const std::vector& options) { std::string err; if (!write_bootloader_message(options, &err)) { LOG(ERROR) << "failed to set bootloader message: " << err; return -1; } - reboot("recovery"); -} - -static void unmount_and_fsck(const struct mntent *entry) { - if (strcmp(entry->mnt_type, "f2fs") && strcmp(entry->mnt_type, "ext4")) - return; - - /* First, lazily unmount the directory. This unmount request finishes when - * all processes that open a file or directory in |entry->mnt_dir| exit. - */ - TEMP_FAILURE_RETRY(umount2(entry->mnt_dir, MNT_DETACH)); - - /* Next, kill all processes except init, kthreadd, and kthreadd's - * children to finish the lazy unmount. Killing all processes here is okay - * because this callback function is only called right before reboot(). - * It might be cleaner to selectively kill processes that actually use - * |entry->mnt_dir| rather than killing all, probably by reusing a function - * like killProcessesWithOpenFiles() in vold/, but the selinux policy does - * not allow init to scan /proc/ files which the utility function - * heavily relies on. The policy does not allow the process to execute - * killall/pkill binaries either. Note that some processes might - * automatically restart after kill(), but that is not really a problem - * because |entry->mnt_dir| is no longer visible to such new processes. - */ - ServiceManager::GetInstance().ForEachService([] (Service* s) { s->Stop(); }); - TEMP_FAILURE_RETRY(kill(-1, SIGKILL)); - - // Restart Watchdogd to allow us to complete umounting and fsck - Service *svc = ServiceManager::GetInstance().FindServiceByName("watchdogd"); - if (svc) { - do { - sched_yield(); // do not be so eager, let cleanup have priority - ServiceManager::GetInstance().ReapAnyOutstandingChildren(); - } while (svc->flags() & SVC_RUNNING); // Paranoid Cargo - svc->Start(); - } - - turnOffBacklight(); - - int count = 0; - while (count++ < UNMOUNT_CHECK_TIMES) { - int fd = TEMP_FAILURE_RETRY(open(entry->mnt_fsname, O_RDONLY | O_EXCL)); - if (fd >= 0) { - /* |entry->mnt_dir| has sucessfully been unmounted. */ - close(fd); - break; - } else if (errno == EBUSY) { - // Some processes using |entry->mnt_dir| are still alive. Wait for a - // while then retry. - std::this_thread::sleep_for(5000ms / UNMOUNT_CHECK_TIMES); - continue; - } else { - /* Cannot open the device. Give up. */ - return; - } - } - - // NB: With watchdog still running, there is no cap on the time it takes - // to complete the fsck, from the users perspective the device graphics - // and responses are locked-up and they may choose to hold the power - // button in frustration if it drags out. - - int st; - if (!strcmp(entry->mnt_type, "f2fs")) { - const char *f2fs_argv[] = { - "/system/bin/fsck.f2fs", "-f", entry->mnt_fsname, - }; - android_fork_execvp_ext(arraysize(f2fs_argv), (char **)f2fs_argv, - &st, true, LOG_KLOG, true, NULL, NULL, 0); - } else if (!strcmp(entry->mnt_type, "ext4")) { - const char *ext4_argv[] = { - "/system/bin/e2fsck", "-f", "-y", entry->mnt_fsname, - }; - android_fork_execvp_ext(arraysize(ext4_argv), (char **)ext4_argv, - &st, true, LOG_KLOG, true, NULL, NULL, 0); - } + DoReboot(ANDROID_RB_RESTART2, "reboot", "recovery", false); + return 0; } static int do_class_start(const std::vector& args) { @@ -706,86 +606,51 @@ static int do_restart(const std::vector& args) { } static int do_powerctl(const std::vector& args) { - const char* command = args[1].c_str(); - int len = 0; + const std::string& command = args[1]; unsigned int cmd = 0; - const char *reboot_target = ""; - void (*callback_on_ro_remount)(const struct mntent*) = NULL; + std::vector cmd_params = android::base::Split(command, ","); + std::string reason_string = cmd_params[0]; + std::string reboot_target = ""; + bool runFsck = false; + bool commandInvalid = false; - if (strncmp(command, "shutdown", 8) == 0) { + if (cmd_params.size() > 2) { + commandInvalid = true; + } else if (cmd_params[0] == "shutdown") { cmd = ANDROID_RB_POWEROFF; - len = 8; - } else if (strncmp(command, "reboot", 6) == 0) { + if (cmd_params.size() == 2 && cmd_params[1] == "userrequested") { + // The shutdown reason is PowerManager.SHUTDOWN_USER_REQUESTED. + // Run fsck once the file system is remounted in read-only mode. + runFsck = true; + reason_string = cmd_params[1]; + } + } else if (cmd_params[0] == "reboot") { cmd = ANDROID_RB_RESTART2; - len = 6; - } else if (strncmp(command, "thermal-shutdown", 16) == 0) { + if (cmd_params.size() == 2) { + reboot_target = cmd_params[1]; + // 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 (command == "thermal-shutdown") { // no additional parameter allowed cmd = ANDROID_RB_THERMOFF; - len = 16; } else { + commandInvalid = true; + } + if (commandInvalid) { LOG(ERROR) << "powerctl: unrecognized command '" << command << "'"; return -EINVAL; } - if (command[len] == ',') { - if (cmd == ANDROID_RB_POWEROFF && - !strcmp(&command[len + 1], "userrequested")) { - // The shutdown reason is PowerManager.SHUTDOWN_USER_REQUESTED. - // Run fsck once the file system is remounted in read-only mode. - callback_on_ro_remount = unmount_and_fsck; - } else if (cmd == ANDROID_RB_RESTART2) { - reboot_target = &command[len + 1]; - // When rebooting to the bootloader notify the bootloader writing - // also the BCB. - if (strcmp(reboot_target, "bootloader") == 0) { - std::string err; - if (!write_reboot_bootloader(&err)) { - LOG(ERROR) << "reboot-bootloader: Error writing " - "bootloader_message: " << err; - } - } - } - } else if (command[len] != '\0') { - LOG(ERROR) << "powerctl: unrecognized reboot target '" << &command[len] << "'"; - return -EINVAL; - } - - std::string timeout = property_get("ro.build.shutdown_timeout"); - unsigned int delay = 0; - - if (android::base::ParseUint(timeout, &delay) && delay > 0) { - Timer t; - // Ask all services to terminate. - ServiceManager::GetInstance().ForEachService( - [] (Service* s) { s->Terminate(); }); - - while (t.duration_s() < delay) { - ServiceManager::GetInstance().ReapAnyOutstandingChildren(); - - int service_count = 0; - ServiceManager::GetInstance().ForEachService( - [&service_count] (Service* s) { - // Count the number of services running. - // Exclude the console as it will ignore the SIGTERM signal - // and not exit. - // Note: SVC_CONSOLE actually means "requires console" but - // it is only used by the shell. - if (s->pid() != 0 && (s->flags() & SVC_CONSOLE) == 0) { - service_count++; - } - }); - - if (service_count == 0) { - // All terminable services terminated. We can exit early. - break; - } - - // Wait a bit before recounting the number or running services. - std::this_thread::sleep_for(50ms); - } - LOG(VERBOSE) << "Terminating running services took " << t; - } - - return android_reboot_with_callback(cmd, 0, reboot_target, callback_on_ro_remount); + DoReboot(cmd, reason_string, reboot_target, runFsck); + return 0; } static int do_trigger(const std::vector& args) { diff --git a/init/reboot.cpp b/init/reboot.cpp new file mode 100644 index 000000000..3e2d61e94 --- /dev/null +++ b/init/reboot.cpp @@ -0,0 +1,413 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "property_service.h" +#include "reboot.h" +#include "service.h" +#include "util.h" + +using android::base::StringPrintf; + +// 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, bool isMounted = true) + : mnt_fsname_(entry.mnt_fsname), + mnt_dir_(entry.mnt_dir), + mnt_type_(entry.mnt_type), + is_mounted_(isMounted) {} + + bool IsF2Fs() const { return mnt_type_ == "f2fs"; } + + bool IsExt4() const { return mnt_type_ == "ext4"; } + + bool is_mounted() const { return is_mounted_; } + + void set_is_mounted() { is_mounted_ = false; } + + const std::string& mnt_fsname() const { return mnt_fsname_; } + + const std::string& mnt_dir() const { return mnt_dir_; } + + static bool IsBlockDevice(const struct mntent& mntent) { + return android::base::StartsWith(mntent.mnt_fsname, "/dev/block"); + } + + static bool IsEmulatedDevice(const struct mntent& mntent) { + static const std::string SDCARDFS_NAME = "sdcardfs"; + return android::base::StartsWith(mntent.mnt_fsname, "/data/") && + SDCARDFS_NAME == mntent.mnt_type; + } + + private: + std::string mnt_fsname_; + std::string mnt_dir_; + std::string mnt_type_; + bool is_mounted_; +}; + +// Turn off backlight while we are performing power down cleanup activities. +static void TurnOffBacklight() { + static constexpr char OFF[] = "0"; + + android::base::WriteStringToFile(OFF, "/sys/class/leds/lcd-backlight/brightness"); + + static const char backlightDir[] = "/sys/class/backlight"; + std::unique_ptr dir(opendir(backlightDir), closedir); + if (!dir) { + return; + } + + struct dirent* dp; + while ((dp = readdir(dir.get())) != nullptr) { + if (((dp->d_type != DT_DIR) && (dp->d_type != DT_LNK)) || (dp->d_name[0] == '.')) { + continue; + } + + std::string fileName = StringPrintf("%s/%s/brightness", backlightDir, dp->d_name); + android::base::WriteStringToFile(OFF, fileName); + } +} + +static void DoFsck(const MountEntry& entry) { + static constexpr int UNMOUNT_CHECK_TIMES = 10; + + if (!entry.IsF2Fs() && !entry.IsExt4()) return; + + int count = 0; + while (count++ < UNMOUNT_CHECK_TIMES) { + int fd = TEMP_FAILURE_RETRY(open(entry.mnt_fsname().c_str(), O_RDONLY | O_EXCL)); + if (fd >= 0) { + /* |entry->mnt_dir| has sucessfully been unmounted. */ + close(fd); + break; + } else if (errno == EBUSY) { + // Some processes using |entry->mnt_dir| are still alive. Wait for a + // while then retry. + std::this_thread::sleep_for(5000ms / UNMOUNT_CHECK_TIMES); + continue; + } else { + /* Cannot open the device. Give up. */ + return; + } + } + + // NB: With watchdog still running, there is no cap on the time it takes + // to complete the fsck, from the users perspective the device graphics + // and responses are locked-up and they may choose to hold the power + // button in frustration if it drags out. + + int st; + if (entry.IsF2Fs()) { + const char* f2fs_argv[] = { + "/system/bin/fsck.f2fs", "-f", entry.mnt_fsname().c_str(), + }; + android_fork_execvp_ext(arraysize(f2fs_argv), (char**)f2fs_argv, &st, true, LOG_KLOG, true, + nullptr, nullptr, 0); + } else if (entry.IsExt4()) { + const char* ext4_argv[] = { + "/system/bin/e2fsck", "-f", "-y", entry.mnt_fsname().c_str(), + }; + android_fork_execvp_ext(arraysize(ext4_argv), (char**)ext4_argv, &st, true, LOG_KLOG, true, + nullptr, nullptr, 0); + } +} + +static void ShutdownVold() { + const char* vdc_argv[] = {"/system/bin/vdc", "volume", "shutdown"}; + int status; + android_fork_execvp_ext(arraysize(vdc_argv), (char**)vdc_argv, &status, true, LOG_KLOG, true, + nullptr, nullptr, 0); +} + +static void LogShutdownTime(UmountStat stat, Timer* t) { + LOG(WARNING) << "powerctl_shutdown_time_ms:" << std::to_string(t->duration_ms()) << ":" << stat; +} + +static void __attribute__((noreturn)) +RebootSystem(unsigned int cmd, const std::string& rebootTarget) { + switch (cmd) { + case ANDROID_RB_POWEROFF: + reboot(RB_POWER_OFF); + break; + + case ANDROID_RB_RESTART2: + syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, + LINUX_REBOOT_CMD_RESTART2, rebootTarget.c_str()); + break; + + case ANDROID_RB_THERMOFF: + reboot(RB_POWER_OFF); + break; + } + // In normal case, reboot should not return. + PLOG(FATAL) << "reboot call returned"; + abort(); +} + +/* Find all read+write block devices and emulated devices in /proc/mounts + * and add them to correpsponding list. + */ +static bool FindPartitionsToUmount(std::vector* blockDevPartitions, + std::vector* emulatedPartitions) { + std::unique_ptr fp(setmntent("/proc/mounts", "r"), endmntent); + if (fp == nullptr) { + PLOG(ERROR) << "Failed to open /proc/mounts"; + return false; + } + mntent* mentry; + while ((mentry = getmntent(fp.get())) != nullptr) { + if (MountEntry::IsBlockDevice(*mentry) && hasmntopt(mentry, "rw")) { + blockDevPartitions->emplace_back(*mentry); + } else if (MountEntry::IsEmulatedDevice(*mentry)) { + emulatedPartitions->emplace_back(*mentry); + } + } + return true; +} + +static bool UmountPartitions(std::vector* partitions, int maxRetry, int flags) { + static constexpr int SLEEP_AFTER_RETRY_US = 100000; + + bool umountDone; + int retryCounter = 0; + + while (true) { + umountDone = true; + for (auto& entry : *partitions) { + if (entry.is_mounted()) { + int r = umount2(entry.mnt_dir().c_str(), flags); + if (r == 0) { + entry.set_is_mounted(); + LOG(INFO) << StringPrintf("umounted %s, flags:0x%x", entry.mnt_fsname().c_str(), + flags); + } else { + umountDone = false; + PLOG(WARNING) << StringPrintf("cannot umount %s, flags:0x%x", + entry.mnt_fsname().c_str(), flags); + } + } + } + if (umountDone) break; + retryCounter++; + if (retryCounter >= maxRetry) break; + usleep(SLEEP_AFTER_RETRY_US); + } + return umountDone; +} + +/* 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(bool runFsck) { + std::vector emulatedPartitions; + std::vector blockDevRwPartitions; + + TurnOffBacklight(); // this part can take time. save power. + + if (!FindPartitionsToUmount(&blockDevRwPartitions, &emulatedPartitions)) { + return UMOUNT_STAT_ERROR; + } + if (emulatedPartitions.size() > 0) { + LOG(WARNING) << "emulated partitions still exist, will umount"; + /* Pending writes in emulated partitions can fail umount. After a few trials, detach + * it so that it can be umounted when all writes are done. + */ + if (!UmountPartitions(&emulatedPartitions, 1, 0)) { + UmountPartitions(&emulatedPartitions, 1, MNT_DETACH); + } + } + UmountStat stat = UMOUNT_STAT_SUCCESS; + /* data partition needs all pending writes to be completed and all emulated partitions + * umounted. If umount failed in the above step, it DETACH is requested, so umount can + * still happen while waiting for /data. If the current waiting is not good enough, give + * up and leave it to e2fsck after reboot to fix it. + */ + /* TODO update max waiting time based on usage data */ + if (!UmountPartitions(&blockDevRwPartitions, 100, 0)) { + /* Last resort, detach and hope it finish before shutdown. */ + UmountPartitions(&blockDevRwPartitions, 1, MNT_DETACH); + stat = UMOUNT_STAT_TIMEOUT; + } + if (stat == UMOUNT_STAT_SUCCESS && runFsck) { + for (auto& entry : blockDevRwPartitions) { + DoFsck(entry); + } + } + + return stat; +} + +static void DoSync() { + // quota sync is not done by sync cal, so should be done separately. + // quota sync is in VFS level, so do it before sync, which goes down to fs level. + int r = quotactl(QCMD(Q_SYNC, 0), nullptr, 0 /* do not care */, 0 /* do not care */); + if (r < 0) { + PLOG(ERROR) << "quotactl failed"; + } + sync(); +} + +static void __attribute__((noreturn)) DoThermalOff() { + LOG(WARNING) << "Thermal system shutdown"; + DoSync(); + RebootSystem(ANDROID_RB_THERMOFF, ""); + abort(); +} + +void DoReboot(unsigned int cmd, const std::string& reason, const std::string& rebootTarget, + bool runFsck) { + Timer t; + std::string timeout = property_get("ro.build.shutdown_timeout"); + unsigned int delay = 0; + + if (!android::base::ParseUint(timeout, &delay)) { + delay = 3; // force service termination by default + } + + android::base::WriteStringToFile(StringPrintf("%s\n", reason.c_str()), LAST_REBOOT_REASON_FILE); + + if (cmd == ANDROID_RB_THERMOFF) { // do not wait if it is thermal + DoThermalOff(); + abort(); + } + static const constexpr char* shutdown_critical_services[] = {"vold", "watchdogd"}; + for (const char* name : shutdown_critical_services) { + Service* s = ServiceManager::GetInstance().FindServiceByName(name); + if (s == nullptr) { + LOG(WARNING) << "Shutdown critical service not found:" << name; + continue; + } + s->Start(); // make sure that it is running. + s->SetShutdownCritical(); + } + // optional shutdown step + // 1. terminate all services except shutdown critical ones. wait for delay to finish + if (delay > 0) { + LOG(INFO) << "terminating init services"; + // tombstoned can write to data when other services are killed. so finish it first. + static const constexpr char* first_to_kill[] = {"tombstoned"}; + for (const char* name : first_to_kill) { + Service* s = ServiceManager::GetInstance().FindServiceByName(name); + if (s != nullptr) s->Stop(); + } + + // Ask all services to terminate except shutdown critical ones. + ServiceManager::GetInstance().ForEachService([](Service* s) { + if (!s->IsShutdownCritical()) s->Terminate(); + }); + + int service_count = 0; + while (t.duration_s() < delay) { + ServiceManager::GetInstance().ReapAnyOutstandingChildren(); + + service_count = 0; + ServiceManager::GetInstance().ForEachService([&service_count](Service* s) { + // Count the number of services running except shutdown critical. + // Exclude the console as it will ignore the SIGTERM signal + // and not exit. + // Note: SVC_CONSOLE actually means "requires console" but + // it is only used by the shell. + if (!s->IsShutdownCritical() && s->pid() != 0 && (s->flags() & SVC_CONSOLE) == 0) { + service_count++; + } + }); + + if (service_count == 0) { + // All terminable services terminated. We can exit early. + break; + } + + // Wait a bit before recounting the number or running services. + std::this_thread::sleep_for(50ms); + } + LOG(INFO) << "Terminating running services took " << t + << " with remaining services:" << service_count; + } + + // minimum safety steps before restarting + // 2. kill all services except ones that are necessary for the shutdown sequence. + ServiceManager::GetInstance().ForEachService([](Service* s) { + if (!s->IsShutdownCritical()) s->Stop(); + }); + ServiceManager::GetInstance().ReapAnyOutstandingChildren(); + + // 3. send volume shutdown to vold + Service* voldService = ServiceManager::GetInstance().FindServiceByName("vold"); + if (voldService != nullptr && voldService->IsRunning()) { + ShutdownVold(); + voldService->Terminate(); + } else { + LOG(INFO) << "vold not running, skipping vold shutdown"; + } + + // 4. sync, try umount, and optionally run fsck for user shutdown + DoSync(); + UmountStat stat = TryUmountAndFsck(runFsck); + LogShutdownTime(stat, &t); + // Reboot regardless of umount status. If umount fails, fsck after reboot will fix it. + RebootSystem(cmd, rebootTarget); + abort(); +} diff --git a/init/reboot.h b/init/reboot.h new file mode 100644 index 000000000..395624922 --- /dev/null +++ b/init/reboot.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#ifndef _INIT_REBOOT_H +#define _INIT_REBOOT_H + +/* Reboot / shutdown the system. + * cmd ANDROID_RB_* as defined in android_reboot.h + * reason Reason string like "reboot", "userrequested" + * rebootTarget Reboot target string like "bootloader". Otherwise, it should be an + * empty string. + * runFsck Whether to run fsck after umount is done. + */ +void DoReboot(unsigned int cmd, const std::string& reason, const std::string& rebootTarget, + bool runFsck) __attribute__((__noreturn__)); + +#endif diff --git a/init/service.h b/init/service.h index 013e65f04..08388e239 100644 --- a/init/service.h +++ b/init/service.h @@ -32,18 +32,21 @@ #include "keyword_map.h" #include "util.h" -#define SVC_DISABLED 0x001 // do not autostart with class -#define SVC_ONESHOT 0x002 // do not restart on exit -#define SVC_RUNNING 0x004 // currently active -#define SVC_RESTARTING 0x008 // waiting to restart -#define SVC_CONSOLE 0x010 // requires console -#define SVC_CRITICAL 0x020 // will reboot into recovery if keeps crashing -#define SVC_RESET 0x040 // Use when stopping a process, +#define SVC_DISABLED 0x001 // do not autostart with class +#define SVC_ONESHOT 0x002 // do not restart on exit +#define SVC_RUNNING 0x004 // currently active +#define SVC_RESTARTING 0x008 // waiting to restart +#define SVC_CONSOLE 0x010 // requires console +#define SVC_CRITICAL 0x020 // will reboot into recovery if keeps crashing +#define SVC_RESET 0x040 // Use when stopping a process, // but not disabling so it can be restarted with its class. -#define SVC_RC_DISABLED 0x080 // Remember if the disabled flag was set in the rc script. -#define SVC_RESTART 0x100 // Use to safely restart (stop, wait, start) a service. +#define SVC_RC_DISABLED 0x080 // Remember if the disabled flag was set in the rc script. +#define SVC_RESTART 0x100 // Use to safely restart (stop, wait, start) a service. #define SVC_DISABLED_START 0x200 // A start was requested but it was disabled at the time. -#define SVC_EXEC 0x400 // This synthetic service corresponds to an 'exec'. +#define SVC_EXEC 0x400 // This synthetic service corresponds to an 'exec'. + +#define SVC_SHUTDOWN_CRITICAL 0x800 // This service is critical for shutdown and + // should not be killed during shutdown #define NR_SVC_SUPP_GIDS 12 // twelve supplementary groups @@ -68,6 +71,7 @@ public: unsigned namespace_flags, const std::string& seclabel, const std::vector& args); + bool IsRunning() { return (flags_ & SVC_RUNNING) != 0; } bool ParseLine(const std::vector& args, std::string* err); bool Start(); bool StartIfNotDisabled(); @@ -79,6 +83,8 @@ public: void RestartIfNeeded(time_t* process_needs_restart_at); bool Reap(); void DumpState() const; + void SetShutdownCritical() { flags_ |= SVC_SHUTDOWN_CRITICAL; } + bool IsShutdownCritical() { return (flags_ & SVC_SHUTDOWN_CRITICAL) != 0; } const std::string& name() const { return name_; } const std::string& classname() const { return classname_; } diff --git a/init/util.cpp b/init/util.cpp index c98718101..b90e5b1ae 100644 --- a/init/util.cpp +++ b/init/util.cpp @@ -49,6 +49,7 @@ #include "init.h" #include "log.h" #include "property_service.h" +#include "reboot.h" #include "util.h" static unsigned int do_decode_uid(const char *s) @@ -410,18 +411,9 @@ bool expand_props(const std::string& src, std::string* dst) { return true; } -void reboot(const char* destination) { - android_reboot(ANDROID_RB_RESTART2, 0, destination); - // We're init, so android_reboot will actually have been a syscall so there's nothing - // to wait for. If android_reboot returns, just abort so that the kernel will reboot - // itself when init dies. - PLOG(FATAL) << "reboot failed"; - abort(); -} - void panic() { LOG(ERROR) << "panic: rebooting to bootloader"; - reboot("bootloader"); + DoReboot(ANDROID_RB_RESTART2, "reboot", "bootloader", false); } std::ostream& operator<<(std::ostream& os, const Timer& t) { diff --git a/init/util.h b/init/util.h index 5c38dc3c9..81c64d79b 100644 --- a/init/util.h +++ b/init/util.h @@ -78,7 +78,6 @@ std::string bytes_to_hex(const uint8_t *bytes, size_t bytes_len); bool is_dir(const char* pathname); bool expand_props(const std::string& src, std::string* dst); -void reboot(const char* destination) __attribute__((__noreturn__)); void panic() __attribute__((__noreturn__)); #endif diff --git a/libcutils/android_reboot.c b/libcutils/android_reboot.c index 06026d125..a33e45ffe 100644 --- a/libcutils/android_reboot.c +++ b/libcutils/android_reboot.c @@ -13,259 +13,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#include -#include -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include +#include #define TAG "android_reboot" -#define READONLY_CHECK_MS 5000 -#define READONLY_CHECK_TIMES 50 -typedef struct { - struct listnode list; - struct mntent entry; -} mntent_list; - -static bool is_block_device(const char* fsname) -{ - return !strncmp(fsname, "/dev/block", 10); -} - -/* Find all read+write block devices in /proc/mounts and add them to - * |rw_entries|. - */ -static void find_rw(struct listnode* rw_entries) -{ - FILE* fp; - struct mntent* mentry; - - if ((fp = setmntent("/proc/mounts", "r")) == NULL) { - KLOG_WARNING(TAG, "Failed to open /proc/mounts.\n"); - return; - } - while ((mentry = getmntent(fp)) != NULL) { - if (is_block_device(mentry->mnt_fsname) && hasmntopt(mentry, "rw")) { - mntent_list* item = (mntent_list*)calloc(1, sizeof(mntent_list)); - item->entry = *mentry; - item->entry.mnt_fsname = strdup(mentry->mnt_fsname); - item->entry.mnt_dir = strdup(mentry->mnt_dir); - item->entry.mnt_type = strdup(mentry->mnt_type); - item->entry.mnt_opts = strdup(mentry->mnt_opts); - list_add_tail(rw_entries, &item->list); - } - } - endmntent(fp); -} - -static void free_entries(struct listnode* entries) -{ - struct listnode* node; - struct listnode* n; - list_for_each_safe(node, n, entries) { - mntent_list* item = node_to_item(node, mntent_list, list); - free(item->entry.mnt_fsname); - free(item->entry.mnt_dir); - free(item->entry.mnt_type); - free(item->entry.mnt_opts); - free(item); - } -} - -static mntent_list* find_item(struct listnode* rw_entries, const char* fsname_to_find) -{ - struct listnode* node; - list_for_each(node, rw_entries) { - mntent_list* item = node_to_item(node, mntent_list, list); - if (!strcmp(item->entry.mnt_fsname, fsname_to_find)) { - return item; - } - } - return NULL; -} - -/* Remounting filesystems read-only is difficult when there are files - * opened for writing or pending deletes on the filesystem. There is - * no way to force the remount with the mount(2) syscall. The magic sysrq - * 'u' command does an emergency remount read-only on all writable filesystems - * that have a block device (i.e. not tmpfs filesystems) by calling - * emergency_remount(), which knows how to force the remount to read-only. - * Unfortunately, that is asynchronous, and just schedules the work and - * returns. The best way to determine if it is done is to read /proc/mounts - * repeatedly until there are no more writable filesystems mounted on - * block devices. - */ -static void remount_ro(void (*cb_on_remount)(const struct mntent*)) -{ - int fd, cnt; - FILE* fp; - struct mntent* mentry; - struct listnode* node; - - list_declare(rw_entries); - list_declare(ro_entries); - - sync(); - find_rw(&rw_entries); - - /* Trigger the remount of the filesystems as read-only, - * which also marks them clean. - */ - fd = TEMP_FAILURE_RETRY(open("/proc/sysrq-trigger", O_WRONLY)); - if (fd < 0) { - KLOG_WARNING(TAG, "Failed to open sysrq-trigger.\n"); - /* TODO: Try to remount each rw parition manually in readonly mode. - * This may succeed if no process is using the partition. - */ - goto out; - } - if (TEMP_FAILURE_RETRY(write(fd, "u", 1)) != 1) { - close(fd); - KLOG_WARNING(TAG, "Failed to write to sysrq-trigger.\n"); - /* TODO: The same. Manually remount the paritions. */ - goto out; - } - close(fd); - - /* Now poll /proc/mounts till it's done */ - cnt = 0; - while (cnt < READONLY_CHECK_TIMES) { - if ((fp = setmntent("/proc/mounts", "r")) == NULL) { - /* If we can't read /proc/mounts, just give up. */ - KLOG_WARNING(TAG, "Failed to open /proc/mounts.\n"); - goto out; - } - while ((mentry = getmntent(fp)) != NULL) { - if (!is_block_device(mentry->mnt_fsname) || !hasmntopt(mentry, "ro")) { - continue; - } - mntent_list* item = find_item(&rw_entries, mentry->mnt_fsname); - if (item) { - /* |item| has now been ro remounted. */ - list_remove(&item->list); - list_add_tail(&ro_entries, &item->list); - } - } - endmntent(fp); - if (list_empty(&rw_entries)) { - /* All rw block devices are now readonly. */ - break; - } - TEMP_FAILURE_RETRY( - usleep(READONLY_CHECK_MS * 1000 / READONLY_CHECK_TIMES)); - cnt++; - } - - list_for_each(node, &rw_entries) { - mntent_list* item = node_to_item(node, mntent_list, list); - KLOG_WARNING(TAG, "Failed to remount %s in readonly mode.\n", - item->entry.mnt_fsname); - } - - if (cb_on_remount) { - list_for_each(node, &ro_entries) { - mntent_list* item = node_to_item(node, mntent_list, list); - cb_on_remount(&item->entry); - } - } - -out: - free_entries(&rw_entries); - free_entries(&ro_entries); -} - -static void save_reboot_reason(int cmd, const char *arg) -{ - FILE *fp; - const char *reason = NULL; - - fp = fopen(LAST_REBOOT_REASON_FILE, "w"); - if (fp == NULL) { - KLOG_WARNING(TAG, "Error creating " LAST_REBOOT_REASON_FILE - ": %s\n", strerror(errno)); - return; - } - switch (cmd) { - case ANDROID_RB_RESTART: - reason = "restart"; - break; - - case ANDROID_RB_POWEROFF: - reason = "power-off"; - break; - - case ANDROID_RB_RESTART2: - reason = arg && strlen(arg) ? arg : "restart"; - break; - - case ANDROID_RB_THERMOFF: - reason = "thermal-shutdown"; - break; - - default: - fprintf(fp,"0x%08X\n", cmd); - break; - } - - if (reason) { - if (fprintf(fp, "%s\n", reason) < 0) { - KLOG_WARNING(TAG, "Error writing " LAST_REBOOT_REASON_FILE - ": %s\n", strerror(errno)); - } - } - - fclose(fp); -} - -int android_reboot_with_callback( - int cmd, int flags __unused, const char *arg, - void (*cb_on_remount)(const struct mntent*)) -{ +int android_reboot(int cmd, int flags __unused, const char* arg) { int ret; + const char* restart_cmd = NULL; + char* prop_value; - save_reboot_reason(cmd, arg); - remount_ro(cb_on_remount); switch (cmd) { - case ANDROID_RB_RESTART: - ret = reboot(RB_AUTOBOOT); - break; - - case ANDROID_RB_POWEROFF: - ret = reboot(RB_POWER_OFF); - break; - + case ANDROID_RB_RESTART: // deprecated case ANDROID_RB_RESTART2: - ret = syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, - LINUX_REBOOT_CMD_RESTART2, arg); + restart_cmd = "reboot"; + break; + case ANDROID_RB_POWEROFF: + restart_cmd = "shutdown"; break; - case ANDROID_RB_THERMOFF: - ret = reboot(RB_POWER_OFF); + restart_cmd = "thermal-shutdown"; break; - - default: - ret = -1; } - + if (!restart_cmd) return -1; + if (arg) { + ret = asprintf(&prop_value, "%s,%s", restart_cmd, arg); + } else { + ret = asprintf(&prop_value, "%s", restart_cmd); + } + if (ret < 0) return -1; + ret = property_set(ANDROID_RB_PROPERTY, prop_value); + free(prop_value); return ret; } - -int android_reboot(int cmd, int flags, const char *arg) -{ - return android_reboot_with_callback(cmd, flags, arg, NULL); -} diff --git a/libcutils/include/cutils/android_reboot.h b/libcutils/include/cutils/android_reboot.h index 2e3b42986..716567a2b 100644 --- a/libcutils/include/cutils/android_reboot.h +++ b/libcutils/include/cutils/android_reboot.h @@ -17,12 +17,11 @@ #ifndef __CUTILS_ANDROID_REBOOT_H__ #define __CUTILS_ANDROID_REBOOT_H__ -#include __BEGIN_DECLS /* Commands */ -#define ANDROID_RB_RESTART 0xDEAD0001 +#define ANDROID_RB_RESTART 0xDEAD0001 /* deprecated. Use RESTART2. */ #define ANDROID_RB_POWEROFF 0xDEAD0002 #define ANDROID_RB_RESTART2 0xDEAD0003 #define ANDROID_RB_THERMOFF 0xDEAD0004 @@ -33,10 +32,12 @@ __BEGIN_DECLS /* Android reboot reason stored in this file */ #define LAST_REBOOT_REASON_FILE "/data/misc/reboot/last_reboot_reason" +/* Reboot or shutdown the system. + * This call uses ANDROID_RB_PROPERTY to request reboot to init process. + * Due to that, process calling this should have proper selinux permission + * to write to the property. Otherwise, the call will fail. + */ int android_reboot(int cmd, int flags, const char *arg); -int android_reboot_with_callback( - int cmd, int flags, const char *arg, - void (*cb_on_remount)(const struct mntent*)); __END_DECLS