普通文本  |  455行  |  13.68 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/test/multiprocess_test.h"

#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#include <memory>
#include <utility>
#include <vector>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/containers/hash_tables.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/pickle.h"
#include "base/posix/global_descriptors.h"
#include "base/posix/unix_domain_socket_linux.h"
#include "testing/multiprocess_func_list.h"

namespace base {

namespace {

const int kMaxMessageSize = 1024 * 1024;
const int kFragmentSize = 4096;

// Message sent between parent process and helper child process.
enum class MessageType : uint32_t {
  START_REQUEST,
  START_RESPONSE,
  WAIT_REQUEST,
  WAIT_RESPONSE,
};

struct MessageHeader {
  uint32_t size;
  MessageType type;
};

struct StartProcessRequest {
  MessageHeader header =
      {sizeof(StartProcessRequest), MessageType::START_REQUEST};

  uint32_t num_args = 0;
  uint32_t num_fds = 0;
};

struct StartProcessResponse {
  MessageHeader header =
      {sizeof(StartProcessResponse), MessageType::START_RESPONSE};

  pid_t child_pid;
};

struct WaitProcessRequest {
  MessageHeader header =
      {sizeof(WaitProcessRequest), MessageType::WAIT_REQUEST};

  pid_t pid;
  uint64_t timeout_ms;
};

struct WaitProcessResponse {
  MessageHeader header =
      {sizeof(WaitProcessResponse), MessageType::WAIT_RESPONSE};

  bool success = false;
  int32_t exit_code = 0;
};

// Helper class that implements an alternate test child launcher for
// multi-process tests. The default implementation doesn't work if the child is
// launched after starting threads. However, for some tests (i.e. Mojo), this
// is necessary. This implementation works around that issue by forking a helper
// process very early in main(), before any real work is done. Then, when a
// child needs to be spawned, a message is sent to that helper process, which
// then forks and returns the result to the parent. The forked child then calls
// main() and things look as though a brand new process has been fork/exec'd.
class LaunchHelper {
 public:
  using MainFunction = int (*)(int, char**);

  LaunchHelper() {}

  // Initialise the alternate test child implementation.
  void Init(MainFunction main);

  // Starts a child test helper process.
  Process StartChildTestHelper(const std::string& procname,
                               const CommandLine& base_command_line,
                               const LaunchOptions& options);

  // Waits for a child test helper process.
  bool WaitForChildExitWithTimeout(const Process& process, TimeDelta timeout,
                                   int* exit_code);

  bool IsReady() const { return child_fd_ != -1; }
  bool IsChild() const { return is_child_; }

 private:
  // Wrappers around sendmsg/recvmsg that supports message fragmentation.
  void Send(int fd, const MessageHeader* msg, const std::vector<int>& fds);
  ssize_t Recv(int fd, void* buf, std::vector<ScopedFD>* fds);

  // Parent process implementation.
  void DoParent(int fd);
  // Helper process implementation.
  void DoHelper(int fd);

  void StartProcessInHelper(const StartProcessRequest* request,
                           std::vector<ScopedFD> fds);
  void WaitForChildInHelper(const WaitProcessRequest* request);

  bool is_child_ = false;

  // Parent vars.
  int child_fd_ = -1;

  // Helper vars.
  int parent_fd_ = -1;
  MainFunction main_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(LaunchHelper);
};

void LaunchHelper::Init(MainFunction main) {
  main_ = main;

  // Create a communication channel between the parent and child launch helper.
  // fd[0] belongs to the parent, fd[1] belongs to the child.
  int fds[2] = {-1, -1};
  int rv = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds);
  PCHECK(rv == 0);
  CHECK_NE(-1, fds[0]);
  CHECK_NE(-1, fds[1]);

  pid_t pid = fork();
  PCHECK(pid >= 0) << "Fork failed";
  if (pid) {
    // Parent.
    rv = close(fds[1]);
    PCHECK(rv == 0);
    DoParent(fds[0]);
  } else {
    // Helper.
    rv = close(fds[0]);
    PCHECK(rv == 0);
    DoHelper(fds[1]);
    NOTREACHED();
    _exit(0);
  }
}

void LaunchHelper::Send(
    int fd, const MessageHeader* msg, const std::vector<int>& fds) {
  uint32_t bytes_remaining = msg->size;
  const char* buf = reinterpret_cast<const char*>(msg);
  while (bytes_remaining) {
    size_t send_size =
        (bytes_remaining > kFragmentSize) ? kFragmentSize : bytes_remaining;
    bool success = UnixDomainSocket::SendMsg(
        fd, buf, send_size,
        (bytes_remaining == msg->size) ? fds : std::vector<int>());
    CHECK(success);
    bytes_remaining -= send_size;
    buf += send_size;
  }
}

ssize_t LaunchHelper::Recv(int fd, void* buf, std::vector<ScopedFD>* fds) {
  ssize_t size = UnixDomainSocket::RecvMsg(fd, buf, kFragmentSize, fds);
  if (size <= 0)
    return size;

  const MessageHeader* header = reinterpret_cast<const MessageHeader*>(buf);
  CHECK(header->size < kMaxMessageSize);
  uint32_t bytes_remaining = header->size - size;
  char* buffer = reinterpret_cast<char*>(buf);
  buffer += size;
  while (bytes_remaining) {
    std::vector<ScopedFD> dummy_fds;
    size = UnixDomainSocket::RecvMsg(fd, buffer, kFragmentSize, &dummy_fds);
    if (size <= 0)
      return size;

    CHECK(dummy_fds.empty());
    CHECK(size == kFragmentSize ||
          static_cast<size_t>(size) == bytes_remaining);
    bytes_remaining -= size;
    buffer += size;
  }
  return header->size;
}

void LaunchHelper::DoParent(int fd) {
  child_fd_ = fd;
}

void LaunchHelper::DoHelper(int fd) {
  parent_fd_ = fd;
  is_child_ = true;
  std::unique_ptr<char[]> buf(new char[kMaxMessageSize]);
  while (true) {
    // Wait for a message from the parent.
    std::vector<ScopedFD> fds;
    ssize_t size = Recv(parent_fd_, buf.get(), &fds);
    if (size == 0 || (size < 0 && errno == ECONNRESET)) {
      _exit(0);
    }
    PCHECK(size > 0);

    const MessageHeader* header =
        reinterpret_cast<const MessageHeader*>(buf.get());
    CHECK_EQ(static_cast<ssize_t>(header->size), size);
    switch (header->type) {
      case MessageType::START_REQUEST:
        StartProcessInHelper(
            reinterpret_cast<const StartProcessRequest*>(buf.get()),
            std::move(fds));
        break;
      case MessageType::WAIT_REQUEST:
        WaitForChildInHelper(
            reinterpret_cast<const WaitProcessRequest*>(buf.get()));
        break;
      default:
        LOG(FATAL) << "Unsupported message type: "
                   << static_cast<uint32_t>(header->type);
    }
  }
}

void LaunchHelper::StartProcessInHelper(const StartProcessRequest* request,
                                        std::vector<ScopedFD> fds) {
  pid_t pid = fork();
  PCHECK(pid >= 0) << "Fork failed";
  if (pid) {
    // Helper.
    StartProcessResponse resp;
    resp.child_pid = pid;
    Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp),
         std::vector<int>());
  } else {
    // Child.
    PCHECK(close(parent_fd_) == 0);
    parent_fd_ = -1;
    CommandLine::Reset();

    Pickle serialised_extra(reinterpret_cast<const char*>(request + 1),
                            request->header.size - sizeof(StartProcessRequest));
    PickleIterator iter(serialised_extra);
    std::vector<std::string> args;
    for (size_t i = 0; i < request->num_args; i++) {
      std::string arg;
      CHECK(iter.ReadString(&arg));
      args.push_back(std::move(arg));
    }

    CHECK_EQ(request->num_fds, fds.size());
    for (size_t i = 0; i < request->num_fds; i++) {
      int new_fd;
      CHECK(iter.ReadInt(&new_fd));
      int old_fd = fds[i].release();
      if (new_fd != old_fd) {
        if (dup2(old_fd, new_fd) < 0) {
          PLOG(FATAL) << "dup2";
        }
        PCHECK(close(old_fd) == 0);
      }
    }

    // argv has argc+1 elements, where the last element is NULL.
    std::unique_ptr<char*[]> argv(new char*[args.size() + 1]);
    for (size_t i = 0; i < args.size(); i++) {
      argv[i] = const_cast<char*>(args[i].c_str());
    }
    argv[args.size()] = nullptr;
    _exit(main_(args.size(), argv.get()));
    NOTREACHED();
  }
}

void LaunchHelper::WaitForChildInHelper(const WaitProcessRequest* request) {
  Process process(request->pid);
  TimeDelta timeout = TimeDelta::FromMilliseconds(request->timeout_ms);
  int exit_code = -1;
  bool success = process.WaitForExitWithTimeout(timeout, &exit_code);

  WaitProcessResponse resp;
  resp.exit_code = exit_code;
  resp.success = success;
  Send(parent_fd_, reinterpret_cast<const MessageHeader*>(&resp),
       std::vector<int>());
}

Process LaunchHelper::StartChildTestHelper(const std::string& procname,
                                           const CommandLine& base_command_line,
                                           const LaunchOptions& options) {

  CommandLine command_line(base_command_line);
  if (!command_line.HasSwitch(switches::kTestChildProcess))
    command_line.AppendSwitchASCII(switches::kTestChildProcess, procname);

  StartProcessRequest request;
  Pickle serialised_extra;
  const CommandLine::StringVector& argv = command_line.argv();
  for (const auto& arg : argv)
    CHECK(serialised_extra.WriteString(arg));
  request.num_args = argv.size();

  std::vector<int> fds_to_send;
  if (options.fds_to_remap) {
    for (auto p : *options.fds_to_remap) {
      CHECK(serialised_extra.WriteInt(p.second));
      fds_to_send.push_back(p.first);
    }
    request.num_fds = options.fds_to_remap->size();
  }

  size_t buf_size = sizeof(StartProcessRequest) + serialised_extra.size();
  request.header.size = buf_size;
  std::unique_ptr<char[]> buffer(new char[buf_size]);
  memcpy(buffer.get(), &request, sizeof(StartProcessRequest));
  memcpy(buffer.get() + sizeof(StartProcessRequest), serialised_extra.data(),
         serialised_extra.size());

  // Send start message.
  Send(child_fd_, reinterpret_cast<const MessageHeader*>(buffer.get()),
       fds_to_send);

  // Synchronously get response.
  StartProcessResponse response;
  std::vector<ScopedFD> recv_fds;
  ssize_t resp_size = Recv(child_fd_, &response, &recv_fds);
  PCHECK(resp_size == sizeof(StartProcessResponse));

  return Process(response.child_pid);
}

bool LaunchHelper::WaitForChildExitWithTimeout(
    const Process& process, TimeDelta timeout, int* exit_code) {

  WaitProcessRequest request;
  request.pid = process.Handle();
  request.timeout_ms = timeout.InMilliseconds();

  Send(child_fd_, reinterpret_cast<const MessageHeader*>(&request),
       std::vector<int>());

  WaitProcessResponse response;
  std::vector<ScopedFD> recv_fds;
  ssize_t resp_size = Recv(child_fd_, &response, &recv_fds);
  PCHECK(resp_size == sizeof(WaitProcessResponse));

  if (!response.success)
    return false;

  *exit_code = response.exit_code;
  return true;
}

LazyInstance<LaunchHelper>::Leaky g_launch_helper;

}  // namespace

void InitAndroidMultiProcessTestHelper(int (*main)(int, char**)) {
  DCHECK(main);
  // Don't allow child processes to themselves create new child processes.
  if (g_launch_helper.Get().IsChild())
    return;
  g_launch_helper.Get().Init(main);
}

bool AndroidIsChildProcess() {
  return g_launch_helper.Get().IsChild();
}

bool AndroidWaitForChildExitWithTimeout(
    const Process& process, TimeDelta timeout, int* exit_code) {
  CHECK(g_launch_helper.Get().IsReady());
  return g_launch_helper.Get().WaitForChildExitWithTimeout(
      process, timeout, exit_code);
}

// A very basic implementation for Android. On Android tests can run in an APK
// and we don't have an executable to exec*. This implementation does the bare
// minimum to execute the method specified by procname (in the child process).
//  - All options except |fds_to_remap| are ignored.
Process SpawnMultiProcessTestChild(const std::string& procname,
                                   const CommandLine& base_command_line,
                                   const LaunchOptions& options) {
  if (g_launch_helper.Get().IsReady()) {
    return g_launch_helper.Get().StartChildTestHelper(
        procname, base_command_line, options);
  }

  // TODO(viettrungluu): The FD-remapping done below is wrong in the presence of
  // cycles (e.g., fd1 -> fd2, fd2 -> fd1). crbug.com/326576
  FileHandleMappingVector empty;
  const FileHandleMappingVector* fds_to_remap =
      options.fds_to_remap ? options.fds_to_remap : &empty;

  pid_t pid = fork();

  if (pid < 0) {
    PLOG(ERROR) << "fork";
    return Process();
  }
  if (pid > 0) {
    // Parent process.
    return Process(pid);
  }
  // Child process.
  base::hash_set<int> fds_to_keep_open;
  for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin();
       it != fds_to_remap->end(); ++it) {
    fds_to_keep_open.insert(it->first);
  }
  // Keep standard FDs (stdin, stdout, stderr, etc.) open since this
  // is not meant to spawn a daemon.
  int base = GlobalDescriptors::kBaseDescriptor;
  for (int fd = base; fd < sysconf(_SC_OPEN_MAX); ++fd) {
    if (fds_to_keep_open.find(fd) == fds_to_keep_open.end()) {
      close(fd);
    }
  }
  for (FileHandleMappingVector::const_iterator it = fds_to_remap->begin();
       it != fds_to_remap->end(); ++it) {
    int old_fd = it->first;
    int new_fd = it->second;
    if (dup2(old_fd, new_fd) < 0) {
      PLOG(FATAL) << "dup2";
    }
    close(old_fd);
  }
  CommandLine::Reset();
  CommandLine::Init(0, nullptr);
  CommandLine* command_line = CommandLine::ForCurrentProcess();
  command_line->InitFromArgv(base_command_line.argv());
  if (!command_line->HasSwitch(switches::kTestChildProcess))
    command_line->AppendSwitchASCII(switches::kTestChildProcess, procname);

  _exit(multi_process_function_list::InvokeChildProcessTest(procname));
  return Process();
}

}  // namespace base