summaryrefslogblamecommitdiffstats
path: root/minadbd/minadbd_services_test.cpp
blob: 413ba0df6205ad02d72c68626f70289cc7e0ba45 (plain) (tree)




















































































































































































































                                                                                                  
/*
 * 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), "");
}