/* * Copyright (C) 2012 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 "install/adb_install.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fuse_sideload.h" #include "install/install.h" #include "install/wipe_data.h" #include "minadbd/types.h" #include "otautil/sysutil.h" #include "recovery_ui/device.h" #include "recovery_ui/ui.h" // 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 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 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(MinadbdCommand::kError)) { LOG(ERROR) << "Unsupported command code: " << cmd_code; return MinadbdCommand::kError; } return static_cast(cmd_code); } static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) { char message[kMinadbdMessageSize]; memcpy(message, kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix)); android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), status); if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) { PLOG(ERROR) << "Failed to write message " << message; return false; } return true; } // Installs the package from FUSE. Returns the installation result and whether it should continue // waiting for new commands. static auto AdbInstallPackageHandler(Device* device, InstallResult* 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.) auto ui = device->GetUI(); 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; if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) { if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) { sleep(1); continue; } else { should_continue = false; ui->Print("\nTimed out waiting for fuse to be ready.\n\n"); break; } } auto package = Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1)); *result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0, device); break; } // Calling stat() on this magic filename signals the FUSE to exit. struct stat st; stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); return std::make_pair(*result == INSTALL_SUCCESS, should_continue); } static auto AdbRebootHandler(MinadbdCommand command, InstallResult* result, Device::BuiltinAction* reboot_action) { // Use Device::REBOOT_{FASTBOOT,RECOVERY,RESCUE}, instead of the ones with ENTER_. This allows // rebooting back into fastboot/recovery/rescue mode through bootloader, which may use a newly // installed bootloader/recovery image. switch (command) { case MinadbdCommand::kRebootBootloader: *reboot_action = Device::REBOOT_BOOTLOADER; break; case MinadbdCommand::kRebootFastboot: *reboot_action = Device::REBOOT_FASTBOOT; break; case MinadbdCommand::kRebootRecovery: *reboot_action = Device::REBOOT_RECOVERY; break; case MinadbdCommand::kRebootRescue: *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"; return false; } std::string message(buffer, buffer + kMinadbdMessageSize); auto command_type = ParseMinadbdCommand(message); if (command_type == MinadbdCommand::kError) { return false; } if (command_map.find(command_type) == command_map.end()) { LOG(ERROR) << "Unsupported command: " << android::base::get_unaligned( message.substr(strlen(kMinadbdCommandPrefix)).c_str()); return false; } // We have received a valid command, execute the corresponding function. const auto& command_func = command_map.at(command_type); 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 should_continue; } // TODO(xunchang) add a wrapper function and kill the minadbd service there. static void ListenAndExecuteMinadbdCommands( RecoveryUI* ui, pid_t minadbd_pid, android::base::unique_fd&& socket_fd, 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"; kill(minadbd_pid, SIGKILL); return; } constexpr int EPOLL_MAX_EVENTS = 10; struct epoll_event ev = {}; ev.events = EPOLLIN | EPOLLHUP; ev.data.fd = socket_fd.get(); struct epoll_event events[EPOLL_MAX_EVENTS]; if (epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, socket_fd.get(), &ev) == -1) { PLOG(ERROR) << "Failed to add socket fd to epoll"; kill(minadbd_pid, SIGKILL); return; } // Set the timeout to be 300s when waiting for minadbd commands. constexpr int TIMEOUT_MILLIS = 300 * 1000; while (true) { // Reset the progress bar and the background image before each command. ui->SetProgressType(RecoveryUI::EMPTY); ui->SetBackground(RecoveryUI::NO_COMMAND); // Poll for the status change of the socket_fd, and handle the message if the fd is ready to // read. int event_count = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, TIMEOUT_MILLIS)); if (event_count == -1) { PLOG(ERROR) << "Failed to wait for epoll events"; kill(minadbd_pid, SIGKILL); return; } if (event_count == 0) { LOG(ERROR) << "Timeout waiting for messages from minadbd"; kill(minadbd_pid, SIGKILL); return; } for (int n = 0; n < event_count; n++) { if (events[n].events & EPOLLHUP) { LOG(INFO) << "Socket has been closed"; kill(minadbd_pid, SIGKILL); return; } if (!HandleMessageFromMinadbd(socket_fd.get(), command_map)) { kill(minadbd_pid, SIGKILL); return; } } } } // Recovery starts minadbd service as a child process, and spawns another thread to listen for the // message from minadbd through a socket pair. Here is an example to execute one command from adb // host. // a. recovery b. listener thread c. minadbd service // // a1. create socket pair // a2. fork minadbd service // c3. wait for the adb commands // from host // c4. after receiving host commands: // 1) set up pre-condition (i.e. // start fuse for adb sideload) // 2) issue command through // socket. // 3) wait for result // a5. start listener thread // b6. listen for message from // minadbd in a loop. // b7. After receiving a minadbd // command from socket // 1) execute the command function // 2) send the result back to // minadbd // ...... // c8. exit upon receiving the // result // a9. wait for listener thread // to exit. // // a10. wait for minadbd to // exit // b11. exit the listening loop // static void CreateMinadbdServiceAndExecuteCommands( RecoveryUI* ui, const std::map& command_map, bool rescue_mode) { signal(SIGPIPE, SIG_IGN); android::base::unique_fd recovery_socket; android::base::unique_fd minadbd_socket; if (!android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &recovery_socket, &minadbd_socket)) { PLOG(ERROR) << "Failed to create socket"; return; } pid_t child = fork(); if (child == -1) { PLOG(ERROR) << "Failed to fork child process"; return; } if (child == 0) { recovery_socket.reset(); std::vector minadbd_commands = { "/system/bin/minadbd", "--socket_fd", std::to_string(minadbd_socket.release()), }; if (rescue_mode) { minadbd_commands.push_back("--rescue"); } auto exec_args = StringVectorToNullTerminatedArray(minadbd_commands); execv(exec_args[0], exec_args.data()); _exit(EXIT_FAILURE); } minadbd_socket.reset(); // We need to call SetUsbConfig() after forking minadbd service. Because the function waits for // the usb state to be updated, which depends on sys.usb.ffs.ready=1 set in the adb daemon. if (!SetUsbConfig("sideload")) { LOG(ERROR) << "Failed to set usb config to sideload"; return; } std::thread listener_thread(ListenAndExecuteMinadbdCommands, ui, child, std::move(recovery_socket), std::ref(command_map)); if (listener_thread.joinable()) { listener_thread.join(); } int status; waitpid(child, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { if (WEXITSTATUS(status) == MinadbdErrorCode::kMinadbdAdbVersionError) { LOG(ERROR) << "\nYou need adb 1.0.32 or newer to sideload\nto this device.\n"; } else if (!WIFSIGNALED(status)) { LOG(ERROR) << "\n(adbd status " << WEXITSTATUS(status) << ")"; } } signal(SIGPIPE, SIG_DFL); } InstallResult ApplyFromAdb(Device* device, 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. if (usb_state != "none" && !SetUsbConfig("none")) { LOG(ERROR) << "Failed to clear USB config"; return INSTALL_ERROR; } RecoveryUI* ui = device->GetUI(); InstallResult install_result = INSTALL_NONE; std::map command_map{ { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, device, &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) }, }; if (!rescue_mode) { ui->Print( "\n\nNow send the package you want to apply\n" "to the device with \"adb sideload \"...\n"); } else { command_map.emplace(MinadbdCommand::kWipeData, [&device]() { bool result = WipeData(device); return std::make_pair(result, true); }); command_map.emplace(MinadbdCommand::kNoOp, []() { return std::make_pair(true, true); }); ui->Print("\n\nWaiting for rescue commands...\n"); } CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode); // Clean up before switching to the older state, for example setting the state // to none sets sys/class/android_usb/android0/enable to 0. if (!SetUsbConfig("none")) { LOG(ERROR) << "Failed to clear USB config"; } if (usb_state != "none") { if (!SetUsbConfig(usb_state)) { LOG(ERROR) << "Failed to set USB config to " << usb_state; } } return install_result; }