After commit f7d5bcd35d42 ("selftests: kselftest: Mark functions that unconditionally call exit() as __noreturn"), ksft_exit_...() functions are marked as __noreturn, which means the return type should not be 'int' but 'void' because they are not returning anything (and never were since exit() has always been called). To facilitate updating the return type of these functions, remove 'return' before the calls to ksft_exit_{pass,fail}(), as __noreturn prevents the compiler from warning that a caller of the ksft_exit functions does not return a value because the program will terminate upon calling these functions. Just removing 'return' would have resulted in !ret ? ksft_exit_pass() : ksft_exit_fail(); so convert that into the more idiomatic if (ret) ksft_exit_fail(); ksft_exit_pass(); Reviewed-by: Muhammad Usama Anjum <usama.anjum@collabora.com> Reviewed-by: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Nathan Chancellor <nathan@kernel.org> Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
577 lines
14 KiB
C
577 lines
14 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/types.h>
|
|
#include <pthread.h>
|
|
#include <sched.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syscall.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "pidfd.h"
|
|
#include "../kselftest.h"
|
|
|
|
#define str(s) _str(s)
|
|
#define _str(s) #s
|
|
#define CHILD_THREAD_MIN_WAIT 3 /* seconds */
|
|
|
|
#define MAX_EVENTS 5
|
|
|
|
static bool have_pidfd_send_signal;
|
|
|
|
static pid_t pidfd_clone(int flags, int *pidfd, int (*fn)(void *))
|
|
{
|
|
size_t stack_size = 1024;
|
|
char *stack[1024] = { 0 };
|
|
|
|
#ifdef __ia64__
|
|
return __clone2(fn, stack, stack_size, flags | SIGCHLD, NULL, pidfd);
|
|
#else
|
|
return clone(fn, stack + stack_size, flags | SIGCHLD, NULL, pidfd);
|
|
#endif
|
|
}
|
|
|
|
static int signal_received;
|
|
|
|
static void set_signal_received_on_sigusr1(int sig)
|
|
{
|
|
if (sig == SIGUSR1)
|
|
signal_received = 1;
|
|
}
|
|
|
|
/*
|
|
* Straightforward test to see whether pidfd_send_signal() works is to send
|
|
* a signal to ourself.
|
|
*/
|
|
static int test_pidfd_send_signal_simple_success(void)
|
|
{
|
|
int pidfd, ret;
|
|
const char *test_name = "pidfd_send_signal send SIGUSR1";
|
|
|
|
if (!have_pidfd_send_signal) {
|
|
ksft_test_result_skip(
|
|
"%s test: pidfd_send_signal() syscall not supported\n",
|
|
test_name);
|
|
return 0;
|
|
}
|
|
|
|
pidfd = open("/proc/self", O_DIRECTORY | O_CLOEXEC);
|
|
if (pidfd < 0)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Failed to open process file descriptor\n",
|
|
test_name);
|
|
|
|
signal(SIGUSR1, set_signal_received_on_sigusr1);
|
|
|
|
ret = sys_pidfd_send_signal(pidfd, SIGUSR1, NULL, 0);
|
|
close(pidfd);
|
|
if (ret < 0)
|
|
ksft_exit_fail_msg("%s test: Failed to send signal\n",
|
|
test_name);
|
|
|
|
if (signal_received != 1)
|
|
ksft_exit_fail_msg("%s test: Failed to receive signal\n",
|
|
test_name);
|
|
|
|
signal_received = 0;
|
|
ksft_test_result_pass("%s test: Sent signal\n", test_name);
|
|
return 0;
|
|
}
|
|
|
|
static int test_pidfd_send_signal_exited_fail(void)
|
|
{
|
|
int pidfd, ret, saved_errno;
|
|
char buf[256];
|
|
pid_t pid;
|
|
const char *test_name = "pidfd_send_signal signal exited process";
|
|
|
|
if (!have_pidfd_send_signal) {
|
|
ksft_test_result_skip(
|
|
"%s test: pidfd_send_signal() syscall not supported\n",
|
|
test_name);
|
|
return 0;
|
|
}
|
|
|
|
pid = fork();
|
|
if (pid < 0)
|
|
ksft_exit_fail_msg("%s test: Failed to create new process\n",
|
|
test_name);
|
|
|
|
if (pid == 0)
|
|
_exit(EXIT_SUCCESS);
|
|
|
|
snprintf(buf, sizeof(buf), "/proc/%d", pid);
|
|
|
|
pidfd = open(buf, O_DIRECTORY | O_CLOEXEC);
|
|
|
|
ret = wait_for_pid(pid);
|
|
ksft_print_msg("waitpid WEXITSTATUS=%d\n", ret);
|
|
|
|
if (pidfd < 0)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Failed to open process file descriptor\n",
|
|
test_name);
|
|
|
|
ret = sys_pidfd_send_signal(pidfd, 0, NULL, 0);
|
|
saved_errno = errno;
|
|
close(pidfd);
|
|
if (ret == 0)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Managed to send signal to process even though it should have failed\n",
|
|
test_name);
|
|
|
|
if (saved_errno != ESRCH)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Expected to receive ESRCH as errno value but received %d instead\n",
|
|
test_name, saved_errno);
|
|
|
|
ksft_test_result_pass("%s test: Failed to send signal as expected\n",
|
|
test_name);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Maximum number of cycles we allow. This is equivalent to PID_MAX_DEFAULT.
|
|
* If users set a higher limit or we have cycled PIDFD_MAX_DEFAULT number of
|
|
* times then we skip the test to not go into an infinite loop or block for a
|
|
* long time.
|
|
*/
|
|
#define PIDFD_MAX_DEFAULT 0x8000
|
|
|
|
static int test_pidfd_send_signal_recycled_pid_fail(void)
|
|
{
|
|
int i, ret;
|
|
pid_t pid1;
|
|
const char *test_name = "pidfd_send_signal signal recycled pid";
|
|
|
|
if (!have_pidfd_send_signal) {
|
|
ksft_test_result_skip(
|
|
"%s test: pidfd_send_signal() syscall not supported\n",
|
|
test_name);
|
|
return 0;
|
|
}
|
|
|
|
ret = unshare(CLONE_NEWPID);
|
|
if (ret < 0) {
|
|
if (errno == EPERM) {
|
|
ksft_test_result_skip("%s test: Unsharing pid namespace not permitted\n",
|
|
test_name);
|
|
return 0;
|
|
}
|
|
ksft_exit_fail_msg("%s test: Failed to unshare pid namespace\n",
|
|
test_name);
|
|
}
|
|
|
|
ret = unshare(CLONE_NEWNS);
|
|
if (ret < 0) {
|
|
if (errno == EPERM) {
|
|
ksft_test_result_skip("%s test: Unsharing mount namespace not permitted\n",
|
|
test_name);
|
|
return 0;
|
|
}
|
|
ksft_exit_fail_msg("%s test: Failed to unshare mount namespace\n",
|
|
test_name);
|
|
}
|
|
|
|
ret = mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0);
|
|
if (ret < 0)
|
|
ksft_exit_fail_msg("%s test: Failed to remount / private\n",
|
|
test_name);
|
|
|
|
/* pid 1 in new pid namespace */
|
|
pid1 = fork();
|
|
if (pid1 < 0)
|
|
ksft_exit_fail_msg("%s test: Failed to create new process\n",
|
|
test_name);
|
|
|
|
if (pid1 == 0) {
|
|
char buf[256];
|
|
pid_t pid2;
|
|
int pidfd = -1;
|
|
|
|
(void)umount2("/proc", MNT_DETACH);
|
|
ret = mount("proc", "/proc", "proc", 0, NULL);
|
|
if (ret < 0)
|
|
_exit(PIDFD_ERROR);
|
|
|
|
/* grab pid PID_RECYCLE */
|
|
for (i = 0; i <= PIDFD_MAX_DEFAULT; i++) {
|
|
pid2 = fork();
|
|
if (pid2 < 0)
|
|
_exit(PIDFD_ERROR);
|
|
|
|
if (pid2 == 0)
|
|
_exit(PIDFD_PASS);
|
|
|
|
if (pid2 == PID_RECYCLE) {
|
|
snprintf(buf, sizeof(buf), "/proc/%d", pid2);
|
|
ksft_print_msg("pid to recycle is %d\n", pid2);
|
|
pidfd = open(buf, O_DIRECTORY | O_CLOEXEC);
|
|
}
|
|
|
|
if (wait_for_pid(pid2))
|
|
_exit(PIDFD_ERROR);
|
|
|
|
if (pid2 >= PID_RECYCLE)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We want to be as predictable as we can so if we haven't been
|
|
* able to grab pid PID_RECYCLE skip the test.
|
|
*/
|
|
if (pid2 != PID_RECYCLE) {
|
|
/* skip test */
|
|
close(pidfd);
|
|
_exit(PIDFD_SKIP);
|
|
}
|
|
|
|
if (pidfd < 0)
|
|
_exit(PIDFD_ERROR);
|
|
|
|
for (i = 0; i <= PIDFD_MAX_DEFAULT; i++) {
|
|
char c;
|
|
int pipe_fds[2];
|
|
pid_t recycled_pid;
|
|
int child_ret = PIDFD_PASS;
|
|
|
|
ret = pipe2(pipe_fds, O_CLOEXEC);
|
|
if (ret < 0)
|
|
_exit(PIDFD_ERROR);
|
|
|
|
recycled_pid = fork();
|
|
if (recycled_pid < 0)
|
|
_exit(PIDFD_ERROR);
|
|
|
|
if (recycled_pid == 0) {
|
|
close(pipe_fds[1]);
|
|
(void)read(pipe_fds[0], &c, 1);
|
|
close(pipe_fds[0]);
|
|
|
|
_exit(PIDFD_PASS);
|
|
}
|
|
|
|
/*
|
|
* Stop the child so we can inspect whether we have
|
|
* recycled pid PID_RECYCLE.
|
|
*/
|
|
close(pipe_fds[0]);
|
|
ret = kill(recycled_pid, SIGSTOP);
|
|
close(pipe_fds[1]);
|
|
if (ret) {
|
|
(void)wait_for_pid(recycled_pid);
|
|
_exit(PIDFD_ERROR);
|
|
}
|
|
|
|
/*
|
|
* We have recycled the pid. Try to signal it. This
|
|
* needs to fail since this is a different process than
|
|
* the one the pidfd refers to.
|
|
*/
|
|
if (recycled_pid == PID_RECYCLE) {
|
|
ret = sys_pidfd_send_signal(pidfd, SIGCONT,
|
|
NULL, 0);
|
|
if (ret && errno == ESRCH)
|
|
child_ret = PIDFD_XFAIL;
|
|
else
|
|
child_ret = PIDFD_FAIL;
|
|
}
|
|
|
|
/* let the process move on */
|
|
ret = kill(recycled_pid, SIGCONT);
|
|
if (ret)
|
|
(void)kill(recycled_pid, SIGKILL);
|
|
|
|
if (wait_for_pid(recycled_pid))
|
|
_exit(PIDFD_ERROR);
|
|
|
|
switch (child_ret) {
|
|
case PIDFD_FAIL:
|
|
/* fallthrough */
|
|
case PIDFD_XFAIL:
|
|
_exit(child_ret);
|
|
case PIDFD_PASS:
|
|
break;
|
|
default:
|
|
/* not reached */
|
|
_exit(PIDFD_ERROR);
|
|
}
|
|
|
|
/*
|
|
* If the user set a custom pid_max limit we could be
|
|
* in the millions.
|
|
* Skip the test in this case.
|
|
*/
|
|
if (recycled_pid > PIDFD_MAX_DEFAULT)
|
|
_exit(PIDFD_SKIP);
|
|
}
|
|
|
|
/* failed to recycle pid */
|
|
_exit(PIDFD_SKIP);
|
|
}
|
|
|
|
ret = wait_for_pid(pid1);
|
|
switch (ret) {
|
|
case PIDFD_FAIL:
|
|
ksft_exit_fail_msg(
|
|
"%s test: Managed to signal recycled pid %d\n",
|
|
test_name, PID_RECYCLE);
|
|
case PIDFD_PASS:
|
|
ksft_exit_fail_msg("%s test: Failed to recycle pid %d\n",
|
|
test_name, PID_RECYCLE);
|
|
case PIDFD_SKIP:
|
|
ksft_test_result_skip("%s test: Skipping test\n", test_name);
|
|
ret = 0;
|
|
break;
|
|
case PIDFD_XFAIL:
|
|
ksft_test_result_pass(
|
|
"%s test: Failed to signal recycled pid as expected\n",
|
|
test_name);
|
|
ret = 0;
|
|
break;
|
|
default /* PIDFD_ERROR */:
|
|
ksft_exit_fail_msg("%s test: Error while running tests\n",
|
|
test_name);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int test_pidfd_send_signal_syscall_support(void)
|
|
{
|
|
int pidfd, ret;
|
|
const char *test_name = "pidfd_send_signal check for support";
|
|
|
|
pidfd = open("/proc/self", O_DIRECTORY | O_CLOEXEC);
|
|
if (pidfd < 0)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Failed to open process file descriptor\n",
|
|
test_name);
|
|
|
|
ret = sys_pidfd_send_signal(pidfd, 0, NULL, 0);
|
|
if (ret < 0) {
|
|
if (errno == ENOSYS) {
|
|
ksft_test_result_skip(
|
|
"%s test: pidfd_send_signal() syscall not supported\n",
|
|
test_name);
|
|
return 0;
|
|
}
|
|
ksft_exit_fail_msg("%s test: Failed to send signal\n",
|
|
test_name);
|
|
}
|
|
|
|
have_pidfd_send_signal = true;
|
|
close(pidfd);
|
|
ksft_test_result_pass(
|
|
"%s test: pidfd_send_signal() syscall is supported. Tests can be executed\n",
|
|
test_name);
|
|
return 0;
|
|
}
|
|
|
|
static void *test_pidfd_poll_exec_thread(void *priv)
|
|
{
|
|
ksft_print_msg("Child Thread: starting. pid %d tid %ld ; and sleeping\n",
|
|
getpid(), syscall(SYS_gettid));
|
|
ksft_print_msg("Child Thread: doing exec of sleep\n");
|
|
|
|
execl("/bin/sleep", "sleep", str(CHILD_THREAD_MIN_WAIT), (char *)NULL);
|
|
|
|
ksft_print_msg("Child Thread: DONE. pid %d tid %ld\n",
|
|
getpid(), syscall(SYS_gettid));
|
|
return NULL;
|
|
}
|
|
|
|
static void poll_pidfd(const char *test_name, int pidfd)
|
|
{
|
|
int c;
|
|
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
|
struct epoll_event event, events[MAX_EVENTS];
|
|
|
|
if (epoll_fd == -1)
|
|
ksft_exit_fail_msg("%s test: Failed to create epoll file descriptor "
|
|
"(errno %d)\n",
|
|
test_name, errno);
|
|
|
|
event.events = EPOLLIN;
|
|
event.data.fd = pidfd;
|
|
|
|
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pidfd, &event)) {
|
|
ksft_exit_fail_msg("%s test: Failed to add epoll file descriptor "
|
|
"(errno %d)\n",
|
|
test_name, errno);
|
|
}
|
|
|
|
c = epoll_wait(epoll_fd, events, MAX_EVENTS, 5000);
|
|
if (c != 1 || !(events[0].events & EPOLLIN))
|
|
ksft_exit_fail_msg("%s test: Unexpected epoll_wait result (c=%d, events=%x) "
|
|
"(errno %d)\n",
|
|
test_name, c, events[0].events, errno);
|
|
|
|
close(epoll_fd);
|
|
return;
|
|
|
|
}
|
|
|
|
static int child_poll_exec_test(void *args)
|
|
{
|
|
pthread_t t1;
|
|
|
|
ksft_print_msg("Child (pidfd): starting. pid %d tid %ld\n", getpid(),
|
|
syscall(SYS_gettid));
|
|
pthread_create(&t1, NULL, test_pidfd_poll_exec_thread, NULL);
|
|
/*
|
|
* Exec in the non-leader thread will destroy the leader immediately.
|
|
* If the wait in the parent returns too soon, the test fails.
|
|
*/
|
|
while (1)
|
|
sleep(1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void test_pidfd_poll_exec(int use_waitpid)
|
|
{
|
|
int pid, pidfd = 0;
|
|
int status, ret;
|
|
time_t prog_start = time(NULL);
|
|
const char *test_name = "pidfd_poll check for premature notification on child thread exec";
|
|
|
|
ksft_print_msg("Parent: pid: %d\n", getpid());
|
|
pid = pidfd_clone(CLONE_PIDFD, &pidfd, child_poll_exec_test);
|
|
if (pid < 0)
|
|
ksft_exit_fail_msg("%s test: pidfd_clone failed (ret %d, errno %d)\n",
|
|
test_name, pid, errno);
|
|
|
|
ksft_print_msg("Parent: Waiting for Child (%d) to complete.\n", pid);
|
|
|
|
if (use_waitpid) {
|
|
ret = waitpid(pid, &status, 0);
|
|
if (ret == -1)
|
|
ksft_print_msg("Parent: error\n");
|
|
|
|
if (ret == pid)
|
|
ksft_print_msg("Parent: Child process waited for.\n");
|
|
} else {
|
|
poll_pidfd(test_name, pidfd);
|
|
}
|
|
|
|
time_t prog_time = time(NULL) - prog_start;
|
|
|
|
ksft_print_msg("Time waited for child: %lu\n", prog_time);
|
|
|
|
close(pidfd);
|
|
|
|
if (prog_time < CHILD_THREAD_MIN_WAIT || prog_time > CHILD_THREAD_MIN_WAIT + 2)
|
|
ksft_exit_fail_msg("%s test: Failed\n", test_name);
|
|
else
|
|
ksft_test_result_pass("%s test: Passed\n", test_name);
|
|
}
|
|
|
|
static void *test_pidfd_poll_leader_exit_thread(void *priv)
|
|
{
|
|
ksft_print_msg("Child Thread: starting. pid %d tid %ld ; and sleeping\n",
|
|
getpid(), syscall(SYS_gettid));
|
|
sleep(CHILD_THREAD_MIN_WAIT);
|
|
ksft_print_msg("Child Thread: DONE. pid %d tid %ld\n", getpid(), syscall(SYS_gettid));
|
|
return NULL;
|
|
}
|
|
|
|
static time_t *child_exit_secs;
|
|
static int child_poll_leader_exit_test(void *args)
|
|
{
|
|
pthread_t t1, t2;
|
|
|
|
ksft_print_msg("Child: starting. pid %d tid %ld\n", getpid(), syscall(SYS_gettid));
|
|
pthread_create(&t1, NULL, test_pidfd_poll_leader_exit_thread, NULL);
|
|
pthread_create(&t2, NULL, test_pidfd_poll_leader_exit_thread, NULL);
|
|
|
|
/*
|
|
* glibc exit calls exit_group syscall, so explicity call exit only
|
|
* so that only the group leader exits, leaving the threads alone.
|
|
*/
|
|
*child_exit_secs = time(NULL);
|
|
syscall(SYS_exit, 0);
|
|
/* Never reached, but appeases compiler thinking we should return. */
|
|
exit(0);
|
|
}
|
|
|
|
static void test_pidfd_poll_leader_exit(int use_waitpid)
|
|
{
|
|
int pid, pidfd = 0;
|
|
int status, ret = 0;
|
|
const char *test_name = "pidfd_poll check for premature notification on non-empty"
|
|
"group leader exit";
|
|
|
|
child_exit_secs = mmap(NULL, sizeof *child_exit_secs, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
|
|
|
if (child_exit_secs == MAP_FAILED)
|
|
ksft_exit_fail_msg("%s test: mmap failed (errno %d)\n",
|
|
test_name, errno);
|
|
|
|
ksft_print_msg("Parent: pid: %d\n", getpid());
|
|
pid = pidfd_clone(CLONE_PIDFD, &pidfd, child_poll_leader_exit_test);
|
|
if (pid < 0)
|
|
ksft_exit_fail_msg("%s test: pidfd_clone failed (ret %d, errno %d)\n",
|
|
test_name, pid, errno);
|
|
|
|
ksft_print_msg("Parent: Waiting for Child (%d) to complete.\n", pid);
|
|
|
|
if (use_waitpid) {
|
|
ret = waitpid(pid, &status, 0);
|
|
if (ret == -1)
|
|
ksft_print_msg("Parent: error\n");
|
|
} else {
|
|
/*
|
|
* This sleep tests for the case where if the child exits, and is in
|
|
* EXIT_ZOMBIE, but the thread group leader is non-empty, then the poll
|
|
* doesn't prematurely return even though there are active threads
|
|
*/
|
|
sleep(1);
|
|
poll_pidfd(test_name, pidfd);
|
|
}
|
|
|
|
if (ret == pid)
|
|
ksft_print_msg("Parent: Child process waited for.\n");
|
|
|
|
time_t since_child_exit = time(NULL) - *child_exit_secs;
|
|
|
|
ksft_print_msg("Time since child exit: %lu\n", since_child_exit);
|
|
|
|
close(pidfd);
|
|
|
|
if (since_child_exit < CHILD_THREAD_MIN_WAIT ||
|
|
since_child_exit > CHILD_THREAD_MIN_WAIT + 2)
|
|
ksft_exit_fail_msg("%s test: Failed\n", test_name);
|
|
else
|
|
ksft_test_result_pass("%s test: Passed\n", test_name);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
ksft_print_header();
|
|
ksft_set_plan(8);
|
|
|
|
test_pidfd_poll_exec(0);
|
|
test_pidfd_poll_exec(1);
|
|
test_pidfd_poll_leader_exit(0);
|
|
test_pidfd_poll_leader_exit(1);
|
|
test_pidfd_send_signal_syscall_support();
|
|
test_pidfd_send_signal_simple_success();
|
|
test_pidfd_send_signal_exited_fail();
|
|
test_pidfd_send_signal_recycled_pid_fail();
|
|
|
|
ksft_exit_pass();
|
|
}
|