summaryrefslogtreecommitdiffstats
path: root/install/adb_install.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'install/adb_install.cpp')
-rw-r--r--install/adb_install.cpp285
1 files changed, 237 insertions, 48 deletions
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;
}