github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/src/subprocess-posix.cc (about)

     1  // Copyright 2012 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include "subprocess.h"
    16  
    17  #include <sys/select.h>
    18  #include <assert.h>
    19  #include <errno.h>
    20  #include <fcntl.h>
    21  #include <unistd.h>
    22  #include <stdio.h>
    23  #include <string.h>
    24  #include <sys/wait.h>
    25  #include <spawn.h>
    26  
    27  #if defined(USE_PPOLL)
    28  #include <poll.h>
    29  #else
    30  #include <sys/select.h>
    31  #endif
    32  
    33  extern char** environ;
    34  
    35  #include "util.h"
    36  
    37  using namespace std;
    38  
    39  Subprocess::Subprocess(bool use_console) : fd_(-1), pid_(-1),
    40                                             use_console_(use_console) {
    41  }
    42  
    43  Subprocess::~Subprocess() {
    44    if (fd_ >= 0)
    45      close(fd_);
    46    // Reap child if forgotten.
    47    if (pid_ != -1)
    48      Finish();
    49  }
    50  
    51  bool Subprocess::Start(SubprocessSet* set, const string& command) {
    52    int output_pipe[2];
    53    if (pipe(output_pipe) < 0)
    54      Fatal("pipe: %s", strerror(errno));
    55    fd_ = output_pipe[0];
    56  #if !defined(USE_PPOLL)
    57    // If available, we use ppoll in DoWork(); otherwise we use pselect
    58    // and so must avoid overly-large FDs.
    59    if (fd_ >= static_cast<int>(FD_SETSIZE))
    60      Fatal("pipe: %s", strerror(EMFILE));
    61  #endif  // !USE_PPOLL
    62    SetCloseOnExec(fd_);
    63  
    64    posix_spawn_file_actions_t action;
    65    int err = posix_spawn_file_actions_init(&action);
    66    if (err != 0)
    67      Fatal("posix_spawn_file_actions_init: %s", strerror(err));
    68  
    69    err = posix_spawn_file_actions_addclose(&action, output_pipe[0]);
    70    if (err != 0)
    71      Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
    72  
    73    posix_spawnattr_t attr;
    74    err = posix_spawnattr_init(&attr);
    75    if (err != 0)
    76      Fatal("posix_spawnattr_init: %s", strerror(err));
    77  
    78    short flags = 0;
    79  
    80    flags |= POSIX_SPAWN_SETSIGMASK;
    81    err = posix_spawnattr_setsigmask(&attr, &set->old_mask_);
    82    if (err != 0)
    83      Fatal("posix_spawnattr_setsigmask: %s", strerror(err));
    84    // Signals which are set to be caught in the calling process image are set to
    85    // default action in the new process image, so no explicit
    86    // POSIX_SPAWN_SETSIGDEF parameter is needed.
    87  
    88    if (!use_console_) {
    89      // Put the child in its own process group, so ctrl-c won't reach it.
    90      flags |= POSIX_SPAWN_SETPGROUP;
    91      // No need to posix_spawnattr_setpgroup(&attr, 0), it's the default.
    92  
    93      // Open /dev/null over stdin.
    94      err = posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY,
    95            0);
    96      if (err != 0) {
    97        Fatal("posix_spawn_file_actions_addopen: %s", strerror(err));
    98      }
    99  
   100      err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1);
   101      if (err != 0)
   102        Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
   103      err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2);
   104      if (err != 0)
   105        Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
   106      err = posix_spawn_file_actions_addclose(&action, output_pipe[1]);
   107      if (err != 0)
   108        Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
   109      // In the console case, output_pipe is still inherited by the child and
   110      // closed when the subprocess finishes, which then notifies ninja.
   111    }
   112  #ifdef POSIX_SPAWN_USEVFORK
   113    flags |= POSIX_SPAWN_USEVFORK;
   114  #endif
   115  
   116    err = posix_spawnattr_setflags(&attr, flags);
   117    if (err != 0)
   118      Fatal("posix_spawnattr_setflags: %s", strerror(err));
   119  
   120    const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL };
   121    err = posix_spawn(&pid_, "/bin/sh", &action, &attr,
   122          const_cast<char**>(spawned_args), environ);
   123    if (err != 0)
   124      Fatal("posix_spawn: %s", strerror(err));
   125  
   126    err = posix_spawnattr_destroy(&attr);
   127    if (err != 0)
   128      Fatal("posix_spawnattr_destroy: %s", strerror(err));
   129    err = posix_spawn_file_actions_destroy(&action);
   130    if (err != 0)
   131      Fatal("posix_spawn_file_actions_destroy: %s", strerror(err));
   132  
   133    close(output_pipe[1]);
   134    return true;
   135  }
   136  
   137  void Subprocess::OnPipeReady() {
   138    char buf[4 << 10];
   139    ssize_t len = read(fd_, buf, sizeof(buf));
   140    if (len > 0) {
   141      buf_.append(buf, len);
   142    } else {
   143      if (len < 0)
   144        Fatal("read: %s", strerror(errno));
   145      close(fd_);
   146      fd_ = -1;
   147    }
   148  }
   149  
   150  ExitStatus Subprocess::Finish() {
   151    assert(pid_ != -1);
   152    int status;
   153    if (waitpid(pid_, &status, 0) < 0)
   154      Fatal("waitpid(%d): %s", pid_, strerror(errno));
   155    pid_ = -1;
   156  
   157  #ifdef _AIX
   158    if (WIFEXITED(status) && WEXITSTATUS(status) & 0x80) {
   159      // Map the shell's exit code used for signal failure (128 + signal) to the
   160      // status code expected by AIX WIFSIGNALED and WTERMSIG macros which, unlike
   161      // other systems, uses a different bit layout.
   162      int signal = WEXITSTATUS(status) & 0x7f;
   163      status = (signal << 16) | signal;
   164    }
   165  #endif
   166  
   167    if (WIFEXITED(status)) {
   168      int exit = WEXITSTATUS(status);
   169      if (exit == 0)
   170        return ExitSuccess;
   171    } else if (WIFSIGNALED(status)) {
   172      if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM
   173          || WTERMSIG(status) == SIGHUP)
   174        return ExitInterrupted;
   175    }
   176    return ExitFailure;
   177  }
   178  
   179  bool Subprocess::Done() const {
   180    return fd_ == -1;
   181  }
   182  
   183  const string& Subprocess::GetOutput() const {
   184    return buf_;
   185  }
   186  
   187  int SubprocessSet::interrupted_;
   188  
   189  void SubprocessSet::SetInterruptedFlag(int signum) {
   190    interrupted_ = signum;
   191  }
   192  
   193  void SubprocessSet::HandlePendingInterruption() {
   194    sigset_t pending;
   195    sigemptyset(&pending);
   196    if (sigpending(&pending) == -1) {
   197      perror("ninja: sigpending");
   198      return;
   199    }
   200    if (sigismember(&pending, SIGINT))
   201      interrupted_ = SIGINT;
   202    else if (sigismember(&pending, SIGTERM))
   203      interrupted_ = SIGTERM;
   204    else if (sigismember(&pending, SIGHUP))
   205      interrupted_ = SIGHUP;
   206  }
   207  
   208  SubprocessSet::SubprocessSet() {
   209    sigset_t set;
   210    sigemptyset(&set);
   211    sigaddset(&set, SIGINT);
   212    sigaddset(&set, SIGTERM);
   213    sigaddset(&set, SIGHUP);
   214    if (sigprocmask(SIG_BLOCK, &set, &old_mask_) < 0)
   215      Fatal("sigprocmask: %s", strerror(errno));
   216  
   217    struct sigaction act;
   218    memset(&act, 0, sizeof(act));
   219    act.sa_handler = SetInterruptedFlag;
   220    if (sigaction(SIGINT, &act, &old_int_act_) < 0)
   221      Fatal("sigaction: %s", strerror(errno));
   222    if (sigaction(SIGTERM, &act, &old_term_act_) < 0)
   223      Fatal("sigaction: %s", strerror(errno));
   224    if (sigaction(SIGHUP, &act, &old_hup_act_) < 0)
   225      Fatal("sigaction: %s", strerror(errno));
   226  }
   227  
   228  SubprocessSet::~SubprocessSet() {
   229    Clear();
   230  
   231    if (sigaction(SIGINT, &old_int_act_, 0) < 0)
   232      Fatal("sigaction: %s", strerror(errno));
   233    if (sigaction(SIGTERM, &old_term_act_, 0) < 0)
   234      Fatal("sigaction: %s", strerror(errno));
   235    if (sigaction(SIGHUP, &old_hup_act_, 0) < 0)
   236      Fatal("sigaction: %s", strerror(errno));
   237    if (sigprocmask(SIG_SETMASK, &old_mask_, 0) < 0)
   238      Fatal("sigprocmask: %s", strerror(errno));
   239  }
   240  
   241  Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
   242    Subprocess *subprocess = new Subprocess(use_console);
   243    if (!subprocess->Start(this, command)) {
   244      delete subprocess;
   245      return 0;
   246    }
   247    running_.push_back(subprocess);
   248    return subprocess;
   249  }
   250  
   251  #ifdef USE_PPOLL
   252  bool SubprocessSet::DoWork() {
   253    vector<pollfd> fds;
   254    nfds_t nfds = 0;
   255  
   256    for (vector<Subprocess*>::iterator i = running_.begin();
   257         i != running_.end(); ++i) {
   258      int fd = (*i)->fd_;
   259      if (fd < 0)
   260        continue;
   261      pollfd pfd = { fd, POLLIN | POLLPRI, 0 };
   262      fds.push_back(pfd);
   263      ++nfds;
   264    }
   265  
   266    interrupted_ = 0;
   267    int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_);
   268    if (ret == -1) {
   269      if (errno != EINTR) {
   270        perror("ninja: ppoll");
   271        return false;
   272      }
   273      return IsInterrupted();
   274    }
   275  
   276    HandlePendingInterruption();
   277    if (IsInterrupted())
   278      return true;
   279  
   280    nfds_t cur_nfd = 0;
   281    for (vector<Subprocess*>::iterator i = running_.begin();
   282         i != running_.end(); ) {
   283      int fd = (*i)->fd_;
   284      if (fd < 0)
   285        continue;
   286      assert(fd == fds[cur_nfd].fd);
   287      if (fds[cur_nfd++].revents) {
   288        (*i)->OnPipeReady();
   289        if ((*i)->Done()) {
   290          finished_.push(*i);
   291          i = running_.erase(i);
   292          continue;
   293        }
   294      }
   295      ++i;
   296    }
   297  
   298    return IsInterrupted();
   299  }
   300  
   301  #else  // !defined(USE_PPOLL)
   302  bool SubprocessSet::DoWork() {
   303    fd_set set;
   304    int nfds = 0;
   305    FD_ZERO(&set);
   306  
   307    for (vector<Subprocess*>::iterator i = running_.begin();
   308         i != running_.end(); ++i) {
   309      int fd = (*i)->fd_;
   310      if (fd >= 0) {
   311        FD_SET(fd, &set);
   312        if (nfds < fd+1)
   313          nfds = fd+1;
   314      }
   315    }
   316  
   317    interrupted_ = 0;
   318    int ret = pselect(nfds, &set, 0, 0, 0, &old_mask_);
   319    if (ret == -1) {
   320      if (errno != EINTR) {
   321        perror("ninja: pselect");
   322        return false;
   323      }
   324      return IsInterrupted();
   325    }
   326  
   327    HandlePendingInterruption();
   328    if (IsInterrupted())
   329      return true;
   330  
   331    for (vector<Subprocess*>::iterator i = running_.begin();
   332         i != running_.end(); ) {
   333      int fd = (*i)->fd_;
   334      if (fd >= 0 && FD_ISSET(fd, &set)) {
   335        (*i)->OnPipeReady();
   336        if ((*i)->Done()) {
   337          finished_.push(*i);
   338          i = running_.erase(i);
   339          continue;
   340        }
   341      }
   342      ++i;
   343    }
   344  
   345    return IsInterrupted();
   346  }
   347  #endif  // !defined(USE_PPOLL)
   348  
   349  Subprocess* SubprocessSet::NextFinished() {
   350    if (finished_.empty())
   351      return NULL;
   352    Subprocess* subproc = finished_.front();
   353    finished_.pop();
   354    return subproc;
   355  }
   356  
   357  void SubprocessSet::Clear() {
   358    for (vector<Subprocess*>::iterator i = running_.begin();
   359         i != running_.end(); ++i)
   360      // Since the foreground process is in our process group, it will receive
   361      // the interruption signal (i.e. SIGINT or SIGTERM) at the same time as us.
   362      if (!(*i)->use_console_)
   363        kill(-(*i)->pid_, interrupted_);
   364    for (vector<Subprocess*>::iterator i = running_.begin();
   365         i != running_.end(); ++i)
   366      delete *i;
   367    running_.clear();
   368  }