| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/fit/defer.h> |
| #include <sys/poll.h> |
| #include <sys/syscall.h> |
| #include <unistd.h> |
| |
| #include <thread> |
| |
| #include <gtest/gtest.h> |
| #include <linux/sched.h> |
| |
| #include "src/lib/files/file.h" |
| #include "src/lib/fxl/strings/string_number_conversions.h" |
| #include "src/starnix/tests/syscalls/cpp/test_helper.h" |
| |
| namespace { |
| |
| // As of this writing, our sysroot's syscall.h lacks the SYS_clone3 definition. |
| #ifndef SYS_clone3 |
| #if defined(__aarch64__) || defined(__x86_64__) || defined(__riscv) |
| #define SYS_clone3 435 |
| #else |
| #error SYS_clone3 needs a definition for this architecture. |
| #endif |
| #endif |
| |
| pid_t ForkUsingClone3(const clone_args* cl_args, size_t size) { |
| return static_cast<pid_t>(syscall(SYS_clone3, cl_args, size)); |
| } |
| |
| // Our Linux sysroot doesn't seem to have pidfd_open() and gettid(). |
| int DoPidFdOpen(pid_t pid) { return static_cast<int>(syscall(SYS_pidfd_open, pid, 0u)); } |
| pid_t DoGetTid() { return static_cast<pid_t>(syscall(SYS_gettid)); } |
| |
| // Returns (readable_end, writable_end). |
| std::pair<int, int> CreatePipe() { |
| int pipe_fds[2]; |
| SAFE_SYSCALL(pipe(pipe_fds)); |
| return {pipe_fds[0], pipe_fds[1]}; |
| } |
| |
| // Waits until the given process' main thread becomes a zombie. |
| void WaitUntilMainThreadIsZombie(pid_t pid) { |
| std::string stat_path = |
| "/proc/" + fxl::NumberToString(pid) + "/task/" + fxl::NumberToString(pid) + "/stat"; |
| |
| while (true) { |
| std::string contents; |
| ASSERT_TRUE(files::ReadFileToString(stat_path, &contents)); |
| |
| char state; |
| ASSERT_EQ(sscanf(contents.c_str(), "%*d %*s %c", &state), 1) << contents; |
| |
| if (state == 'Z') { |
| return; // thread is a Zombie, we're done waiting! |
| } |
| |
| usleep(10000); // check again in 10 ms. |
| } |
| } |
| |
| TEST(PidFdTest, ProcessCanBeOpened) { |
| int pid_fd = DoPidFdOpen(getpid()); |
| ASSERT_GE(pid_fd, 0); |
| close(pid_fd); |
| } |
| |
| TEST(PidFdTest, ThreadCannotBeOpened) { |
| std::thread([] { |
| int pid_fd = DoPidFdOpen(DoGetTid()); |
| ASSERT_EQ(pid_fd, -1); |
| EXPECT_EQ(errno, EINVAL); |
| }).join(); |
| } |
| |
| TEST(PidFdTest, CanPollProcessExit) { |
| // Create a pipe that will be used to ask the child process to exit. |
| auto [r_fd, w_fd] = CreatePipe(); |
| |
| test_helper::ForkHelper helper; |
| pid_t pid = helper.RunInForkedProcess([r_fd = r_fd, w_fd = w_fd] { |
| close(w_fd); |
| |
| // Wait for the readable end to signal the end of the stream. |
| char buf; |
| read(r_fd, &buf, 1); |
| |
| _exit(0); |
| }); |
| |
| close(r_fd); |
| |
| int pid_fd = SAFE_SYSCALL(DoPidFdOpen(pid)); |
| |
| // Verify that poll does not return POLLIN while the process is running. |
| pollfd pfd = {.fd = pid_fd, .events = POLLIN}; |
| ASSERT_EQ(poll(&pfd, 1, 0), 0); |
| |
| // Verify that poll returns POLLIN when the process stops running. |
| { |
| // Do not let SIGCHLD interrupt our poll() call. |
| test_helper::SignalMaskHelper signal_mask_helper; |
| signal_mask_helper.blockSignal(SIGCHLD); |
| auto restorer = fit::defer([&]() { signal_mask_helper.restoreSigmask(); }); |
| |
| close(w_fd); |
| ASSERT_EQ(poll(&pfd, 1, -1), 1); |
| EXPECT_EQ(pfd.revents, POLLIN); |
| } |
| |
| // Verify that poll returns POLLIN even if the wait starts after the process has exited. |
| ASSERT_EQ(poll(&pfd, 1, -1), 1); |
| EXPECT_EQ(pfd.revents, POLLIN); |
| |
| close(pid_fd); |
| ASSERT_TRUE(helper.WaitForChildren()); |
| } |
| |
| TEST(PidFdTest, PollWaitsForSecondaryThreadsToo) { |
| // Create a pipe that will be used to ask the child process to exit. |
| auto [r_fd, w_fd] = CreatePipe(); |
| |
| test_helper::ForkHelper helper; |
| pid_t pid = helper.RunInForkedProcess([r_fd = r_fd, w_fd = w_fd] { |
| close(w_fd); |
| |
| std::thread([r_fd] { |
| // Wait for the readable end to signal the end of the stream. |
| char buf; |
| read(r_fd, &buf, 1); |
| }).detach(); |
| |
| // Immediately exit the main thread, leaving the secondary thread running. |
| syscall(SYS_exit, 0); |
| }); |
| |
| close(r_fd); |
| |
| // Wait for the main thread to exit. |
| ASSERT_NO_FATAL_FAILURE(WaitUntilMainThreadIsZombie(pid)); |
| |
| // Open a pidfd using the main thread's pid. |
| int pid_fd = DoPidFdOpen(pid); |
| ASSERT_GE(pid_fd, 0); |
| |
| // Verify that poll does not return POLLIN even after the main thread exited, |
| // if a secondary thread is still running. |
| pollfd pfd = {.fd = pid_fd, .events = POLLIN}; |
| ASSERT_EQ(poll(&pfd, 1, 500), 0); |
| |
| // Verify that poll returns POLLIN when the secondary thread stops running. |
| { |
| // Do not let SIGCHLD interrupt our poll() call. |
| test_helper::SignalMaskHelper signal_mask_helper; |
| signal_mask_helper.blockSignal(SIGCHLD); |
| auto restorer = fit::defer([&]() { signal_mask_helper.restoreSigmask(); }); |
| |
| close(w_fd); |
| ASSERT_EQ(poll(&pfd, 1, -1), 1); |
| EXPECT_EQ(pfd.revents, POLLIN); |
| } |
| |
| close(pid_fd); |
| ASSERT_TRUE(helper.WaitForChildren()); |
| } |
| |
| TEST(PidFdTest, PidFdOpenAfterZombification) { |
| struct clone_args ca; |
| bzero(&ca, sizeof(ca)); |
| |
| ca.flags = CLONE_PIDFD; |
| ca.exit_signal = SIGCHLD; // Needed in order to wait on the child. |
| |
| // Ask for a PID FD through which the child process can be observed. |
| fbl::unique_fd pid_fd; |
| ca.pidfd = reinterpret_cast<uint64_t>(pid_fd.reset_and_get_address()); |
| |
| auto child_pid = ForkUsingClone3(&ca, sizeof(ca)); |
| ASSERT_NE(child_pid, -1); |
| if (child_pid == 0) { |
| exit(0); |
| } else { |
| ASSERT_TRUE(pid_fd.is_valid()); |
| |
| // Use the `pid_fd` to wait for the child to exit, becoming a zombie. |
| pollfd pfd{.fd = pid_fd.get(), .events = POLLIN}; |
| ASSERT_EQ(poll(&pfd, 1, -1), 1); |
| |
| // Connect a new PID-FD, which should be immediately in the signalled state. |
| auto new_pid_fd = fbl::unique_fd(DoPidFdOpen(child_pid)); |
| ASSERT_TRUE(new_pid_fd.is_valid()) << strerror(errno); |
| pfd = {.fd = new_pid_fd.get(), .events = POLLIN}; |
| EXPECT_EQ(poll(&pfd, 1, 0), 1); |
| |
| // Now reap the zombie child process. |
| int wait_status = 0; |
| pid_t wait_result = waitpid(child_pid, &wait_status, 0); |
| EXPECT_EQ(wait_result, child_pid); |
| } |
| } |
| |
| } // namespace |