diff --git a/minadbd/Android.bp b/minadbd/Android.bp index b1c68ca9..007e5057 100644 --- a/minadbd/Android.bp +++ b/minadbd/Android.bp @@ -90,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/minadbd_services.cpp b/minadbd/minadbd_services.cpp index 136392a6..f6aff71f 100644 --- a/minadbd/minadbd_services.cpp +++ b/minadbd/minadbd_services.cpp @@ -50,6 +50,7 @@ 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; @@ -59,6 +60,10 @@ void SetMinadbdRescueMode(bool rescue) { rescue_mode = rescue; } +void SetSideloadMountPoint(const std::string& path) { + sideload_mount_point = path; +} + static bool WriteCommandToFd(MinadbdCommands cmd, int fd) { char message[kMinadbdMessageSize]; memcpy(message, kMinadbdCommandPrefix, strlen(kMinadbdStatusPrefix)); @@ -109,7 +114,8 @@ static MinadbdErrorCode RunAdbFuseSideload(int sfd, const std::string& args, } auto adb_data_reader = std::make_unique(sfd, file_size, block_size); - if (int result = run_fuse_sideload(std::move(adb_data_reader)); result != 0) { + if (int result = run_fuse_sideload(std::move(adb_data_reader), sideload_mount_point.c_str()); + result != 0) { LOG(ERROR) << "Failed to start fuse"; return kMinadbdFuseStartError; } diff --git a/minadbd/minadbd_services.h b/minadbd/minadbd_services.h index 20e3410c..5575c6b8 100644 --- a/minadbd/minadbd_services.h +++ b/minadbd/minadbd_services.h @@ -16,6 +16,10 @@ #pragma once +#include + 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 00000000..413ba0df --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#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, MinadbdCommands expected_command) { + std::vector received(kMinadbdMessageSize, '\0'); + ASSERT_TRUE(android::base::ReadFully(fd, received.data(), kMinadbdMessageSize)); + + std::vector 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_, MinadbdCommands::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 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 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_, MinadbdCommands::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), ""); +}