blob: 90a5a571db210a44edd5f1b4d84c5c26233e036a [file] [log] [blame]
// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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 <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <spawn.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <unistd.h>
#include "process_utils.h"
#include "subprocess.h"
extern char** environ;
#include "util.h"
using namespace std;
Subprocess::Subprocess(SubprocessSet& subprocess_set, bool use_console)
: subprocess_set_(subprocess_set), pid_(-1), use_console_(use_console) {}
Subprocess::~Subprocess() {
Stop();
// Reap child if forgotten.
if (pid_ != -1)
Finish();
}
void Subprocess::Stop() {
async_fd_.Close();
}
bool Subprocess::Start(const string& command) {
SubprocessSet* set = &subprocess_set_;
int output_pipe[2];
if (pipe(output_pipe) < 0)
ErrnoFatal("pipe");
IpcHandle fd = IpcHandle(output_pipe[0]);
fd.SetInheritable(false);
posix_spawn_file_actions_t action;
int err = posix_spawn_file_actions_init(&action);
if (err != 0)
ErrnoFatal("posix_spawn_file_actions_init", err);
err = posix_spawn_file_actions_addclose(&action, output_pipe[0]);
if (err != 0)
ErrnoFatal("posix_spawn_file_actions_addclose", err);
posix_spawnattr_t attr;
err = posix_spawnattr_init(&attr);
if (err != 0)
ErrnoFatal("posix_spawnattr_init", err);
short flags = 0;
flags |= POSIX_SPAWN_SETSIGMASK;
sigset_t old_mask = set->async_loop_.GetOldSignalMask();
#ifndef _NDEBUG
// Consistency check, ensure that SIGINT/SIGHUP/SIGTERM can reach
// spawned processes.
if (sigismember(&old_mask, SIGINT))
Fatal("SubprocessSet: SIGINT is blocked in current signal mask");
if (sigismember(&old_mask, SIGHUP))
Fatal("SubprocessSet: SIGHUP is blocked in current signal mask");
if (sigismember(&old_mask, SIGTERM))
Fatal("SubprocessSet: SIGTERM is blocked in current signal mask");
#endif
err = posix_spawnattr_setsigmask(&attr, &old_mask);
if (err != 0)
ErrnoFatal("posix_spawnattr_setsigmask", err);
// Signals which are set to be caught in the calling process image are set to
// default action in the new process image, so no explicit
// POSIX_SPAWN_SETSIGDEF parameter is needed.
if (!use_console_) {
// Put the child in its own process group, so ctrl-c won't reach it.
flags |= POSIX_SPAWN_SETPGROUP;
// No need to posix_spawnattr_setpgroup(&attr, 0), it's the default.
// Open /dev/null over stdin.
err = posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY,
0);
if (err != 0) {
ErrnoFatal("posix_spawn_file_actions_addopen", err);
}
err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1);
if (err != 0)
ErrnoFatal("posix_spawn_file_actions_adddup2", err);
err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2);
if (err != 0)
ErrnoFatal("posix_spawn_file_actions_adddup2", err);
err = posix_spawn_file_actions_addclose(&action, output_pipe[1]);
if (err != 0)
ErrnoFatal("posix_spawn_file_actions_addclose", err);
// In the console case, output_pipe is still inherited by the child and
// closed when the subprocess finishes, which then notifies ninja.
}
#ifdef POSIX_SPAWN_USEVFORK
flags |= POSIX_SPAWN_USEVFORK;
#endif
err = posix_spawnattr_setflags(&attr, flags);
if (err != 0)
ErrnoFatal("posix_spawnattr_setflags", err);
char** env = environ;
if (set->environment_)
env = set->environment_->AsExecEnvironmentBlock();
const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL };
err = posix_spawn(&pid_, "/bin/sh", &action, &attr,
const_cast<char**>(spawned_args), env);
if (err != 0)
ErrnoFatal("posix_spawn", err);
err = posix_spawnattr_destroy(&attr);
if (err != 0)
ErrnoFatal("posix_spawnattr_destroy", err);
err = posix_spawn_file_actions_destroy(&action);
if (err != 0)
ErrnoFatal("posix_spawn_file_actions_destroy", err);
close(output_pipe[1]);
async_fd_ = AsyncHandle::Create(std::move(fd), subprocess_set_.async_loop_,
[this](AsyncError error, size_t size) {
if (error)
Fatal("read: %s", strerror(error));
if (size == 0) {
Stop();
subprocess_set_.OnProcessCompletion(this);
return;
}
// Append result, continue reading.
buf_.append(read_buf_, size);
async_fd_.StartRead(read_buf_, sizeof(read_buf_));
});
async_fd_.StartRead(read_buf_, sizeof(read_buf_));
return true;
}
ExitStatus Subprocess::Finish() {
assert(pid_ != -1);
int status;
if (waitpid(pid_, &status, 0) < 0)
Fatal("waitpid(%d): %s", pid_, strerror(errno));
pid_ = -1;
#ifdef _AIX
if (WIFEXITED(status) && WEXITSTATUS(status) & 0x80) {
// Map the shell's exit code used for signal failure (128 + signal) to the
// status code expected by AIX WIFSIGNALED and WTERMSIG macros which, unlike
// other systems, uses a different bit layout.
int signal = WEXITSTATUS(status) & 0x7f;
status = (signal << 16) | signal;
}
#endif
if (WIFEXITED(status)) {
int exit = WEXITSTATUS(status);
if (exit == 0)
return ExitSuccess;
} else if (WIFSIGNALED(status)) {
if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM
|| WTERMSIG(status) == SIGHUP)
return ExitInterrupted;
}
return ExitFailure;
}
bool Subprocess::Done() const {
return !async_fd_;
}
const string& Subprocess::GetOutput() const {
return buf_;
}
SubprocessSet::SubprocessSet()
: async_loop_(AsyncLoop::Get()), interrupt_catcher_(async_loop_) {}
SubprocessSet::SubprocessSet(const EnvironmentBlock& environment)
: async_loop_(AsyncLoop::Get()), interrupt_catcher_(async_loop_),
environment_(&environment) {}
SubprocessSet::~SubprocessSet() {
Clear();
}
Subprocess* SubprocessSet::Add(const string& command, bool use_console) {
auto subprocess =
std::unique_ptr<Subprocess>(new Subprocess(*this, use_console));
if (!subprocess->Start(command)) {
return 0;
}
Subprocess* result = subprocess.get();
running_.push_back(std::move(subprocess));
return result;
}
void SubprocessSet::OnProcessCompletion(Subprocess* subprocess) {
// Move the subprocess from the running_ vector to the finished_ queue.
for (auto it = running_.begin(); it != running_.end(); ++it) {
if (it->get() == subprocess) {
finished_.emplace(it->release());
running_.erase(it);
return;
}
}
}
bool SubprocessSet::DoWork() {
size_t running_count = running_.size();
if (!running_count)
return false;
AsyncLoop::ExitStatus loop_status =
async_loop_.RunUntil([this, &running_count]() -> bool {
return running_.size() != running_count;
});
return loop_status == AsyncLoop::ExitInterrupted;
}
std::unique_ptr<Subprocess> SubprocessSet::NextFinished() {
if (finished_.empty())
return {};
std::unique_ptr<Subprocess> subproc = std::move(finished_.front());
finished_.pop();
return subproc;
}
void SubprocessSet::Clear() {
int int_signal = async_loop_.GetInterruptSignal();
for (auto& subproc : running_) {
if (!subproc->use_console_)
kill(-subproc->pid_, int_signal);
}
running_.clear();
}