Commit c17112a5 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'core-process-v5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux

Pull pidfd/waitid updates from Christian Brauner:
 "This contains two features and various tests.

  First, it adds support for waiting on process through pidfds by adding
  the P_PIDFD type to the waitid() syscall. This completes the basic
  functionality of the pidfd api (cf. [1]). In the meantime we also have
  a new adition to the userspace projects that make use of the pidfd
  api. The qt project was nice enough to send a mail pointing out that
  they have a pr up to switch to the pidfd api (cf. [2]).

  Second, this tag contains an extension to the waitid() syscall to make
  it possible to wait on the current process group in a race free manner
  (even though the actual problem is very unlikely) by specifing 0
  together with the P_PGID type. This extension traces back to a
  discussion on the glibc development mailing list.

  There are also a range of tests for the features above. Additionally,
  the test-suite which detected the pidfd-polling race we fixed in [3]
  is included in this tag"

[1] https://lwn.net/Articles/794707/
[2] https://codereview.qt-project.org/c/qt/qtbase/+/108456
[3] commit b191d649 ("pidfd: fix a poll race when setting exit_state")

* tag 'core-process-v5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux:
  waitid: Add support for waiting for the current process group
  tests: add pidfd poll tests
  tests: move common definitions and functions into pidfd.h
  pidfd: add pidfd_wait tests
  pidfd: add P_PIDFD to waitid()
parents 4d856f72 821cc7b0
...@@ -72,6 +72,10 @@ extern struct pid init_struct_pid; ...@@ -72,6 +72,10 @@ extern struct pid init_struct_pid;
extern const struct file_operations pidfd_fops; extern const struct file_operations pidfd_fops;
struct file;
extern struct pid *pidfd_pid(const struct file *file);
static inline struct pid *get_pid(struct pid *pid) static inline struct pid *get_pid(struct pid *pid)
{ {
if (pid) if (pid)
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#define P_ALL 0 #define P_ALL 0
#define P_PID 1 #define P_PID 1
#define P_PGID 2 #define P_PGID 2
#define P_PIDFD 3
#endif /* _UAPI_LINUX_WAIT_H */ #endif /* _UAPI_LINUX_WAIT_H */
...@@ -1554,6 +1554,23 @@ static long do_wait(struct wait_opts *wo) ...@@ -1554,6 +1554,23 @@ static long do_wait(struct wait_opts *wo)
return retval; return retval;
} }
static struct pid *pidfd_get_pid(unsigned int fd)
{
struct fd f;
struct pid *pid;
f = fdget(fd);
if (!f.file)
return ERR_PTR(-EBADF);
pid = pidfd_pid(f.file);
if (!IS_ERR(pid))
get_pid(pid);
fdput(f);
return pid;
}
static long kernel_waitid(int which, pid_t upid, struct waitid_info *infop, static long kernel_waitid(int which, pid_t upid, struct waitid_info *infop,
int options, struct rusage *ru) int options, struct rusage *ru)
{ {
...@@ -1576,19 +1593,32 @@ static long kernel_waitid(int which, pid_t upid, struct waitid_info *infop, ...@@ -1576,19 +1593,32 @@ static long kernel_waitid(int which, pid_t upid, struct waitid_info *infop,
type = PIDTYPE_PID; type = PIDTYPE_PID;
if (upid <= 0) if (upid <= 0)
return -EINVAL; return -EINVAL;
pid = find_get_pid(upid);
break; break;
case P_PGID: case P_PGID:
type = PIDTYPE_PGID; type = PIDTYPE_PGID;
if (upid <= 0) if (upid < 0)
return -EINVAL; return -EINVAL;
if (upid)
pid = find_get_pid(upid);
else
pid = get_task_pid(current, PIDTYPE_PGID);
break;
case P_PIDFD:
type = PIDTYPE_PID;
if (upid < 0)
return -EINVAL;
pid = pidfd_get_pid(upid);
if (IS_ERR(pid))
return PTR_ERR(pid);
break; break;
default: default:
return -EINVAL; return -EINVAL;
} }
if (type < PIDTYPE_MAX)
pid = find_get_pid(upid);
wo.wo_type = type; wo.wo_type = type;
wo.wo_pid = pid; wo.wo_pid = pid;
wo.wo_flags = options; wo.wo_flags = options;
......
...@@ -1690,6 +1690,14 @@ static inline void rcu_copy_process(struct task_struct *p) ...@@ -1690,6 +1690,14 @@ static inline void rcu_copy_process(struct task_struct *p)
#endif /* #ifdef CONFIG_TASKS_RCU */ #endif /* #ifdef CONFIG_TASKS_RCU */
} }
struct pid *pidfd_pid(const struct file *file)
{
if (file->f_op == &pidfd_fops)
return file->private_data;
return ERR_PTR(-EBADF);
}
static int pidfd_release(struct inode *inode, struct file *file) static int pidfd_release(struct inode *inode, struct file *file)
{ {
struct pid *pid = file->private_data; struct pid *pid = file->private_data;
......
...@@ -3678,8 +3678,11 @@ static int copy_siginfo_from_user_any(kernel_siginfo_t *kinfo, siginfo_t *info) ...@@ -3678,8 +3678,11 @@ static int copy_siginfo_from_user_any(kernel_siginfo_t *kinfo, siginfo_t *info)
static struct pid *pidfd_to_pid(const struct file *file) static struct pid *pidfd_to_pid(const struct file *file)
{ {
if (file->f_op == &pidfd_fops) struct pid *pid;
return file->private_data;
pid = pidfd_pid(file);
if (!IS_ERR(pid))
return pid;
return tgid_pidfd_to_pid(file); return tgid_pidfd_to_pid(file);
} }
......
pidfd_open_test pidfd_open_test
pidfd_poll_test
pidfd_test pidfd_test
pidfd_wait
# SPDX-License-Identifier: GPL-2.0-only # SPDX-License-Identifier: GPL-2.0-only
CFLAGS += -g -I../../../../usr/include/ -lpthread CFLAGS += -g -I../../../../usr/include/ -lpthread
TEST_GEN_PROGS := pidfd_test pidfd_open_test TEST_GEN_PROGS := pidfd_test pidfd_open_test pidfd_poll_test pidfd_wait
include ../lib.mk include ../lib.mk
...@@ -16,6 +16,26 @@ ...@@ -16,6 +16,26 @@
#include "../kselftest.h" #include "../kselftest.h"
#ifndef P_PIDFD
#define P_PIDFD 3
#endif
#ifndef CLONE_PIDFD
#define CLONE_PIDFD 0x00001000
#endif
#ifndef __NR_pidfd_open
#define __NR_pidfd_open -1
#endif
#ifndef __NR_pidfd_send_signal
#define __NR_pidfd_send_signal -1
#endif
#ifndef __NR_clone3
#define __NR_clone3 -1
#endif
/* /*
* The kernel reserves 300 pids via RESERVED_PIDS in kernel/pid.c * The kernel reserves 300 pids via RESERVED_PIDS in kernel/pid.c
* That means, when it wraps around any pid < 300 will be skipped. * That means, when it wraps around any pid < 300 will be skipped.
...@@ -53,5 +73,15 @@ int wait_for_pid(pid_t pid) ...@@ -53,5 +73,15 @@ int wait_for_pid(pid_t pid)
return WEXITSTATUS(status); return WEXITSTATUS(status);
} }
static inline int sys_pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(__NR_pidfd_open, pid, flags);
}
static inline int sys_pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
unsigned int flags)
{
return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags);
}
#endif /* __PIDFD_H */ #endif /* __PIDFD_H */
...@@ -22,11 +22,6 @@ ...@@ -22,11 +22,6 @@
#include "pidfd.h" #include "pidfd.h"
#include "../kselftest.h" #include "../kselftest.h"
static inline int sys_pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(__NR_pidfd_open, pid, flags);
}
static int safe_int(const char *numstr, int *converted) static int safe_int(const char *numstr, int *converted)
{ {
char *err = NULL; char *err = NULL;
......
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include "pidfd.h"
#include "../kselftest.h"
static bool timeout;
static void handle_alarm(int sig)
{
timeout = true;
}
int main(int argc, char **argv)
{
struct pollfd fds;
int iter, nevents;
int nr_iterations = 10000;
fds.events = POLLIN;
if (argc > 2)
ksft_exit_fail_msg("Unexpected command line argument\n");
if (argc == 2) {
nr_iterations = atoi(argv[1]);
if (nr_iterations <= 0)
ksft_exit_fail_msg("invalid input parameter %s\n",
argv[1]);
}
ksft_print_msg("running pidfd poll test for %d iterations\n",
nr_iterations);
for (iter = 0; iter < nr_iterations; iter++) {
int pidfd;
int child_pid = fork();
if (child_pid < 0) {
if (errno == EAGAIN) {
iter--;
continue;
}
ksft_exit_fail_msg(
"%s - failed to fork a child process\n",
strerror(errno));
}
if (child_pid == 0) {
/* Child process just sleeps for a min and exits */
sleep(60);
exit(EXIT_SUCCESS);
}
/* Parent kills the child and waits for its death */
pidfd = sys_pidfd_open(child_pid, 0);
if (pidfd < 0)
ksft_exit_fail_msg("%s - pidfd_open failed\n",
strerror(errno));
/* Setup 3 sec alarm - plenty of time */
if (signal(SIGALRM, handle_alarm) == SIG_ERR)
ksft_exit_fail_msg("%s - signal failed\n",
strerror(errno));
alarm(3);
/* Send SIGKILL to the child */
if (sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0))
ksft_exit_fail_msg("%s - pidfd_send_signal failed\n",
strerror(errno));
/* Wait for the death notification */
fds.fd = pidfd;
nevents = poll(&fds, 1, -1);
/* Check for error conditions */
if (nevents < 0)
ksft_exit_fail_msg("%s - poll failed\n",
strerror(errno));
if (nevents != 1)
ksft_exit_fail_msg("unexpected poll result: %d\n",
nevents);
if (!(fds.revents & POLLIN))
ksft_exit_fail_msg(
"unexpected event type received: 0x%x\n",
fds.revents);
if (timeout)
ksft_exit_fail_msg(
"death notification wait timeout\n");
close(pidfd);
/* Wait for child to prevent zombies */
if (waitpid(child_pid, NULL, 0) < 0)
ksft_exit_fail_msg("%s - waitpid failed\n",
strerror(errno));
}
ksft_test_result_pass("pidfd poll test: pass\n");
return ksft_exit_pass();
}
...@@ -21,20 +21,12 @@ ...@@ -21,20 +21,12 @@
#include "pidfd.h" #include "pidfd.h"
#include "../kselftest.h" #include "../kselftest.h"
#ifndef __NR_pidfd_send_signal
#define __NR_pidfd_send_signal -1
#endif
#define str(s) _str(s) #define str(s) _str(s)
#define _str(s) #s #define _str(s) #s
#define CHILD_THREAD_MIN_WAIT 3 /* seconds */ #define CHILD_THREAD_MIN_WAIT 3 /* seconds */
#define MAX_EVENTS 5 #define MAX_EVENTS 5
#ifndef CLONE_PIDFD
#define CLONE_PIDFD 0x00001000
#endif
static pid_t pidfd_clone(int flags, int *pidfd, int (*fn)(void *)) static pid_t pidfd_clone(int flags, int *pidfd, int (*fn)(void *))
{ {
size_t stack_size = 1024; size_t stack_size = 1024;
...@@ -47,12 +39,6 @@ static pid_t pidfd_clone(int flags, int *pidfd, int (*fn)(void *)) ...@@ -47,12 +39,6 @@ static pid_t pidfd_clone(int flags, int *pidfd, int (*fn)(void *))
#endif #endif
} }
static inline int sys_pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
unsigned int flags)
{
return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags);
}
static int signal_received; static int signal_received;
static void set_signal_received_on_sigusr1(int sig) static void set_signal_received_on_sigusr1(int sig)
......
/* 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.h"
#define ptr_to_u64(ptr) ((__u64)((uintptr_t)(ptr)))
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);
}
static int test_pidfd_wait_simple(void)
{
const char *test_name = "pidfd wait simple";
int pidfd = -1, status = 0;
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 ret;
pid_t pid;
siginfo_t info = {
.si_signo = 0,
};
pidfd = open("/proc/self", O_DIRECTORY | O_RDONLY | O_CLOEXEC);
if (pidfd < 0)
ksft_exit_fail_msg("%s test: failed to open /proc/self %s\n",
test_name, strerror(errno));
pid = sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL);
if (pid == 0)
ksft_exit_fail_msg(
"%s test: succeeded to wait on invalid pidfd %s\n",
test_name, strerror(errno));
close(pidfd);
pidfd = -1;
pidfd = open("/dev/null", O_RDONLY | O_CLOEXEC);
if (pidfd == 0)
ksft_exit_fail_msg("%s test: failed to open /dev/null %s\n",
test_name, strerror(errno));
pid = sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL);
if (pid == 0)
ksft_exit_fail_msg(
"%s test: succeeded to wait on invalid pidfd %s\n",
test_name, strerror(errno));
close(pidfd);
pidfd = -1;
pid = sys_clone3(&args);
if (pid < 0)
ksft_exit_fail_msg("%s test: failed to create new process %s\n",
test_name, strerror(errno));
if (pid == 0)
exit(EXIT_SUCCESS);
pid = sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL);
if (pid < 0)
ksft_exit_fail_msg(
"%s test: failed to wait on process with pid %d and pidfd %d: %s\n",
test_name, parent_tid, pidfd, strerror(errno));
if (!WIFEXITED(info.si_status) || WEXITSTATUS(info.si_status))
ksft_exit_fail_msg(
"%s test: unexpected status received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, parent_tid, pidfd, strerror(errno));
close(pidfd);
if (info.si_signo != SIGCHLD)
ksft_exit_fail_msg(
"%s test: unexpected si_signo value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_signo, parent_tid, pidfd,
strerror(errno));
if (info.si_code != CLD_EXITED)
ksft_exit_fail_msg(
"%s test: unexpected si_code value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_code, parent_tid, pidfd,
strerror(errno));
if (info.si_pid != parent_tid)
ksft_exit_fail_msg(
"%s test: unexpected si_pid value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_pid, parent_tid, pidfd,
strerror(errno));
ksft_test_result_pass("%s test: Passed\n", test_name);
return 0;
}
static int test_pidfd_wait_states(void)
{
const char *test_name = "pidfd wait states";
int pidfd = -1, status = 0;
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 ret;
pid_t pid;
siginfo_t info = {
.si_signo = 0,
};
pid = sys_clone3(&args);
if (pid < 0)
ksft_exit_fail_msg("%s test: failed to create new process %s\n",
test_name, strerror(errno));
if (pid == 0) {
kill(getpid(), SIGSTOP);
kill(getpid(), SIGSTOP);
exit(EXIT_SUCCESS);
}
ret = sys_waitid(P_PIDFD, pidfd, &info, WSTOPPED, NULL);
if (ret < 0)
ksft_exit_fail_msg(
"%s test: failed to wait on WSTOPPED process with pid %d and pidfd %d: %s\n",
test_name, parent_tid, pidfd, strerror(errno));
if (info.si_signo != SIGCHLD)
ksft_exit_fail_msg(
"%s test: unexpected si_signo value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_signo, parent_tid, pidfd,
strerror(errno));
if (info.si_code != CLD_STOPPED)
ksft_exit_fail_msg(
"%s test: unexpected si_code value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_code, parent_tid, pidfd,
strerror(errno));
if (info.si_pid != parent_tid)
ksft_exit_fail_msg(
"%s test: unexpected si_pid value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_pid, parent_tid, pidfd,
strerror(errno));
ret = sys_pidfd_send_signal(pidfd, SIGCONT, NULL, 0);
if (ret < 0)
ksft_exit_fail_msg(
"%s test: failed to send signal to process with pid %d and pidfd %d: %s\n",
test_name, parent_tid, pidfd, strerror(errno));
ret = sys_waitid(P_PIDFD, pidfd, &info, WCONTINUED, NULL);
if (ret < 0)
ksft_exit_fail_msg(
"%s test: failed to wait WCONTINUED on process with pid %d and pidfd %d: %s\n",
test_name, parent_tid, pidfd, strerror(errno));
if (info.si_signo != SIGCHLD)
ksft_exit_fail_msg(
"%s test: unexpected si_signo value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_signo, parent_tid, pidfd,
strerror(errno));
if (info.si_code != CLD_CONTINUED)
ksft_exit_fail_msg(
"%s test: unexpected si_code value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_code, parent_tid, pidfd,
strerror(errno));
if (info.si_pid != parent_tid)
ksft_exit_fail_msg(
"%s test: unexpected si_pid value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_pid, parent_tid, pidfd,
strerror(errno));
ret = sys_waitid(P_PIDFD, pidfd, &info, WUNTRACED, NULL);
if (ret < 0)
ksft_exit_fail_msg(
"%s test: failed to wait on WUNTRACED process with pid %d and pidfd %d: %s\n",
test_name, parent_tid, pidfd, strerror(errno));
if (info.si_signo != SIGCHLD)
ksft_exit_fail_msg(
"%s test: unexpected si_signo value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_signo, parent_tid, pidfd,
strerror(errno));
if (info.si_code != CLD_STOPPED)
ksft_exit_fail_msg(
"%s test: unexpected si_code value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_code, parent_tid, pidfd,
strerror(errno));
if (info.si_pid != parent_tid)
ksft_exit_fail_msg(
"%s test: unexpected si_pid value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_pid, parent_tid, pidfd,
strerror(errno));
ret = sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
if (ret < 0)
ksft_exit_fail_msg(
"%s test: failed to send SIGKILL to process with pid %d and pidfd %d: %s\n",
test_name, parent_tid, pidfd, strerror(errno));
ret = sys_waitid(P_PIDFD, pidfd, &info, WEXITED, NULL);
if (ret < 0)
ksft_exit_fail_msg(
"%s test: failed to wait on WEXITED process with pid %d and pidfd %d: %s\n",
test_name, parent_tid, pidfd, strerror(errno));
if (info.si_signo != SIGCHLD)
ksft_exit_fail_msg(
"%s test: unexpected si_signo value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_signo, parent_tid, pidfd,
strerror(errno));
if (info.si_code != CLD_KILLED)
ksft_exit_fail_msg(
"%s test: unexpected si_code value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_code, parent_tid, pidfd,
strerror(errno));
if (info.si_pid != parent_tid)
ksft_exit_fail_msg(
"%s test: unexpected si_pid value %d received after waiting on process with pid %d and pidfd %d: %s\n",
test_name, info.si_pid, parent_tid, pidfd,
strerror(errno));
close(pidfd);
ksft_test_result_pass("%s test: Passed\n", test_name);
return 0;
}
int main(int argc, char **argv)
{
ksft_print_header();
ksft_set_plan(2);
test_pidfd_wait_simple();
test_pidfd_wait_states();
return ksft_exit_pass();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment