summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Magefile.go2
-rw-r--r--integrationtests/cmd/ioworkload/scenario_pidfd.go119
-rw-r--r--integrationtests/cmd/ioworkload/scenarios.go2
-rw-r--r--integrationtests/pidfd_test.go24
4 files changed, 146 insertions, 1 deletions
diff --git a/Magefile.go b/Magefile.go
index 1c2e793..4a49f42 100644
--- a/Magefile.go
+++ b/Magefile.go
@@ -79,7 +79,7 @@ func Test() error {
if err := sh.RunWithV(goEnv(), "go", "clean", "-testcache"); err != nil {
return err
}
- return sh.RunWithV(goEnv(), "go", "test", "./...", "-v", "-failfast")
+ return sh.RunWithV(goEnv(), "go", "test", "./...", "-failfast", "-timeout=90m")
}
// TestWithName runs a specific test by name.
diff --git a/integrationtests/cmd/ioworkload/scenario_pidfd.go b/integrationtests/cmd/ioworkload/scenario_pidfd.go
new file mode 100644
index 0000000..1ac7c4d
--- /dev/null
+++ b/integrationtests/cmd/ioworkload/scenario_pidfd.go
@@ -0,0 +1,119 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "syscall"
+ "time"
+)
+
+// pidfdGetfdSuccess duplicates an existing file descriptor through pidfd_getfd.
+func pidfdGetfdSuccess() error {
+ dir, cleanup, err := makeTempDir("pidfd-getfd-success")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "pidfd-getfd-source.txt")
+ fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644)
+ if err != nil {
+ return fmt.Errorf("open source: %w", err)
+ }
+ defer syscall.Close(fd)
+
+ pidfd, err := pidfdOpen(os.Getpid(), 0)
+ if err != nil {
+ return fmt.Errorf("pidfd_open self: %w", err)
+ }
+ defer syscall.Close(pidfd)
+
+ dupFd, err := pidfdGetfd(pidfd, fd, 0)
+ if err != nil {
+ return fmt.Errorf("pidfd_getfd: %w", err)
+ }
+
+ if _, err := syscall.Write(dupFd, []byte("via pidfd_getfd")); err != nil {
+ syscall.Close(dupFd)
+ return fmt.Errorf("write dup fd: %w", err)
+ }
+
+ // Keep the duplicated fd alive briefly so eventloop can resolve /proc fd path.
+ time.Sleep(500 * time.Millisecond)
+ if err := syscall.Close(dupFd); err != nil {
+ return fmt.Errorf("close dup fd: %w", err)
+ }
+ return nil
+}
+
+// pidfdGetfdFailure performs a guaranteed-failing pidfd_getfd call while
+// also probing a cross-process call that may fail under ptrace/Yama policy.
+func pidfdGetfdFailure() error {
+ pidfd, err := pidfdOpen(os.Getpid(), 0)
+ if err != nil {
+ return fmt.Errorf("pidfd_open self: %w", err)
+ }
+ defer syscall.Close(pidfd)
+
+ // Best-effort probe. Depending on kernel ptrace/Yama policy, this may fail
+ // with EPERM/EACCES; if it succeeds we close the returned fd and continue.
+ if initPidfd, err := pidfdOpen(1, 0); err == nil {
+ func() {
+ defer syscall.Close(initPidfd)
+ if probeFd, err := pidfdGetfd(initPidfd, 1, 0); err == nil {
+ syscall.Close(probeFd)
+ }
+ }()
+ }
+
+ _, err = pidfdGetfd(pidfd, 99999, 0)
+ if err == nil {
+ return fmt.Errorf("expected pidfd_getfd with invalid source fd to fail")
+ }
+ return nil
+}
+
+func pidfdOpen(pid int, flags uintptr) (int, error) {
+ fd, _, errno := syscall.Syscall(pidfdOpenSyscallNr(), uintptr(pid), flags, 0)
+ if errno != 0 {
+ return 0, errno
+ }
+ return int(fd), nil
+}
+
+func pidfdGetfd(pidfd int, targetFd int, flags uintptr) (int, error) {
+ fd, _, errno := syscall.Syscall(
+ pidfdGetfdSyscallNr(),
+ uintptr(pidfd),
+ uintptr(targetFd),
+ flags,
+ )
+ if errno != 0 {
+ return 0, errno
+ }
+ return int(fd), nil
+}
+
+func pidfdOpenSyscallNr() uintptr {
+ // Go's syscall package does not expose pidfd constants on all toolchains.
+ if runtime.GOARCH == "amd64" {
+ return 434
+ }
+ if runtime.GOARCH == "arm64" {
+ return 434
+ }
+ panic("pidfd_open syscall number not defined for GOARCH=" + runtime.GOARCH)
+}
+
+func pidfdGetfdSyscallNr() uintptr {
+ // Go's syscall package does not expose pidfd constants on all toolchains.
+ if runtime.GOARCH == "amd64" {
+ return 438
+ }
+ if runtime.GOARCH == "arm64" {
+ return 438
+ }
+ panic("pidfd_getfd syscall number not defined for GOARCH=" + runtime.GOARCH)
+}
diff --git a/integrationtests/cmd/ioworkload/scenarios.go b/integrationtests/cmd/ioworkload/scenarios.go
index a1c32f7..a6e7a5f 100644
--- a/integrationtests/cmd/ioworkload/scenarios.go
+++ b/integrationtests/cmd/ioworkload/scenarios.go
@@ -92,6 +92,8 @@ var scenarios = map[string]func() error{
"truncate-ftruncate": truncateFtruncate,
"truncate-enoent": truncateEnoent,
"truncate-ftruncate-ebadf": truncateFtruncateEbadf,
+ "pidfd-getfd-success": pidfdGetfdSuccess,
+ "pidfd-getfd-failure": pidfdGetfdFailure,
"iouring-setup": iouringSetup,
"iouring-enter": iouringEnter,
"iouring-register": iouringRegister,
diff --git a/integrationtests/pidfd_test.go b/integrationtests/pidfd_test.go
new file mode 100644
index 0000000..d742078
--- /dev/null
+++ b/integrationtests/pidfd_test.go
@@ -0,0 +1,24 @@
+package integrationtests
+
+import "testing"
+
+func TestPidfdGetfdSuccess(t *testing.T) {
+ runScenario(t, "pidfd-getfd-success", []ExpectedEvent{
+ {
+ PathContains: "pidfd-getfd-source.txt",
+ Tracepoint: "enter_pidfd_getfd",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+}
+
+func TestPidfdGetfdFailure(t *testing.T) {
+ runScenario(t, "pidfd-getfd-failure", []ExpectedEvent{
+ {
+ Tracepoint: "enter_pidfd_getfd",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+}