diff options
Diffstat (limited to 'install')
-rw-r--r-- | install/Android.bp | 4 | ||||
-rw-r--r-- | install/adb_install.cpp | 285 |
2 files changed, 241 insertions, 48 deletions
diff --git a/install/Android.bp b/install/Android.bp index 85cf9ac4a..2ffc4421c 100644 --- a/install/Android.bp +++ b/install/Android.bp @@ -19,6 +19,10 @@ cc_defaults { "recovery_defaults", ], + header_libs: [ + "libminadbd_headers", + ], + shared_libs: [ "libbase", "libbootloader_message", diff --git a/install/adb_install.cpp b/install/adb_install.cpp index 5296ff608..dc7ee0b32 100644 --- a/install/adb_install.cpp +++ b/install/adb_install.cpp @@ -21,97 +21,286 @@ #include <signal.h> #include <stdlib.h> #include <string.h> +#include <sys/epoll.h> +#include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> +#include <atomic> +#include <functional> +#include <map> + +#include <android-base/file.h> #include <android-base/logging.h> +#include <android-base/memory.h> #include <android-base/properties.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> #include "fuse_sideload.h" #include "install/install.h" +#include "minadbd_types.h" #include "recovery_ui/ui.h" +using CommandFunction = std::function<bool()>; + static bool SetUsbConfig(const std::string& state) { android::base::SetProperty("sys.usb.config", state); return android::base::WaitForProperty("sys.usb.state", state); } -int apply_from_adb(bool* wipe_cache, RecoveryUI* ui) { - // 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; +// Parses the minadbd command in |message|; returns MinadbdCommands::kError upon errors. +static MinadbdCommands ParseMinadbdCommands(const std::string& message) { + if (!android::base::StartsWith(message, kMinadbdCommandPrefix)) { + LOG(ERROR) << "Failed to parse command in message " << message; + return MinadbdCommands::kError; } - ui->Print( - "\n\nNow send the package you want to apply\n" - "to the device with \"adb sideload <filename>\"...\n"); - - pid_t child; - if ((child = fork()) == 0) { - execl("/system/bin/recovery", "recovery", "--adbd", nullptr); - _exit(EXIT_FAILURE); + auto cmd_code_string = message.substr(strlen(kMinadbdCommandPrefix)); + auto cmd_code = android::base::get_unaligned<uint32_t>(cmd_code_string.c_str()); + if (cmd_code >= static_cast<uint32_t>(MinadbdCommands::kError)) { + LOG(ERROR) << "Unsupported command code: " << cmd_code; + return MinadbdCommands::kError; } - if (!SetUsbConfig("sideload")) { - LOG(ERROR) << "Failed to set usb config to sideload"; - return INSTALL_ERROR; - } + return static_cast<MinadbdCommands>(cmd_code); +} - // How long (in seconds) we wait for the host to start sending us a package, before timing out. - static constexpr int ADB_INSTALL_TIMEOUT = 300; +static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) { + char message[kMinadbdMessageSize]; + memcpy(message, kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix)); + android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), status); - // 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.) - int result = INSTALL_ERROR; - int status; - bool waited = false; - for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) { - if (waitpid(child, &status, WNOHANG) != 0) { - result = INSTALL_ERROR; - waited = true; - break; - } + if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) { + PLOG(ERROR) << "Failed to write message " << message; + return false; + } + return true; +} +// Installs the package from FUSE. Returns true if the installation succeeds, and false otherwise. +static bool AdbInstallPackageHandler(bool* wipe_cache, 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; + *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 { - ui->Print("\nTimed out waiting for package.\n\n"); - result = INSTALL_ERROR; - kill(child, SIGKILL); + ui->Print("\nTimed out waiting for fuse to be ready.\n\n"); break; } } - result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, false, 0, ui); + *result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, false, 0, ui); break; } - if (!waited) { - // Calling stat() on this magic filename signals the minadbd subprocess to shut down. - struct stat st; - stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st); + // 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; +} + +// 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<MinadbdCommands, CommandFunction>& 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 = ParseMinadbdCommands(message); + if (command_type == MinadbdCommands::kError) { + return false; + } + if (command_map.find(command_type) == command_map.end()) { + LOG(ERROR) << "Unsupported command: " + << android::base::get_unaligned<unsigned int>( + 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); + if (!command_func()) { + LOG(ERROR) << "Failed to execute command " << static_cast<unsigned int>(command_type); + return WriteStatusToFd(MinadbdCommandStatus::kFailure, socket_fd); + } + return WriteStatusToFd(MinadbdCommandStatus::kSuccess, socket_fd); +} + +// 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<MinadbdCommands, CommandFunction>& 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) { + // 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( + const std::map<MinadbdCommands, CommandFunction>& command_map) { + 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(); + execl("/system/bin/minadbd", "minadbd", "--socket_fd", + std::to_string(minadbd_socket.release()).c_str(), nullptr); + + _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, child, std::move(recovery_socket), + std::ref(command_map)); - // TODO: there should be a way to cancel waiting for a package (by pushing some button combo on - // the device). For now you just have to 'adb sideload' a file that's not a valid package, like - // "/dev/null". - waitpid(child, &status, 0); + if (listener_thread.joinable()) { + listener_thread.join(); } + int status; + waitpid(child, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - if (WEXITSTATUS(status) == 3) { - ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n"); + 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)) { - ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status)); + LOG(ERROR) << "\n(adbd status " << WEXITSTATUS(status) << ")"; } } + signal(SIGPIPE, SIG_DFL); +} + +int apply_from_adb(bool* wipe_cache, RecoveryUI* ui) { + // 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; + } + + ui->Print( + "\n\nNow send the package you want to apply\n" + "to the device with \"adb sideload <filename>\"...\n"); + + int install_result = INSTALL_ERROR; + std::map<MinadbdCommands, CommandFunction> command_map{ + { MinadbdCommands::kInstall, + std::bind(&AdbInstallPackageHandler, wipe_cache, ui, &install_result) }, + }; + + CreateMinadbdServiceAndExecuteCommands(command_map); + // 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")) { @@ -124,5 +313,5 @@ int apply_from_adb(bool* wipe_cache, RecoveryUI* ui) { } } - return result; + return install_result; } |