summaryrefslogtreecommitdiff
path: root/cmd/ioworkload/scenario_pidfd.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-09 22:09:13 +0300
committerPaul Buetow <paul@buetow.org>2026-06-09 22:09:13 +0300
commit3cd5e655ee1768b4118815d1ea887acdd57eb498 (patch)
tree05285bc148faff79f931059aff97b8d3fbb9b126 /cmd/ioworkload/scenario_pidfd.go
parent57615945e6796950e7095a3ee8a97651ae3f1bd9 (diff)
test: add coverage for pidfd_send_signal and fadvise64
pidfd_send_signal (FamilyIPC, KindFd@arg0) and fadvise64 (KindFd, UNCLASSIFIED fd-based hint) previously had no end-to-end integration coverage despite correct classification/tracing. pidfd_send_signal: add a pidfd-send-signal ioworkload scenario that opens a pidfd for the current process and issues a sig-0 liveness probe (delivers nothing, safe to target self) via syscall.Syscall6 with the per-arch nr 424. TestPidfdSendSignal asserts enter_pidfd_send_signal is captured; pidfd_send_signal added to the pidfd -trace-syscalls list. fadvise64: add readwrite-fadvise64 and readwrite-fadvise64-ebadf scenarios using unix.Fadvise(fd, 0, 0, FADV_NORMAL), mirroring the readahead tests. TestReadwriteFadvise64 asserts enter_fadvise64 with Bytes==0 (UNCLASSIFIED: offset/len are hints, not bytes transferred) and positive duration; the ebadf variant asserts enter capture with Bytes==0 on the failing call. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'cmd/ioworkload/scenario_pidfd.go')
-rw-r--r--cmd/ioworkload/scenario_pidfd.go54
1 files changed, 54 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_pidfd.go b/cmd/ioworkload/scenario_pidfd.go
index 2aafced..fd3df5a 100644
--- a/cmd/ioworkload/scenario_pidfd.go
+++ b/cmd/ioworkload/scenario_pidfd.go
@@ -75,6 +75,26 @@ func pidfdGetfdFailure() error {
return nil
}
+// pidfdSendSignal opens a pidfd for the current process and issues a
+// pidfd_send_signal liveness probe against it. Signal 0 is a special "no signal"
+// value: the kernel performs only the permission/existence checks and delivers
+// NOTHING, so targeting our own process is completely safe (no signal handler
+// runs and the process is not affected). The scenario exercises the enter
+// fd_event (pidfd at args[0]) and the exit ret_event (UNCLASSIFIED) end-to-end.
+func pidfdSendSignal() error {
+ pidfd, err := pidfdOpen(os.Getpid(), 0)
+ if err != nil {
+ return fmt.Errorf("pidfd_open self: %w", err)
+ }
+ defer syscall.Close(pidfd)
+
+ // pidfd_send_signal(pidfd, sig=0, info=NULL, flags=0): liveness probe only.
+ if err := pidfdSendSignalRaw(pidfd, 0, 0, 0); err != nil {
+ return fmt.Errorf("pidfd_send_signal: %w", err)
+ }
+ return nil
+}
+
func pidfdOpen(pid int, flags uintptr) (int, error) {
syscallNr, err := pidfdOpenSyscallNr()
if err != nil {
@@ -112,6 +132,30 @@ func pidfdGetfdSyscallNr() (uintptr, error) {
return pidfdGetfdSyscallNrForArch(runtime.GOARCH)
}
+func pidfdSendSignalRaw(pidfd int, sig int, info uintptr, flags uintptr) error {
+ syscallNr, err := pidfdSendSignalSyscallNr()
+ if err != nil {
+ return err
+ }
+ _, _, errno := syscall.Syscall6(
+ syscallNr,
+ uintptr(pidfd),
+ uintptr(sig),
+ info,
+ flags,
+ 0,
+ 0,
+ )
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
+
+func pidfdSendSignalSyscallNr() (uintptr, error) {
+ return pidfdSendSignalSyscallNrForArch(runtime.GOARCH)
+}
+
func pidfdOpenSyscallNrForArch(arch string) (uintptr, error) {
// Go's syscall package does not expose pidfd constants on all toolchains.
switch arch {
@@ -131,3 +175,13 @@ func pidfdGetfdSyscallNrForArch(arch string) (uintptr, error) {
return 0, fmt.Errorf("pidfd_getfd syscall number not defined for GOARCH=%s", arch)
}
}
+
+func pidfdSendSignalSyscallNrForArch(arch string) (uintptr, error) {
+ // Go's syscall package does not expose pidfd constants on all toolchains.
+ switch arch {
+ case "amd64", "arm64":
+ return 424, nil
+ default:
+ return 0, fmt.Errorf("pidfd_send_signal syscall number not defined for GOARCH=%s", arch)
+ }
+}