// Copyright 2017 The Crashpad Authors
//
// 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 "util/posix/spawn_subprocess.h"

#include <errno.h>
#include <spawn.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#include "base/check.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "util/posix/close_multiple.h"

#if BUILDFLAG(IS_ANDROID)
#include <android/api-level.h>
#endif

extern char** environ;

namespace crashpad {

namespace {

#if BUILDFLAG(IS_APPLE)

class PosixSpawnAttr {
 public:
  PosixSpawnAttr() {
    PCHECK((errno = posix_spawnattr_init(&attr_)) == 0)
        << "posix_spawnattr_init";
  }

  PosixSpawnAttr(const PosixSpawnAttr&) = delete;
  PosixSpawnAttr& operator=(const PosixSpawnAttr&) = delete;

  ~PosixSpawnAttr() {
    PCHECK((errno = posix_spawnattr_destroy(&attr_)) == 0)
        << "posix_spawnattr_destroy";
  }

  void SetFlags(short flags) {
    PCHECK((errno = posix_spawnattr_setflags(&attr_, flags)) == 0)
        << "posix_spawnattr_setflags";
  }

  const posix_spawnattr_t* Get() const { return &attr_; }

 private:
  posix_spawnattr_t attr_;
};

class PosixSpawnFileActions {
 public:
  PosixSpawnFileActions() {
    PCHECK((errno = posix_spawn_file_actions_init(&file_actions_)) == 0)
        << "posix_spawn_file_actions_init";
  }

  PosixSpawnFileActions(const PosixSpawnFileActions&) = delete;
  PosixSpawnFileActions& operator=(const PosixSpawnFileActions&) = delete;

  ~PosixSpawnFileActions() {
    PCHECK((errno = posix_spawn_file_actions_destroy(&file_actions_)) == 0)
        << "posix_spawn_file_actions_destroy";
  }

  void AddInheritedFileDescriptor(int fd) {
    PCHECK((errno = posix_spawn_file_actions_addinherit_np(&file_actions_,
                                                           fd)) == 0)
        << "posix_spawn_file_actions_addinherit_np";
  }

  const posix_spawn_file_actions_t* Get() const { return &file_actions_; }

 private:
  posix_spawn_file_actions_t file_actions_;
};

#endif

}  // namespace

bool SpawnSubprocess(const std::vector<std::string>& argv,
                     const std::vector<std::string>* envp,
                     int preserve_fd,
                     bool use_path,
                     void (*child_function)()) {
  // argv_c contains const char* pointers and is terminated by nullptr. This is
  // suitable for passing to posix_spawn*() and execv*(). Although argv_c is not
  // used in the parent process, it must be built in the parent process because
  // it’s unsafe to do so in the child or grandchild process.
  std::vector<const char*> argv_c;
  argv_c.reserve(argv.size() + 1);
  for (const std::string& argument : argv) {
    argv_c.push_back(argument.c_str());
  }
  argv_c.push_back(nullptr);

  std::vector<const char*> envp_c;
  if (envp) {
    envp_c.reserve(envp->size() + 1);
    for (const std::string& variable : *envp) {
      envp_c.push_back(variable.c_str());
    }
    envp_c.push_back(nullptr);
  }

  // The three processes involved are parent, child, and grandchild. The child
  // exits immediately after spawning the grandchild, so the grandchild becomes
  // an orphan and its parent process ID becomes 1. This relieves the parent and
  // child of the responsibility to reap the grandchild with waitpid() or
  // similar. The grandchild is expected to outlive the parent process, so the
  // parent shouldn’t be concerned with reaping it. This approach means that
  // accidental early termination of the handler process will not result in a
  // zombie process.
  pid_t pid = fork();
  if (pid < 0) {
    PLOG(ERROR) << "fork";
    return false;
  }

  if (pid == 0) {
    // Child process.

    if (child_function) {
      child_function();
    }

    // Call setsid(), creating a new process group and a new session, both led
    // by this process. The new process group has no controlling terminal. This
    // disconnects it from signals generated by the parent process’ terminal.
    //
    // setsid() is done in the child instead of the grandchild so that the
    // grandchild will not be a session leader. If it were a session leader, an
    // accidental open() of a terminal device without O_NOCTTY would make that
    // terminal the controlling terminal.
    //
    // It’s not desirable for the grandchild to have a controlling terminal. The
    // grandchild manages its own lifetime, such as by monitoring clients on its
    // own and exiting when it loses all clients and when it deems it
    // appropraite to do so. It may serve clients in different process groups or
    // sessions than its original client, and receiving signals intended for its
    // original client’s process group could be harmful in that case.
    PCHECK(setsid() != -1) << "setsid";

    // &argv_c[0] is a pointer to a pointer to const char data, but because of
    // how C (not C++) works, posix_spawn*() and execv*() want a pointer to
    // a const pointer to char data. They modify neither the data nor the
    // pointers, so the const_cast is safe.
    char* const* argv_for_spawn = const_cast<char* const*>(argv_c.data());

    // This cast is safe for the same reason that the argv_for_spawn cast is.
    char* const* envp_for_spawn =
        envp ? const_cast<char* const*>(envp_c.data()) : environ;

#if BUILDFLAG(IS_ANDROID) && __ANDROID_API__ < 28
    pid = fork();
    if (pid < 0) {
      PLOG(FATAL) << "fork";
    }

    if (pid > 0) {
      // Child process.

      // _exit() instead of exit(), because fork() was called.
      _exit(EXIT_SUCCESS);
    }

    // Grandchild process.

    CloseMultipleNowOrOnExec(STDERR_FILENO + 1, preserve_fd);

    auto execve_fp = use_path ? execvpe : execve;
    execve_fp(argv_for_spawn[0], argv_for_spawn, envp_for_spawn);
    PLOG(FATAL) << (use_path ? "execvpe" : "execve");
#else
#if BUILDFLAG(IS_APPLE)
    PosixSpawnAttr attr;
    attr.SetFlags(POSIX_SPAWN_CLOEXEC_DEFAULT);

    PosixSpawnFileActions file_actions;
    for (int fd = 0; fd <= STDERR_FILENO; ++fd) {
      file_actions.AddInheritedFileDescriptor(fd);
    }
    file_actions.AddInheritedFileDescriptor(preserve_fd);

    const posix_spawnattr_t* attr_p = attr.Get();
    const posix_spawn_file_actions_t* file_actions_p = file_actions.Get();
#else
    CloseMultipleNowOrOnExec(STDERR_FILENO + 1, preserve_fd);

    const posix_spawnattr_t* attr_p = nullptr;
    const posix_spawn_file_actions_t* file_actions_p = nullptr;
#endif

    auto posix_spawn_fp = use_path ? posix_spawnp : posix_spawn;
    if ((errno = posix_spawn_fp(nullptr,
                                argv_for_spawn[0],
                                file_actions_p,
                                attr_p,
                                argv_for_spawn,
                                envp_for_spawn)) != 0) {
      PLOG(FATAL) << (use_path ? "posix_spawnp" : "posix_spawn");
    }

    // _exit() instead of exit(), because fork() was called.
    _exit(EXIT_SUCCESS);
#endif
  }

  // waitpid() for the child, so that it does not become a zombie process. The
  // child normally exits quickly.
  //
  // Failures from this point on may result in the accumulation of a zombie, but
  // should not be considered fatal. Log only warnings, but don’t treat these
  // failures as a failure of the function overall.
  int status;
  pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0));
  if (wait_pid == -1) {
    PLOG(WARNING) << "waitpid";
    return true;
  }
  DCHECK_EQ(wait_pid, pid);

  if (WIFSIGNALED(status)) {
    int sig = WTERMSIG(status);
    LOG(WARNING) << base::StringPrintf(
        "intermediate process terminated by signal %d (%s)%s",
        sig,
        strsignal(sig),
        WCOREDUMP(status) ? " (core dumped)" : "");
  } else if (!WIFEXITED(status)) {
    LOG(WARNING) << base::StringPrintf(
        "intermediate process: unknown termination 0x%x", status);
  } else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
    LOG(WARNING) << "intermediate process exited with code "
                 << WEXITSTATUS(status);
  }

  return true;
}

}  // namespace crashpad
