// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "platform/globals.h"
#if defined(HOST_OS_LINUX)

#include "bin/process.h"

#include <errno.h>         // NOLINT
#include <fcntl.h>         // NOLINT
#include <poll.h>          // NOLINT
#include <stdio.h>         // NOLINT
#include <stdlib.h>        // NOLINT
#include <string.h>        // NOLINT
#include <sys/resource.h>  // NOLINT
#include <sys/wait.h>      // NOLINT
#include <unistd.h>        // NOLINT

#include "bin/dartutils.h"
#include "bin/directory.h"
#include "bin/fdutils.h"
#include "bin/file.h"
#include "bin/lockers.h"
#include "bin/reference_counting.h"
#include "bin/thread.h"
#include "platform/syslog.h"

#include "platform/signal_blocker.h"
#include "platform/utils.h"

extern char** environ;

namespace dart {
namespace bin {

int Process::global_exit_code_ = 0;
Mutex* Process::global_exit_code_mutex_ = new Mutex();
Process::ExitHook Process::exit_hook_ = NULL;

// ProcessInfo is used to map a process id to the file descriptor for
// the pipe used to communicate the exit code of the process to Dart.
// ProcessInfo objects are kept in the static singly-linked
// ProcessInfoList.
class ProcessInfo {
 public:
  ProcessInfo(pid_t pid, intptr_t fd) : pid_(pid), fd_(fd) {}
  ~ProcessInfo() {
    int closed = close(fd_);
    if (closed != 0) {
      FATAL("Failed to close process exit code pipe");
    }
  }
  pid_t pid() { return pid_; }
  intptr_t fd() { return fd_; }
  ProcessInfo* next() { return next_; }
  void set_next(ProcessInfo* info) { next_ = info; }

 private:
  pid_t pid_;
  intptr_t fd_;
  ProcessInfo* next_;

  DISALLOW_COPY_AND_ASSIGN(ProcessInfo);
};

// Singly-linked list of ProcessInfo objects for all active processes
// started from Dart.
class ProcessInfoList {
 public:
  static void AddProcess(pid_t pid, intptr_t fd) {
    MutexLocker locker(mutex_);
    ProcessInfo* info = new ProcessInfo(pid, fd);
    info->set_next(active_processes_);
    active_processes_ = info;
  }

  static intptr_t LookupProcessExitFd(pid_t pid) {
    MutexLocker locker(mutex_);
    ProcessInfo* current = active_processes_;
    while (current != NULL) {
      if (current->pid() == pid) {
        return current->fd();
      }
      current = current->next();
    }
    return 0;
  }

  static void RemoveProcess(pid_t pid) {
    MutexLocker locker(mutex_);
    ProcessInfo* prev = NULL;
    ProcessInfo* current = active_processes_;
    while (current != NULL) {
      if (current->pid() == pid) {
        if (prev == NULL) {
          active_processes_ = current->next();
        } else {
          prev->set_next(current->next());
        }
        delete current;
        return;
      }
      prev = current;
      current = current->next();
    }
  }

 private:
  // Linked list of ProcessInfo objects for all active processes
  // started from Dart code.
  static ProcessInfo* active_processes_;
  // Mutex protecting all accesses to the linked list of active
  // processes.
  static Mutex* mutex_;

  DISALLOW_ALLOCATION();
  DISALLOW_IMPLICIT_CONSTRUCTORS(ProcessInfoList);
};

ProcessInfo* ProcessInfoList::active_processes_ = NULL;
Mutex* ProcessInfoList::mutex_ = new Mutex();

// The exit code handler sets up a separate thread which waits for child
// processes to terminate. That separate thread can then get the exit code from
// processes that have exited and communicate it to Dart through the
// event loop.
class ExitCodeHandler {
 public:
  // Notify the ExitCodeHandler that another process exists.
  static void ProcessStarted() {
    // Multiple isolates could be starting processes at the same
    // time. Make sure that only one ExitCodeHandler thread exists.
    MonitorLocker locker(monitor_);
    process_count_++;

    monitor_->Notify();

    if (running_) {
      return;
    }

    // Start thread that handles process exits when wait returns.
    int result =
        Thread::Start("dart:io Process.start", ExitCodeHandlerEntry, 0);
    if (result != 0) {
      FATAL1("Failed to start exit code handler worker thread %d", result);
    }

    running_ = true;
  }

  static void TerminateExitCodeThread() {
    MonitorLocker locker(monitor_);

    if (!running_) {
      return;
    }

    // Set terminate_done_ to false, so we can use it as a guard for our
    // monitor.
    running_ = false;

    // Wake up the [ExitCodeHandler] thread which is blocked on `wait()` (see
    // [ExitCodeHandlerEntry]).
    if (TEMP_FAILURE_RETRY(fork()) == 0) {
      // We avoid running through registered atexit() handlers because that is
      // unnecessary work.
      _exit(0);
    }

    monitor_->Notify();

    while (!terminate_done_) {
      monitor_->Wait(Monitor::kNoTimeout);
    }
  }

 private:
  // Entry point for the separate exit code handler thread started by
  // the ExitCodeHandler.
  static void ExitCodeHandlerEntry(uword param) {
    pid_t pid = 0;
    int status = 0;
    while (true) {
      {
        MonitorLocker locker(monitor_);
        while (running_ && process_count_ == 0) {
          monitor_->Wait(Monitor::kNoTimeout);
        }
        if (!running_) {
          terminate_done_ = true;
          monitor_->Notify();
          return;
        }
      }

      if ((pid = TEMP_FAILURE_RETRY(wait(&status))) > 0) {
        int exit_code = 0;
        int negative = 0;
        if (WIFEXITED(status)) {
          exit_code = WEXITSTATUS(status);
        }
        if (WIFSIGNALED(status)) {
          exit_code = WTERMSIG(status);
          negative = 1;
        }
        intptr_t exit_code_fd = ProcessInfoList::LookupProcessExitFd(pid);
        if (exit_code_fd != 0) {
          int message[2] = {exit_code, negative};
          ssize_t result =
              FDUtils::WriteToBlocking(exit_code_fd, &message, sizeof(message));
          // If the process has been closed, the read end of the exit
          // pipe has been closed. It is therefore not a problem that
          // write fails with a broken pipe error. Other errors should
          // not happen.
          if ((result != -1) && (result != sizeof(message))) {
            FATAL("Failed to write entire process exit message");
          } else if ((result == -1) && (errno != EPIPE)) {
            FATAL1("Failed to write exit code: %d", errno);
          }
          ProcessInfoList::RemoveProcess(pid);
          {
            MonitorLocker locker(monitor_);
            process_count_--;
          }
        }
      }
    }
  }

  static bool terminate_done_;
  static int process_count_;
  static bool running_;
  static Monitor* monitor_;

  DISALLOW_ALLOCATION();
  DISALLOW_IMPLICIT_CONSTRUCTORS(ExitCodeHandler);
};

bool ExitCodeHandler::running_ = false;
int ExitCodeHandler::process_count_ = 0;
bool ExitCodeHandler::terminate_done_ = false;
Monitor* ExitCodeHandler::monitor_ = new Monitor();

class ProcessStarter {
 public:
  ProcessStarter(Namespace* namespc,
                 const char* path,
                 char* arguments[],
                 intptr_t arguments_length,
                 const char* working_directory,
                 char* environment[],
                 intptr_t environment_length,
                 ProcessStartMode mode,
                 intptr_t* in,
                 intptr_t* out,
                 intptr_t* err,
                 intptr_t* id,
                 intptr_t* exit_event,
                 char** os_error_message)
      : namespc_(namespc),
        path_(path),
        working_directory_(working_directory),
        mode_(mode),
        in_(in),
        out_(out),
        err_(err),
        id_(id),
        exit_event_(exit_event),
        os_error_message_(os_error_message) {
    read_in_[0] = -1;
    read_in_[1] = -1;
    read_err_[0] = -1;
    read_err_[1] = -1;
    write_out_[0] = -1;
    write_out_[1] = -1;
    exec_control_[0] = -1;
    exec_control_[1] = -1;

    program_arguments_ = reinterpret_cast<char**>(Dart_ScopeAllocate(
        (arguments_length + 2) * sizeof(*program_arguments_)));
    program_arguments_[0] = const_cast<char*>(path_);
    for (int i = 0; i < arguments_length; i++) {
      program_arguments_[i + 1] = arguments[i];
    }
    program_arguments_[arguments_length + 1] = NULL;

    program_environment_ = NULL;
    if (environment != NULL) {
      program_environment_ = reinterpret_cast<char**>(Dart_ScopeAllocate(
          (environment_length + 1) * sizeof(*program_environment_)));
      for (int i = 0; i < environment_length; i++) {
        program_environment_[i] = environment[i];
      }
      program_environment_[environment_length] = NULL;
    }
  }

  int Start() {
    // Create pipes required.
    int err = CreatePipes();
    if (err != 0) {
      return err;
    }

    // Fork to create the new process.
    pid_t pid = TEMP_FAILURE_RETRY(fork());
    if (pid < 0) {
      // Failed to fork.
      return CleanupAndReturnError();
    } else if (pid == 0) {
      // This runs in the new process.
      NewProcess();
    }

    // This runs in the original process.

    // If the child process is not started in detached mode, be sure to
    // listen for exit-codes, now that we have a non detached child process
    // and also Register this child process.
    if (Process::ModeIsAttached(mode_)) {
      ExitCodeHandler::ProcessStarted();
      err = RegisterProcess(pid);
      if (err != 0) {
        return err;
      }
    }

    // Notify child process to start. This is done to delay the call to exec
    // until the process is registered above, and we are ready to receive the
    // exit code.
    char msg = '1';
    int bytes_written =
        FDUtils::WriteToBlocking(read_in_[1], &msg, sizeof(msg));
    if (bytes_written != sizeof(msg)) {
      return CleanupAndReturnError();
    }

    // Read the result of executing the child process.
    close(exec_control_[1]);
    exec_control_[1] = -1;
    if (Process::ModeIsAttached(mode_)) {
      err = ReadExecResult();
    } else {
      err = ReadDetachedExecResult(&pid);
    }
    close(exec_control_[0]);
    exec_control_[0] = -1;

    // Return error code if any failures.
    if (err != 0) {
      if (Process::ModeIsAttached(mode_)) {
        // Since exec() failed, we're not interested in the exit code.
        // We close the reading side of the exit code pipe here.
        // GetProcessExitCodes will get a broken pipe error when it
        // tries to write to the writing side of the pipe and it will
        // ignore the error.
        close(*exit_event_);
        *exit_event_ = -1;
      }
      CloseAllPipes();
      return err;
    }

    if (Process::ModeHasStdio(mode_)) {
      // Connect stdio, stdout and stderr.
      FDUtils::SetNonBlocking(read_in_[0]);
      *in_ = read_in_[0];
      close(read_in_[1]);
      FDUtils::SetNonBlocking(write_out_[1]);
      *out_ = write_out_[1];
      close(write_out_[0]);
      FDUtils::SetNonBlocking(read_err_[0]);
      *err_ = read_err_[0];
      close(read_err_[1]);
    } else {
      // Close all fds.
      close(read_in_[0]);
      close(read_in_[1]);
      ASSERT(write_out_[0] == -1);
      ASSERT(write_out_[1] == -1);
      ASSERT(read_err_[0] == -1);
      ASSERT(read_err_[1] == -1);
    }
    ASSERT(exec_control_[0] == -1);
    ASSERT(exec_control_[1] == -1);

    *id_ = pid;
    return 0;
  }

 private:
  int CreatePipes() {
    int result;
    result = TEMP_FAILURE_RETRY(pipe2(exec_control_, O_CLOEXEC));
    if (result < 0) {
      return CleanupAndReturnError();
    }

    // For a detached process the pipe to connect stdout is still used for
    // signaling when to do the first fork.
    result = TEMP_FAILURE_RETRY(pipe2(read_in_, O_CLOEXEC));
    if (result < 0) {
      return CleanupAndReturnError();
    }

    // For detached processes the pipe to connect stderr and stdin are not used.
    if (Process::ModeHasStdio(mode_)) {
      result = TEMP_FAILURE_RETRY(pipe2(read_err_, O_CLOEXEC));
      if (result < 0) {
        return CleanupAndReturnError();
      }

      result = TEMP_FAILURE_RETRY(pipe2(write_out_, O_CLOEXEC));
      if (result < 0) {
        return CleanupAndReturnError();
      }
    }

    return 0;
  }

  void NewProcess() {
    // Wait for parent process before setting up the child process.
    char msg;
    int bytes_read = FDUtils::ReadFromBlocking(read_in_[0], &msg, sizeof(msg));
    if (bytes_read != sizeof(msg)) {
      perror("Failed receiving notification message");
      exit(1);
    }
    if (Process::ModeIsAttached(mode_)) {
      ExecProcess();
    } else {
      ExecDetachedProcess();
    }
  }

  // Tries to find path_ relative to the current namespace unless it should be
  // searched in the PATH.
  // The path that should be passed to exec is returned in realpath.
  // Returns true on success, and false if there was an error that should
  // be reported to the parent.
  bool FindPathInNamespace(char* realpath, intptr_t realpath_size) {
    // Perform a PATH search if there's no slash in the path.
    if (strchr(path_, '/') == NULL) {
      // TODO(zra): If there is a non-default namespace, the entries in PATH
      // should be treated as relative to the namespace.
      strncpy(realpath, path_, realpath_size);
      realpath[realpath_size - 1] = '\0';
      return true;
    }
    NamespaceScope ns(namespc_, path_);
    const int fd =
        TEMP_FAILURE_RETRY(openat64(ns.fd(), ns.path(), O_RDONLY | O_CLOEXEC));
    if (fd == -1) {
      return false;
    }
    char procpath[PATH_MAX];
    snprintf(procpath, PATH_MAX, "/proc/self/fd/%d", fd);
    const intptr_t length =
        TEMP_FAILURE_RETRY(readlink(procpath, realpath, realpath_size));
    if (length < 0) {
      FDUtils::SaveErrorAndClose(fd);
      return false;
    }
    realpath[length] = '\0';
    FDUtils::SaveErrorAndClose(fd);
    return true;
  }

  void ExecProcess() {
    if (mode_ == kNormal) {
      if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) {
        ReportChildError();
      }

      if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) {
        ReportChildError();
      }

      if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) {
        ReportChildError();
      }
    } else {
      ASSERT(mode_ == kInheritStdio);
    }

    if (working_directory_ != NULL &&
        !Directory::SetCurrent(namespc_, working_directory_)) {
      ReportChildError();
    }

    if (program_environment_ != NULL) {
      environ = program_environment_;
    }

    char realpath[PATH_MAX];
    if (!FindPathInNamespace(realpath, PATH_MAX)) {
      ReportChildError();
    }
    // TODO(dart:io) Test for the existence of execveat, and use it instead.
    execvp(realpath, const_cast<char* const*>(program_arguments_));
    ReportChildError();
  }

  void ExecDetachedProcess() {
    if (mode_ == kDetached) {
      ASSERT(write_out_[0] == -1);
      ASSERT(write_out_[1] == -1);
      ASSERT(read_err_[0] == -1);
      ASSERT(read_err_[1] == -1);
      // For a detached process the pipe to connect stdout is only used for
      // signaling when to do the first fork.
      close(read_in_[0]);
      read_in_[0] = -1;
      close(read_in_[1]);
      read_in_[1] = -1;
    } else {
      // Don't close any fds if keeping stdio open to the detached process.
      ASSERT(mode_ == kDetachedWithStdio);
    }
    // Fork once more to start a new session.
    pid_t pid = TEMP_FAILURE_RETRY(fork());
    if (pid < 0) {
      ReportChildError();
    } else if (pid == 0) {
      // Start a new session.
      if (TEMP_FAILURE_RETRY(setsid()) == -1) {
        ReportChildError();
      } else {
        // Do a final fork to not be the session leader.
        pid = TEMP_FAILURE_RETRY(fork());
        if (pid < 0) {
          ReportChildError();
        } else if (pid == 0) {
          if (mode_ == kDetached) {
            SetupDetached();
          } else {
            SetupDetachedWithStdio();
          }

          if ((working_directory_ != NULL) &&
              !Directory::SetCurrent(namespc_, working_directory_)) {
            ReportChildError();
          }
          if (program_environment_ != NULL) {
            environ = program_environment_;
          }

          // Report the final PID and do the exec.
          ReportPid(getpid());  // getpid cannot fail.
          char realpath[PATH_MAX];
          if (!FindPathInNamespace(realpath, PATH_MAX)) {
            ReportChildError();
          }
          // TODO(dart:io) Test for the existence of execveat, and use it
          // instead.
          execvp(realpath, const_cast<char* const*>(program_arguments_));
          ReportChildError();
        } else {
          // Exit the intermediate process.
          exit(0);
        }
      }
    } else {
      // Exit the intermediate process.
      exit(0);
    }
  }

  int RegisterProcess(pid_t pid) {
    int result;
    int event_fds[2];
    result = TEMP_FAILURE_RETRY(pipe2(event_fds, O_CLOEXEC));
    if (result < 0) {
      return CleanupAndReturnError();
    }

    ProcessInfoList::AddProcess(pid, event_fds[1]);
    *exit_event_ = event_fds[0];
    FDUtils::SetNonBlocking(event_fds[0]);
    return 0;
  }

  int ReadExecResult() {
    int child_errno;
    int bytes_read = -1;
    // Read exec result from child. If no data is returned the exec was
    // successful and the exec call closed the pipe. Otherwise the errno
    // is written to the pipe.
    bytes_read = FDUtils::ReadFromBlocking(exec_control_[0], &child_errno,
                                           sizeof(child_errno));
    if (bytes_read == sizeof(child_errno)) {
      ReadChildError();
      return child_errno;
    } else if (bytes_read == -1) {
      return errno;
    }
    return 0;
  }

  int ReadDetachedExecResult(pid_t* pid) {
    int child_errno;
    int bytes_read = -1;
    // Read exec result from child. If only pid data is returned the exec was
    // successful and the exec call closed the pipe. Otherwise the errno
    // is written to the pipe as well.
    int result[2];
    bytes_read =
        FDUtils::ReadFromBlocking(exec_control_[0], result, sizeof(result));
    if (bytes_read == sizeof(int)) {
      *pid = result[0];
    } else if (bytes_read == 2 * sizeof(int)) {
      *pid = result[0];
      child_errno = result[1];
      ReadChildError();
      return child_errno;
    } else if (bytes_read == -1) {
      return errno;
    }
    return 0;
  }

  void SetupDetached() {
    ASSERT(mode_ == kDetached);

    // Close all open file descriptors except for exec_control_[1].
    int max_fds = sysconf(_SC_OPEN_MAX);
    if (max_fds == -1) {
      max_fds = _POSIX_OPEN_MAX;
    }
    for (int fd = 0; fd < max_fds; fd++) {
      if (fd != exec_control_[1]) {
        close(fd);
      }
    }

    // Re-open stdin, stdout and stderr and connect them to /dev/null.
    // The loop above should already have closed all of them, so
    // creating new file descriptors should start at STDIN_FILENO.
    int fd = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR));
    if (fd != STDIN_FILENO) {
      ReportChildError();
    }
    if (TEMP_FAILURE_RETRY(dup2(STDIN_FILENO, STDOUT_FILENO)) !=
        STDOUT_FILENO) {
      ReportChildError();
    }
    if (TEMP_FAILURE_RETRY(dup2(STDIN_FILENO, STDERR_FILENO)) !=
        STDERR_FILENO) {
      ReportChildError();
    }
  }

  void SetupDetachedWithStdio() {
    // Close all open file descriptors except for
    // exec_control_[1], write_out_[0], read_in_[1] and
    // read_err_[1].
    int max_fds = sysconf(_SC_OPEN_MAX);
    if (max_fds == -1) {
      max_fds = _POSIX_OPEN_MAX;
    }
    for (int fd = 0; fd < max_fds; fd++) {
      if ((fd != exec_control_[1]) && (fd != write_out_[0]) &&
          (fd != read_in_[1]) && (fd != read_err_[1])) {
        close(fd);
      }
    }

    if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) {
      ReportChildError();
    }
    close(write_out_[0]);

    if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) {
      ReportChildError();
    }
    close(read_in_[1]);

    if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) {
      ReportChildError();
    }
    close(read_err_[1]);
  }

  int CleanupAndReturnError() {
    int actual_errno = errno;
    // If CleanupAndReturnError is called without an actual errno make
    // sure to return an error anyway.
    if (actual_errno == 0) {
      actual_errno = EPERM;
    }
    SetChildOsErrorMessage();
    CloseAllPipes();
    return actual_errno;
  }

  void SetChildOsErrorMessage() {
    const int kBufferSize = 1024;
    char* error_message = DartUtils::ScopedCString(kBufferSize);
    Utils::StrError(errno, error_message, kBufferSize);
    *os_error_message_ = error_message;
  }

  void ReportChildError() {
    // In the case of failure in the child process write the errno and
    // the OS error message to the exec control pipe and exit.
    int child_errno = errno;
    const int kBufferSize = 1024;
    char error_buf[kBufferSize];
    char* os_error_message = Utils::StrError(errno, error_buf, kBufferSize);
    int bytes_written = FDUtils::WriteToBlocking(exec_control_[1], &child_errno,
                                                 sizeof(child_errno));
    if (bytes_written == sizeof(child_errno)) {
      FDUtils::WriteToBlocking(exec_control_[1], os_error_message,
                               strlen(os_error_message) + 1);
    }
    close(exec_control_[1]);

    // We avoid running through registered atexit() handlers because that is
    // unnecessary work.
    _exit(1);
  }

  void ReportPid(int pid) {
    // In the case of starting a detached process the actual pid of that process
    // is communicated using the exec control pipe.
    int bytes_written =
        FDUtils::WriteToBlocking(exec_control_[1], &pid, sizeof(pid));
    ASSERT(bytes_written == sizeof(int));
    USE(bytes_written);
  }

  void ReadChildError() {
    const int kMaxMessageSize = 256;
    char* message = DartUtils::ScopedCString(kMaxMessageSize);
    if (message != NULL) {
      FDUtils::ReadFromBlocking(exec_control_[0], message, kMaxMessageSize);
      message[kMaxMessageSize - 1] = '\0';
      *os_error_message_ = message;
    } else {
      // Could not get error message. It will be NULL.
      ASSERT(*os_error_message_ == NULL);
    }
  }

  void ClosePipe(int* fds) {
    for (int i = 0; i < 2; i++) {
      if (fds[i] != -1) {
        close(fds[i]);
        fds[i] = -1;
      }
    }
  }

  void CloseAllPipes() {
    ClosePipe(exec_control_);
    ClosePipe(read_in_);
    ClosePipe(read_err_);
    ClosePipe(write_out_);
  }

  int read_in_[2];       // Pipe for stdout to child process.
  int read_err_[2];      // Pipe for stderr to child process.
  int write_out_[2];     // Pipe for stdin to child process.
  int exec_control_[2];  // Pipe to get the result from exec.

  char** program_arguments_;
  char** program_environment_;

  Namespace* namespc_;
  const char* path_;
  const char* working_directory_;
  ProcessStartMode mode_;
  intptr_t* in_;
  intptr_t* out_;
  intptr_t* err_;
  intptr_t* id_;
  intptr_t* exit_event_;
  char** os_error_message_;

  DISALLOW_ALLOCATION();
  DISALLOW_IMPLICIT_CONSTRUCTORS(ProcessStarter);
};

int Process::Start(Namespace* namespc,
                   const char* path,
                   char* arguments[],
                   intptr_t arguments_length,
                   const char* working_directory,
                   char* environment[],
                   intptr_t environment_length,
                   ProcessStartMode mode,
                   intptr_t* in,
                   intptr_t* out,
                   intptr_t* err,
                   intptr_t* id,
                   intptr_t* exit_event,
                   char** os_error_message) {
  ProcessStarter starter(namespc, path, arguments, arguments_length,
                         working_directory, environment, environment_length,
                         mode, in, out, err, id, exit_event, os_error_message);
  return starter.Start();
}

static bool CloseProcessBuffers(struct pollfd fds[3]) {
  int e = errno;
  close(fds[0].fd);
  close(fds[1].fd);
  close(fds[2].fd);
  errno = e;
  return false;
}

bool Process::Wait(intptr_t pid,
                   intptr_t in,
                   intptr_t out,
                   intptr_t err,
                   intptr_t exit_event,
                   ProcessResult* result) {
  // Close input to the process right away.
  close(in);

  // There is no return from this function using Dart_PropagateError
  // as memory used by the buffer lists is freed through their
  // destructors.
  BufferList out_data;
  BufferList err_data;
  union {
    uint8_t bytes[8];
    int32_t ints[2];
  } exit_code_data;

  struct pollfd fds[3];
  fds[0].fd = out;
  fds[1].fd = err;
  fds[2].fd = exit_event;

  for (int i = 0; i < 3; i++) {
    fds[i].events = POLLIN;
  }

  int alive = 3;
  while (alive > 0) {
    // Blocking call waiting for events from the child process.
    if (TEMP_FAILURE_RETRY(poll(fds, alive, -1)) <= 0) {
      return CloseProcessBuffers(fds);
    }

    // Process incoming data.
    int current_alive = alive;
    for (int i = 0; i < current_alive; i++) {
      if ((fds[i].revents & POLLIN) != 0) {
        intptr_t avail = FDUtils::AvailableBytes(fds[i].fd);
        if (fds[i].fd == out) {
          if (!out_data.Read(out, avail)) {
            return CloseProcessBuffers(fds);
          }
        } else if (fds[i].fd == err) {
          if (!err_data.Read(err, avail)) {
            return CloseProcessBuffers(fds);
          }
        } else if (fds[i].fd == exit_event) {
          if (avail == 8) {
            intptr_t b =
                TEMP_FAILURE_RETRY(read(exit_event, exit_code_data.bytes, 8));
            if (b != 8) {
              return CloseProcessBuffers(fds);
            }
          }
        } else {
          UNREACHABLE();
        }
      }
      if ((fds[i].revents & POLLHUP) != 0) {
        close(fds[i].fd);
        alive--;
        if (i < alive) {
          fds[i] = fds[alive];
        }
      }
    }
  }

  // All handles closed and all data read.
  result->set_stdout_data(out_data.GetData());
  result->set_stderr_data(err_data.GetData());
  DEBUG_ASSERT(out_data.IsEmpty());
  DEBUG_ASSERT(err_data.IsEmpty());

  // Calculate the exit code.
  intptr_t exit_code = exit_code_data.ints[0];
  intptr_t negative = exit_code_data.ints[1];
  if (negative != 0) {
    exit_code = -exit_code;
  }
  result->set_exit_code(exit_code);

  return true;
}

bool Process::Kill(intptr_t id, int signal) {
  return (TEMP_FAILURE_RETRY(kill(id, signal)) != -1);
}

void Process::TerminateExitCodeHandler() {
  ExitCodeHandler::TerminateExitCodeThread();
}

intptr_t Process::CurrentProcessId() {
  return static_cast<intptr_t>(getpid());
}

static void SaveErrorAndClose(FILE* file) {
  int actual_errno = errno;
  fclose(file);
  errno = actual_errno;
}

int64_t Process::CurrentRSS() {
  // The second value in /proc/self/statm is the current RSS in pages.
  // It is not possible to use getrusage() because the interested fields are not
  // implemented by the linux kernel.
  FILE* statm = fopen("/proc/self/statm", "r");
  if (statm == NULL) {
    return -1;
  }
  int64_t current_rss_pages = 0;
  int matches = fscanf(statm, "%*s%" Pd64 "", &current_rss_pages);
  if (matches != 1) {
    SaveErrorAndClose(statm);
    return -1;
  }
  fclose(statm);
  return current_rss_pages * getpagesize();
}

int64_t Process::MaxRSS() {
  struct rusage usage;
  usage.ru_maxrss = 0;
  int r = getrusage(RUSAGE_SELF, &usage);
  if (r < 0) {
    return -1;
  }
  return usage.ru_maxrss * KB;
}

static Mutex* signal_mutex = new Mutex();
static SignalInfo* signal_handlers = NULL;
static const int kSignalsCount = 7;
static const int kSignals[kSignalsCount] = {
    SIGHUP, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGWINCH,
    SIGQUIT  // Allow VMService to listen on SIGQUIT.
};

SignalInfo::~SignalInfo() {
  close(fd_);
}

static void SignalHandler(int signal) {
  MutexLocker lock(signal_mutex);
  const SignalInfo* handler = signal_handlers;
  while (handler != NULL) {
    if (handler->signal() == signal) {
      int value = 0;
      VOID_TEMP_FAILURE_RETRY(write(handler->fd(), &value, 1));
    }
    handler = handler->next();
  }
}

intptr_t Process::SetSignalHandler(intptr_t signal) {
  bool found = false;
  for (int i = 0; i < kSignalsCount; i++) {
    if (kSignals[i] == signal) {
      found = true;
      break;
    }
  }
  if (!found) {
    return -1;
  }
  int fds[2];
  if (NO_RETRY_EXPECTED(pipe2(fds, O_CLOEXEC)) != 0) {
    return -1;
  }
  ThreadSignalBlocker blocker(kSignalsCount, kSignals);
  MutexLocker lock(signal_mutex);
  SignalInfo* handler = signal_handlers;
  bool listen = true;
  while (handler != NULL) {
    if (handler->signal() == signal) {
      listen = false;
      break;
    }
    handler = handler->next();
  }
  if (listen) {
    struct sigaction act = {};
    act.sa_handler = SignalHandler;
    sigemptyset(&act.sa_mask);
    for (int i = 0; i < kSignalsCount; i++) {
      sigaddset(&act.sa_mask, kSignals[i]);
    }
    int status = sigaction(signal, &act, NULL);
    if (status < 0) {
      int err = errno;
      close(fds[0]);
      close(fds[1]);
      errno = err;
      return -1;
    }
  }
  signal_handlers = new SignalInfo(fds[1], signal, signal_handlers);
  return fds[0];
}

void Process::ClearSignalHandler(intptr_t signal, Dart_Port port) {
  // Either the port is illegal or there is no current isolate, but not both.
  ASSERT((port != ILLEGAL_PORT) || (Dart_CurrentIsolate() == NULL));
  ASSERT((port == ILLEGAL_PORT) || (Dart_CurrentIsolate() != NULL));
  ThreadSignalBlocker blocker(kSignalsCount, kSignals);
  MutexLocker lock(signal_mutex);
  SignalInfo* handler = signal_handlers;
  bool unlisten = true;
  while (handler != NULL) {
    bool remove = false;
    if (handler->signal() == signal) {
      if ((port == ILLEGAL_PORT) || (handler->port() == port)) {
        if (signal_handlers == handler) {
          signal_handlers = handler->next();
        }
        handler->Unlink();
        remove = true;
      } else {
        unlisten = false;
      }
    }
    SignalInfo* next = handler->next();
    if (remove) {
      delete handler;
    }
    handler = next;
  }
  if (unlisten) {
    struct sigaction act = {};
    act.sa_handler = SIG_DFL;
    sigaction(signal, &act, NULL);
  }
}

}  // namespace bin
}  // namespace dart

#endif  // defined(HOST_OS_LINUX)
