From 84bced252b670004d88b0e482cb2bc100e031dde Mon Sep 17 00:00:00 2001 From: li041 Date: Wed, 28 Jan 2026 03:13:50 +0000 Subject: [PATCH] Add `pidfd_send_signal` syscall --- book/src/kernel/linux-compatibility/README.md | 1 + .../fully_covered.scml | 2 +- .../signals-and-timers/fully_covered.scml | 3 + kernel/src/process/signal/signals/mod.rs | 1 + kernel/src/process/signal/signals/raw.rs | 40 +++ kernel/src/process/signal/signals/user.rs | 24 +- kernel/src/syscall/arch/generic.rs | 2 + kernel/src/syscall/arch/x86.rs | 2 + kernel/src/syscall/kill.rs | 11 +- kernel/src/syscall/mod.rs | 1 + kernel/src/syscall/pidfd_getfd.rs | 2 +- kernel/src/syscall/pidfd_send_signal.rs | 170 ++++++++++++ test/initramfs/src/apps/scripts/process.sh | 1 + .../src/apps/signal_c/pidfd_send_signal.c | 260 ++++++++++++++++++ 14 files changed, 503 insertions(+), 17 deletions(-) create mode 100644 kernel/src/process/signal/signals/raw.rs create mode 100644 kernel/src/syscall/pidfd_send_signal.rs create mode 100644 test/initramfs/src/apps/signal_c/pidfd_send_signal.c diff --git a/book/src/kernel/linux-compatibility/README.md b/book/src/kernel/linux-compatibility/README.md index f4d44dda7..f2dfca238 100644 --- a/book/src/kernel/linux-compatibility/README.md +++ b/book/src/kernel/linux-compatibility/README.md @@ -343,6 +343,7 @@ which are summarized in the table below. | 327 | preadv2 | ✅ | [⚠️](syscall-flag-coverage/file-and-directory-operations/#preadv2-and-pwritev2) | | 328 | pwritev2 | ✅ | [⚠️](syscall-flag-coverage/file-and-directory-operations/#preadv2-and-pwritev2) | | 332 | statx | ✅ | [⚠️](syscall-flag-coverage/file-and-directory-operations/#statx) | +| 424 | pidfd_send_signal | ✅ | 💯 | | 434 | pidfd_open | ✅ | 💯 | | 435 | clone3 | ✅ | [⚠️](syscall-flag-coverage/process-and-thread-management/#clone-and-clone3) | | 436 | close_range | ✅ | 💯 | diff --git a/book/src/kernel/linux-compatibility/syscall-flag-coverage/file-descriptor-and-io-control/fully_covered.scml b/book/src/kernel/linux-compatibility/syscall-flag-coverage/file-descriptor-and-io-control/fully_covered.scml index 0b0d712a5..c7f670146 100644 --- a/book/src/kernel/linux-compatibility/syscall-flag-coverage/file-descriptor-and-io-control/fully_covered.scml +++ b/book/src/kernel/linux-compatibility/syscall-flag-coverage/file-descriptor-and-io-control/fully_covered.scml @@ -33,7 +33,7 @@ select(nfds, readfds, writefds, exceptfds, timeout); pidfd_open(pid, flags = PIDFD_NONBLOCK); // Obtain a duplicate of another process's file descriptor -pidfd_getfd(pid, targetfd, flags = 0); +pidfd_getfd(pidfd, targetfd, flags = 0); // Close all file descriptors in the inclusive range [first, last] close_range(first, last, flags = CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC); \ No newline at end of file diff --git a/book/src/kernel/linux-compatibility/syscall-flag-coverage/signals-and-timers/fully_covered.scml b/book/src/kernel/linux-compatibility/syscall-flag-coverage/signals-and-timers/fully_covered.scml index f9211835c..637fd7026 100644 --- a/book/src/kernel/linux-compatibility/syscall-flag-coverage/signals-and-timers/fully_covered.scml +++ b/book/src/kernel/linux-compatibility/syscall-flag-coverage/signals-and-timers/fully_covered.scml @@ -16,6 +16,9 @@ kill(pid, sig); // Send signal to a thread tgkill(tgid, tid, sig); +// Send signal to the target process referred to by pidfd +pidfd_send_signal(pidfd, sig, info, flags = PIDFD_SIGNAL_THREAD | PIDFD_SIGNAL_THREAD_GROUP | PIDFD_SIGNAL_PROCESS_GROUP); + // Wait for signal pause(); diff --git a/kernel/src/process/signal/signals/mod.rs b/kernel/src/process/signal/signals/mod.rs index e37e73a70..c6994d6e4 100644 --- a/kernel/src/process/signal/signals/mod.rs +++ b/kernel/src/process/signal/signals/mod.rs @@ -2,6 +2,7 @@ pub mod fault; pub mod kernel; +pub mod raw; pub mod user; use core::{any::Any, fmt::Debug}; diff --git a/kernel/src/process/signal/signals/raw.rs b/kernel/src/process/signal/signals/raw.rs new file mode 100644 index 000000000..2114faff9 --- /dev/null +++ b/kernel/src/process/signal/signals/raw.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::fmt::Debug; + +use crate::process::signal::{c_types::siginfo_t, sig_num::SigNum, signals::Signal}; + +/// A signal that carries raw [`siginfo_t`] information. +#[derive(Clone, Copy)] +pub struct RawSignal { + info: siginfo_t, +} + +impl Debug for RawSignal { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("RawSignal") + .field("signo", &self.info.si_signo) + .field("errno", &self.info.si_errno) + .field("code", &self.info.si_code) + .finish_non_exhaustive() + } +} + +impl RawSignal { + /// Creates a signal that carries raw [`siginfo_t`] information. + /// + /// The caller must ensure that the `info.si_signo` is a valid signal number. + pub fn new(info: siginfo_t) -> Self { + Self { info } + } +} + +impl Signal for RawSignal { + fn num(&self) -> SigNum { + SigNum::from_u8(self.info.si_signo as u8) + } + + fn to_info(&self) -> siginfo_t { + self.info + } +} diff --git a/kernel/src/process/signal/signals/user.rs b/kernel/src/process/signal/signals/user.rs index 2a41cc4ba..0f5dfbe06 100644 --- a/kernel/src/process/signal/signals/user.rs +++ b/kernel/src/process/signal/signals/user.rs @@ -3,12 +3,15 @@ #![expect(dead_code)] use super::Signal; -use crate::process::{ - Pid, Uid, - signal::{ - c_types::siginfo_t, - constants::{SI_QUEUE, SI_TKILL, SI_USER}, - sig_num::SigNum, +use crate::{ + context::Context, + process::{ + Pid, Uid, + signal::{ + c_types::siginfo_t, + constants::{SI_QUEUE, SI_TKILL, SI_USER}, + sig_num::SigNum, + }, }, }; @@ -37,6 +40,15 @@ impl UserSignal { } } + pub fn new_kill(num: SigNum, ctx: &Context) -> Self { + Self { + num, + kind: UserSignalKind::Kill, + pid: ctx.process.pid(), + uid: ctx.posix_thread.credentials().ruid(), + } + } + pub fn pid(&self) -> Pid { self.pid } diff --git a/kernel/src/syscall/arch/generic.rs b/kernel/src/syscall/arch/generic.rs index e01389fdd..309fc37eb 100644 --- a/kernel/src/syscall/arch/generic.rs +++ b/kernel/src/syscall/arch/generic.rs @@ -82,6 +82,7 @@ macro_rules! import_generic_syscall_entries { open::sys_openat, pidfd_getfd::sys_pidfd_getfd, pidfd_open::sys_pidfd_open, + pidfd_send_signal::sys_pidfd_send_signal, pipe::sys_pipe2, ppoll::sys_ppoll, prctl::sys_prctl, @@ -390,6 +391,7 @@ macro_rules! define_syscalls_with_generic_syscall_table { SYS_PREADV2 = 286 => sys_preadv2(args[..6]); SYS_PWRITEV2 = 287 => sys_pwritev2(args[..6]); SYS_STATX = 291 => sys_statx(args[..5]); + SYS_PIDFD_SEND_SIGNAL = 424 => sys_pidfd_send_signal(args[..4]); SYS_PIDFD_OPEN = 434 => sys_pidfd_open(args[..2]); SYS_CLONE3 = 435 => sys_clone3(args[..2], &user_ctx); SYS_CLOSE_RANGE = 436 => sys_close_range(args[..3]); diff --git a/kernel/src/syscall/arch/x86.rs b/kernel/src/syscall/arch/x86.rs index 4157bca91..b1a8cddb4 100644 --- a/kernel/src/syscall/arch/x86.rs +++ b/kernel/src/syscall/arch/x86.rs @@ -83,6 +83,7 @@ use super::{ pause::sys_pause, pidfd_getfd::sys_pidfd_getfd, pidfd_open::sys_pidfd_open, + pidfd_send_signal::sys_pidfd_send_signal, pipe::{sys_pipe, sys_pipe2}, poll::sys_poll, ppoll::sys_ppoll, @@ -405,6 +406,7 @@ impl_syscall_nums_and_dispatch_fn! { SYS_PREADV2 = 327 => sys_preadv2(args[..6]); SYS_PWRITEV2 = 328 => sys_pwritev2(args[..6]); SYS_STATX = 332 => sys_statx(args[..5]); + SYS_PIDFD_SEND_SIGNAL = 424 => sys_pidfd_send_signal(args[..4]); SYS_PIDFD_OPEN = 434 => sys_pidfd_open(args[..2]); SYS_CLONE3 = 435 => sys_clone3(args[..2], &user_ctx); SYS_CLOSE_RANGE = 436 => sys_close_range(args[..3]); diff --git a/kernel/src/syscall/kill.rs b/kernel/src/syscall/kill.rs index f1b3a5f8e..bfff79873 100644 --- a/kernel/src/syscall/kill.rs +++ b/kernel/src/syscall/kill.rs @@ -7,10 +7,7 @@ use crate::{ ProcessFilter, kill, kill_all, kill_group, signal::{ sig_num::SigNum, - signals::{ - Signal, - user::{UserSignal, UserSignalKind}, - }, + signals::{Signal, user::UserSignal}, }, }, }; @@ -31,11 +28,7 @@ pub fn sys_kill(process_filter: u64, sig_num: u64, ctx: &Context) -> Result, ctx: &Context) -> Result<()> { - let signal = sig_num.map(|sig_num| { - let pid = ctx.process.pid(); - let uid = ctx.posix_thread.credentials().ruid(); - UserSignal::new(sig_num, UserSignalKind::Kill, pid, uid) - }); + let signal = sig_num.map(|sig_num| UserSignal::new_kill(sig_num, ctx)); match filter { ProcessFilter::Any => kill_all(signal, ctx)?, diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index b6b57b2d3..a41f85c27 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -95,6 +95,7 @@ mod open; mod pause; mod pidfd_getfd; mod pidfd_open; +mod pidfd_send_signal; mod pipe; mod poll; mod ppoll; diff --git a/kernel/src/syscall/pidfd_getfd.rs b/kernel/src/syscall/pidfd_getfd.rs index b0cfdf952..1aaf85279 100644 --- a/kernel/src/syscall/pidfd_getfd.rs +++ b/kernel/src/syscall/pidfd_getfd.rs @@ -25,7 +25,7 @@ pub fn sys_pidfd_getfd( let mut file_table = ctx.thread_local.borrow_file_table_mut(); let file = get_file_fast!(&mut file_table, pidfd); let Some(pid_file) = file.downcast_ref::() else { - return_errno_with_message!(Errno::EINVAL, "the file is not a PID file"); + return_errno_with_message!(Errno::EBADF, "the file is not a PID file"); }; let process = pid_file diff --git a/kernel/src/syscall/pidfd_send_signal.rs b/kernel/src/syscall/pidfd_send_signal.rs new file mode 100644 index 000000000..03eaa116c --- /dev/null +++ b/kernel/src/syscall/pidfd_send_signal.rs @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MPL-2.0 + +use ostd::mm::VmIo; + +use crate::{ + fs::file_table::{FileDesc, get_file_fast}, + prelude::*, + process::{ + Pgid, Pid, PidFile, kill, kill_group, + posix_thread::AsPosixThread, + signal::{ + c_types::siginfo_t, + constants::SI_TKILL, + sig_num::SigNum, + signals::{Signal, raw::RawSignal, user::UserSignal}, + }, + tgkill, + }, + syscall::SyscallReturn, + thread::Tid, +}; + +pub fn sys_pidfd_send_signal( + pidfd: FileDesc, + sig_num: u64, + info_ptr: Vaddr, + flags: u32, + ctx: &Context, +) -> Result { + let flags = PidfdSendSignalFlags::try_from(flags)?; + let sig_num = SigNum::try_from(sig_num as u8)?; + debug!( + "pidfd={}, info_ptr={:#x}, flags={:?}", + pidfd, info_ptr, flags + ); + + let siginfo = read_siginfo_from_user(info_ptr, sig_num, ctx)?; + let signal = RawSignal::new(siginfo); + + let target = get_target_from_pidfd(pidfd, flags, ctx)?; + + let is_self = match &target { + SignalTarget::Thread { tid, tgid: _ } => *tid == ctx.posix_thread.tid(), + SignalTarget::Process { pid } => *pid == ctx.posix_thread.tid(), + SignalTarget::ProcessGroup { pgid: _ } => false, + }; + + if !is_self && (siginfo.si_code >= 0 || siginfo.si_code == SI_TKILL) { + return_errno_with_message!( + Errno::EPERM, + "signals with custom code can only be sent to the current thread/process" + ); + } + + match target { + SignalTarget::Thread { tid, tgid } => { + tgkill(tid, tgid, Some(Box::new(signal) as Box), ctx)?; + } + SignalTarget::Process { pid } => { + kill(pid, Some(Box::new(signal) as Box), ctx)?; + } + SignalTarget::ProcessGroup { pgid } => { + kill_group(pgid, Some(signal), ctx)?; + } + } + + Ok(SyscallReturn::Return(0)) +} + +fn read_siginfo_from_user(info_ptr: Vaddr, sig_num: SigNum, ctx: &Context) -> Result { + if info_ptr != 0 { + let si = ctx.user_space().read_val::(info_ptr)?; + if si.si_signo != sig_num.as_u8() as i32 { + return_errno_with_message!( + Errno::EINVAL, + "`siginfo.si_signo` does not match the specified signal number" + ); + } + Ok(si) + } else { + // If `info_ptr` is NULL, the kernel constructs a default `siginfo_t` structure + // whose fields match the values that are implicitly supplied when a signal is sent using the kill(2). + Ok(UserSignal::new_kill(sig_num, ctx).to_info()) + } +} + +fn get_target_from_pidfd( + pidfd: FileDesc, + flags: PidfdSendSignalFlags, + ctx: &Context, +) -> Result { + // Helper closures to create signal targets. + let thread_target = |tid: Tid, tgid: Pid| SignalTarget::Thread { tid, tgid }; + let process_target = |pid: Pid| SignalTarget::Process { pid }; + let group_target = |pgid: Pgid| SignalTarget::ProcessGroup { pgid }; + + let target = match pidfd { + PIDFD_SELF_THREAD => match flags { + PidfdSendSignalFlags::Default | PidfdSendSignalFlags::Thread => { + thread_target(ctx.posix_thread.tid(), ctx.process.pid()) + } + PidfdSendSignalFlags::ThreadGroup => process_target(ctx.process.pid()), + PidfdSendSignalFlags::ProcessGroup => group_target(ctx.posix_thread.tid()), + }, + PIDFD_SELF_THREAD_GROUP => match flags { + PidfdSendSignalFlags::Default | PidfdSendSignalFlags::ThreadGroup => { + process_target(ctx.process.pid()) + } + PidfdSendSignalFlags::Thread => thread_target( + ctx.process.main_thread().as_posix_thread().unwrap().tid(), + ctx.process.pid(), + ), + PidfdSendSignalFlags::ProcessGroup => group_target(ctx.process.pid()), + }, + _ => { + let mut file_table = ctx.thread_local.borrow_file_table_mut(); + let file = get_file_fast!(&mut file_table, pidfd); + + // FIXME: On Linux, a pidfd can be also obtained by opening a `/proc/pid` directory. + // Reference: + let Some(pid_file) = file.downcast_ref::() else { + return_errno_with_message!(Errno::EBADF, "the file is not a PID file"); + }; + + let Some(process) = pid_file.process_opt() else { + return_errno_with_message!(Errno::ESRCH, "the target process has been reaped"); + }; + + match flags { + PidfdSendSignalFlags::Default => { + // FIXME: On Linux, a pidfd can refer to either a process or a thread. + // We currently only support pidfds that refer to processes. + process_target(process.pid()) + } + PidfdSendSignalFlags::Thread => { + // FIXME: On Linux, the signal can be sent to any thread. + // We currently only support the main thread. + thread_target( + process.main_thread().as_posix_thread().unwrap().tid(), + process.pid(), + ) + } + PidfdSendSignalFlags::ThreadGroup => process_target(process.pid()), + PidfdSendSignalFlags::ProcessGroup => group_target(process.pid()), + } + } + }; + + Ok(target) +} + +enum SignalTarget { + Thread { tid: Tid, tgid: Pid }, + Process { pid: Pid }, + ProcessGroup { pgid: Pgid }, +} + +// Reference: . +#[derive(Clone, Copy, PartialEq, Eq, Debug, TryFromInt)] +#[repr(u32)] +enum PidfdSendSignalFlags { + Default = 0x0, + Thread = 0x1, + ThreadGroup = 0x2, + ProcessGroup = 0x4, +} + +// Reference: . +const PIDFD_SELF_THREAD: i32 = -10000; +const PIDFD_SELF_THREAD_GROUP: i32 = -10001; diff --git a/test/initramfs/src/apps/scripts/process.sh b/test/initramfs/src/apps/scripts/process.sh index d347cb079..773de695b 100755 --- a/test/initramfs/src/apps/scripts/process.sh +++ b/test/initramfs/src/apps/scripts/process.sh @@ -70,6 +70,7 @@ sched/sched_param_idle shm/posix_shm signal_c/kill signal_c/parent_death_signal +signal_c/pidfd_send_signal signal_c/sigaltstack signal_c/signal_fd signal_c/signal_fpu diff --git a/test/initramfs/src/apps/signal_c/pidfd_send_signal.c b/test/initramfs/src/apps/signal_c/pidfd_send_signal.c new file mode 100644 index 000000000..d3e35bcf1 --- /dev/null +++ b/test/initramfs/src/apps/signal_c/pidfd_send_signal.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: MPL-2.0 + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include "../test.h" + +// Reference: . +#define PIDFD_SELF_THREAD -10000 +#define PIDFD_SELF_THREAD_GROUP -10001 + +// Reference: +#define PIDFD_SIGNAL_THREAD_GROUP (1UL << 1) +#define PIDFD_SIGNAL_PROCESS_GROUP (1UL << 2) + +const int sig = SIGUSR1; +siginfo_t siginfo; + +static int pidfd_open(pid_t pid, unsigned int flags) +{ + return syscall(SYS_pidfd_open, pid, flags); +} + +static int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, + unsigned int flags) +{ + return syscall(SYS_pidfd_send_signal, pidfd, sig, info, flags); +} + +void setup_test_siginfo(siginfo_t *info, int sig, int si_code) +{ + memset(info, 0, sizeof(*info)); + info->si_signo = sig; + info->si_code = si_code; + info->si_pid = getpid(); + info->si_uid = getuid(); +} + +/* ========================== + * Tests for processes + * ========================== */ + +int process_pid; +int process_pidfd; + +FN_SETUP(create_process) +{ + process_pid = CHECK(fork()); + if (process_pid == 0) { + while (1) { + usleep(100); + } + } + + process_pidfd = CHECK(pidfd_open(process_pid, 0)); +} +END_SETUP() + +FN_TEST(pidfd_send_signal_errnos) +{ + setup_test_siginfo(&siginfo, sig, SI_USER); + TEST_ERRNO(pidfd_send_signal(process_pidfd, sig, &siginfo, + PIDFD_SIGNAL_PROCESS_GROUP), + EPERM); + + setup_test_siginfo(&siginfo, sig, -666); + TEST_ERRNO(pidfd_send_signal(process_pidfd, sig + 1, &siginfo, 0), + EINVAL); +} +END_TEST() + +FN_TEST(pidfd_send_signal_process) +{ + TEST_SUCC(pidfd_send_signal(process_pidfd, sig, &siginfo, 0)); + TEST_SUCC(waitid(P_PID, process_pid, NULL, WNOWAIT | WEXITED)); +} +END_TEST() + +FN_SETUP(cleanup_process) +{ + CHECK(close(process_pidfd)); +} +END_SETUP() + +/* ========================== + * Tests for `PIDFD_SELF_*` + * ========================== */ + +// PIDFD_SELF_THREAD/PIDFD_SELF_THREAD_GROUP won't work with +// PIDFD_SIGNAL_PROCESS_GROUP unless the current process is +// the process group leader. +FN_TEST(pidfd_send_signal_self_process_group) +{ + pid_t pid; + int stat; + + pid = TEST_SUCC(fork()); + if (pid == 0) { + setup_test_siginfo(&siginfo, SIGTERM, -666); + + CHECK_WITH(pidfd_send_signal(PIDFD_SELF_THREAD, SIGTERM, + &siginfo, + PIDFD_SIGNAL_PROCESS_GROUP), + _ret == -1 && errno == ESRCH); + CHECK_WITH(pidfd_send_signal(PIDFD_SELF_THREAD_GROUP, SIGTERM, + &siginfo, + PIDFD_SIGNAL_PROCESS_GROUP), + _ret == -1 && errno == ESRCH); + + exit(0); + } + + TEST_RES(waitpid(pid, &stat, 0), + WIFEXITED(stat) && WEXITSTATUS(stat) == 0); +} +END_TEST() + +void *pidfd_send_signal_self_child_thread(void *arg) +{ + setup_test_siginfo(&siginfo, SIGTERM, -666); + + CHECK_WITH(pidfd_send_signal(PIDFD_SELF_THREAD, SIGTERM, &siginfo, + PIDFD_SIGNAL_THREAD_GROUP), + _ret == 0); + + return NULL; +} + +// PIDFD_SELF_THREAD will work with PIDFD_SIGNAL_THREAD_GROUP +// even if the current thread is not the thread group leader. +FN_TEST(pidfd_send_signal_self_thread_group) +{ + pid_t pid; + int stat; + pthread_t thread; + + pid = TEST_SUCC(fork()); + if (pid == 0) { + CHECK(pthread_create(&thread, NULL, + &pidfd_send_signal_self_child_thread, + NULL)); + CHECK(pthread_join(thread, NULL)); + + exit(0); + } + + TEST_RES(waitpid(pid, &stat, 0), + WIFSIGNALED(stat) && WTERMSIG(stat) == SIGTERM); +} +END_TEST() + +void *pidfd_send_signal_self_process_group_child_thread(void *arg) +{ + setup_test_siginfo(&siginfo, SIGTERM, -666); + + CHECK_WITH(pidfd_send_signal(PIDFD_SELF_THREAD, SIGTERM, &siginfo, + PIDFD_SIGNAL_PROCESS_GROUP), + _ret == -1 && errno == ESRCH); + + return NULL; +} + +// PIDFD_SELF_THREAD won't work with PIDFD_SIGNAL_PROCESS_GROUP +// unless the current process is the process group leader and +// the current thread is the main thread. +FN_TEST(pidfd_send_signal_self_process_group_non_main_thread) +{ + pid_t pid; + int stat; + pthread_t thread; + + pid = TEST_SUCC(fork()); + if (pid == 0) { + CHECK(setpgid(0, 0)); + + CHECK(pthread_create( + &thread, NULL, + pidfd_send_signal_self_process_group_child_thread, + NULL)); + CHECK(pthread_join(thread, NULL)); + + exit(0); + } + + TEST_RES(waitpid(pid, &stat, 0), + WIFEXITED(stat) && WEXITSTATUS(stat) == 0); +} +END_TEST() + +// FIXME: Enable thread tests once pidfd for threads is supported +#ifndef __asterinas__ +/* ========================== + * Tests for threads + * ========================== */ + +volatile sig_atomic_t signal_received = 0; + +pthread_t thread; +volatile pid_t thread_tid; + +int proc_fd; +int proc_task_fd; + +void signal_handler(int signo) +{ + signal_received = 1; +} + +void *thread_func(void *arg) +{ + thread_tid = syscall(SYS_gettid); + signal(sig, signal_handler); + + while (!signal_received) { + usleep(100); + } + + return NULL; +} + +FN_SETUP(create_thread) +{ + static char path[256]; + + CHECK(pthread_create(&thread, NULL, thread_func, NULL)); + + while (thread_tid == 0) { + usleep(100); + } + + snprintf(path, sizeof(path), "/proc/%d/", thread_tid); + proc_fd = CHECK(open(path, O_DIRECTORY | O_CLOEXEC)); + + snprintf(path, sizeof(path), "/proc/%d/task/%d", getpid(), thread_tid); + proc_task_fd = CHECK(open(path, O_DIRECTORY | O_CLOEXEC)); +} +END_SETUP() + +FN_TEST(pidfd_send_signal_thread) +{ + TEST_ERRNO(pidfd_send_signal(proc_task_fd, sig, &siginfo, 0), EBADF); + + TEST_SUCC(pidfd_send_signal(proc_fd, sig, &siginfo, 0)); + TEST_SUCC(pthread_join(thread, NULL)); +} +END_TEST() + +FN_SETUP(cleanup_thread) +{ + CHECK(close(proc_fd)); + CHECK(close(proc_task_fd)); +} +END_SETUP() +#endif \ No newline at end of file