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