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.cc913
1 files changed, 0 insertions, 913 deletions
diff --git a/test/syscalls/linux/wait.cc b/test/syscalls/linux/wait.cc
deleted file mode 100644
index 944149d5e..000000000
--- a/test/syscalls/linux/wait.cc
+++ /dev/null
@@ -1,913 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// 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/ptrace.h>
-#include <sys/resource.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include <functional>
-#include <tuple>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/str_cat.h"
-#include "absl/synchronization/mutex.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/cleanup.h"
-#include "test/util/file_descriptor.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"
-#include "test/util/thread_util.h"
-#include "test/util/time_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(b/22640830,b/27680907,b/29049891): Some functionality is not tested as
-// it is not currently supported by gVisor:
-// * Process groups.
-// * Core dump status (WCOREDUMP).
-//
-// 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([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([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::_));
-}
-
-// Bad options passed
-TEST_P(WaitAnyChildTest, BadOption) {
- EXPECT_THAT(WaitAnyWithOptions(0, 123456),
- PosixErrorIs(EINVAL, ::testing::_));
-}
-
-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::_));
- 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_SUITE_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 (sysno, function) tuple. The function
-// 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::tuple<int, std::function<PosixError(pid_t, int, int)>>> {
- protected:
- int Sysno() { return std::get<0>(GetParam()); }
-
- PosixError WaitForWithOptions(pid_t pid, int options, int code) {
- return std::get<1>(GetParam())(pid, options, code);
- }
-
- PosixError WaitFor(pid_t pid, int code) {
- return std::get<1>(GetParam())(pid, 0, 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 child of sibling thread.
-TEST_P(WaitSpecificChildTest, SiblingChildren) {
- absl::Mutex mu;
- pid_t child;
- bool ready = false;
- bool stop = false;
-
- ScopedThread t([&] {
- absl::MutexLock ml(&mu);
- EXPECT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
- ready = true;
- mu.Await(absl::Condition(&stop));
- });
-
- // N.B. This must be declared after ScopedThread, so it is destructed first,
- // thus waking the thread.
- absl::MutexLock ml(&mu);
- mu.Await(absl::Condition(&ready));
-
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-
- // Keep the sibling alive until after we've waited so the child isn't
- // reparented.
- stop = true;
-}
-
-// Waiting for child of sibling thread not allowed with WNOTHREAD.
-TEST_P(WaitSpecificChildTest, SiblingChildrenWNOTHREAD) {
- // Linux added WNOTHREAD support to waitid(2) in
- // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to
- // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7.
- //
- // Skip the test if it isn't supported yet.
- if (Sysno() == SYS_waitid) {
- int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WNOTHREAD);
- SKIP_IF(ret < 0 && errno == EINVAL);
- }
-
- absl::Mutex mu;
- pid_t child;
- bool ready = false;
- bool stop = false;
-
- ScopedThread t([&] {
- absl::MutexLock ml(&mu);
- EXPECT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
- ready = true;
- mu.Await(absl::Condition(&stop));
-
- // This thread can wait on child.
- EXPECT_NO_ERRNO(WaitForWithOptions(child, __WNOTHREAD, 0));
- });
-
- // N.B. This must be declared after ScopedThread, so it is destructed first,
- // thus waking the thread.
- absl::MutexLock ml(&mu);
- mu.Await(absl::Condition(&ready));
-
- // This thread can't wait on child.
- EXPECT_THAT(WaitForWithOptions(child, __WNOTHREAD, 0),
- PosixErrorIs(ECHILD, ::testing::_));
-
- // Keep the sibling alive until after we've waited so the child isn't
- // reparented.
- stop = true;
-}
-
-// 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([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([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- int child;
- ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds());
-
- EXPECT_THAT(WaitFor(child, 0), PosixErrorIs(ECHILD, ::testing::_));
-}
-
-// Waiting after the child has already exited returns immediately.
-TEST_P(WaitSpecificChildTest, CloneAfterExit) {
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([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([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::_));
-
- // 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));
-}
-
-// A child that does not send a SIGCHLD on exit may be waited on with
-// the __WCLONE flag.
-TEST_P(WaitSpecificChildTest, CloneWCLONE) {
- // Linux added WCLONE support to waitid(2) in
- // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to
- // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7.
- //
- // Skip the test if it isn't supported yet.
- if (Sysno() == SYS_waitid) {
- int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WCLONE);
- SKIP_IF(ret < 0 && errno == EINVAL);
- }
-
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- int child;
- ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitForWithOptions(child, __WCLONE, 0));
-}
-
-// A forked child cannot be waited on with WCLONE.
-TEST_P(WaitSpecificChildTest, ForkWCLONE) {
- // Linux added WCLONE support to waitid(2) in
- // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to
- // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7.
- //
- // Skip the test if it isn't supported yet.
- if (Sysno() == SYS_waitid) {
- int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WCLONE);
- SKIP_IF(ret < 0 && errno == EINVAL);
- }
-
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
-
- EXPECT_THAT(WaitForWithOptions(child, WNOHANG | __WCLONE, 0),
- PosixErrorIs(ECHILD, ::testing::_));
-
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-}
-
-// Any type of child can be waited on with WALL.
-TEST_P(WaitSpecificChildTest, WALL) {
- // Linux added WALL support to waitid(2) in
- // 91c4e8ea8f05916df0c8a6f383508ac7c9e10dba ("wait: allow sys_waitid() to
- // accept __WNOTHREAD/__WCLONE/__WALL"). i.e., Linux 4.7.
- //
- // Skip the test if it isn't supported yet.
- if (Sysno() == SYS_waitid) {
- int ret = waitid(P_ALL, 0, nullptr, WEXITED | WNOHANG | __WALL);
- SKIP_IF(ret < 0 && errno == EINVAL);
- }
-
- pid_t child;
- ASSERT_THAT(child = ForkAndExit(0, 0), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitForWithOptions(child, __WALL, 0));
-
- uintptr_t stack;
- ASSERT_THAT(stack = AllocStack(), SyscallSucceeds());
- auto free =
- Cleanup([stack] { ASSERT_THAT(FreeStack(stack), SyscallSucceeds()); });
-
- ASSERT_THAT(child = CloneAndExit(0, stack, 0), SyscallSucceeds());
-
- EXPECT_NO_ERRNO(WaitForWithOptions(child, __WALL, 0));
-}
-
-// Return ECHILD for bad child.
-TEST_P(WaitSpecificChildTest, BadChild) {
- EXPECT_THAT(WaitFor(42, 0), PosixErrorIs(ECHILD, ::testing::_));
-}
-
-// 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([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);
- }
- ASSERT_THAT(child, SyscallSucceeds());
- EXPECT_NO_ERRNO(WaitFor(child, 0));
-}
-
-PosixError CheckWait4(pid_t pid, int options, int code) {
- int status;
- auto const rv = Wait4(pid, &status, options, 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();
-};
-
-PosixError CheckWaitid(pid_t pid, int options, int code) {
- siginfo_t si;
- auto const rv = Waitid(P_PID, pid, &si, options | 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();
-}
-
-INSTANTIATE_TEST_SUITE_P(
- Waiters, WaitSpecificChildTest,
- ::testing::Values(std::make_tuple(SYS_wait4, CheckWait4),
- std::make_tuple(SYS_waitid, CheckWaitid)));
-
-// 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));
-}
-
-// 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);
-}
-
-// After bf959931ddb88c4e4366e96dd22e68fa0db9527c ("wait/ptrace: assume __WALL
-// if the child is traced") (Linux 4.7), tracees are always eligible for
-// waiting, regardless of type.
-TEST(WaitTest, TraceeWALL) {
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
- FileDescriptor rfd(fds[0]);
- FileDescriptor wfd(fds[1]);
-
- pid_t child = fork();
- if (child == 0) {
- // Child.
- rfd.reset();
-
- TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) == 0);
-
- // Notify parent that we're now a tracee.
- wfd.reset();
-
- _exit(0);
- }
- ASSERT_THAT(child, SyscallSucceeds());
-
- wfd.reset();
-
- // Wait for child to become tracee.
- char c;
- EXPECT_THAT(ReadFd(rfd.get(), &c, sizeof(c)), SyscallSucceedsWithValue(0));
-
- // We can wait on the fork child with WCLONE, as it is a tracee.
- int status;
- if (IsRunningOnGvisor()) {
- ASSERT_THAT(Wait4(child, &status, __WCLONE, nullptr),
- SyscallSucceedsWithValue(child));
-
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status;
- } else {
- // On older versions of Linux, we may get ECHILD.
- ASSERT_THAT(Wait4(child, &status, __WCLONE, nullptr),
- ::testing::AnyOf(SyscallSucceedsWithValue(child),
- SyscallFailsWithErrno(ECHILD)));
- }
-}
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor