Merge "Add test for minadbd"
diff --git a/minadbd/Android.bp b/minadbd/Android.bp
index b1c68ca..007e505 100644
--- a/minadbd/Android.bp
+++ b/minadbd/Android.bp
@@ -90,12 +90,14 @@
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 136392a..f6aff71 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 @@
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 @@
}
auto adb_data_reader = std::make_unique<FuseAdbDataProvider>(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 20e3410..5575c6b 100644
--- a/minadbd/minadbd_services.h
+++ b/minadbd/minadbd_services.h
@@ -16,6 +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 0000000..413ba0d
--- /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, MinadbdCommands 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_, 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<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_, 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), "");
+}