| // 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(); |
| } |