Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/tools/testing/selftests/pidfd/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 15 kB image not shown  

Quelle  pidfd_test.c   Sprache: 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 pthread_t signal_received;

static void set_signal_received_on_sigusr1(int sig)
{
 if (sig == SIGUSR1)
  signal_received = pthread_self();
}

static int send_signal(int pidfd)
{
 int ret = 0;

 if (sys_pidfd_send_signal(pidfd, SIGUSR1, NULL, 0) < 0) {
  ret = -EINVAL;
  goto exit;
 }

 if (signal_received != pthread_self()) {
  ret = -EINVAL;
  goto exit;
 }

exit:
 signal_received = 0;
 return ret;
}

static void *send_signal_worker(void *arg)
{
 int pidfd = (int)(intptr_t)arg;
 int ret;

 /* We forward any errors for the caller to handle. */
 ret = send_signal(pidfd);
 return (void *)(intptr_t)ret;
}

/*
 * 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;
 const char *test_name = "pidfd_send_signal send SIGUSR1";
 pthread_t thread;
 void *thread_res;
 int err;

 if (!have_pidfd_send_signal) {
  ksft_test_result_skip(
   "%s test: pidfd_send_signal() syscall not supported\n",
   test_name);
  return 0;
 }

 signal(SIGUSR1, set_signal_received_on_sigusr1);

 /* Try sending a signal to ourselves via /proc/self. */
 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);
 err = send_signal(pidfd);
 if (err)
  ksft_exit_fail_msg(
   "%s test: Error %d on sending pidfd signal\n",
   test_name, err);
 close(pidfd);

 /* Now try the same thing only using PIDFD_SELF_THREAD_GROUP. */
 err = send_signal(PIDFD_SELF_THREAD_GROUP);
 if (err)
  ksft_exit_fail_msg(
   "%s test: Error %d on PIDFD_SELF_THREAD_GROUP signal\n",
   test_name, err);

 /*
 * Now try the same thing in a thread and assert thread ID is equal to
 * worker thread ID.
 */

 if (pthread_create(&thread, NULL, send_signal_worker,
      (void *)(intptr_t)PIDFD_SELF_THREAD))
  ksft_exit_fail_msg("%s test: Failed to create thread\n",
       test_name);
 if (pthread_join(thread, &thread_res))
  ksft_exit_fail_msg("%s test: Failed to join thread\n",
       test_name);
 err = (int)(intptr_t)thread_res;
 if (err)
  ksft_exit_fail_msg(
   "%s test: Error %d on PIDFD_SELF_THREAD signal\n",
   test_name, err);

 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 explicitly 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();
}

Messung V0.5
C=95 H=94 G=94

¤ Dauer der Verarbeitung: 0.4 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.