Merge "Add test for minadbd" am: c92f412b8d am: b63e6963ec
am: ba4f02398f

Change-Id: I12ec9c785ff99435c021ab8ead89964e843bcb3f
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), "");
+}