/* * 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/types.h" #include "minadbd_services.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 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(kMinadbdHostCommandArgumentError), ""); } 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 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_, 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), ""); }