summaryrefslogtreecommitdiff
path: root/integrationtests
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-21 21:12:12 +0200
committerPaul Buetow <paul@buetow.org>2026-02-21 21:12:12 +0200
commit603972340549cd3265f184457ee072fd8915e27b (patch)
tree130bddd56929ddf6bd517375a3c1e57b983271ba /integrationtests
parent70ccf3c69fec44833b15d93be754f1fadd25614c (diff)
Add negative integration tests for fcntl syscalls (task 348)
Add two negative scenarios: - fcntl-invalid-fd: calls SYS_FCNTL F_GETFL on invalid fd 99999 (EBADF) - fcntl-dupfd-max: calls F_DUPFD with minfd=1<<30 beyond RLIMIT_NOFILE (EINVAL) Both verify ior captures enter_fcntl tracepoints even when syscalls fail. Amp-Thread-ID: https://ampcode.com/threads/T-019c819b-5673-75ab-8eb4-227b6cf56b38 Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'integrationtests')
-rw-r--r--integrationtests/cmd/ioworkload/scenarios.go38
-rw-r--r--integrationtests/fcntl_test.go21
2 files changed, 59 insertions, 0 deletions
diff --git a/integrationtests/cmd/ioworkload/scenarios.go b/integrationtests/cmd/ioworkload/scenarios.go
index dd8d46e..4275fb6 100644
--- a/integrationtests/cmd/ioworkload/scenarios.go
+++ b/integrationtests/cmd/ioworkload/scenarios.go
@@ -42,6 +42,8 @@ var scenarios = map[string]func() error{
"fcntl-dupfd": fcntlDupfd,
"fcntl-setfl": fcntlSetfl,
"fcntl-dupfd-cloexec": fcntlDupfdCloexec,
+ "fcntl-invalid-fd": fcntlInvalidFd,
+ "fcntl-dupfd-max": fcntlDupfdMax,
"rename-basic": renameBasic,
"rename-renameat": renameRenameat,
"rename-renameat2": renameRenameat2,
@@ -789,6 +791,42 @@ func fcntlDupfdCloexec() error {
return nil
}
+// fcntlInvalidFd calls fcntl F_GETFL on an invalid fd (99999).
+// The syscall fails with EBADF, but ior should capture the enter_fcntl
+// tracepoint because it is recorded on syscall entry.
+func fcntlInvalidFd() error {
+ _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, 99999, syscall.F_GETFL, 0)
+ if errno == 0 {
+ return fmt.Errorf("expected fcntl on invalid fd to fail")
+ }
+ return nil
+}
+
+// fcntlDupfdMax opens a file and calls fcntl F_DUPFD with a minfd value
+// that exceeds the process RLIMIT_NOFILE. The kernel rejects this with
+// EINVAL, but ior should capture the enter_fcntl tracepoint.
+func fcntlDupfdMax() error {
+ dir, cleanup, err := makeTempDir("fcntl-dupfd-max")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "fcntldupfdmaxfile.txt")
+ fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644)
+ if err != nil {
+ return fmt.Errorf("open: %w", err)
+ }
+ defer syscall.Close(fd)
+
+ // Use a minfd far beyond any realistic RLIMIT_NOFILE.
+ _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_DUPFD, 1<<30)
+ if errno == 0 {
+ return fmt.Errorf("expected fcntl F_DUPFD with extreme minfd to fail")
+ }
+ return nil
+}
+
// renameBasic creates a file and renames it via rename(2).
// Uses raw SYS_RENAME because Go's syscall.Rename wraps renameat on amd64.
func renameBasic() error {
diff --git a/integrationtests/fcntl_test.go b/integrationtests/fcntl_test.go
index f9c95bd..5062bd6 100644
--- a/integrationtests/fcntl_test.go
+++ b/integrationtests/fcntl_test.go
@@ -34,3 +34,24 @@ func TestFcntlDupfdCloexec(t *testing.T) {
},
})
}
+
+func TestFcntlInvalidFd(t *testing.T) {
+ runScenario(t, "fcntl-invalid-fd", []ExpectedEvent{
+ {
+ Tracepoint: "enter_fcntl",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+}
+
+func TestFcntlDupfdMax(t *testing.T) {
+ runScenario(t, "fcntl-dupfd-max", []ExpectedEvent{
+ {
+ PathContains: "fcntldupfdmaxfile.txt",
+ Tracepoint: "enter_fcntl",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+}