From 10f441a9dbb91be3124f455439631abcf8e96cde Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Fri, 19 Apr 2019 15:22:15 -0700 Subject: minadbd: Support `adb reboot` under sideload/rescue modes. Bug: 128415917 Test: Run the following commands under sideload and rescue modes respectively. $ adb reboot $ adb reboot bootloader $ adb reboot recovery $ adb reboot rescue $ adb reboot invalid Change-Id: I84daf63e3360b7b4a0af5e055149a4f54e10ba90 --- install/adb_install.cpp | 98 +++++++++++++++++++++++--------- install/include/install/adb_install.h | 6 +- install/include/install/install.h | 3 +- minadbd/minadbd_services.cpp | 43 +++++++++++++- minadbd/minadbd_services_test.cpp | 6 +- minadbd/minadbd_types.h | 13 ++++- recovery.cpp | 58 +++++++++++-------- recovery_main.cpp | 5 ++ recovery_ui/include/recovery_ui/device.h | 4 ++ 9 files changed, 176 insertions(+), 60 deletions(-) diff --git a/install/adb_install.cpp b/install/adb_install.cpp index f430920a4..d79f6f4b0 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -44,30 +45,34 @@ #include "install/install.h" #include "minadbd_types.h" #include "otautil/sysutil.h" +#include "recovery_ui/device.h" #include "recovery_ui/ui.h" -using CommandFunction = std::function; +// A CommandFunction returns a pair of (result, should_continue), which indicates the command +// execution result and whether it should proceed to the next iteration. The execution result will +// always be sent to the minadbd side. +using CommandFunction = std::function()>; static bool SetUsbConfig(const std::string& state) { android::base::SetProperty("sys.usb.config", state); return android::base::WaitForProperty("sys.usb.state", state); } -// Parses the minadbd command in |message|; returns MinadbdCommands::kError upon errors. -static MinadbdCommands ParseMinadbdCommands(const std::string& message) { +// Parses the minadbd command in |message|; returns MinadbdCommand::kError upon errors. +static MinadbdCommand ParseMinadbdCommand(const std::string& message) { if (!android::base::StartsWith(message, kMinadbdCommandPrefix)) { LOG(ERROR) << "Failed to parse command in message " << message; - return MinadbdCommands::kError; + return MinadbdCommand::kError; } auto cmd_code_string = message.substr(strlen(kMinadbdCommandPrefix)); auto cmd_code = android::base::get_unaligned(cmd_code_string.c_str()); - if (cmd_code >= static_cast(MinadbdCommands::kError)) { + if (cmd_code >= static_cast(MinadbdCommand::kError)) { LOG(ERROR) << "Unsupported command code: " << cmd_code; - return MinadbdCommands::kError; + return MinadbdCommand::kError; } - return static_cast(cmd_code); + return static_cast(cmd_code); } static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) { @@ -82,13 +87,15 @@ static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) { return true; } -// Installs the package from FUSE. Returns true if the installation succeeds, and false otherwise. -static bool AdbInstallPackageHandler(RecoveryUI* ui, int* result) { +// Installs the package from FUSE. Returns the installation result and whether it should continue +// waiting for new commands. +static auto AdbInstallPackageHandler(RecoveryUI* ui, int* result) { // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long // because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME // will start to exist once the host connects and starts serving a package. Poll for its // appearance. (Note that inotify doesn't work with FUSE.) constexpr int ADB_INSTALL_TIMEOUT = 15; + bool should_continue = true; *result = INSTALL_ERROR; for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { struct stat st; @@ -97,6 +104,7 @@ static bool AdbInstallPackageHandler(RecoveryUI* ui, int* result) { sleep(1); continue; } else { + should_continue = false; ui->Print("\nTimed out waiting for fuse to be ready.\n\n"); break; } @@ -108,13 +116,39 @@ static bool AdbInstallPackageHandler(RecoveryUI* ui, int* result) { // Calling stat() on this magic filename signals the FUSE to exit. struct stat st; stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); - return *result == INSTALL_SUCCESS; + return std::make_pair(*result == INSTALL_SUCCESS, should_continue); } -// Parses and executes the command from minadbd. Returns false if we enter an invalid state so that -// the caller can kill the minadbd service properly. -static bool HandleMessageFromMinadbd( - int socket_fd, const std::map& command_map) { +static auto AdbRebootHandler(MinadbdCommand command, int* result, + Device::BuiltinAction* reboot_action) { + switch (command) { + case MinadbdCommand::kRebootBootloader: + *reboot_action = Device::REBOOT_BOOTLOADER; + break; + case MinadbdCommand::kRebootFastboot: + *reboot_action = Device::ENTER_FASTBOOT; + break; + case MinadbdCommand::kRebootRecovery: + *reboot_action = Device::ENTER_RECOVERY; + break; + case MinadbdCommand::kRebootRescue: + // Use Device::REBOOT_RESCUE instead of Device::ENTER_RESCUE. This allows rebooting back into + // rescue mode (potentially using a newly installed recovery image). + *reboot_action = Device::REBOOT_RESCUE; + break; + case MinadbdCommand::kRebootAndroid: + default: + *reboot_action = Device::REBOOT; + break; + } + *result = INSTALL_REBOOT; + return std::make_pair(true, false); +} + +// Parses and executes the command from minadbd. Returns whether the caller should keep waiting for +// next command. +static bool HandleMessageFromMinadbd(int socket_fd, + const std::map& command_map) { char buffer[kMinadbdMessageSize]; if (!android::base::ReadFully(socket_fd, buffer, kMinadbdMessageSize)) { PLOG(ERROR) << "Failed to read message from minadbd"; @@ -122,8 +156,8 @@ static bool HandleMessageFromMinadbd( } std::string message(buffer, buffer + kMinadbdMessageSize); - auto command_type = ParseMinadbdCommands(message); - if (command_type == MinadbdCommands::kError) { + auto command_type = ParseMinadbdCommand(message); + if (command_type == MinadbdCommand::kError) { return false; } if (command_map.find(command_type) == command_map.end()) { @@ -135,17 +169,19 @@ static bool HandleMessageFromMinadbd( // We have received a valid command, execute the corresponding function. const auto& command_func = command_map.at(command_type); - if (!command_func()) { - LOG(ERROR) << "Failed to execute command " << static_cast(command_type); - return WriteStatusToFd(MinadbdCommandStatus::kFailure, socket_fd); + const auto [result, should_continue] = command_func(); + LOG(INFO) << "Command " << static_cast(command_type) << " finished with " << result; + if (!WriteStatusToFd(result ? MinadbdCommandStatus::kSuccess : MinadbdCommandStatus::kFailure, + socket_fd)) { + return false; } - return WriteStatusToFd(MinadbdCommandStatus::kSuccess, socket_fd); + return should_continue; } // TODO(xunchang) add a wrapper function and kill the minadbd service there. static void ListenAndExecuteMinadbdCommands( pid_t minadbd_pid, android::base::unique_fd&& socket_fd, - const std::map& command_map) { + const std::map& command_map) { android::base::unique_fd epoll_fd(epoll_create1(O_CLOEXEC)); if (epoll_fd == -1) { PLOG(ERROR) << "Failed to create epoll"; @@ -230,7 +266,7 @@ static void ListenAndExecuteMinadbdCommands( // b11. exit the listening loop // static void CreateMinadbdServiceAndExecuteCommands( - const std::map& command_map, bool rescue_mode) { + const std::map& command_map, bool rescue_mode) { signal(SIGPIPE, SIG_IGN); android::base::unique_fd recovery_socket; @@ -271,7 +307,6 @@ static void CreateMinadbdServiceAndExecuteCommands( std::thread listener_thread(ListenAndExecuteMinadbdCommands, child, std::move(recovery_socket), std::ref(command_map)); - if (listener_thread.joinable()) { listener_thread.join(); } @@ -289,7 +324,7 @@ static void CreateMinadbdServiceAndExecuteCommands( signal(SIGPIPE, SIG_DFL); } -int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode) { +int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot_action) { // Save the usb state to restore after the sideload operation. std::string usb_state = android::base::GetProperty("sys.usb.state", "none"); // Clean up state and stop adbd. @@ -307,8 +342,19 @@ int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode) { } int install_result = INSTALL_ERROR; - std::map command_map{ - { MinadbdCommands::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) }, + std::map command_map{ + { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, ui, &install_result) }, + { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid, + &install_result, reboot_action) }, + { MinadbdCommand::kRebootBootloader, + std::bind(&AdbRebootHandler, MinadbdCommand::kRebootBootloader, &install_result, + reboot_action) }, + { MinadbdCommand::kRebootFastboot, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootFastboot, + &install_result, reboot_action) }, + { MinadbdCommand::kRebootRecovery, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRecovery, + &install_result, reboot_action) }, + { MinadbdCommand::kRebootRescue, + std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) }, }; CreateMinadbdServiceAndExecuteCommands(command_map, rescue_mode); diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h index 208d0c780..49b32b54f 100644 --- a/install/include/install/adb_install.h +++ b/install/include/install/adb_install.h @@ -16,6 +16,10 @@ #pragma once +#include #include -int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode); +// Applies a package via `adb sideload` or `adb rescue`. Returns the install result (in `enum +// InstallResult`). When a reboot has been requested, INSTALL_REBOOT will be the return value, with +// the reboot target set in reboot_action. +int ApplyFromAdb(RecoveryUI* ui, bool rescue_mode, Device::BuiltinAction* reboot_action); diff --git a/install/include/install/install.h b/install/include/install/install.h index 1e41b4843..c0a8f1f4c 100644 --- a/install/include/install/install.h +++ b/install/include/install/install.h @@ -34,7 +34,8 @@ enum InstallResult { INSTALL_NONE, INSTALL_SKIPPED, INSTALL_RETRY, - INSTALL_KEY_INTERRUPTED + INSTALL_KEY_INTERRUPTED, + INSTALL_REBOOT, }; enum class OtaType { diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index f6aff71f8..9b1999d90 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -64,7 +64,7 @@ void SetSideloadMountPoint(const std::string& path) { sideload_mount_point = path; } -static bool WriteCommandToFd(MinadbdCommands cmd, int fd) { +static bool WriteCommandToFd(MinadbdCommand cmd, int fd) { char message[kMinadbdMessageSize]; memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix)); android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), cmd); @@ -109,7 +109,7 @@ static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args, LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size; - if (!WriteCommandToFd(MinadbdCommands::kInstall, minadbd_socket)) { + if (!WriteCommandToFd(MinadbdCommand::kInstall, minadbd_socket)) { return kMinadbdSocketIOError; } @@ -175,7 +175,45 @@ static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) { } } +// Reboots into the given target. We don't reboot directly from minadbd, but going through recovery +// instead. This allows recovery to finish all the pending works (clear BCB, save logs etc) before +// the reboot. +static void RebootHostService(unique_fd /* sfd */, const std::string& target) { + MinadbdCommand command; + if (target == "bootloader") { + command = MinadbdCommand::kRebootBootloader; + } else if (target == "rescue") { + command = MinadbdCommand::kRebootRescue; + } else if (target == "recovery") { + command = MinadbdCommand::kRebootRecovery; + } else if (target == "fastboot") { + command = MinadbdCommand::kRebootFastboot; + } else { + command = MinadbdCommand::kRebootAndroid; + } + if (!WriteCommandToFd(command, minadbd_socket)) { + exit(kMinadbdSocketIOError); + } + MinadbdCommandStatus status; + if (!WaitForCommandStatus(minadbd_socket, &status)) { + exit(kMinadbdMessageFormatError); + } +} + unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) { + // Common services that are supported both in sideload and rescue modes. + if (ConsumePrefix(&name, "reboot:")) { + // "reboot:", where target must be one of the following. + std::string args(name); + if (args.empty() || args == "bootloader" || args == "rescue" || args == "recovery" || + args == "fastboot") { + return create_service_thread("reboot", + std::bind(RebootHostService, std::placeholders::_1, args)); + } + return unique_fd{}; + } + + // Rescue-specific services. if (rescue_mode) { if (ConsumePrefix(&name, "rescue-install:")) { // rescue-install:: @@ -191,6 +229,7 @@ unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport * return unique_fd{}; } + // Sideload-specific services. if (name.starts_with("sideload:")) { // This exit status causes recovery to print a special error message saying to use a newer adb // (that supports sideload-host). diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp index 413ba0df6..593180bb3 100644 --- a/minadbd/minadbd_services_test.cpp +++ b/minadbd/minadbd_services_test.cpp @@ -62,7 +62,7 @@ class MinadbdServicesTest : public ::testing::Test { signal(SIGPIPE, SIG_DFL); } - void ReadAndCheckCommandMessage(int fd, MinadbdCommands expected_command) { + void ReadAndCheckCommandMessage(int fd, MinadbdCommand expected_command) { std::vector received(kMinadbdMessageSize, '\0'); ASSERT_TRUE(android::base::ReadFully(fd, received.data(), kMinadbdMessageSize)); @@ -147,7 +147,7 @@ TEST_F(MinadbdServicesTest, SideloadHostService_wrong_command_format) { unique_fd fd = daemon_service_to_fd(command, nullptr); ASSERT_NE(-1, fd); WaitForFusePath(); - ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommands::kInstall); + ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall); struct stat sb; ASSERT_EQ(0, stat(exit_flag_.c_str(), &sb)); @@ -188,7 +188,7 @@ TEST_F(MinadbdServicesTest, SideloadHostService_read_data_from_fuse) { unique_fd fd = daemon_service_to_fd("sideload-host:4096:4096", nullptr); ASSERT_NE(-1, fd); - ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommands::kInstall); + ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall); // Mimic the response from adb host. std::string adb_message(8, '\0'); diff --git a/minadbd/minadbd_types.h b/minadbd/minadbd_types.h index 5fb7803e7..b370b7952 100644 --- a/minadbd/minadbd_types.h +++ b/minadbd/minadbd_types.h @@ -43,12 +43,19 @@ enum class MinadbdCommandStatus : uint32_t { kFailure = 1, }; -enum class MinadbdCommands : uint32_t { +enum class MinadbdCommand : uint32_t { kInstall = 0, kUiPrint = 1, - kError = 2, + kRebootAndroid = 2, + kRebootBootloader = 3, + kRebootFastboot = 4, + kRebootRecovery = 5, + kRebootRescue = 6, + + // Last but invalid command. + kError, }; -static_assert(kMinadbdMessageSize == sizeof(kMinadbdCommandPrefix) - 1 + sizeof(MinadbdCommands)); +static_assert(kMinadbdMessageSize == sizeof(kMinadbdCommandPrefix) - 1 + sizeof(MinadbdCommand)); static_assert(kMinadbdMessageSize == sizeof(kMinadbdStatusPrefix) - 1 + sizeof(MinadbdCommandStatus)); diff --git a/recovery.cpp b/recovery.cpp index ce29cb27b..5bd9b1728 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -512,6 +512,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { case Device::REBOOT: case Device::SHUTDOWN: case Device::REBOOT_BOOTLOADER: + case Device::REBOOT_RESCUE: case Device::ENTER_FASTBOOT: case Device::ENTER_RECOVERY: return chosen_action; @@ -537,32 +538,36 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { if (!ui->IsTextVisible()) return Device::NO_ACTION; break; } + case Device::APPLY_ADB_SIDELOAD: case Device::APPLY_SDCARD: case Device::ENTER_RESCUE: { save_current_log = true; bool adb = true; + Device::BuiltinAction reboot_action; if (chosen_action == Device::ENTER_RESCUE) { // Switch to graphics screen. ui->ShowText(false); - status = ApplyFromAdb(ui, true /* rescue_mode */); - ui->ShowText(true); + status = ApplyFromAdb(ui, true /* rescue_mode */, &reboot_action); } else if (chosen_action == Device::APPLY_ADB_SIDELOAD) { - status = ApplyFromAdb(ui, false /* rescue_mode */); + status = ApplyFromAdb(ui, false /* rescue_mode */, &reboot_action); } else { adb = false; status = ApplyFromSdcard(device, ui); } + ui->Print("\nInstall from %s completed with status %d.\n", adb ? "ADB" : "SD card", status); + if (status == INSTALL_REBOOT) { + return reboot_action; + } + if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); copy_logs(save_current_log, has_cache, sehandle); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible - } else { - ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); } break; } @@ -841,6 +846,9 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorPrint("Supported API: %d\n", kRecoveryApiVersion); int status = INSTALL_SUCCESS; + // next_action indicates the next target to reboot into upon finishing the install. It could be + // overridden to a different reboot target per user request. + Device::BuiltinAction next_action = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; if (update_package != nullptr) { // It's not entirely true that we will modify the flash. But we want @@ -924,19 +932,18 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorShowText(true); } - status = ApplyFromAdb(ui, false /* rescue_mode */); + status = ApplyFromAdb(ui, false /* rescue_mode */, &next_action); ui->Print("\nInstall from ADB complete (status: %d).\n", status); if (sideload_auto_reboot) { + status = INSTALL_REBOOT; ui->Print("Rebooting automatically.\n"); } } else if (fsck_unshare_blocks) { @@ -961,23 +968,26 @@ Device::BuiltinAction start_recovery(Device* device, const std::vectorIsTextVisible()) { - Device::BuiltinAction temp = prompt_and_wait(device, status); - if (temp != Device::NO_ACTION) { - after = temp; + // Determine the next action. + // - If the state is INSTALL_REBOOT, device will reboot into the target as specified in + // `next_action`. + // - If the recovery menu is visible, prompt and wait for commands. + // - If the state is INSTALL_NONE, wait for commands (e.g. in user build, one manually boots + // into recovery to sideload a package or to wipe the device). + // - In all other cases, reboot the device. Therefore, normal users will observe the device + // rebooting a) immediately upon successful finish (INSTALL_SUCCESS); or b) an "error" screen + // for 5s followed by an automatic reboot. + if (status != INSTALL_REBOOT) { + if (status == INSTALL_NONE || ui->IsTextVisible()) { + Device::BuiltinAction temp = prompt_and_wait(device, status); + if (temp != Device::NO_ACTION) { + next_action = temp; + } } } // Save logs and clean up before rebooting or shutting down. finish_recovery(); - return after; + return next_action; } diff --git a/recovery_main.cpp b/recovery_main.cpp index 37d9da0d7..0eb2962e1 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -477,6 +477,11 @@ int main(int argc, char** argv) { android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); break; + case Device::REBOOT_RESCUE: + ui->Print("Rebooting to rescue...\n"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,rescue"); + break; + case Device::ENTER_FASTBOOT: if (logical_partitions_mapped()) { ui->Print("Partitions may be mounted - rebooting to enter fastboot."); diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h index 8f17639d6..09b5d1f4d 100644 --- a/recovery_ui/include/recovery_ui/device.h +++ b/recovery_ui/include/recovery_ui/device.h @@ -50,7 +50,11 @@ class Device { KEY_INTERRUPTED = 13, ENTER_FASTBOOT = 14, ENTER_RECOVERY = 15, + // ENTER vs REBOOT: The latter will trigger a reboot that uses `rescue` as the reboot target. + // So it goes from rescue -> bootloader -> rescue, whereas ENTER_RESCUE switches from recovery + // -> rescue directly. ENTER_RESCUE = 16, + REBOOT_RESCUE = 17, }; explicit Device(RecoveryUI* ui); -- cgit v1.2.3