diff --git a/os/StarryOS/kernel/src/file/pidfd.rs b/os/StarryOS/kernel/src/file/pidfd.rs index 9f418b69e3..74e0fc902d 100644 --- a/os/StarryOS/kernel/src/file/pidfd.rs +++ b/os/StarryOS/kernel/src/file/pidfd.rs @@ -9,16 +9,18 @@ use core::{ use ax_errno::{AxError, AxResult}; use axpoll::{IoEvents, PollSet, Pollable}; +use starry_process::Pid; use crate::{ file::FileLike, - task::{ProcessData, Thread}, + task::{ProcessData, Thread, get_process_data}, }; pub struct PidFd { proc_data: Weak, exit_event: Arc, thread_exit: Option>, + tid: Option, non_blocking: AtomicBool, } @@ -28,21 +30,31 @@ impl PidFd { proc_data: Arc::downgrade(proc_data), exit_event: proc_data.exit_event.clone(), thread_exit: None, + tid: None, non_blocking: AtomicBool::new(false), } } - pub fn new_thread(thread: &Thread) -> Self { + pub fn new_thread(thread: &Thread, tid: Pid) -> Self { Self { proc_data: Arc::downgrade(&thread.proc_data), exit_event: thread.exit_event.clone(), thread_exit: Some(thread.exit.clone()), + tid: Some(tid), non_blocking: AtomicBool::new(false), } } + pub fn is_thread(&self) -> bool { + self.tid.is_some() + } + + pub fn tid(&self) -> Option { + self.tid + } + pub fn process_data(&self) -> AxResult> { // For threads, the pidfd is invalid once the thread exits, even if its // process is still alive. @@ -51,7 +63,12 @@ impl PidFd { { return Err(AxError::NoSuchProcess); } - self.proc_data.upgrade().ok_or(AxError::NoSuchProcess) + let proc_data = self.proc_data.upgrade().ok_or(AxError::NoSuchProcess)?; + // `ProcessData` may outlive `waitpid` while the pid is no longer in + // `PROCESS_TABLE`. Linux pidfd ops on a reaped pid return ESRCH instead + // of falling through to EBADF from an empty fd table. + get_process_data(proc_data.proc.pid())?; + Ok(proc_data) } } impl FileLike for PidFd { diff --git a/os/StarryOS/kernel/src/syscall/fs/pidfd.rs b/os/StarryOS/kernel/src/syscall/fs/pidfd.rs index db1037ba99..3432a127f4 100644 --- a/os/StarryOS/kernel/src/syscall/fs/pidfd.rs +++ b/os/StarryOS/kernel/src/syscall/fs/pidfd.rs @@ -1,12 +1,19 @@ +use alloc::sync::Arc; + use ax_errno::{AxError, AxResult}; +use ax_task::current; use bitflags::bitflags; -use linux_raw_sys::general::SI_USER; -use starry_signal::SignalInfo; +use linux_raw_sys::general::{SI_TKILL, SI_USER}; +use starry_signal::{SignalInfo, Signo}; +use starry_vm::VmPtr; use crate::{ file::{FD_TABLE, FileLike, PidFd, add_file_like}, - syscall::signal::{check_kill_permission, make_queue_signal_info, make_siginfo}, - task::{AsThread, get_process_data, get_task, send_signal_to_process}, + syscall::signal::check_kill_permission, + task::{ + AsThread, get_process_data, get_task, send_signal_to_process, send_signal_to_process_group, + send_signal_to_thread, + }, }; bitflags! { @@ -17,20 +24,56 @@ bitflags! { } } +bitflags! { + #[derive(Debug, Clone, Copy, Default)] + struct PidFdSignalFlags: u32 { + const THREAD = 1 << 0; + const THREAD_GROUP = 1 << 1; + const PROCESS_GROUP = 1 << 2; + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum PidFdSignalScope { + Thread, + ThreadGroup, + ProcessGroup, +} + +fn parse_signo(signo: u32) -> AxResult { + Signo::from_repr(signo as u8).ok_or(AxError::InvalidInput) +} + +fn make_pidfd_siginfo(signo: Signo, scope: PidFdSignalScope) -> SignalInfo { + let code = if scope == PidFdSignalScope::Thread { + SI_TKILL + } else { + SI_USER as _ + }; + let curr = current(); + let thread = curr.as_thread(); + SignalInfo::new_user(signo, code, thread.proc_data.proc.pid(), thread.cred().uid) +} + pub fn sys_pidfd_open(pid: u32, flags: u32) -> AxResult { debug!("sys_pidfd_open <= pid: {pid}, flags: {flags}"); let flags = PidFdFlags::from_bits(flags).ok_or(AxError::InvalidInput)?; - // Linux pidfd_open(2): EINVAL if pid is not valid. PID 0 is not a - // userspace-visible process id for this syscall (do not alias to self). - if pid == 0 { + // Linux pidfd_open(2): EINVAL if pid is not valid (includes pid <= 0). + if (pid as i32) <= 0 { return Err(AxError::InvalidInput); } let fd = if flags.contains(PidFdFlags::THREAD) { - PidFd::new_thread(get_task(pid)?.as_thread()) + PidFd::new_thread(get_task(pid)?.as_thread(), pid) } else { + // Without PIDFD_THREAD the target must be a thread-group leader. + if let Ok(task) = get_task(pid) + && task.as_thread().proc_data.proc.pid() != pid + { + return Err(AxError::NotFound); + } PidFd::new_process(&get_process_data(pid)?) }; if flags.contains(PidFdFlags::NONBLOCK) { @@ -49,16 +92,28 @@ pub fn sys_pidfd_getfd(pidfd: i32, target_fd: i32, flags: u32) -> AxResult AxResult { - if flags != 0 { + let flags = PidFdSignalFlags::from_bits(flags).ok_or(AxError::InvalidInput)?; + if flags.bits().count_ones() > 1 { return Err(AxError::InvalidInput); } - let pidfd = PidFd::from_fd(pidfd)?; - let pid = pidfd.process_data()?.proc.pid(); - check_kill_permission(pid)?; + let pidfd_obj = PidFd::from_fd(pidfd)?; + let proc_data = pidfd_obj.process_data()?; + let target_pid = proc_data.proc.pid(); - let sig = if sig.is_null() { - make_siginfo(signo, SI_USER as _)? + let scope = if flags.contains(PidFdSignalFlags::THREAD) + || (flags.is_empty() && pidfd_obj.is_thread()) + { + PidFdSignalScope::Thread + } else if flags.contains(PidFdSignalFlags::PROCESS_GROUP) { + PidFdSignalScope::ProcessGroup } else { - make_queue_signal_info(pid, signo, sig)? + PidFdSignalScope::ThreadGroup }; - send_signal_to_process(pid, sig)?; + + let kinfo = if signo == 0 { + None + } else if sig.is_null() { + let signo = parse_signo(signo)?; + Some(make_pidfd_siginfo(signo, scope)) + } else { + let signo_parsed = parse_signo(signo)?; + let info = unsafe { sig.vm_read_uninit()?.assume_init() }; + if info.signo() != signo_parsed { + return Err(AxError::InvalidInput); + } + if current().as_thread().proc_data.proc.pid() != target_pid + && (info.code() >= 0 || info.code() == SI_TKILL) + { + return Err(AxError::OperationNotPermitted); + } + Some(info) + }; + + match scope { + PidFdSignalScope::Thread => { + let tid = pidfd_obj.tid().ok_or(AxError::InvalidInput)?; + check_kill_permission(tid)?; + send_signal_to_thread(Some(target_pid), tid, kinfo)?; + } + PidFdSignalScope::ThreadGroup => { + check_kill_permission(target_pid)?; + send_signal_to_process(target_pid, kinfo)?; + } + PidFdSignalScope::ProcessGroup => { + let pgid = proc_data.proc.group().pgid(); + check_kill_permission(pgid)?; + send_signal_to_process_group(pgid, kinfo)?; + } + } + Ok(0) } diff --git a/os/StarryOS/kernel/src/syscall/task/clone.rs b/os/StarryOS/kernel/src/syscall/task/clone.rs index 13734cf440..f8c91593db 100644 --- a/os/StarryOS/kernel/src/syscall/task/clone.rs +++ b/os/StarryOS/kernel/src/syscall/task/clone.rs @@ -271,7 +271,7 @@ impl CloneArgs { } if flags.contains(CloneFlags::PIDFD) && pidfd != 0 { let pidfd_obj = if flags.contains(CloneFlags::THREAD) { - PidFd::new_thread(&thr) + PidFd::new_thread(&thr, tid) } else { PidFd::new_process(&new_proc_data) }; diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/common/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/common/test_framework.h new file mode 100644 index 0000000000..2f47878f32 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/common/test_framework.h @@ -0,0 +1,86 @@ +#pragma once + +/* 必须在最前面定义,确保 pipe2/gettid 等可用 */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +/* + * StarryOS Syscall Test Framework + * + * 极简独立测试框架:每个文件测一个 syscall,独立编译运行。 + * 目标:出错时精确定位到 源文件:行号 -> 哪个调用 -> 什么结果 + * + * 用法: + * TEST_START("测试名"); + * CHECK(call == expected, "描述"); + * CHECK_ERR(call, EBADF, "描述"); + * TEST_DONE(); + */ + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +/* ---- 核心: 带文件名+行号的检查宏 ---- */ + +/* 检查条件为真 */ +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +/* 检查 syscall 返回特定值 */ +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +/* 检查 syscall 失败且 errno 符合预期 */ +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, \ + strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +/* ---- 测试边界 ---- */ +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-getfd/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-getfd/c/CMakeLists.txt new file mode 100644 index 0000000000..122e2e28e3 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-getfd/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-pidfd-getfd C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-pidfd-getfd src/main.c) +target_include_directories(test-pidfd-getfd PRIVATE src ${CMAKE_CURRENT_SOURCE_DIR}/../../common) +target_compile_options(test-pidfd-getfd PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-pidfd-getfd RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-getfd/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-getfd/c/src/main.c new file mode 100644 index 0000000000..e179430089 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-getfd/c/src/main.c @@ -0,0 +1,447 @@ +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef __NR_pidfd_open +#error "__NR_pidfd_open required from " +#endif +#ifndef __NR_pidfd_getfd +#error "__NR_pidfd_getfd required from " +#endif + +static int x_pidfd_open(pid_t pid, unsigned int flags) +{ + return (int)syscall(__NR_pidfd_open, pid, flags); +} + +static int x_pidfd_getfd(int pidfd, int target_fd, unsigned int flags) +{ + return (int)syscall(__NR_pidfd_getfd, pidfd, target_fd, flags); +} + +static int get_cloexec(int fd) +{ + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + return -1; + } + return !!(flags & FD_CLOEXEC); +} + +static int read_exact(int fd, void *buf, size_t len) +{ + unsigned char *p = buf; + size_t left = len; + + while (left > 0) { + ssize_t n = read(fd, p, left); + if (n < 0) { + return -1; + } + if (n == 0) { + return -1; + } + p += (size_t)n; + left -= (size_t)n; + } + return 0; +} + +/* Child blocks on sync[0] until parent writes one byte to sync[1]. */ +static int open_pidfd_before_child_exit(pid_t child, int sync[2], int *out_pfd) +{ + char ch = 0; + + *out_pfd = x_pidfd_open(child, 0); + if (*out_pfd < 0) { + return -1; + } + if (write(sync[1], &ch, 1) != 1) { + close(*out_pfd); + return -1; + } + return 0; +} + +static void test_pidfd_getfd_bad_pidfd_closed(void) +{ + printf("--- pidfd_getfd 已关闭 pidfd ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd < 0) { + return; + } + close(pfd); + CHECK_ERR(x_pidfd_getfd(pfd, 0, 0), EBADF, "已 close pidfd -> EBADF"); +} + +static void test_pidfd_getfd_bad_pidfd_minus_one(void) +{ + printf("--- pidfd_getfd pidfd=-1 ---\n"); + + CHECK_ERR(x_pidfd_getfd(-1, 0, 0), EBADF, "pidfd=-1 -> EBADF"); +} + +static void test_pidfd_getfd_bad_pidfd(void) +{ + printf("--- pidfd_getfd 非 pidfd ---\n"); + + int pipe_fds[2]; + CHECK_RET(pipe(pipe_fds), 0, "pipe 创建成功"); + errno = 0; + if (x_pidfd_getfd(pipe_fds[0], 0, 0) == -1 && (errno == EINVAL || errno == EBADF)) { + CHECK(1, "普通 fd 作 pidfd -> EINVAL/EBADF"); + } else { + CHECK(0, "普通 fd 作 pidfd -> EINVAL/EBADF"); + } + close(pipe_fds[0]); + close(pipe_fds[1]); +} + +static void test_pidfd_getfd_reaped_target(void) +{ + printf("--- pidfd_getfd reap 后目标进程 ---\n"); + + int sync[2]; + if (pipe(sync) != 0) { + return; + } + + pid_t child = fork(); + CHECK(child >= 0, "fork 成功"); + if (child < 0) { + close(sync[0]); + close(sync[1]); + return; + } + + if (child == 0) { + char ch; + close(sync[1]); + if (read(sync[0], &ch, 1) != 1) { + _exit(1); + } + close(sync[0]); + _exit(0); + } + + close(sync[0]); + int pfd = -1; + CHECK(open_pidfd_before_child_exit(child, sync, &pfd) == 0, "reap 前 pidfd_open 成功"); + if (pfd < 0) { + close(sync[1]); + waitpid(child, NULL, 0); + return; + } + + int status = 0; + CHECK_RET(waitpid(child, &status, 0), child, "waitpid reap 子进程"); + + CHECK_ERR(x_pidfd_getfd(pfd, 0, 0), ESRCH, "reap 后 target_fd=0 -> ESRCH"); + CHECK_ERR(x_pidfd_getfd(pfd, -1, 0), ESRCH, "reap 后 target_fd=-1 -> ESRCH"); + close(pfd); + close(sync[1]); +} + +static void test_pidfd_getfd_bad_target(void) +{ + printf("--- pidfd_getfd 无效 target_fd ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + CHECK_ERR(x_pidfd_getfd(pfd, 111, 0), EBADF, "不存在 target_fd -> EBADF"); + close(pfd); + } +} + +static void test_pidfd_getfd_negative_target_fd(void) +{ + printf("--- pidfd_getfd target_fd 为负 ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + CHECK_ERR(x_pidfd_getfd(pfd, -1, 0), EBADF, "target_fd=-1 -> EBADF"); + close(pfd); + } +} + +static void test_pidfd_getfd_nonzero_flags(void) +{ + printf("--- pidfd_getfd 非法 flags ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + CHECK_ERR(x_pidfd_getfd(pfd, 0, 1), EINVAL, "flags=1 -> EINVAL"); + CHECK_ERR(x_pidfd_getfd(pfd, 0, 0xffffffffu), EINVAL, "flags=0xffffffff -> EINVAL"); + close(pfd); + } +} + +static void test_pidfd_getfd_cloexec(void) +{ + printf("--- pidfd_getfd FD_CLOEXEC ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + int pipe_fds[2]; + if (pfd < 0 || pipe(pipe_fds) != 0) { + if (pfd >= 0) { + close(pfd); + } + return; + } + + CHECK(get_cloexec(pipe_fds[0]) == 0, "原 fd 无 FD_CLOEXEC"); + + int dup_fd = x_pidfd_getfd(pfd, pipe_fds[0], 0); + if (dup_fd >= 0) { + CHECK(get_cloexec(dup_fd) == 1, "pidfd_getfd 返回 FD_CLOEXEC"); + CHECK(get_cloexec(pipe_fds[0]) == 0, "原 fd FD_CLOEXEC 未被修改"); + close(dup_fd); + } + + close(pipe_fds[0]); + close(pipe_fds[1]); + close(pfd); +} + +static void test_pidfd_getfd_self_basic(void) +{ + printf("--- pidfd_getfd 正常路径 ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open(getpid(), 0) 成功"); + if (pfd < 0) { + return; + } + + int pipe_fds[2]; + CHECK_RET(pipe(pipe_fds), 0, "pipe 创建成功"); + + const char msg[] = "pidfd-getfd"; + CHECK_RET(write(pipe_fds[1], msg, sizeof(msg) - 1), (ssize_t)(sizeof(msg) - 1), + "pipe write"); + + int dup_fd = x_pidfd_getfd(pfd, pipe_fds[0], 0); + CHECK(dup_fd >= 0, "pidfd_getfd 返回新 fd"); + if (dup_fd >= 0) { + char buf[32] = {0}; + ssize_t n = read(dup_fd, buf, sizeof(buf) - 1); + CHECK(n == (ssize_t)(sizeof(msg) - 1), "dup fd read 长度正确"); + CHECK(strcmp(buf, msg) == 0, "dup fd read 内容正确"); + close(dup_fd); + } + + close(pipe_fds[0]); + close(pipe_fds[1]); + close(pfd); +} + +static void test_pidfd_getfd_cross_cred_eperm(void) +{ + printf("--- pidfd_getfd 跨 cred -> EPERM ---\n"); + + int notify[2]; + int release[2]; + if (pipe(notify) != 0 || pipe(release) != 0) { + return; + } + + pid_t child = fork(); + CHECK(child >= 0, "fork 成功"); + if (child < 0) { + close(notify[0]); + close(notify[1]); + close(release[0]); + close(release[1]); + return; + } + + if (child == 0) { + int data[2]; + int target_fd; + char ack; + + close(notify[0]); + close(release[1]); + if (setresuid(2000, 2000, 2000) != 0) { + _exit(1); + } + if (pipe(data) != 0) { + _exit(1); + } + target_fd = data[0]; + if (write(notify[1], &target_fd, sizeof(target_fd)) != (ssize_t)sizeof(target_fd)) { + _exit(1); + } + close(notify[1]); + if (read(release[0], &ack, 1) != 1) { + _exit(1); + } + close(release[0]); + close(data[0]); + close(data[1]); + _exit(0); + } + + close(notify[1]); + close(release[0]); + + if (geteuid() == 0 && setresuid(1000, 1000, 1000) != 0) { + CHECK(0, "parent setresuid(1000) 失败"); + kill(child, SIGKILL); + waitpid(child, NULL, 0); + close(notify[0]); + close(release[1]); + return; + } + + int child_fd = -1; + if (read_exact(notify[0], &child_fd, sizeof(child_fd)) != 0) { + CHECK(0, "从子进程读取 target_fd 失败"); + close(notify[0]); + close(release[1]); + kill(child, SIGKILL); + waitpid(child, NULL, 0); + return; + } + close(notify[0]); + + int pfd = x_pidfd_open(child, 0); + CHECK(pfd >= 0, "pidfd_open(child) 成功"); + if (pfd < 0) { + kill(child, SIGKILL); + waitpid(child, NULL, 0); + close(release[1]); + return; + } + + CHECK_ERR(x_pidfd_getfd(pfd, child_fd, 0), EPERM, + "uid 1000 对 uid 2000 子进程 pidfd_getfd -> EPERM"); + + char ack = 'x'; + (void)write(release[1], &ack, 1); + close(pfd); + close(release[1]); + CHECK_RET(waitpid(child, NULL, 0), child, "waitpid 子进程"); +} + +static void test_pidfd_getfd_child_process(void) +{ + printf("--- pidfd_getfd 跨进程 dup ---\n"); + + int notify[2]; + int release[2]; + if (pipe(notify) != 0 || pipe(release) != 0) { + return; + } + + const char msg[] = "pidfd-child"; + pid_t child = fork(); + CHECK(child >= 0, "fork 成功"); + if (child < 0) { + close(notify[0]); + close(notify[1]); + close(release[0]); + close(release[1]); + return; + } + + if (child == 0) { + int data[2]; + char ack; + int target_fd; + + close(notify[0]); + close(release[1]); + if (pipe(data) != 0) { + _exit(1); + } + target_fd = data[0]; + if (write(notify[1], &target_fd, sizeof(target_fd)) != (ssize_t)sizeof(target_fd)) { + _exit(1); + } + if (write(data[1], msg, sizeof(msg) - 1) != (ssize_t)(sizeof(msg) - 1)) { + _exit(1); + } + close(notify[1]); + if (read(release[0], &ack, 1) != 1) { + _exit(1); + } + close(release[0]); + close(data[0]); + close(data[1]); + _exit(0); + } + + close(notify[1]); + close(release[0]); + + int child_fd = -1; + if (read_exact(notify[0], &child_fd, sizeof(child_fd)) != 0) { + CHECK(0, "从子进程读取 target_fd 失败"); + close(notify[0]); + close(release[1]); + kill(child, SIGKILL); + waitpid(child, NULL, 0); + return; + } + close(notify[0]); + + int pfd = x_pidfd_open(child, 0); + CHECK(pfd >= 0, "pidfd_open(child) 成功"); + if (pfd < 0) { + kill(child, SIGKILL); + waitpid(child, NULL, 0); + close(release[1]); + return; + } + + int dup_fd = x_pidfd_getfd(pfd, child_fd, 0); + CHECK(dup_fd >= 0, "pidfd_getfd(child fd) 成功"); + if (dup_fd >= 0) { + char buf[32] = {0}; + ssize_t n = read(dup_fd, buf, sizeof(buf) - 1); + CHECK(n == (ssize_t)(sizeof(msg) - 1), "跨进程 dup read 长度正确"); + CHECK(strcmp(buf, msg) == 0, "跨进程 dup read 内容正确"); + close(dup_fd); + } + + char ack = 'x'; + CHECK(write(release[1], &ack, 1) == 1, "放行子进程退出"); + + close(pfd); + close(release[1]); + CHECK_RET(waitpid(child, NULL, 0), child, "waitpid 子进程"); +} + +int main(void) +{ + TEST_START("pidfd_getfd"); + + signal(SIGPIPE, SIG_IGN); + + test_pidfd_getfd_bad_pidfd_closed(); + test_pidfd_getfd_bad_pidfd_minus_one(); + test_pidfd_getfd_bad_pidfd(); + test_pidfd_getfd_reaped_target(); + test_pidfd_getfd_bad_target(); + test_pidfd_getfd_negative_target_fd(); + test_pidfd_getfd_nonzero_flags(); + test_pidfd_getfd_cloexec(); + test_pidfd_getfd_self_basic(); + test_pidfd_getfd_cross_cred_eperm(); + test_pidfd_getfd_child_process(); + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-open/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-open/c/CMakeLists.txt new file mode 100644 index 0000000000..79f5448eb5 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-open/c/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.20) +project(test-pidfd-open C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-pidfd-open src/main.c) +target_include_directories(test-pidfd-open PRIVATE src ${CMAKE_CURRENT_SOURCE_DIR}/../../common) +target_compile_options(test-pidfd-open PRIVATE -Wall -Wextra -Werror) +target_link_libraries(test-pidfd-open PRIVATE -lpthread) +install(TARGETS test-pidfd-open RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-open/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-open/c/src/main.c new file mode 100644 index 0000000000..bcb756bd4f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-open/c/src/main.c @@ -0,0 +1,180 @@ +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __NR_pidfd_open +#error "__NR_pidfd_open required from for this arch/toolchain" +#endif + +#ifndef PIDFD_THREAD +#define PIDFD_THREAD O_EXCL +#endif + +#ifndef PIDFD_NONBLOCK +#define PIDFD_NONBLOCK O_NONBLOCK +#endif + +static int x_pidfd_open(pid_t pid, unsigned int flags) +{ + return (int)syscall(__NR_pidfd_open, pid, flags); +} + +static int get_cloexec(int fd) +{ + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + return -1; + } + return !!(flags & FD_CLOEXEC); +} + +static void test_pidfd_open_invalid_pid(void) +{ + printf("--- pidfd_open 非法 pid ---\n"); + + CHECK_ERR(x_pidfd_open(-1, 0), EINVAL, "pidfd_open(-1, 0) -> EINVAL"); + CHECK_ERR(x_pidfd_open(0, 0), EINVAL, "pidfd_open(0, 0) -> EINVAL"); +} + +static void test_pidfd_open_self(void) +{ + printf("--- pidfd_open 正常路径 ---\n"); + + errno = 0; + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open(getpid(), 0) 返回 fd"); + if (pfd >= 0) { + CHECK_RET(close(pfd), 0, "close pidfd"); + } +} + +static void test_pidfd_open_cloexec(void) +{ + printf("--- pidfd_open O_CLOEXEC ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open(getpid(), 0) 成功"); + if (pfd >= 0) { + CHECK(get_cloexec(pfd) == 1, "pidfd 带 FD_CLOEXEC"); + close(pfd); + } +} + +static void test_pidfd_open_stale(void) +{ + printf("--- pidfd_open 不存在 pid ---\n"); + + errno = 0; + pid_t stale = (pid_t)999999001; + if (stale <= 0) { + stale = (pid_t)2147483644; + } + CHECK_ERR(x_pidfd_open(stale, 0), ESRCH, "不存在 pid -> ESRCH"); +} + +static void test_pidfd_open_bad_flags(void) +{ + printf("--- pidfd_open 非法 flags ---\n"); + + CHECK_ERR(x_pidfd_open(getpid(), 0xFFFFFFFFu), EINVAL, "非法 flags -> EINVAL"); + CHECK_ERR(x_pidfd_open(getpid(), 1u), EINVAL, "未知 flags 位 -> EINVAL"); +} + +struct thread_tid_sync { + volatile pid_t tid; +}; + +static void *thread_publish_tid(void *arg) +{ + struct thread_tid_sync *sync = arg; + + sync->tid = (pid_t)syscall(SYS_gettid); + return NULL; +} + +static void test_pidfd_open_thread_tid(void) +{ + printf("--- pidfd_open 线程 TID ---\n"); + + struct thread_tid_sync sync = { .tid = -1 }; + pthread_t thread; + + CHECK(pthread_create(&thread, NULL, thread_publish_tid, &sync) == 0, + "pthread_create 成功"); + + for (int i = 0; i < 1000000 && sync.tid <= 0; i++) { + sched_yield(); + } + CHECK(sync.tid > 0 && sync.tid != getpid(), "子线程 tid 与 getpid 不同"); + + CHECK_ERR(x_pidfd_open(sync.tid, 0), ENOENT, + "非 leader 线程 tid 无 PIDFD_THREAD -> ENOENT"); + + int pfd = x_pidfd_open(sync.tid, PIDFD_THREAD); + CHECK(pfd >= 0, "PIDFD_THREAD 打开子线程 tid 成功"); + if (pfd >= 0) { + close(pfd); + } + + pthread_join(thread, NULL); +} + +static void test_pidfd_open_zombie(void) +{ + printf("--- pidfd_open zombie ---\n"); + + pid_t child = fork(); + CHECK(child >= 0, "fork 成功"); + if (child < 0) { + return; + } + + if (child == 0) { + _exit(0); + } + + /* waitpid(WNOHANG) reaps the child; poll with kill(0) until it is a zombie. */ + for (int i = 0; i < 1000; i++) { + if (kill(child, 0) == 0) { + break; + } + usleep(1000); + } + CHECK(kill(child, 0) == 0, "子进程已退出且尚未 reap"); + + int pfd = x_pidfd_open(child, 0); + CHECK(pfd >= 0, "reap 前 pidfd_open(zombie child) 成功"); + if (pfd >= 0) { + close(pfd); + } + + int status = 0; + CHECK_RET(waitpid(child, &status, 0), child, "waitpid reap 子进程"); + CHECK_ERR(x_pidfd_open(child, 0), ESRCH, "reap 后 pidfd_open(child) -> ESRCH"); +} + +int main(void) +{ + TEST_START("pidfd_open"); + + signal(SIGPIPE, SIG_IGN); + + test_pidfd_open_invalid_pid(); + test_pidfd_open_self(); + test_pidfd_open_cloexec(); + test_pidfd_open_stale(); + test_pidfd_open_bad_flags(); + test_pidfd_open_thread_tid(); + test_pidfd_open_zombie(); + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-send-signal/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-send-signal/c/CMakeLists.txt new file mode 100644 index 0000000000..4b04a50a9a --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-send-signal/c/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.20) +project(test-pidfd-send-signal C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-pidfd-send-signal src/main.c) +target_include_directories(test-pidfd-send-signal PRIVATE src ${CMAKE_CURRENT_SOURCE_DIR}/../../common) +target_compile_options(test-pidfd-send-signal PRIVATE -Wall -Wextra -Werror) +target_link_libraries(test-pidfd-send-signal PRIVATE -lpthread) +install(TARGETS test-pidfd-send-signal RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-send-signal/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-send-signal/c/src/main.c new file mode 100644 index 0000000000..817f7ad350 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-pidfd-send-signal/c/src/main.c @@ -0,0 +1,521 @@ +#define _GNU_SOURCE +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __NR_pidfd_open +#error "__NR_pidfd_open required" +#endif +#ifndef __NR_pidfd_send_signal +#error "__NR_pidfd_send_signal required" +#endif + +#ifndef PIDFD_THREAD +#define PIDFD_THREAD O_EXCL +#endif +#ifndef PIDFD_SIGNAL_THREAD +#define PIDFD_SIGNAL_THREAD (1U << 0) +#define PIDFD_SIGNAL_THREAD_GROUP (1U << 1) +#define PIDFD_SIGNAL_PROCESS_GROUP (1U << 2) +#endif + +#ifndef SI_USER +#define SI_USER 0 +#endif + +static volatile int g_usr1_count; +static volatile siginfo_t g_last_si; +static sigset_t g_usr1_mask; + +static void block_usr1(void) +{ + sigemptyset(&g_usr1_mask); + sigaddset(&g_usr1_mask, SIGUSR1); + pthread_sigmask(SIG_BLOCK, &g_usr1_mask, NULL); +} + +static void unblock_usr1(void) +{ + pthread_sigmask(SIG_UNBLOCK, &g_usr1_mask, NULL); +} + +static int x_pidfd_open(pid_t pid, unsigned int flags) +{ + return (int)syscall(__NR_pidfd_open, pid, flags); +} + +static int x_pidfd_send_signal(int pidfd, int sig, void *info, unsigned int flags) +{ + return (int)syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags); +} + +/* Child blocks on sync[0] until parent writes one byte to sync[1]. */ +static int open_pidfd_before_child_exit(pid_t child, int sync[2], int *out_pfd) +{ + char ch = 0; + + *out_pfd = x_pidfd_open(child, 0); + if (*out_pfd < 0) { + return -1; + } + if (write(sync[1], &ch, 1) != 1) { + close(*out_pfd); + return -1; + } + return 0; +} + +static void usr1_handler(int signo) +{ + (void)signo; + g_usr1_count++; +} + +static void usr1_sigaction_handler(int signo, siginfo_t *si, void *ctx) +{ + (void)signo; + (void)ctx; + if (si) { + g_last_si = *si; + } + g_usr1_count++; +} + +static void test_send_signal_bad_pidfd(void) +{ + printf("--- pidfd_send_signal 无效 pidfd ---\n"); + + CHECK_ERR(x_pidfd_send_signal(-1, SIGUSR1, NULL, 0), EBADF, "pidfd=-1 -> EBADF"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + close(pfd); + CHECK_ERR(x_pidfd_send_signal(pfd, SIGUSR1, NULL, 0), EBADF, + "已 close pidfd -> EBADF"); + } + + int pipe_fds[2]; + CHECK_RET(pipe(pipe_fds), 0, "pipe 创建成功"); + errno = 0; + if (x_pidfd_send_signal(pipe_fds[0], SIGUSR1, NULL, 0) == -1 && + (errno == EINVAL || errno == EBADF)) { + CHECK(1, "普通 fd 作 pidfd -> EINVAL/EBADF"); + } else { + CHECK(0, "普通 fd 作 pidfd -> EINVAL/EBADF"); + } + close(pipe_fds[0]); + close(pipe_fds[1]); +} + +static void test_send_signal_reaped_target(void) +{ + printf("--- pidfd_send_signal reap 后目标进程 ---\n"); + + int sync[2]; + if (pipe(sync) != 0) { + return; + } + + pid_t child = fork(); + CHECK(child >= 0, "fork 成功"); + if (child < 0) { + close(sync[0]); + close(sync[1]); + return; + } + + if (child == 0) { + char ch; + close(sync[1]); + if (read(sync[0], &ch, 1) != 1) { + _exit(1); + } + close(sync[0]); + _exit(0); + } + + close(sync[0]); + int pfd = -1; + CHECK(open_pidfd_before_child_exit(child, sync, &pfd) == 0, "reap 前 pidfd_open 成功"); + if (pfd < 0) { + close(sync[1]); + waitpid(child, NULL, 0); + return; + } + + int status = 0; + CHECK_RET(waitpid(child, &status, 0), child, "waitpid reap 子进程"); + + CHECK_ERR(x_pidfd_send_signal(pfd, SIGUSR1, NULL, 0), ESRCH, + "reap 后 SIGUSR1 -> ESRCH"); + CHECK_ERR(x_pidfd_send_signal(pfd, 0, NULL, 0), ESRCH, "reap 后 signo=0 -> ESRCH"); + close(pfd); + close(sync[1]); +} + +static void test_send_signal_invalid_signo(void) +{ + printf("--- pidfd_send_signal 非法 signo ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + CHECK_ERR(x_pidfd_send_signal(pfd, -1, NULL, 0), EINVAL, "signo=-1 -> EINVAL"); + CHECK_ERR(x_pidfd_send_signal(pfd, 999, NULL, 0), EINVAL, "signo=999 -> EINVAL"); + close(pfd); + } +} + +static void test_send_signal_bad_info_pointer(void) +{ + printf("--- pidfd_send_signal info 非法指针 ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + CHECK_ERR(x_pidfd_send_signal(pfd, SIGUSR1, (void *)1, 0), EFAULT, + "info=(void*)1 -> EFAULT"); + close(pfd); + } +} + +static void test_send_signal_sig_mismatch(void) +{ + printf("--- pidfd_send_signal sig 与 info 不一致 ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd < 0) { + return; + } + + siginfo_t info; + memset(&info, 0, sizeof(info)); + info.si_signo = SIGUSR2; + + CHECK_ERR(x_pidfd_send_signal(pfd, SIGUSR1, &info, 0), EINVAL, + "sig != info.si_signo -> EINVAL"); + close(pfd); +} + +static void test_send_signal_flag_multi(void) +{ + printf("--- pidfd_send_signal 多个 scope flags ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + unsigned int flags = PIDFD_SIGNAL_THREAD | PIDFD_SIGNAL_THREAD_GROUP; + CHECK_ERR(x_pidfd_send_signal(pfd, SIGUSR1, NULL, flags), EINVAL, + "两个 scope flags -> EINVAL"); + close(pfd); + } +} + +static void test_send_signal_flag_unknown(void) +{ + printf("--- pidfd_send_signal 未知 flags ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + CHECK_ERR(x_pidfd_send_signal(pfd, SIGUSR1, NULL, 0x10000u), EINVAL, + "未知 flags -> EINVAL"); + close(pfd); + } +} + +static void test_send_signal_tgid_with_thread_flag(void) +{ + printf("--- tgid pidfd + PIDFD_SIGNAL_THREAD ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + CHECK_ERR(x_pidfd_send_signal(pfd, SIGUSR1, NULL, PIDFD_SIGNAL_THREAD), EINVAL, + "tgid pidfd + THREAD flag -> EINVAL"); + close(pfd); + } +} + +static void test_send_signal_process_group(void) +{ + printf("--- pidfd_send_signal PROCESS_GROUP ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + CHECK_RET(x_pidfd_send_signal(pfd, 0, NULL, PIDFD_SIGNAL_PROCESS_GROUP), 0, + "PROCESS_GROUP signo=0 探活成功"); + close(pfd); + } +} + +static void test_send_signal_valid_info(void) +{ + printf("--- pidfd_send_signal 有效 info ---\n"); + + unblock_usr1(); + g_usr1_count = 0; + struct sigaction sa = {0}; + sa.sa_sigaction = usr1_sigaction_handler; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + CHECK_RET(sigaction(SIGUSR1, &sa, NULL), 0, "sigaction 安装"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd < 0) { + return; + } + + siginfo_t info; + memset(&info, 0, sizeof(info)); + info.si_signo = SIGUSR1; + + CHECK_RET(x_pidfd_send_signal(pfd, SIGUSR1, &info, 0), 0, "send_signal 带 info 成功"); + usleep(100000); + CHECK(g_usr1_count >= 1, "handler 被调用"); + close(pfd); + block_usr1(); +} + +static void test_send_signal_default_self(void) +{ + printf("--- pidfd_send_signal 默认 SIGUSR1 ---\n"); + + unblock_usr1(); + g_usr1_count = 0; + signal(SIGUSR1, usr1_handler); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd < 0) { + return; + } + + CHECK_RET(x_pidfd_send_signal(pfd, SIGUSR1, NULL, 0), 0, "pidfd_send_signal 成功"); + usleep(100000); + CHECK(g_usr1_count == 1, "SIGUSR1 handler 被调用一次"); + close(pfd); + block_usr1(); +} + +static void test_send_signal_null_info_fills_pid(void) +{ + printf("--- pidfd_send_signal info=NULL si_pid ---\n"); + + unblock_usr1(); + g_usr1_count = 0; + struct sigaction sa = {0}; + sa.sa_sigaction = usr1_sigaction_handler; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + CHECK_RET(sigaction(SIGUSR1, &sa, NULL), 0, "sigaction 安装"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd < 0) { + return; + } + + CHECK_RET(x_pidfd_send_signal(pfd, SIGUSR1, NULL, 0), 0, "send_signal 成功"); + usleep(100000); + CHECK(g_usr1_count >= 1, "handler 被调用"); + CHECK((int)g_last_si.si_pid == (int)getpid(), "si_pid == getpid()"); + CHECK(g_last_si.si_code == SI_USER, "si_code == SI_USER"); + close(pfd); + block_usr1(); +} + +static void test_send_signal_zero_probes(void) +{ + printf("--- pidfd_send_signal signo=0 探活 ---\n"); + + int pfd = x_pidfd_open(getpid(), 0); + CHECK(pfd >= 0, "pidfd_open 成功"); + if (pfd >= 0) { + CHECK_RET(x_pidfd_send_signal(pfd, 0, NULL, 0), 0, "存活进程 signo=0 探活"); + close(pfd); + } + + pid_t child = fork(); + CHECK(child >= 0, "fork 成功"); + if (child < 0) { + return; + } + if (child == 0) { + _exit(0); + } + + for (int i = 0; i < 1000 && kill(child, 0) != 0; i++) { + usleep(1000); + } + CHECK(kill(child, 0) == 0, "子进程 zombie 未 reap"); + + pfd = x_pidfd_open(child, 0); + if (pfd >= 0) { + CHECK_RET(x_pidfd_send_signal(pfd, 0, NULL, 0), 0, "zombie signo=0 探活"); + close(pfd); + } + + int status = 0; + waitpid(child, &status, 0); + errno = 0; + pfd = x_pidfd_open(child, 0); + CHECK(pfd == -1 && errno == ESRCH, "reap 后 pidfd_open -> ESRCH"); +} + +struct thread_tid_sync { + int notify_pipe[2]; + pid_t tid; + volatile int thread_got_usr1; +}; + +static struct thread_tid_sync *g_thread_sync; + +static void thread_usr1_handler(int signo) +{ + (void)signo; + if (g_thread_sync) { + g_thread_sync->thread_got_usr1 = 1; + } +} + +static void *thread_wait_usr1(void *arg) +{ + struct thread_tid_sync *sync = arg; + + g_thread_sync = sync; + unblock_usr1(); + signal(SIGUSR1, thread_usr1_handler); + sync->tid = (pid_t)syscall(SYS_gettid); + if (write(sync->notify_pipe[1], "x", 1) != 1) { + return (void *)1; + } + + for (int i = 0; i < 3000 && !sync->thread_got_usr1; i++) { + usleep(10000); + } + g_thread_sync = NULL; + return NULL; +} + +static void test_send_signal_flag_thread_with_thread_pidfd(void) +{ + printf("--- PIDFD_THREAD pidfd + PIDFD_SIGNAL_THREAD ---\n"); + + struct thread_tid_sync sync = { .tid = -1, .thread_got_usr1 = 0 }; + pthread_t thread; + + if (pipe(sync.notify_pipe) != 0) { + return; + } + + g_usr1_count = 0; + signal(SIGUSR1, SIG_IGN); + unblock_usr1(); + + CHECK(pthread_create(&thread, NULL, thread_wait_usr1, &sync) == 0, + "pthread_create 成功"); + + char ch; + CHECK(read(sync.notify_pipe[0], &ch, 1) == 1, "等待子线程 tid"); + + int pfd = x_pidfd_open(sync.tid, PIDFD_THREAD); + CHECK(pfd >= 0, "pidfd_open(tid, PIDFD_THREAD) 成功"); + if (pfd >= 0) { + CHECK_RET(x_pidfd_send_signal(pfd, SIGUSR1, NULL, PIDFD_SIGNAL_THREAD), + 0, "向线程发 SIGUSR1"); + for (int i = 0; i < 3000 && !sync.thread_got_usr1; i++) { + usleep(10000); + } + CHECK(sync.thread_got_usr1 == 1, "子线程收到 SIGUSR1"); + CHECK(g_usr1_count == 0, "主线程 SIG_IGN 未收到 SIGUSR1"); + pthread_join(thread, NULL); + close(pfd); + } + + block_usr1(); + close(sync.notify_pipe[0]); + close(sync.notify_pipe[1]); +} + +/* + * THREAD_GROUP on a thread-level pidfd selects ThreadGroup scope: signal goes + * to the whole thread group. Main thread SIG_IGN; worker thread should receive. + */ +static void test_send_signal_thread_pidfd_thread_group_flag(void) +{ + printf("--- thread pidfd + PIDFD_SIGNAL_THREAD_GROUP ---\n"); + + struct thread_tid_sync sync = { .tid = -1, .thread_got_usr1 = 0 }; + pthread_t thread; + + if (pipe(sync.notify_pipe) != 0) { + return; + } + + g_usr1_count = 0; + signal(SIGUSR1, SIG_IGN); + unblock_usr1(); + + CHECK(pthread_create(&thread, NULL, thread_wait_usr1, &sync) == 0, + "pthread_create 成功"); + + char ch; + CHECK(read(sync.notify_pipe[0], &ch, 1) == 1, "等待子线程 tid"); + + int pfd = x_pidfd_open(sync.tid, PIDFD_THREAD); + CHECK(pfd >= 0, "pidfd_open(tid, PIDFD_THREAD) 成功"); + if (pfd >= 0) { + CHECK_RET(x_pidfd_send_signal(pfd, SIGUSR1, NULL, PIDFD_SIGNAL_THREAD_GROUP), + 0, "THREAD_GROUP 向线程组发 SIGUSR1"); + for (int i = 0; i < 3000 && !sync.thread_got_usr1; i++) { + usleep(10000); + } + CHECK(sync.thread_got_usr1 == 1, "子线程收到 SIGUSR1"); + CHECK(g_usr1_count == 0, "主线程 SIG_IGN 未收到 SIGUSR1"); + pthread_join(thread, NULL); + close(pfd); + } + + block_usr1(); + close(sync.notify_pipe[0]); + close(sync.notify_pipe[1]); +} + +int main(void) +{ + TEST_START("pidfd_send_signal"); + + signal(SIGPIPE, SIG_IGN); + signal(SIGUSR1, SIG_IGN); + block_usr1(); + + test_send_signal_bad_pidfd(); + test_send_signal_reaped_target(); + test_send_signal_invalid_signo(); + test_send_signal_bad_info_pointer(); + test_send_signal_sig_mismatch(); + test_send_signal_flag_multi(); + test_send_signal_flag_unknown(); + test_send_signal_tgid_with_thread_flag(); + test_send_signal_process_group(); + test_send_signal_valid_info(); + test_send_signal_default_self(); + test_send_signal_null_info_fills_pid(); + test_send_signal_zero_probes(); + test_send_signal_flag_thread_with_thread_pidfd(); + test_send_signal_thread_pidfd_thread_group_flag(); + + TEST_DONE(); +}