/* SPDX-License-Identifier: GPL-2.0 */ #define _GNU_SOURCE #include <errno.h> #include <linux/sched.h> #include <linux/types.h> #include <signal.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <sched.h> #include <string.h> #include <sys/resource.h> #include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include "pidfd.h" #include "../kselftest_harness.h" #define ptr_to_u64(ptr) ((__u64)((uintptr_t)(ptr))) /* Attempt to de-conflict with the selftests tree. */ #ifndef SKIP #define SKIP(s, ...) XFAIL(s, ##__VA_ARGS__) #endif static pid_t sys_clone3(struct clone_args *args) { return syscall(__NR_clone3, args, sizeof(struct clone_args)); } static int sys_waitid(int which, pid_t pid, siginfo_t *info, int options, struct rusage *ru) { return syscall(__NR_waitid, which, pid, info, options, ru); } TEST(wait_simple) { int pidfd = -1; pid_t parent_tid = -1; struct clone_args args = { .parent_tid = ptr_to_u64(&parent_tid), .pidfd = ptr_to_u64(&pidfd), .flags = CLONE_PIDFD | CLONE_PARENT_SETTID, .exit_signal = SIGCHLD, }; pid_t pid; siginfo_t info = { .si_signo = 0, }; pidfd = open("/proc/self", O_DIRECTORY | O_RDONLY | O_CLOEXEC); ASSERT_GE(pidfd, 0); pid = sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL); ASSERT_NE(pid, 0); EXPECT_EQ(close(pidfd), 0); pidfd = -1; pidfd = open("/dev/null", O_RDONLY | O_CLOEXEC); ASSERT_GE(pidfd, 0); pid = sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL); ASSERT_NE(pid, 0); EXPECT_EQ(close(pidfd), 0); pidfd = -1; pid = sys_clone3(&args); ASSERT_GE(pid, 0); if (pid == 0) exit(EXIT_SUCCESS); pid = sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL); ASSERT_GE(pid, 0); ASSERT_EQ(WIFEXITED(info.si_status), true); ASSERT_EQ(WEXITSTATUS(info.si_status), 0); EXPECT_EQ(close(pidfd), 0); ASSERT_EQ(info.si_signo, SIGCHLD); ASSERT_EQ(info.si_code, CLD_EXITED); ASSERT_EQ(info.si_pid, parent_tid); } TEST(wait_states) { int pidfd = -1; pid_t parent_tid = -1; struct clone_args args = { .parent_tid = ptr_to_u64(&parent_tid), .pidfd = ptr_to_u64(&pidfd), .flags = CLONE_PIDFD | CLONE_PARENT_SETTID, .exit_signal = SIGCHLD, }; int pfd[2]; pid_t pid; siginfo_t info = { .si_signo = 0, }; ASSERT_EQ(pipe(pfd), 0); pid = sys_clone3(&args); ASSERT_GE(pid, 0); if (pid == 0) { char buf[2]; close(pfd[1]); kill(getpid(), SIGSTOP); ASSERT_EQ(read(pfd[0], buf, 1), 1); close(pfd[0]); kill(getpid(), SIGSTOP); exit(EXIT_SUCCESS); } close(pfd[0]); ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WSTOPPED, NULL), 0); ASSERT_EQ(info.si_signo, SIGCHLD); ASSERT_EQ(info.si_code, CLD_STOPPED); ASSERT_EQ(info.si_pid, parent_tid); ASSERT_EQ(sys_pidfd_send_signal(pidfd, SIGCONT, NULL, 0), 0); ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WCONTINUED, NULL), 0); ASSERT_EQ(write(pfd[1], "C", 1), 1); close(pfd[1]); ASSERT_EQ(info.si_signo, SIGCHLD); ASSERT_EQ(info.si_code, CLD_CONTINUED); ASSERT_EQ(info.si_pid, parent_tid); ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WUNTRACED, NULL), 0); ASSERT_EQ(info.si_signo, SIGCHLD); ASSERT_EQ(info.si_code, CLD_STOPPED); ASSERT_EQ(info.si_pid, parent_tid); ASSERT_EQ(sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0), 0); ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL), 0); ASSERT_EQ(info.si_signo, SIGCHLD); ASSERT_EQ(info.si_code, CLD_KILLED); ASSERT_EQ(info.si_pid, parent_tid); EXPECT_EQ(close(pidfd), 0); } TEST(wait_nonblock) { int pidfd; unsigned int flags = 0; pid_t parent_tid = -1; struct clone_args args = { .parent_tid = ptr_to_u64(&parent_tid), .flags = CLONE_PARENT_SETTID, .exit_signal = SIGCHLD, }; int ret; pid_t pid; siginfo_t info = { .si_signo = 0, }; /* * Callers need to see ECHILD with non-blocking pidfds when no child * processes exists. */ pidfd = sys_pidfd_open(getpid(), PIDFD_NONBLOCK); EXPECT_GE(pidfd, 0) { /* pidfd_open() doesn't support PIDFD_NONBLOCK. */ ASSERT_EQ(errno, EINVAL); SKIP(return, "Skipping PIDFD_NONBLOCK test"); } ret = sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL); ASSERT_LT(ret, 0); ASSERT_EQ(errno, ECHILD); EXPECT_EQ(close(pidfd), 0); pid = sys_clone3(&args); ASSERT_GE(pid, 0); if (pid == 0) { kill(getpid(), SIGSTOP); exit(EXIT_SUCCESS); } pidfd = sys_pidfd_open(pid, PIDFD_NONBLOCK); EXPECT_GE(pidfd, 0) { /* pidfd_open() doesn't support PIDFD_NONBLOCK. */ ASSERT_EQ(errno, EINVAL); SKIP(return, "Skipping PIDFD_NONBLOCK test"); } flags = fcntl(pidfd, F_GETFL, 0); ASSERT_GT(flags, 0); ASSERT_GT((flags & O_NONBLOCK), 0); /* * Callers need to see EAGAIN/EWOULDBLOCK with non-blocking pidfd when * child processes exist but none have exited. */ ret = sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL); ASSERT_LT(ret, 0); ASSERT_EQ(errno, EAGAIN); /* * Callers need to continue seeing 0 with non-blocking pidfd and * WNOHANG raised explicitly when child processes exist but none have * exited. */ ret = sys_waitid(P_PIDFD, pidfd, &info, WEXITED | WNOHANG, NULL); ASSERT_EQ(ret, 0); ASSERT_EQ(fcntl(pidfd, F_SETFL, (flags & ~O_NONBLOCK)), 0); ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WSTOPPED, NULL), 0); ASSERT_EQ(info.si_signo, SIGCHLD); ASSERT_EQ(info.si_code, CLD_STOPPED); ASSERT_EQ(info.si_pid, parent_tid); ASSERT_EQ(sys_pidfd_send_signal(pidfd, SIGCONT, NULL, 0), 0); ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL), 0); ASSERT_EQ(info.si_signo, SIGCHLD); ASSERT_EQ(info.si_code, CLD_EXITED); ASSERT_EQ(info.si_pid, parent_tid); EXPECT_EQ(close(pidfd), 0); } TEST_HARNESS_MAIN