diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-04-30 05:15:13 +0200 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-04-30 05:15:13 +0200 |
commit | e1299d94095e5fc018472b27cc8d980234f2ffb2 (patch) | |
tree | b0e3acf9305aec92ab4f388b6948caeccb05a8f7 /minadbd | |
parent | Snap for 5480912 from d0052c30cef1202fff6bb0f87d64eddb98cfbc99 to qt-release (diff) | |
parent | Parse BCB command to enter rescue mode. (diff) | |
download | android_bootable_recovery-e1299d94095e5fc018472b27cc8d980234f2ffb2.tar android_bootable_recovery-e1299d94095e5fc018472b27cc8d980234f2ffb2.tar.gz android_bootable_recovery-e1299d94095e5fc018472b27cc8d980234f2ffb2.tar.bz2 android_bootable_recovery-e1299d94095e5fc018472b27cc8d980234f2ffb2.tar.lz android_bootable_recovery-e1299d94095e5fc018472b27cc8d980234f2ffb2.tar.xz android_bootable_recovery-e1299d94095e5fc018472b27cc8d980234f2ffb2.tar.zst android_bootable_recovery-e1299d94095e5fc018472b27cc8d980234f2ffb2.zip |
Diffstat (limited to 'minadbd')
-rw-r--r-- | minadbd/Android.bp | 3 | ||||
-rw-r--r-- | minadbd/fuse_adb_provider.cpp | 4 | ||||
-rw-r--r-- | minadbd/fuse_adb_provider.h | 15 | ||||
-rw-r--r-- | minadbd/minadbd.cpp | 12 | ||||
-rw-r--r-- | minadbd/minadbd_services.cpp | 153 | ||||
-rw-r--r-- | minadbd/minadbd_services.h | 6 | ||||
-rw-r--r-- | minadbd/minadbd_services_test.cpp | 213 | ||||
-rw-r--r-- | minadbd/minadbd_types.h | 14 |
8 files changed, 383 insertions, 37 deletions
diff --git a/minadbd/Android.bp b/minadbd/Android.bp index e4f7712e5..007e5057b 100644 --- a/minadbd/Android.bp +++ b/minadbd/Android.bp @@ -76,7 +76,6 @@ cc_binary { "libadbd", "libbase", "libcrypto", - "libfusesideload", "libminadbd_services", ], } @@ -91,12 +90,14 @@ cc_test { srcs: [ "fuse_adb_provider_test.cpp", + "minadbd_services_test.cpp", ], static_libs: [ "libminadbd_services", "libfusesideload", "libadbd", + "libcrypto", ], shared_libs: [ diff --git a/minadbd/fuse_adb_provider.cpp b/minadbd/fuse_adb_provider.cpp index 9d19a1ec3..47719b07a 100644 --- a/minadbd/fuse_adb_provider.cpp +++ b/minadbd/fuse_adb_provider.cpp @@ -37,7 +37,3 @@ bool FuseAdbDataProvider::ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_s return true; } - -void FuseAdbDataProvider::Close() { - WriteFdExactly(fd_, "DONEDONE"); -} diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h index 3fb689bd4..c5561e57d 100644 --- a/minadbd/fuse_adb_provider.h +++ b/minadbd/fuse_adb_provider.h @@ -14,25 +14,22 @@ * limitations under the License. */ -#ifndef __FUSE_ADB_PROVIDER_H -#define __FUSE_ADB_PROVIDER_H +#pragma once #include <stdint.h> -#include "android-base/unique_fd.h" - #include "fuse_provider.h" // This class reads data from adb server. class FuseAdbDataProvider : public FuseDataProvider { public: - FuseAdbDataProvider(android::base::unique_fd&& fd, uint64_t file_size, uint32_t block_size) - : FuseDataProvider(std::move(fd), file_size, block_size) {} + FuseAdbDataProvider(int fd, uint64_t file_size, uint32_t block_size) + : FuseDataProvider(file_size, block_size), fd_(fd) {} bool ReadBlockAlignedData(uint8_t* buffer, uint32_t fetch_size, uint32_t start_block) const override; - void Close() override; + private: + // The underlying source to read data from (i.e. the one that talks to the host). + int fd_; }; - -#endif diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp index 57158ad57..c80d5490a 100644 --- a/minadbd/minadbd.cpp +++ b/minadbd/minadbd.cpp @@ -31,10 +31,13 @@ #include "minadbd_services.h" #include "minadbd_types.h" +using namespace std::string_literals; + 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) { + if ((argc != 3 && argc != 4) || argv[1] != "--socket_fd"s || + (argc == 4 && argv[3] != "--rescue"s)) { LOG(ERROR) << "minadbd has invalid arguments, argc: " << argc; exit(kMinadbdArgumentsParsingError); } @@ -50,7 +53,12 @@ int main(int argc, char** argv) { } SetMinadbdSocketFd(socket_fd); - adb_device_banner = "sideload"; + if (argc == 4) { + SetMinadbdRescueMode(true); + adb_device_banner = "rescue"; + } else { + adb_device_banner = "sideload"; + } signal(SIGPIPE, SIG_IGN); diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp index 79e6fc4e0..9b1999d90 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -28,15 +28,19 @@ #include <string> #include <string_view> #include <thread> +#include <unordered_set> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/memory.h> +#include <android-base/parseint.h> +#include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> #include "adb.h" #include "adb_unique_fd.h" +#include "adb_utils.h" #include "fdevent.h" #include "fuse_adb_provider.h" #include "fuse_sideload.h" @@ -45,11 +49,22 @@ #include "sysdeps.h" static int minadbd_socket = -1; +static bool rescue_mode = false; +static std::string sideload_mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT; + void SetMinadbdSocketFd(int socket_fd) { minadbd_socket = socket_fd; } -static bool WriteCommandToFd(MinadbdCommands cmd, int fd) { +void SetMinadbdRescueMode(bool rescue) { + rescue_mode = rescue; +} + +void SetSideloadMountPoint(const std::string& path) { + sideload_mount_point = path; +} + +static bool WriteCommandToFd(MinadbdCommand cmd, int fd) { char message[kMinadbdMessageSize]; memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix)); android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), cmd); @@ -81,47 +96,149 @@ static bool WaitForCommandStatus(int fd, MinadbdCommandStatus* status) { return true; } -static void sideload_host_service(unique_fd sfd, const std::string& args) { +static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args, + MinadbdCommandStatus* status) { + auto pieces = android::base::Split(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) { + if (pieces.size() != 2 || !android::base::ParseInt(pieces[0], &file_size) || file_size <= 0 || + !android::base::ParseInt(pieces[1], &block_size) || block_size <= 0) { LOG(ERROR) << "bad sideload-host arguments: " << args; - exit(kMinadbdPackageSizeError); + return kMinadbdPackageSizeError; } LOG(INFO) << "sideload-host file size " << file_size << ", block size " << block_size; - if (!WriteCommandToFd(MinadbdCommands::kInstall, minadbd_socket)) { - exit(kMinadbdSocketIOError); + if (!WriteCommandToFd(MinadbdCommand::kInstall, minadbd_socket)) { + return kMinadbdSocketIOError; } - auto adb_data_reader = - std::make_unique<FuseAdbDataProvider>(std::move(sfd), file_size, block_size); - if (int result = run_fuse_sideload(std::move(adb_data_reader)); result != 0) { + auto adb_data_reader = std::make_unique<FuseAdbDataProvider>(sfd, file_size, block_size); + if (int result = run_fuse_sideload(std::move(adb_data_reader), sideload_mount_point.c_str()); + result != 0) { LOG(ERROR) << "Failed to start fuse"; - exit(kMinadbdFuseStartError); + return kMinadbdFuseStartError; + } + + if (!WaitForCommandStatus(minadbd_socket, status)) { + return kMinadbdMessageFormatError; } + // Signal host-side adb to stop. For sideload mode, we always send kSideloadServiceExitSuccess + // (i.e. "DONEDONE") regardless of the install result. For rescue mode, we send failure message on + // install error. + if (!rescue_mode || *status == MinadbdCommandStatus::kSuccess) { + if (!android::base::WriteFully(sfd, kSideloadServiceExitSuccess, + strlen(kSideloadServiceExitSuccess))) { + return kMinadbdHostSocketIOError; + } + } else { + if (!android::base::WriteFully(sfd, kSideloadServiceExitFailure, + strlen(kSideloadServiceExitFailure))) { + return kMinadbdHostSocketIOError; + } + } + + return kMinadbdSuccess; +} + +// Sideload service always exits after serving an install command. +static void SideloadHostService(unique_fd sfd, const std::string& args) { + MinadbdCommandStatus status; + exit(RunAdbFuseSideload(sfd.get(), args, &status)); +} + +// Rescue service waits for the next command after an install command. +static void RescueInstallHostService(unique_fd sfd, const std::string& args) { + MinadbdCommandStatus status; + if (auto result = RunAdbFuseSideload(sfd.get(), args, &status); result != kMinadbdSuccess) { + exit(result); + } +} + +static void RescueGetpropHostService(unique_fd sfd, const std::string& prop) { + static const std::unordered_set<std::string> kGetpropAllowedProps = { + "ro.build.fingerprint", + "ro.build.date.utc", + }; + auto allowed = kGetpropAllowedProps.find(prop) != kGetpropAllowedProps.end(); + if (!allowed) { + return; + } + + auto result = android::base::GetProperty(prop, ""); + if (result.empty()) { + return; + } + if (!android::base::WriteFully(sfd, result.data(), result.size())) { + exit(kMinadbdHostSocketIOError); + } +} + +// 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); } - 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 */) { + // Common services that are supported both in sideload and rescue modes. + if (ConsumePrefix(&name, "reboot:")) { + // "reboot:<target>", 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:<file-size>:<block-size> + std::string args(name); + return create_service_thread( + "rescue-install", std::bind(RescueInstallHostService, std::placeholders::_1, args)); + } else if (ConsumePrefix(&name, "rescue-getprop:")) { + // rescue-getprop:<prop> + std::string args(name); + return create_service_thread( + "rescue-getprop", std::bind(RescueGetpropHostService, std::placeholders::_1, args)); + } + 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). exit(kMinadbdAdbVersionError); - } else if (name.starts_with("sideload-host:")) { - std::string arg(name.substr(strlen("sideload-host:"))); + } else if (ConsumePrefix(&name, "sideload-host:")) { + // sideload-host:<file-size>:<block-size> + std::string args(name); return create_service_thread("sideload-host", - std::bind(sideload_host_service, std::placeholders::_1, arg)); + std::bind(SideloadHostService, std::placeholders::_1, args)); } return unique_fd{}; } diff --git a/minadbd/minadbd_services.h b/minadbd/minadbd_services.h index 6835bd770..5575c6b8e 100644 --- a/minadbd/minadbd_services.h +++ b/minadbd/minadbd_services.h @@ -16,4 +16,10 @@ #pragma once +#include <string> + void SetMinadbdSocketFd(int socket_fd); + +void SetMinadbdRescueMode(bool); + +void SetSideloadMountPoint(const std::string& path); diff --git a/minadbd/minadbd_services_test.cpp b/minadbd/minadbd_services_test.cpp new file mode 100644 index 000000000..593180bb3 --- /dev/null +++ b/minadbd/minadbd_services_test.cpp @@ -0,0 +1,213 @@ +/* + * 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. + */ + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <strings.h> +#include <sys/mount.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#include <android-base/file.h> +#include <android-base/unique_fd.h> +#include <gtest/gtest.h> + +#include "adb.h" +#include "adb_io.h" +#include "fuse_adb_provider.h" +#include "fuse_sideload.h" +#include "minadbd_services.h" +#include "minadbd_types.h" +#include "socket.h" + +class MinadbdServicesTest : public ::testing::Test { + protected: + static constexpr int EXIT_TIME_OUT = 10; + + void SetUp() override { + ASSERT_TRUE( + android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &minadbd_socket_, &recovery_socket_)); + SetMinadbdSocketFd(minadbd_socket_); + SetSideloadMountPoint(mount_point_.path); + + package_path_ = std::string(mount_point_.path) + "/" + FUSE_SIDELOAD_HOST_FILENAME; + exit_flag_ = std::string(mount_point_.path) + "/" + FUSE_SIDELOAD_HOST_EXIT_FLAG; + + signal(SIGPIPE, SIG_IGN); + } + + void TearDown() override { + // Umount in case the test fails. Ignore the result. + umount(mount_point_.path); + + signal(SIGPIPE, SIG_DFL); + } + + void ReadAndCheckCommandMessage(int fd, MinadbdCommand expected_command) { + std::vector<uint8_t> received(kMinadbdMessageSize, '\0'); + ASSERT_TRUE(android::base::ReadFully(fd, received.data(), kMinadbdMessageSize)); + + std::vector<uint8_t> expected(kMinadbdMessageSize, '\0'); + memcpy(expected.data(), kMinadbdCommandPrefix, strlen(kMinadbdCommandPrefix)); + memcpy(expected.data() + strlen(kMinadbdCommandPrefix), &expected_command, + sizeof(expected_command)); + ASSERT_EQ(expected, received); + } + + void WaitForFusePath() { + constexpr int TIME_OUT = 10; + for (int i = 0; i < TIME_OUT; ++i) { + struct stat sb; + if (stat(package_path_.c_str(), &sb) == 0) { + return; + } + + if (errno == ENOENT) { + sleep(1); + continue; + } + FAIL() << "Timed out waiting for the fuse-provided package " << strerror(errno); + } + } + + void StatExitFlagAndExitProcess(int exit_code) { + struct stat sb; + if (stat(exit_flag_.c_str(), &sb) != 0) { + PLOG(ERROR) << "Failed to stat " << exit_flag_; + } + + exit(exit_code); + } + + void WriteMinadbdCommandStatus(MinadbdCommandStatus status) { + std::string status_message(kMinadbdMessageSize, '\0'); + memcpy(status_message.data(), kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix)); + memcpy(status_message.data() + strlen(kMinadbdStatusPrefix), &status, sizeof(status)); + ASSERT_TRUE( + android::base::WriteFully(recovery_socket_, status_message.data(), kMinadbdMessageSize)); + } + + void ExecuteCommandAndWaitForExit(const std::string& command) { + unique_fd fd = daemon_service_to_fd(command, nullptr); + ASSERT_NE(-1, fd); + sleep(EXIT_TIME_OUT); + } + + android::base::unique_fd minadbd_socket_; + android::base::unique_fd recovery_socket_; + + TemporaryDir mount_point_; + std::string package_path_; + std::string exit_flag_; +}; + +TEST_F(MinadbdServicesTest, SideloadHostService_wrong_size_argument) { + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:abc:4096"), + ::testing::ExitedWithCode(kMinadbdPackageSizeError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_wrong_block_size) { + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:10:20"), + ::testing::ExitedWithCode(kMinadbdFuseStartError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_broken_minadbd_socket) { + SetMinadbdSocketFd(-1); + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:4096:4096"), + ::testing::ExitedWithCode(kMinadbdSocketIOError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_broken_recovery_socket) { + recovery_socket_.reset(); + ASSERT_EXIT(ExecuteCommandAndWaitForExit("sideload-host:4096:4096"), + ::testing::ExitedWithCode(kMinadbdSocketIOError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_wrong_command_format) { + auto test_body = [&](const std::string& command) { + unique_fd fd = daemon_service_to_fd(command, nullptr); + ASSERT_NE(-1, fd); + WaitForFusePath(); + ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall); + + struct stat sb; + ASSERT_EQ(0, stat(exit_flag_.c_str(), &sb)); + ASSERT_TRUE(android::base::WriteStringToFd("12345678", recovery_socket_)); + sleep(EXIT_TIME_OUT); + }; + + ASSERT_EXIT(test_body("sideload-host:4096:4096"), + ::testing::ExitedWithCode(kMinadbdMessageFormatError), ""); +} + +TEST_F(MinadbdServicesTest, SideloadHostService_read_data_from_fuse) { + auto test_body = [&]() { + std::vector<uint8_t> content(4096, 'a'); + // Start a new process instead of a thread to read from the package mounted by FUSE. Because + // the test may not exit and report failures correctly when the thread blocks by a syscall. + pid_t pid = fork(); + if (pid == 0) { + WaitForFusePath(); + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(package_path_.c_str(), O_RDONLY))); + // Do not use assertion here because we want to stat the exit flag and exit the process. + // Otherwise the test will wait for the time out instead of failing immediately. + if (fd == -1) { + PLOG(ERROR) << "Failed to open " << package_path_; + StatExitFlagAndExitProcess(1); + } + std::vector<uint8_t> content_from_fuse(4096); + if (!android::base::ReadFully(fd, content_from_fuse.data(), 4096)) { + PLOG(ERROR) << "Failed to read from " << package_path_; + StatExitFlagAndExitProcess(1); + } + if (content_from_fuse != content) { + LOG(ERROR) << "Content read from fuse doesn't match with the expected value"; + StatExitFlagAndExitProcess(1); + } + StatExitFlagAndExitProcess(0); + } + + unique_fd fd = daemon_service_to_fd("sideload-host:4096:4096", nullptr); + ASSERT_NE(-1, fd); + ReadAndCheckCommandMessage(recovery_socket_, MinadbdCommand::kInstall); + + // Mimic the response from adb host. + std::string adb_message(8, '\0'); + ASSERT_TRUE(android::base::ReadFully(fd, adb_message.data(), 8)); + ASSERT_EQ(android::base::StringPrintf("%08u", 0), adb_message); + ASSERT_TRUE(android::base::WriteFully(fd, content.data(), 4096)); + + // Check that we read the correct data from fuse. + int child_status; + waitpid(pid, &child_status, 0); + ASSERT_TRUE(WIFEXITED(child_status)); + ASSERT_EQ(0, WEXITSTATUS(child_status)); + + WriteMinadbdCommandStatus(MinadbdCommandStatus::kSuccess); + + // TODO(xunchang) check if adb host-side receives "DONEDONE", there's a race condition between + // receiving the message and exit of test body (by detached thread in minadbd service). + exit(kMinadbdSuccess); + }; + + ASSERT_EXIT(test_body(), ::testing::ExitedWithCode(kMinadbdSuccess), ""); +} diff --git a/minadbd/minadbd_types.h b/minadbd/minadbd_types.h index 7bd69096a..b370b7952 100644 --- a/minadbd/minadbd_types.h +++ b/minadbd/minadbd_types.h @@ -35,6 +35,7 @@ enum MinadbdErrorCode : int { kMinadbdUnsupportedCommandError = 7, kMinadbdCommandExecutionError = 8, kMinadbdErrorUnknown = 9, + kMinadbdHostSocketIOError = 10, }; enum class MinadbdCommandStatus : uint32_t { @@ -42,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)); |