diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-21 21:12:12 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-21 21:12:12 +0200 |
| commit | 603972340549cd3265f184457ee072fd8915e27b (patch) | |
| tree | 130bddd56929ddf6bd517375a3c1e57b983271ba /integrationtests | |
| parent | 70ccf3c69fec44833b15d93be754f1fadd25614c (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.go | 38 | ||||
| -rw-r--r-- | integrationtests/fcntl_test.go | 21 |
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, + }, + }) +} |
