blob: 3f32c66414723ac5c23943aa84c08fb44fb5812b [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "conformance/fork_pipe_runner.h"
#ifndef _WIN32
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <algorithm>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <string>
#include <vector>
#include "absl/log/absl_log.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "absl/types/optional.h"
#include "absl/types/span.h"
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define CHECK_SYSCALL(call) \
do { \
if (call < 0) { \
perror(#call " " __FILE__ ":" TOSTRING(__LINE__)); \
exit(1); \
} \
} while (0)
// For use between fork() and execv() in the child of a multi-threaded process,
// where only async-signal-safe calls are allowed: no perror() (it uses stdio
// and may take locks held by another thread in the parent) and no exit() (it
// runs atexit handlers and flushes stdio), just a fixed message and _exit().
#define CHECK_SYSCALL_IN_CHILD(call) \
do { \
if (call < 0) { \
static constexpr char kMessage[] = \
#call " failed in child " __FILE__ ":" TOSTRING(__LINE__) "\n"; \
/* Best effort; there is nothing left to do if this fails too. */ \
ssize_t written = write(STDERR_FILENO, kMessage, sizeof(kMessage) - 1); \
(void)written; \
_exit(1); \
} \
} while (0)
namespace google {
namespace protobuf {
namespace conformance {
namespace {
// How often Shutdown() checks whether the testee has exited.
constexpr absl::Duration kPollInterval = absl::Milliseconds(10);
// How long Shutdown() waits for a SIGKILLed testee to be reapable before it
// gives up rather than block the runner.
constexpr absl::Duration kKillWait = absl::Seconds(5);
// How long a testee that failed to answer gets to finish dying before it is
// killed, so that one that has just crashed (or is dumping core on the SIGQUIT
// sent after a timeout) is reported with its own wait status.
constexpr absl::Duration kFailureGracePeriod = absl::Seconds(1);
// Bounds on the output logged after a read timeout, see ReadSigquitOutput().
constexpr absl::Duration kSigquitOutputTimeout = absl::Seconds(2);
constexpr size_t kSigquitOutputMaxBytes = 5000;
// Waits until `fd` is readable (data available, EOF or error) or `timeout`
// elapses. Returns false on timeout, and also if poll() itself fails with
// anything but EINTR: the caller then treats the testee as hung rather than
// risk a read() that blocks indefinitely.
bool WaitReadable(int fd, absl::Duration timeout) {
const absl::Time deadline = absl::Now() + timeout;
while (true) {
pollfd pfd = {};
pfd.fd = fd;
pfd.events = POLLIN;
const int64_t remaining_ms =
absl::ToInt64Milliseconds(deadline - absl::Now());
const int ready =
poll(&pfd, 1,
static_cast<int>(std::clamp<int64_t>(remaining_ms, 0, INT_MAX)));
if (ready > 0) return true;
if (ready == 0) return false;
if (errno == EINTR) continue;
ABSL_LOG(ERROR) << "poll() on the testee's pipe failed: "
<< strerror(errno);
return false;
}
}
// Reads what the testee prints in response to SIGQUIT (e.g. a JVM thread
// dump), for logging. Stops at EOF, after `kSigquitOutputTimeout` or once
// `kSigquitOutputMaxBytes` have been read, so a testee that ignores SIGQUIT and
// keeps its stdout open cannot hang the runner.
std::string ReadSigquitOutput(int fd) {
std::string out;
const absl::Time deadline = absl::Now() + kSigquitOutputTimeout;
char buf[1024];
while (out.size() < kSigquitOutputMaxBytes &&
WaitReadable(fd, deadline - absl::Now())) {
const ssize_t bytes_read = read(
fd, buf, std::min(sizeof(buf), kSigquitOutputMaxBytes - out.size()));
if (bytes_read < 0 && errno == EINTR) continue;
if (bytes_read <= 0) break; // EOF or error.
out.append(buf, static_cast<size_t>(bytes_read));
}
return out.empty() ? "(no output)" : out;
}
// Sends `sig` to the testee's process group, so that it also reaches anything
// the testee forked, e.g. the real testee behind a wrapper script that does
// not exec it. The group exists from the moment SpawnTestProgram() returns
// until the testee is reaped (see the setpgid() calls there), so ESRCH means
// that something else reaped it and there is nothing left to signal.
void SignalTestee(pid_t pid, int sig) {
if (kill(-pid, sig) != 0 && errno != ESRCH) {
ABSL_LOG(ERROR) << "kill(-" << pid << ", " << sig
<< ") failed: " << strerror(errno);
}
}
// Outcome of WaitForExit().
struct WaitOutcome {
enum Kind {
kExited, // Reaped; `status` is its wait status.
kStillRunning, // The timeout expired first.
kGone, // Reaped by something else in this process (the kernel
// does it if SIGCHLD is ignored); its status is lost.
};
Kind kind;
int status = 0;
};
// Polls for up to `timeout` for the testee `pid` to exit. Once it has, kills
// whatever it left behind in its process group (e.g. a wrapper script that
// exits on EOF after backgrounding the real testee leaves that behind) and
// reaps it.
WaitOutcome WaitForExit(pid_t pid, absl::Duration timeout) {
const absl::Time deadline = absl::Now() + timeout;
while (true) {
// Look at the testee's state without reaping it: until it is reaped its
// pid cannot be reused, so the group id (which is that pid) still
// unambiguously names its group.
siginfo_t info = {};
if (waitid(P_PID, static_cast<id_t>(pid), &info,
WEXITED | WNOHANG | WNOWAIT) != 0) {
if (errno == EINTR) continue;
if (errno != ECHILD) {
ABSL_LOG(WARNING) << "waitid(" << pid
<< ") failed: " << strerror(errno);
}
return {WaitOutcome::kGone};
}
if (info.si_pid == pid) break; // Exited, not yet reaped.
if (absl::Now() >= deadline) return {WaitOutcome::kStillRunning};
absl::SleepFor(kPollInterval);
}
SignalTestee(pid, SIGKILL); // A no-op for the zombie leader itself.
int status = 0;
pid_t reaped;
do {
reaped = waitpid(pid, &status, 0);
} while (reaped < 0 && errno == EINTR);
if (reaped != pid) {
ABSL_LOG(WARNING) << "waitpid(" << pid << ") failed: " << strerror(errno);
return {WaitOutcome::kGone};
}
return {WaitOutcome::kExited, status};
}
} // namespace
struct ForkPipeRunner::State {
// Ends of the to-testee and from-testee pipes owned by this process; nullopt
// when not open.
absl::optional<int> write_fd;
absl::optional<int> read_fd;
// nullopt when there is no live, unreaped testee.
absl::optional<pid_t> child_pid;
};
ForkPipeRunner::ForkPipeRunner(absl::string_view executable,
absl::Span<const std::string> executable_args,
ForkPipeRunnerOptions options)
: executable_(executable),
executable_args_(executable_args.begin(), executable_args.end()),
options_(options),
state_(std::make_unique<State>()) {}
ForkPipeRunner::~ForkPipeRunner() { Shutdown(options_.shutdown_grace_period); }
bool ForkPipeRunner::IsTestProgramRunning() const {
return state_->child_pid.has_value();
}
void ForkPipeRunner::SpawnTestProgram() {
int toproc_pipe_fd[2];
int fromproc_pipe_fd[2];
if (pipe(toproc_pipe_fd) < 0 || pipe(fromproc_pipe_fd) < 0) {
perror("pipe");
exit(1);
}
// Keep the pipes out of any other program this process execs, such as the
// testee of a second ForkPipeRunner: a stray copy of the write end would hold
// this testee's stdin open after Shutdown() closes ours, so it would never
// see EOF. The child's dup2() below clears the flag on the ends it uses.
// (pipe2(O_CLOEXEC) would be atomic, but macOS doesn't have it.)
for (int fd : {toproc_pipe_fd[0], toproc_pipe_fd[1], fromproc_pipe_fd[0],
fromproc_pipe_fd[1]}) {
CHECK_SYSCALL(fcntl(fd, F_SETFD, FD_CLOEXEC));
}
// Build argv and log it *before* fork(): this process is multi-threaded, so
// the child must only make async-signal-safe calls (no logging) until execv.
std::vector<const char*> argv;
argv.reserve(executable_args_.size() + 2);
argv.push_back(executable_.c_str());
ABSL_LOG(INFO) << argv[0];
for (size_t i = 0; i < executable_args_.size(); ++i) {
argv.push_back(executable_args_[i].c_str());
ABSL_LOG(INFO) << executable_args_[i];
}
argv.push_back(nullptr);
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid) {
// Parent.
// Put the child in its own process group, as the child does for itself
// below: whichever of the two gets there first creates the group, so it
// exists before this function returns and kill(-pid) cannot race the
// child's setpgid(). EACCES means the child already exec'd, i.e. it won.
if (setpgid(pid, pid) != 0 && errno != EACCES) {
ABSL_LOG(WARNING) << "setpgid(" << pid << ") failed: " << strerror(errno);
}
CHECK_SYSCALL(close(toproc_pipe_fd[0]));
CHECK_SYSCALL(close(fromproc_pipe_fd[1]));
state_->write_fd = toproc_pipe_fd[1];
state_->read_fd = fromproc_pipe_fd[0];
state_->child_pid = pid;
} else {
// Child.
CHECK_SYSCALL_IN_CHILD(close(STDIN_FILENO));
CHECK_SYSCALL_IN_CHILD(close(STDOUT_FILENO));
CHECK_SYSCALL_IN_CHILD(dup2(toproc_pipe_fd[0], STDIN_FILENO));
CHECK_SYSCALL_IN_CHILD(dup2(fromproc_pipe_fd[1], STDOUT_FILENO));
CHECK_SYSCALL_IN_CHILD(close(toproc_pipe_fd[0]));
CHECK_SYSCALL_IN_CHILD(close(fromproc_pipe_fd[1]));
CHECK_SYSCALL_IN_CHILD(close(toproc_pipe_fd[1]));
CHECK_SYSCALL_IN_CHILD(close(fromproc_pipe_fd[0]));
// Become the leader of a new process group (setpgid() is
// async-signal-safe), so that Shutdown() can SIGKILL the group and reach
// any process the testee forks. See the class comment for the trade-offs.
// A no-op if the parent's setpgid() above got there first.
CHECK_SYSCALL_IN_CHILD(setpgid(0, 0));
// Never returns.
CHECK_SYSCALL_IN_CHILD(
execv(executable_.c_str(), const_cast<char**>(argv.data())));
}
}
ForkPipeRunner::ShutdownResult ForkPipeRunner::Shutdown(
absl::Duration grace_period) {
// Closing our end of the testee's stdin makes it see EOF, which is how a
// conformance testee learns that the run is over. Closing our end of its
// stdout too means a testee blocked writing to a full pipe gets
// SIGPIPE/EPIPE instead of hanging. Resetting the fds and the pid makes a
// second call (e.g. the destructor after RunTest()'s crash path) a no-op.
if (state_->write_fd.has_value()) {
close(*state_->write_fd);
state_->write_fd.reset();
}
if (state_->read_fd.has_value()) {
close(*state_->read_fd);
state_->read_fd.reset();
}
ShutdownResult result;
if (!state_->child_pid.has_value()) return result;
const pid_t pid = *state_->child_pid;
state_->child_pid.reset();
WaitOutcome outcome = WaitForExit(pid, grace_period);
if (outcome.kind == WaitOutcome::kStillRunning) {
// A blocking wait would let a testee that ignores EOF hang the runner;
// SIGKILL cannot be ignored.
ABSL_LOG(WARNING) << "child pid=" << pid << " has not exited "
<< grace_period
<< " after its pipes were closed, sending SIGKILL";
SignalTestee(pid, SIGKILL);
result.killed = true;
outcome = WaitForExit(pid, kKillWait);
if (outcome.kind == WaitOutcome::kStillRunning) {
ABSL_LOG(ERROR) << "giving up on child pid=" << pid
<< ", which has not exited";
}
}
if (outcome.kind == WaitOutcome::kExited) {
result.wait_status = outcome.status;
} else if (outcome.kind == WaitOutcome::kGone) {
// Not a testee that has not exited: the pid may already belong to some
// other process. Its group cannot have been reused while it has members,
// though, so whatever the testee left behind in it can still be killed.
ABSL_LOG(WARNING) << "child pid=" << pid
<< " was reaped by something other than the runner (is "
"SIGCHLD ignored?), so how it ended is unknown";
SignalTestee(pid, SIGKILL);
result.reaped_elsewhere = true;
}
return result;
}
std::string ForkPipeRunner::GetTestProgramFailure(
absl::string_view what_failed) {
if (state_->child_pid.has_value()) {
ABSL_LOG(INFO) << "Trying to reap child, pid=" << *state_->child_pid;
}
const ShutdownResult shutdown = Shutdown(kFailureGracePeriod);
std::string error_msg(what_failed);
// Say how the testee ended, if known. A kill by the runner is reported as
// such so that it is not mistaken for the testee crashing on its own.
if (shutdown.killed) {
absl::StrAppend(&error_msg, " (killed by runner)");
} else if (shutdown.wait_status.has_value()) {
const int status = *shutdown.wait_status;
if (WIFSIGNALED(status)) {
absl::StrAppendFormat(&error_msg, " (killed by signal %d)",
WTERMSIG(status));
} else if (WIFEXITED(status)) {
absl::StrAppendFormat(&error_msg, " (exited with status=%d)",
WEXITSTATUS(status));
}
} else if (shutdown.reaped_elsewhere) {
absl::StrAppend(&error_msg,
" (exit status unknown: reaped outside the runner)");
}
return error_msg;
}
bool ForkPipeRunner::TryWrite(const void* buf, size_t len) {
// Writing to a pipe whose reader has exited raises SIGPIPE, whose default
// action would kill the runner. The signal is directed at the writing
// thread, so blocking it on this thread alone for the duration of the write
// turns it into an EPIPE error return without changing the process-wide
// disposition (which the testee would inherit across exec). A blocked
// SIGPIPE stays pending until it is consumed or unblocked, so it is consumed
// before the mask is restored.
sigset_t sigpipe;
sigemptyset(&sigpipe);
sigaddset(&sigpipe, SIGPIPE);
sigset_t old_mask;
pthread_sigmask(SIG_BLOCK, &sigpipe, &old_mask);
const char* in = static_cast<const char*>(buf);
size_t ofs = 0;
int error = 0;
while (ofs < len) {
const ssize_t written = write(*state_->write_fd, in + ofs, len - ofs);
if (written < 0) {
if (errno == EINTR) continue;
error = errno;
break;
}
ofs += static_cast<size_t>(written);
}
if (error == EPIPE && !sigismember(&old_mask, SIGPIPE)) {
// Discard the SIGPIPE the failed write left pending for this thread. This
// cannot block: the signal is thread-directed and blocked here, so if
// sigpending() reports it, sigwait() takes it at once; if the process
// ignores SIGPIPE none was raised and sigwait() is not called. (If the
// caller had SIGPIPE blocked already the signal is left for the caller.)
sigset_t pending;
sigemptyset(&pending);
sigpending(&pending);
if (sigismember(&pending, SIGPIPE)) {
int sig;
while (sigwait(&sigpipe, &sig) == EINTR) {
}
}
}
pthread_sigmask(SIG_SETMASK, &old_mask, nullptr);
if (error != 0) {
ABSL_LOG(ERROR) << current_test_name_
<< ": error writing to test program: " << strerror(error);
return false;
}
return true;
}
ForkPipeRunner::ReadResult ForkPipeRunner::TryRead(void* buf, size_t len) {
// The timeout is implemented with poll() on the calling thread rather than
// by racing a blocking read() on a helper thread against a deadline: a
// helper thread stuck in read() on a testee that never answers would have to
// be joined (blocking us) or leaked.
const int fd = *state_->read_fd;
char* out = static_cast<char*>(buf);
size_t ofs = 0;
while (ofs < len) {
if (!WaitReadable(fd, options_.read_timeout)) {
ABSL_LOG(ERROR) << current_test_name_ << ": timeout from test program";
// Some runtimes (notably the JVM) react to SIGQUIT by dumping all their
// threads' stacks to stdout, i.e. into our pipe; log whatever arrives,
// for a bounded time, to help diagnose the hang. The caller then shuts
// the testee down.
// TODO: Only log in flag-guarded mode, since reading output from
// SIGQUIT is slow and verbose.
SignalTestee(*state_->child_pid, SIGQUIT);
ABSL_LOG(ERROR) << "child pid=" << *state_->child_pid << " SIGQUIT: \n"
<< ReadSigquitOutput(fd);
return ReadResult::kTimeout;
}
const ssize_t bytes_read = read(fd, out + ofs, len - ofs);
if (bytes_read == 0) {
ABSL_LOG(ERROR) << current_test_name_
<< ": unexpected EOF from test program";
return ReadResult::kEof;
} else if (bytes_read < 0) {
if (errno == EINTR) continue;
ABSL_LOG(ERROR) << current_test_name_
<< ": error reading from test program: "
<< strerror(errno);
return ReadResult::kError;
}
ofs += static_cast<size_t>(bytes_read);
}
return ReadResult::kOk;
}
} // namespace conformance
} // namespace protobuf
} // namespace google
#endif // !_WIN32