Merge "Add socket communication between recovery and minadbd"

This commit is contained in:
Tianjie Xu
2019-04-11 23:42:25 +00:00
committed by Gerrit Code Review
9 changed files with 441 additions and 97 deletions

View File

@@ -112,7 +112,6 @@ cc_binary {
],
shared_libs: [
"libminadbd_services",
"librecovery_ui",
],
@@ -124,6 +123,7 @@ cc_binary {
required: [
"e2fsdroid.recovery",
"librecovery_ui_ext",
"minadbd",
"mke2fs.conf.recovery",
"mke2fs.recovery",
"recovery_deps",

View File

@@ -19,6 +19,10 @@ cc_defaults {
"recovery_defaults",
],
header_libs: [
"libminadbd_headers",
],
shared_libs: [
"libbase",
"libbootloader_message",

View File

@@ -21,23 +21,265 @@
#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);
}
// 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;
}
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;
}
return static_cast<MinadbdCommands>(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 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 fuse to be ready.\n\n");
break;
}
}
*result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, false, 0, ui);
break;
}
// 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));
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);
}
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");
@@ -51,66 +293,13 @@ int apply_from_adb(bool* wipe_cache, RecoveryUI* ui) {
"\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);
}
int install_result = INSTALL_ERROR;
std::map<MinadbdCommands, CommandFunction> command_map{
{ MinadbdCommands::kInstall,
std::bind(&AdbInstallPackageHandler, wipe_cache, ui, &install_result) },
};
if (!SetUsbConfig("sideload")) {
LOG(ERROR) << "Failed to set usb config to sideload";
return INSTALL_ERROR;
}
// 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;
// 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;
}
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);
break;
}
}
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);
// 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 (!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");
} else if (!WIFSIGNALED(status)) {
ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status));
}
}
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.
@@ -124,5 +313,5 @@ int apply_from_adb(bool* wipe_cache, RecoveryUI* ui) {
}
}
return result;
return install_result;
}

View File

@@ -40,7 +40,6 @@ cc_library {
srcs: [
"fuse_adb_provider.cpp",
"minadbd.cpp",
"minadbd_services.cpp",
],
@@ -52,6 +51,36 @@ cc_library {
],
}
cc_library_headers {
name: "libminadbd_headers",
recovery_available: true,
// TODO create a include dir
export_include_dirs: [
".",
],
}
cc_binary {
name: "minadbd",
recovery: true,
defaults: [
"minadbd_defaults",
],
srcs: [
"minadbd.cpp",
],
shared_libs: [
"libadbd",
"libbase",
"libcrypto",
"libfusesideload",
"libminadbd_services",
],
}
cc_test {
name: "minadbd_test",
isolated: true,

View File

@@ -14,30 +14,54 @@
* limitations under the License.
*/
#include "minadbd.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include "adb.h"
#include "adb_auth.h"
#include "transport.h"
int minadbd_main() {
adb_device_banner = "sideload";
#include "minadbd_services.h"
#include "minadbd_types.h"
signal(SIGPIPE, SIG_IGN);
int main(int argc, char** argv) {
android::base::InitLogging(argv, &android::base::StderrLogger);
// TODO(xunchang) implement a command parser
if (argc != 3 || strcmp("--socket_fd", argv[1]) != 0) {
LOG(ERROR) << "minadbd has invalid arguments, argc: " << argc;
exit(kMinadbdArgumentsParsingError);
}
// We can't require authentication for sideloading. http://b/22025550.
auth_required = false;
int socket_fd;
if (!android::base::ParseInt(argv[2], &socket_fd)) {
LOG(ERROR) << "Failed to parse int in " << argv[2];
exit(kMinadbdArgumentsParsingError);
}
if (fcntl(socket_fd, F_GETFD, 0) == -1) {
PLOG(ERROR) << "Failed to get minadbd socket";
exit(kMinadbdSocketIOError);
}
SetMinadbdSocketFd(socket_fd);
init_transport_registration();
usb_init();
adb_device_banner = "sideload";
VLOG(ADB) << "Event loop starting";
fdevent_loop();
signal(SIGPIPE, SIG_IGN);
return 0;
// We can't require authentication for sideloading. http://b/22025550.
auth_required = false;
init_transport_registration();
usb_init();
VLOG(ADB) << "Event loop starting";
fdevent_loop();
return 0;
}

View File

@@ -14,6 +14,8 @@
* limitations under the License.
*/
#include "minadbd_services.h"
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
@@ -27,38 +29,95 @@
#include <string_view>
#include <thread>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/memory.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include "adb.h"
#include "adb_unique_fd.h"
#include "fdevent.h"
#include "fuse_adb_provider.h"
#include "fuse_sideload.h"
#include "minadbd_types.h"
#include "services.h"
#include "sysdeps.h"
static int minadbd_socket = -1;
void SetMinadbdSocketFd(int socket_fd) {
minadbd_socket = socket_fd;
}
static bool WriteCommandToFd(MinadbdCommands cmd, int fd) {
char message[kMinadbdMessageSize];
memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix));
android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), cmd);
if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) {
PLOG(ERROR) << "Failed to write message " << message;
return false;
}
return true;
}
// Blocks and reads the command status from |fd|. Returns false if the received message has a
// format error.
static bool WaitForCommandStatus(int fd, MinadbdCommandStatus* status) {
char buffer[kMinadbdMessageSize];
if (!android::base::ReadFully(fd, buffer, kMinadbdMessageSize)) {
PLOG(ERROR) << "Failed to response status from socket";
exit(kMinadbdSocketIOError);
}
std::string message(buffer, buffer + kMinadbdMessageSize);
if (!android::base::StartsWith(message, kMinadbdStatusPrefix)) {
LOG(ERROR) << "Failed to parse status in " << message;
return false;
}
*status = android::base::get_unaligned<MinadbdCommandStatus>(
message.substr(strlen(kMinadbdStatusPrefix)).c_str());
return true;
}
static void sideload_host_service(unique_fd sfd, const std::string& args) {
int64_t file_size;
int block_size;
if ((sscanf(args.c_str(), "%" SCNd64 ":%d", &file_size, &block_size) != 2) || file_size <= 0 ||
block_size <= 0) {
printf("bad sideload-host arguments: %s\n", args.c_str());
exit(1);
LOG(ERROR) << "bad sideload-host arguments: " << args;
exit(kMinadbdPackageSizeError);
}
printf("sideload-host file size %" PRId64 " block size %d\n", file_size, block_size);
LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size;
if (!WriteCommandToFd(MinadbdCommands::kInstall, minadbd_socket)) {
exit(kMinadbdSocketIOError);
}
auto adb_data_reader =
std::make_unique<FuseAdbDataProvider>(std::move(sfd), file_size, block_size);
int result = run_fuse_sideload(std::move(adb_data_reader));
if (int result = run_fuse_sideload(std::move(adb_data_reader)); result != 0) {
LOG(ERROR) << "Failed to start fuse";
exit(kMinadbdFuseStartError);
}
printf("sideload_host finished\n");
exit(result == 0 ? 0 : 1);
MinadbdCommandStatus status;
if (!WaitForCommandStatus(minadbd_socket, &status)) {
exit(kMinadbdMessageFormatError);
}
LOG(INFO) << "Got command status: " << static_cast<unsigned int>(status);
LOG(INFO) << "sideload_host finished";
exit(kMinadbdSuccess);
}
unique_fd daemon_service_to_fd(std::string_view name, atransport* /* transport */) {
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).
exit(3);
exit(kMinadbdAdbVersionError);
} else if (name.starts_with("sideload-host:")) {
std::string arg(name.substr(strlen("sideload-host:")));
return create_service_thread("sideload-host",

View File

@@ -14,9 +14,6 @@
* limitations under the License.
*/
#ifndef MINADBD_H__
#define MINADBD_H__
#pragma once
int minadbd_main();
#endif
void SetMinadbdSocketFd(int socket_fd);

53
minadbd/minadbd_types.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2019 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.
*/
#pragma once
#include <stdint.h>
// The message between recovery and minadbd is 8 bytes in size unless the length is explicitly
// specified. Both the command and status has the format |prefix(4 bytes) + encoded enum(4 bytes)|.
constexpr size_t kMinadbdMessageSize = 8;
constexpr char const kMinadbdCommandPrefix[] = "COMD";
constexpr char const kMinadbdStatusPrefix[] = "STAT";
enum MinadbdErrorCode : int {
kMinadbdSuccess = 0,
kMinadbdArgumentsParsingError = 1,
kMinadbdSocketIOError = 2,
kMinadbdMessageFormatError = 3,
kMinadbdAdbVersionError = 4,
kMinadbdPackageSizeError = 5,
kMinadbdFuseStartError = 6,
kMinadbdUnsupportedCommandError = 7,
kMinadbdCommandExecutionError = 8,
kMinadbdErrorUnknown = 9,
};
enum class MinadbdCommandStatus : uint32_t {
kSuccess = 0,
kFailure = 1,
};
enum class MinadbdCommands : uint32_t {
kInstall = 0,
kUiPrint = 1,
kError = 2,
};
static_assert(kMinadbdMessageSize == sizeof(kMinadbdCommandPrefix) - 1 + sizeof(MinadbdCommands));
static_assert(kMinadbdMessageSize ==
sizeof(kMinadbdStatusPrefix) - 1 + sizeof(MinadbdCommandStatus));

View File

@@ -51,7 +51,6 @@
#include "common.h"
#include "fastboot/fastboot.h"
#include "logging.h"
#include "minadbd/minadbd.h"
#include "otautil/paths.h"
#include "otautil/roots.h"
#include "otautil/sysutil.h"
@@ -322,16 +321,6 @@ int main(int argc, char** argv) {
// Take action to refresh pmsg contents
__android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &do_rotate);
// If this binary is started with the single argument "--adbd", instead of being the normal
// recovery binary, it turns into kind of a stripped-down version of adbd that only supports the
// 'sideload' command. Note this must be a real argument, not anything in the command file or
// bootloader control block; the only way recovery should be run with this argument is when it
// starts a copy of itself from the apply_from_adb() function.
if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
minadbd_main();
return 0;
}
time_t start = time(nullptr);
// redirect_stdio should be called only in non-sideload mode. Otherwise we may have two logger