summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/wait.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/wait.cc')
-rw-r--r--test/syscalls/linux/wait.cc748
1 files changed, 748 insertions, 0 deletions
diff --git a/test/syscalls/linux/wait.cc b/test/syscalls/linux/wait.cc
new file mode 100644
index 000000000..0a4ec7c6a
--- /dev/null
+++ b/test/syscalls/linux/wait.cc
@@ -0,0 +1,748 @@
+// Copyright 2018 Google LLC
+//
+// 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 <signal.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <functional>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "test/util/cleanup.h"
+#include "test/util/logging.h"
+#include "test/util/multiprocess_util.h"
+#include "test/util/posix_error.h"
+#include "test/util/signal_util.h"
+#include "test/util/test_util.h"
+
+using ::testing::UnorderedElementsAre;
+
+// These unit tests focus on the wait4(2) system call, but include a basic
+// checks for the i386 waitpid(2) syscall, which is a subset of wait4(2).
+//
+// NOTE: Some functionality is not tested as
+// it is not currently supported by gVisor:
+// * UID in waitid(2) siginfo.
+// * Process groups.
+// * Core dump status (WCOREDUMP).
+// * Linux only option __WNOTHREAD.
+//
+// Tests for waiting on stopped/continued children are in sigstop.cc.
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+// The CloneChild function seems to need more than one page of stack space.
+static const size_t kStackSize = 2 * kPageSize;
+
+// The child thread created in CloneAndExit runs this function.
+// This child does not have the TLS setup, so it must not use glibc functions.
+int CloneChild(void* priv) {
+ int64_t sleep = reinterpret_cast<int64_t>(priv);
+ SleepSafe(absl::Seconds(sleep));
+
+ // glibc's _exit(2) function wrapper will helpfully call exit_group(2),
+ // exiting the entire process.
+ syscall(__NR_exit, 0);
+ return 1;
+}
+
+// ForkAndExit forks a child process which exits with exit_code, after
+// sleeping for the specified duration (seconds).
+pid_t ForkAndExit(int exit_code, int64_t sleep) {
+ pid_t child = fork();
+ if (child == 0) {
+ SleepSafe(absl::Seconds(sleep));
+ _exit(exit_code);
+ }
+ return child;
+}
+
+int64_t clock_gettime_nsecs(clockid_t id) {
+ struct timespec ts;
+ TEST_PCHECK(clock_gettime(id, &ts) == 0);
+ return (ts.tv_sec * 1000000000 + ts.tv_nsec);
+}
+
+void spin(int64_t sec) {
+ int64_t ns = sec * 1000000000;
+ int64_t start = clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID);
+ int64_t end = start + ns;
+
+ do {
+ constexpr int kLoopCount = 1000000; // large and arbitrary
+ // volatile to prevent the compiler from skipping this loop.
+ for (volatile int i = 0; i < kLoopCount; i++) {
+ }
+ } while (clock_gettime_nsecs(CLOCK_THREAD_CPUTIME_ID) < end);
+}
+
+// ForkSpinAndExit forks a child process which exits with exit_code, after
+// spinning for the specified duration (seconds).
+pid_t ForkSpinAndExit(int exit_code, int64_t spintime) {
+ pid_t child = fork();
+ if (child == 0) {
+ spin(spintime);
+ _exit(exit_code);
+ }
+ return child;
+}
+
+absl::Duration RusageCpuTime(const struct rusage& ru) {
+ return absl::DurationFromTimeval(ru.ru_utime) +
+ absl::DurationFromTimeval(ru.ru_stime);
+}
+
+// Returns the address of the top of the stack.
+// Free with FreeStack.
+uintptr_t AllocStack() {
+ void* addr = mmap(nullptr, kStackSize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ if (addr == MAP_FAILED) {
+ return reinterpret_cast<uintptr_t>(MAP_FAILED);
+ }
+
+ return reinterpret_cast<uintptr_t>(addr) + kStackSize;
+}
+
+// Frees a stack page allocated with AllocStack.
+int FreeStack(uintptr_t addr) {
+ addr -= kStackSize;
+ return munmap(reinterpret_cast<void*>(addr), kPageSize);
+}
+
+// CloneAndExit clones a child thread, which exits with 0 after sleeping for
+// the specified duration (must be in seconds). extra_flags are ORed against
+// the standard clone(2) flags.
+int CloneAndExit(int64_t sleep, uintptr_t stack, int extra_flags) {
+ return clone(CloneChild, reinterpret_cast<void*>(stack),
+ CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_VM | extra_flags,
+ reinterpret_cast<void*>(sleep));
+}
+
+// Simple wrappers around wait4(2) and waitid(2) that ignore interrupts.
+constexpr auto Wait4 = RetryEINTR(wait4);
+constexpr auto Waitid = RetryEINTR(waitid);
+
+// Fixture for tests parameterized by a function that waits for any child to
+// exit with the given options, checks that it exited with the given code, and
+// then returns its PID.
+//
+// N.B. These tests run in a multi-threaded environment. We assume that
+// background threads do not create child processes and are not themselves
+// created with clone(... | SIGCHLD). Either may cause these tests to
+// erroneously wait on child processes/threads.
+class WaitAnyChildTest : public ::testing::TestWithParam<
+ std::function<PosixErrorOr<pid_t>(int, int)>> {
+ protected:
+ PosixErrorOr<pid_t> WaitAny(int code) { return WaitAnyWithOptions(code, 0); }
+
+ PosixErrorOr<pid_t> WaitAnyWithOptions(int code, int options) {
+ return GetParam()(code, options);
+ }
+};
+
+// Wait for any child to exit.
+TEST_P(WaitAnyChildTest, Fork) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
+
+ EXPECT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child));
+}
+
+// Call wait4 for any process after the child has already exited.
+TEST_P(WaitAnyChildTest, AfterExit) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
+
+ absl::SleepFor(absl::Seconds(5));
+
+ EXPECT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child));
+}
+
+// Wait for multiple children to exit, waiting for either at a time.
+TEST_P(WaitAnyChildTest, MultipleFork) {
+ pid_t child1, child2;
+ ASSERT_THAT(child1 = ForkAndExit(0, 0), SyscallSucceeds());
+ ASSERT_THAT(child2 = ForkAndExit(0, 0), SyscallSucceeds());
+
+ std::vector<pid_t> pids;
+ pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0)));
+ pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0)));
+ EXPECT_THAT(pids, UnorderedElementsAre(child1, child2));
+}
+
+// Wait for any child to exit.
+// A non-CLONE_THREAD child which sends SIGCHLD upon exit behaves much like
+// a forked process.
+TEST_P(WaitAnyChildTest, CloneSIGCHLD) {
+ uintptr_t stack;
+ ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
+ auto free = Cleanup(
+ [this, stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
+
+ int child;
+ ASSERT_THAT(child = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds());
+
+ EXPECT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child));
+}
+
+// Wait for a child thread and process.
+TEST_P(WaitAnyChildTest, ForkAndClone) {
+ pid_t process;
+ ASSERT_THAT(process = ForkAndExit(0, 0), SyscallSucceeds());
+
+ uintptr_t stack;
+ ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
+ auto free = Cleanup(
+ [this, stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
+
+ int thread;
+ // Send SIGCHLD for normal wait semantics.
+ ASSERT_THAT(thread = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds());
+
+ std::vector<pid_t> pids;
+ pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0)));
+ pids.push_back(ASSERT_NO_ERRNO_AND_VALUE(WaitAny(0)));
+ EXPECT_THAT(pids, UnorderedElementsAre(process, thread));
+}
+
+// Return immediately if no child has exited.
+TEST_P(WaitAnyChildTest, WaitWNOHANG) {
+ EXPECT_THAT(
+ WaitAnyWithOptions(0, WNOHANG),
+ PosixErrorIs(ECHILD, ::testing::AnyOf(::testing::StrEq("waitid"),
+ ::testing::StrEq("wait4"))));
+}
+
+// Bad options passed
+TEST_P(WaitAnyChildTest, BadOption) {
+ EXPECT_THAT(
+ WaitAnyWithOptions(0, 123456),
+ PosixErrorIs(EINVAL, ::testing::AnyOf(::testing::StrEq("waitid"),
+ ::testing::StrEq("wait4"))));
+}
+
+TEST_P(WaitAnyChildTest, WaitedChildRusage) {
+ struct rusage before;
+ ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &before), SyscallSucceeds());
+
+ pid_t child;
+ constexpr absl::Duration kSpin = absl::Seconds(3);
+ ASSERT_THAT(child = ForkSpinAndExit(0, absl::ToInt64Seconds(kSpin)),
+ SyscallSucceeds());
+ ASSERT_THAT(WaitAny(0), IsPosixErrorOkAndHolds(child));
+
+ struct rusage after;
+ ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &after), SyscallSucceeds());
+
+ EXPECT_GE(RusageCpuTime(after) - RusageCpuTime(before), kSpin);
+}
+
+TEST_P(WaitAnyChildTest, IgnoredChildRusage) {
+ // "POSIX.1-2001 specifies that if the disposition of SIGCHLD is
+ // set to SIG_IGN or the SA_NOCLDWAIT flag is set for SIGCHLD (see
+ // sigaction(2)), then children that terminate do not become zombies and a
+ // call to wait() or waitpid() will block until all children have terminated,
+ // and then fail with errno set to ECHILD." - waitpid(2)
+ //
+ // "RUSAGE_CHILDREN: Return resource usage statistics for all children of the
+ // calling process that have terminated *and been waited for*." -
+ // getrusage(2), emphasis added
+
+ struct sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ const auto cleanup_sigact =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGCHLD, sa));
+
+ struct rusage before;
+ ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &before), SyscallSucceeds());
+
+ const absl::Duration start =
+ absl::Nanoseconds(clock_gettime_nsecs(CLOCK_MONOTONIC));
+
+ constexpr absl::Duration kSpin = absl::Seconds(3);
+
+ // ForkAndSpin uses CLOCK_THREAD_CPUTIME_ID, which is lower resolution than,
+ // and may diverge from, CLOCK_MONOTONIC, so we allow a small grace period but
+ // still check that we blocked for a while.
+ constexpr absl::Duration kSpinGrace = absl::Milliseconds(100);
+
+ pid_t child;
+ ASSERT_THAT(child = ForkSpinAndExit(0, absl::ToInt64Seconds(kSpin)),
+ SyscallSucceeds());
+ ASSERT_THAT(WaitAny(0), PosixErrorIs(ECHILD, ::testing::AnyOf(
+ ::testing::StrEq("waitid"),
+ ::testing::StrEq("wait4"))));
+ const absl::Duration end =
+ absl::Nanoseconds(clock_gettime_nsecs(CLOCK_MONOTONIC));
+ EXPECT_GE(end - start, kSpin - kSpinGrace);
+
+ struct rusage after;
+ ASSERT_THAT(getrusage(RUSAGE_CHILDREN, &after), SyscallSucceeds());
+ EXPECT_EQ(before.ru_utime.tv_sec, after.ru_utime.tv_sec);
+ EXPECT_EQ(before.ru_utime.tv_usec, after.ru_utime.tv_usec);
+ EXPECT_EQ(before.ru_stime.tv_sec, after.ru_stime.tv_sec);
+ EXPECT_EQ(before.ru_stime.tv_usec, after.ru_stime.tv_usec);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ Waiters, WaitAnyChildTest,
+ ::testing::Values(
+ [](int code, int options) -> PosixErrorOr<pid_t> {
+ int status;
+ auto const pid = Wait4(-1, &status, options, nullptr);
+ MaybeSave();
+ if (pid < 0) {
+ return PosixError(errno, "wait4");
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != code) {
+ return PosixError(
+ EINVAL, absl::StrCat("unexpected wait status: got ", status,
+ ", wanted ", code));
+ }
+ return static_cast<pid_t>(pid);
+ },
+ [](int code, int options) -> PosixErrorOr<pid_t> {
+ siginfo_t si;
+ auto const rv = Waitid(P_ALL, 0, &si, WEXITED | options);
+ MaybeSave();
+ if (rv < 0) {
+ return PosixError(errno, "waitid");
+ }
+ if (si.si_signo != SIGCHLD) {
+ return PosixError(
+ EINVAL, absl::StrCat("unexpected signo: got ", si.si_signo,
+ ", wanted ", SIGCHLD));
+ }
+ if (si.si_status != code) {
+ return PosixError(
+ EINVAL, absl::StrCat("unexpected status: got ", si.si_status,
+ ", wanted ", code));
+ }
+ if (si.si_code != CLD_EXITED) {
+ return PosixError(EINVAL,
+ absl::StrCat("unexpected code: got ", si.si_code,
+ ", wanted ", CLD_EXITED));
+ }
+ auto const uid = getuid();
+ if (si.si_uid != uid) {
+ return PosixError(EINVAL,
+ absl::StrCat("unexpected uid: got ", si.si_uid,
+ ", wanted ", uid));
+ }
+ return static_cast<pid_t>(si.si_pid);
+ }));
+
+// Fixture for tests parameterized by a function that takes the PID of a
+// specific child to wait for, waits for it to exit, and checks that it exits
+// with the given code.
+class WaitSpecificChildTest
+ : public ::testing::TestWithParam<std::function<PosixError(pid_t, int)>> {
+ protected:
+ PosixError WaitFor(pid_t pid, int code) { return GetParam()(pid, code); }
+};
+
+// Wait for specific child to exit.
+TEST_P(WaitSpecificChildTest, Fork) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
+
+ EXPECT_NO_ERRNO(WaitFor(child, 0));
+}
+
+// Non-zero exit codes are correctly propagated.
+TEST_P(WaitSpecificChildTest, NormalExit) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds());
+
+ EXPECT_NO_ERRNO(WaitFor(child, 42));
+}
+
+// Wait for multiple children to exit.
+TEST_P(WaitSpecificChildTest, MultipleFork) {
+ pid_t child1, child2;
+ ASSERT_THAT(child1 = ForkAndExit(0, 0), SyscallSucceeds());
+ ASSERT_THAT(child2 = ForkAndExit(0, 0), SyscallSucceeds());
+
+ EXPECT_NO_ERRNO(WaitFor(child1, 0));
+ EXPECT_NO_ERRNO(WaitFor(child2, 0));
+}
+
+// Wait for multiple children to exit, out of the order they were created.
+TEST_P(WaitSpecificChildTest, MultipleForkOutOfOrder) {
+ pid_t child1, child2;
+ ASSERT_THAT(child1 = ForkAndExit(0, 0), SyscallSucceeds());
+ ASSERT_THAT(child2 = ForkAndExit(0, 0), SyscallSucceeds());
+
+ EXPECT_NO_ERRNO(WaitFor(child2, 0));
+ EXPECT_NO_ERRNO(WaitFor(child1, 0));
+}
+
+// Wait for specific child to exit, entering wait4 before the exit occurs.
+TEST_P(WaitSpecificChildTest, ForkSleep) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(0, 5), SyscallSucceeds());
+
+ EXPECT_NO_ERRNO(WaitFor(child, 0));
+}
+
+// Wait should block until the child exits.
+TEST_P(WaitSpecificChildTest, ForkBlock) {
+ pid_t child;
+
+ auto start = absl::Now();
+ ASSERT_THAT(child = ForkAndExit(0, 5), SyscallSucceeds());
+
+ EXPECT_NO_ERRNO(WaitFor(child, 0));
+
+ EXPECT_GE(absl::Now() - start, absl::Seconds(5));
+}
+
+// Waiting after the child has already exited returns immediately.
+TEST_P(WaitSpecificChildTest, AfterExit) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
+
+ absl::SleepFor(absl::Seconds(5));
+
+ EXPECT_NO_ERRNO(WaitFor(child, 0));
+}
+
+// Wait for specific child to exit.
+// A non-CLONE_THREAD child which sends SIGCHLD upon exit behaves much like
+// a forked process.
+TEST_P(WaitSpecificChildTest, CloneSIGCHLD) {
+ uintptr_t stack;
+ ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
+ auto free = Cleanup(
+ [this, stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
+
+ int child;
+ ASSERT_THAT(child = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds());
+
+ EXPECT_NO_ERRNO(WaitFor(child, 0));
+}
+
+// Wait for specific child to exit.
+// A non-CLONE_THREAD child which does not send SIGCHLD upon exit can be waited
+// on, but returns ECHILD.
+TEST_P(WaitSpecificChildTest, CloneNoSIGCHLD) {
+ uintptr_t stack;
+ ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
+ auto free = Cleanup(
+ [this, stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
+
+ int child;
+ ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds());
+
+ EXPECT_THAT(
+ WaitFor(child, 0),
+ PosixErrorIs(ECHILD, ::testing::AnyOf(::testing::StrEq("waitid"),
+ ::testing::StrEq("wait4"))));
+}
+
+// Waiting after the child has already exited returns immediately.
+TEST_P(WaitSpecificChildTest, CloneAfterExit) {
+ uintptr_t stack;
+ ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
+ auto free = Cleanup(
+ [this, stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
+
+ int child;
+ // Send SIGCHLD for normal wait semantics.
+ ASSERT_THAT(child = CloneAndExit(0, stack, SIGCHLD), SyscallSucceeds());
+
+ absl::SleepFor(absl::Seconds(5));
+
+ EXPECT_NO_ERRNO(WaitFor(child, 0));
+}
+
+// A CLONE_THREAD child cannot be waited on.
+TEST_P(WaitSpecificChildTest, CloneThread) {
+ uintptr_t stack;
+ ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
+ auto free = Cleanup(
+ [this, stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
+
+ int child;
+ ASSERT_THAT(child = CloneAndExit(15, stack, CLONE_THREAD), SyscallSucceeds());
+ auto start = absl::Now();
+
+ EXPECT_THAT(
+ WaitFor(child, 0),
+ PosixErrorIs(ECHILD, ::testing::AnyOf(::testing::StrEq("waitid"),
+ ::testing::StrEq("wait4"))));
+
+ // Ensure wait4 didn't block.
+ EXPECT_LE(absl::Now() - start, absl::Seconds(10));
+
+ // Since we can't wait on the child, we sleep to try to avoid freeing its
+ // stack before it exits.
+ absl::SleepFor(absl::Seconds(5));
+}
+
+// Return ECHILD for bad child.
+TEST_P(WaitSpecificChildTest, BadChild) {
+ EXPECT_THAT(
+ WaitFor(42, 0),
+ PosixErrorIs(ECHILD, ::testing::AnyOf(::testing::StrEq("waitid"),
+ ::testing::StrEq("wait4"))));
+}
+
+// Wait for a child process that only exits after calling execve(2) from a
+// non-leader thread.
+TEST_P(WaitSpecificChildTest, AfterChildExecve) {
+ ExecveArray const owned_child_argv = {"/bin/true"};
+ char* const* const child_argv = owned_child_argv.get();
+
+ uintptr_t stack;
+ ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
+ auto free = Cleanup(
+ [this, stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
+
+ pid_t const child = fork();
+ if (child == 0) {
+ // Give the parent some time to start waiting.
+ SleepSafe(absl::Seconds(5));
+ // Pass CLONE_VFORK to block the original thread in the child process until
+ // the clone thread calls execve, annihilating them both. (This means that
+ // if clone returns at all, something went wrong.)
+ //
+ // N.B. clone(2) is not officially async-signal-safe, but at minimum glibc's
+ // x86_64 implementation is safe. See glibc
+ // sysdeps/unix/sysv/linux/x86_64/clone.S.
+ clone(
+ +[](void* arg) {
+ auto child_argv = static_cast<char* const*>(arg);
+ execve(child_argv[0], child_argv, /* envp = */ nullptr);
+ return errno;
+ },
+ reinterpret_cast<void*>(stack),
+ CLONE_FILES | CLONE_FS | CLONE_SIGHAND | CLONE_THREAD | CLONE_VM |
+ CLONE_VFORK,
+ const_cast<char**>(child_argv));
+ _exit(errno);
+ }
+ EXPECT_NO_ERRNO(WaitFor(child, 0));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ Waiters, WaitSpecificChildTest,
+ ::testing::Values(
+ [](pid_t pid, int code) -> PosixError {
+ int status;
+ auto const rv = Wait4(pid, &status, 0, nullptr);
+ MaybeSave();
+ if (rv < 0) {
+ return PosixError(errno, "wait4");
+ } else if (rv != pid) {
+ return PosixError(EINVAL, absl::StrCat("unexpected pid: got ", rv,
+ ", wanted ", pid));
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != code) {
+ return PosixError(
+ EINVAL, absl::StrCat("unexpected wait status: got ", status,
+ ", wanted ", code));
+ }
+ return NoError();
+ },
+ [](pid_t pid, int code) -> PosixError {
+ siginfo_t si;
+ auto const rv = Waitid(P_PID, pid, &si, WEXITED);
+ MaybeSave();
+ if (rv < 0) {
+ return PosixError(errno, "waitid");
+ }
+ if (si.si_pid != pid) {
+ return PosixError(EINVAL,
+ absl::StrCat("unexpected pid: got ", si.si_pid,
+ ", wanted ", pid));
+ }
+ if (si.si_signo != SIGCHLD) {
+ return PosixError(
+ EINVAL, absl::StrCat("unexpected signo: got ", si.si_signo,
+ ", wanted ", SIGCHLD));
+ }
+ if (si.si_status != code) {
+ return PosixError(
+ EINVAL, absl::StrCat("unexpected status: got ", si.si_status,
+ ", wanted ", code));
+ }
+ if (si.si_code != CLD_EXITED) {
+ return PosixError(EINVAL,
+ absl::StrCat("unexpected code: got ", si.si_code,
+ ", wanted ", CLD_EXITED));
+ }
+ return NoError();
+ }));
+
+// WIFEXITED, WIFSIGNALED, WTERMSIG indicate signal exit.
+TEST(WaitTest, SignalExit) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(0, 10), SyscallSucceeds());
+
+ EXPECT_THAT(kill(child, SIGKILL), SyscallSucceeds());
+
+ int status;
+ EXPECT_THAT(Wait4(child, &status, 0, nullptr),
+ SyscallSucceedsWithValue(child));
+
+ EXPECT_FALSE(WIFEXITED(status));
+ EXPECT_TRUE(WIFSIGNALED(status));
+ EXPECT_EQ(SIGKILL, WTERMSIG(status));
+}
+
+// A child that does not send a SIGCHLD on exit may be waited on with
+// the __WCLONE flag.
+TEST(WaitTest, CloneWCLONE) {
+ uintptr_t stack;
+ ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
+ auto free = Cleanup(
+ [this, stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
+
+ int child;
+ ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds());
+
+ EXPECT_THAT(Wait4(child, nullptr, __WCLONE, nullptr),
+ SyscallSucceedsWithValue(child));
+}
+
+// waitid requires at least one option.
+TEST(WaitTest, WaitidOptions) {
+ EXPECT_THAT(Waitid(P_ALL, 0, nullptr, 0), SyscallFailsWithErrno(EINVAL));
+}
+
+// waitid does not wait for a child to exit if not passed WEXITED.
+TEST(WaitTest, WaitidNoWEXITED) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
+ EXPECT_THAT(Waitid(P_ALL, 0, nullptr, WSTOPPED),
+ SyscallFailsWithErrno(ECHILD));
+ EXPECT_THAT(Waitid(P_ALL, 0, nullptr, WEXITED), SyscallSucceeds());
+}
+
+// WNOWAIT allows the same wait result to be returned again.
+TEST(WaitTest, WaitidWNOWAIT) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds());
+
+ siginfo_t info;
+ ASSERT_THAT(Waitid(P_PID, child, &info, WEXITED | WNOWAIT),
+ SyscallSucceeds());
+ EXPECT_EQ(child, info.si_pid);
+ EXPECT_EQ(SIGCHLD, info.si_signo);
+ EXPECT_EQ(CLD_EXITED, info.si_code);
+ EXPECT_EQ(42, info.si_status);
+
+ ASSERT_THAT(Waitid(P_PID, child, &info, WEXITED), SyscallSucceeds());
+ EXPECT_EQ(child, info.si_pid);
+ EXPECT_EQ(SIGCHLD, info.si_signo);
+ EXPECT_EQ(CLD_EXITED, info.si_code);
+ EXPECT_EQ(42, info.si_status);
+
+ EXPECT_THAT(Waitid(P_PID, child, &info, WEXITED),
+ SyscallFailsWithErrno(ECHILD));
+}
+
+// waitpid(pid, status, options) is equivalent to
+// wait4(pid, status, options, nullptr).
+// This is a dedicated syscall on i386, glibc maps it to wait4 on amd64.
+TEST(WaitTest, WaitPid) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds());
+
+ int status;
+ EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0),
+ SyscallSucceedsWithValue(child));
+
+ EXPECT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(42, WEXITSTATUS(status));
+}
+
+// Test that signaling a zombie succeeds. This is a signals test that is in this
+// file for some reason.
+TEST(WaitTest, KillZombie) {
+ pid_t child;
+ ASSERT_THAT(child = ForkAndExit(42, 0), SyscallSucceeds());
+
+ // Sleep for three seconds to ensure the child has exited.
+ absl::SleepFor(absl::Seconds(3));
+
+ // The child is now a zombie. Check that killing it returns 0.
+ EXPECT_THAT(kill(child, SIGTERM), SyscallSucceeds());
+ EXPECT_THAT(kill(child, 0), SyscallSucceeds());
+
+ EXPECT_THAT(Wait4(child, nullptr, 0, nullptr),
+ SyscallSucceedsWithValue(child));
+}
+
+TEST(WaitTest, Wait4Rusage) {
+ pid_t child;
+ constexpr absl::Duration kSpin = absl::Seconds(3);
+ ASSERT_THAT(child = ForkSpinAndExit(21, absl::ToInt64Seconds(kSpin)),
+ SyscallSucceeds());
+
+ int status;
+ struct rusage rusage = {};
+ ASSERT_THAT(Wait4(child, &status, 0, &rusage),
+ SyscallSucceedsWithValue(child));
+
+ EXPECT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(21, WEXITSTATUS(status));
+
+ EXPECT_GE(RusageCpuTime(rusage), kSpin);
+}
+
+TEST(WaitTest, WaitidRusage) {
+ pid_t child;
+ constexpr absl::Duration kSpin = absl::Seconds(3);
+ ASSERT_THAT(child = ForkSpinAndExit(27, absl::ToInt64Seconds(kSpin)),
+ SyscallSucceeds());
+
+ siginfo_t si = {};
+ struct rusage rusage = {};
+
+ // From waitid(2):
+ // The raw waitid() system call takes a fifth argument, of type
+ // struct rusage *. If this argument is non-NULL, then it is used
+ // to return resource usage information about the child, in the
+ // same manner as wait4(2).
+ EXPECT_THAT(
+ RetryEINTR(syscall)(SYS_waitid, P_PID, child, &si, WEXITED, &rusage),
+ SyscallSucceeds());
+ EXPECT_EQ(si.si_signo, SIGCHLD);
+ EXPECT_EQ(si.si_code, CLD_EXITED);
+ EXPECT_EQ(si.si_status, 27);
+ EXPECT_EQ(si.si_pid, child);
+
+ EXPECT_GE(RusageCpuTime(rusage), kSpin);
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor