diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-24 20:36:26 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-24 20:36:26 +0300 |
| commit | 92a36a8c5f23756b8c6d721e89450752409ddd75 (patch) | |
| tree | 52adee49828831feb0ca557e7df736726faedac3 /integrationtests | |
| parent | fadbf135d0b251387fd785083df79e27d1025cac (diff) | |
task a8: move all binaries under ./cmd/<name>/main.go
Relocates the two non-canonical main packages so every binary in the repo
lives at ./cmd/<BINARY>/main.go:
- tools/filewriter/ -> cmd/filewriter/
- integrationtests/cmd/ioworkload/ (20 files) -> cmd/ioworkload/
Consumers updated:
- Magefile.go: workloadSourcePath now ./cmd/ioworkload
- integrationtests/README.md: structure note points at ../cmd/ioworkload
Files moved with git mv so git log --follow history is preserved.
cmd/ior/main.go was already canonical and is untouched.
Verified: mage build produces the ior binary; go build ./cmd/...
builds filewriter and ioworkload; go test ./cmd/ioworkload passes;
go vet ./cmd/filewriter ./cmd/ioworkload is clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'integrationtests')
20 files changed, 1 insertions, 3194 deletions
diff --git a/integrationtests/README.md b/integrationtests/README.md index 0036019..c397f8b 100644 --- a/integrationtests/README.md +++ b/integrationtests/README.md @@ -58,7 +58,7 @@ If the suite fails before tracing starts, check for these common causes: ## Structure -- `cmd/ioworkload/` — Standalone binary performing known I/O patterns +- `../cmd/ioworkload/` — Standalone binary performing known I/O patterns (lives at repo-root `cmd/`) - `harness.go` — Test orchestration (start ior + workload, collect output) - `parse.go` — Parse `.ior.zst` into assertable `TestResult` - `expectations.go` — `ExpectedEvent` type and assertion helpers diff --git a/integrationtests/cmd/ioworkload/main.go b/integrationtests/cmd/ioworkload/main.go deleted file mode 100644 index 0276a9c..0000000 --- a/integrationtests/cmd/ioworkload/main.go +++ /dev/null @@ -1,49 +0,0 @@ -// ioworkload is a standalone binary that performs deterministic I/O operations -// for integration testing of ior. It prints its PID to stdout, sleeps to allow -// ior to attach BPF tracepoints, then executes the requested I/O scenario. -package main - -import ( - "flag" - "fmt" - "os" - "slices" - "time" -) - -// Give ior enough time to attach tracepoints before scenarios emit syscalls. -// Under slower CI or locally saturated systems, 5s can still miss first-call -// events for single-shot scenarios. Use a slightly larger delay for stability. -const startupDelay = 8 * time.Second - -func main() { - scenario := flag.String("scenario", "", "I/O scenario to execute") - flag.Parse() - - if *scenario == "" { - fmt.Fprintln(os.Stderr, "usage: ioworkload --scenario=<name>") - os.Exit(2) - } - - run, ok := scenarios[*scenario] - if !ok { - fmt.Fprintf(os.Stderr, "unknown scenario: %s\navailable scenarios:\n", *scenario) - var names []string - for name := range scenarios { - names = append(names, name) - } - slices.Sort(names) - for _, name := range names { - fmt.Fprintf(os.Stderr, " %s\n", name) - } - os.Exit(2) - } - - fmt.Println(os.Getpid()) - time.Sleep(startupDelay) - - if err := run(); err != nil { - fmt.Fprintf(os.Stderr, "scenario %s failed: %v\n", *scenario, err) - os.Exit(1) - } -} diff --git a/integrationtests/cmd/ioworkload/scenario_close.go b/integrationtests/cmd/ioworkload/scenario_close.go deleted file mode 100644 index fc5044c..0000000 --- a/integrationtests/cmd/ioworkload/scenario_close.go +++ /dev/null @@ -1,117 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "syscall" -) - -const sysCloseRange = 436 - -// closeBasic opens multiple files and closes them. -func closeBasic() error { - dir, cleanup, err := makeTempDir("close-basic") - if err != nil { - return err - } - defer cleanup() - - var fds []int - for i := range 3 { - path := filepath.Join(dir, fmt.Sprintf("closefile-%d.txt", i)) - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open %d: %w", i, err) - } - fds = append(fds, fd) - } - for _, fd := range fds { - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close fd %d: %w", fd, err) - } - } - return nil -} - -// closeRange opens multiple files and closes a range of them via close_range(2). -func closeRange() error { - dir, cleanup, err := makeTempDir("close-range") - if err != nil { - return err - } - defer cleanup() - - var fds []int - for i := range 3 { - path := filepath.Join(dir, fmt.Sprintf("closerangefile-%d.txt", i)) - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open %d: %w", i, err) - } - fds = append(fds, fd) - } - - if fds[2]-fds[0] != 2 { - return fmt.Errorf("fds not contiguous: %v", fds) - } - - first := uintptr(fds[0]) - last := uintptr(fds[len(fds)-1]) - _, _, errno := syscall.Syscall(sysCloseRange, first, last, 0) - if errno != 0 { - return fmt.Errorf("close_range: %w", errno) - } - return nil -} - -// closeInvalidFd attempts to close a very high fd number that is not open. -// The close fails with EBADF, but ior should capture the enter_close tracepoint -// because arguments are read on syscall entry before the kernel returns an error. -func closeInvalidFd() error { - err := syscall.Close(99999) - if err == nil { - return fmt.Errorf("expected close of invalid fd to fail") - } - return nil -} - -// closeDoubleClose opens a file, closes it normally, then closes the same fd again. -// The second close fails with EBADF, but ior should capture both enter_close -// tracepoints because arguments are read on syscall entry. -func closeDoubleClose() error { - dir, cleanup, err := makeTempDir("close-double-close") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "doubleclosefile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("first close: %w", err) - } - - err = syscall.Close(fd) - if err == nil { - return fmt.Errorf("expected second close of same fd to fail") - } - return nil -} - -// closeRangeEmpty calls close_range(2) with a range of very high fd numbers -// (9000–9999) where no fds are open. The syscall succeeds (empty range is valid), -// and ior should capture the enter_close_range tracepoint. -func closeRangeEmpty() error { - // Retry a few times to reduce event-loss flakiness under heavy test load. - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall(sysCloseRange, 9000, 9999, 0) - if errno != 0 { - return fmt.Errorf("close_range: %w", errno) - } - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_copy_file_range.go b/integrationtests/cmd/ioworkload/scenario_copy_file_range.go deleted file mode 100644 index ce0524e..0000000 --- a/integrationtests/cmd/ioworkload/scenario_copy_file_range.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "syscall" -) - -// SYS_COPY_FILE_RANGE on x86_64 Linux. -const sysCopyFileRange = 326 - -// copyFileRangeBasic copies bytes from a source file to a destination file -// using copy_file_range(2) with flags=0 as required by the manpage. -func copyFileRangeBasic() error { - dir, cleanup, err := makeTempDir("copy-file-range-basic") - if err != nil { - return err - } - defer cleanup() - - srcPath := filepath.Join(dir, "copyrangesrc.txt") - dstPath := filepath.Join(dir, "copyrangedst.txt") - - srcFd, err := syscall.Open(srcPath, syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0o644) - if err != nil { - return fmt.Errorf("open source: %w", err) - } - defer syscall.Close(srcFd) - - dstFd, err := syscall.Open(dstPath, syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0o644) - if err != nil { - return fmt.Errorf("open destination: %w", err) - } - defer syscall.Close(dstFd) - - data := []byte("copy_file_range integration data") - if _, err := syscall.Write(srcFd, data); err != nil { - return fmt.Errorf("write source: %w", err) - } - if _, err := syscall.Seek(srcFd, 0, 0); err != nil { - return fmt.Errorf("seek source: %w", err) - } - - n, _, errno := syscall.Syscall6(uintptr(sysCopyFileRange), uintptr(srcFd), 0, uintptr(dstFd), 0, uintptr(len(data)), 0) - if errno != 0 { - return fmt.Errorf("copy_file_range: %w", errno) - } - if n == 0 { - return fmt.Errorf("copy_file_range copied 0 bytes") - } - - return nil -} - -// copyFileRangeBadDstFd calls copy_file_range(2) with an invalid destination fd. -// The syscall should fail with EBADF, while still emitting the enter tracepoint. -func copyFileRangeBadDstFd() error { - dir, cleanup, err := makeTempDir("copy-file-range-bad-dst") - if err != nil { - return err - } - defer cleanup() - - srcPath := filepath.Join(dir, "copyrangeebadfsrc.txt") - srcFd, err := syscall.Open(srcPath, syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0o644) - if err != nil { - return fmt.Errorf("open source: %w", err) - } - defer syscall.Close(srcFd) - - if _, err := syscall.Write(srcFd, []byte("copy_file_range ebadf data")); err != nil { - return fmt.Errorf("write source: %w", err) - } - - _, _, errno := syscall.Syscall6(uintptr(sysCopyFileRange), uintptr(srcFd), 0, uintptr(99999), 0, uintptr(16), 0) - if errno != syscall.EBADF { - return fmt.Errorf("expected EBADF from copy_file_range with invalid dst fd, got %v", errno) - } - - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_dir.go b/integrationtests/cmd/ioworkload/scenario_dir.go deleted file mode 100644 index 7a78716..0000000 --- a/integrationtests/cmd/ioworkload/scenario_dir.go +++ /dev/null @@ -1,224 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "syscall" - "time" - "unsafe" -) - -// dirBasic creates a directory via raw SYS_MKDIR, checks access, then removes it -// via raw SYS_RMDIR. We use raw syscalls because Go's syscall.Mkdir wraps mkdirat -// and syscall.Rmdir wraps unlinkat on amd64. -func dirBasic() error { - dir, cleanup, err := makeTempDir("dir-basic") - if err != nil { - return err - } - defer cleanup() - - subDir := filepath.Join(dir, "subdir") - pathBytes, err := syscall.BytePtrFromString(subDir) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - _, _, errno := syscall.Syscall(syscall.SYS_MKDIR, uintptr(unsafe.Pointer(pathBytes)), 0o755, 0) - runtime.KeepAlive(pathBytes) - if errno != 0 { - return fmt.Errorf("mkdir: %w", errno) - } - - if err := syscall.Access(subDir, syscall.F_OK); err != nil { - return fmt.Errorf("access: %w", err) - } - - pathBytes2, err := syscall.BytePtrFromString(subDir) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - _, _, errno = syscall.Syscall(syscall.SYS_RMDIR, uintptr(unsafe.Pointer(pathBytes2)), 0, 0) - runtime.KeepAlive(pathBytes2) - if errno != 0 { - return fmt.Errorf("rmdir: %w", errno) - } - return nil -} - -// dirMkdirat creates a directory via mkdirat(2) using Go's syscall.Mkdir -// which wraps mkdirat with AT_FDCWD on amd64. -func dirMkdirat() error { - dir, cleanup, err := makeTempDir("dir-mkdirat") - if err != nil { - return err - } - defer cleanup() - - subDir := filepath.Join(dir, "mkdirat-subdir") - if err := syscall.Mkdir(subDir, 0o755); err != nil { - return fmt.Errorf("mkdirat: %w", err) - } - return nil -} - -// dirChdir creates a temp directory, then changes to it via chdir(2). -// Restores the original working directory afterward. -func dirChdir() error { - origDir, err := os.Getwd() - if err != nil { - return fmt.Errorf("getwd: %w", err) - } - - dir, cleanup, err := makeTempDir("dir-chdir") - if err != nil { - return err - } - defer cleanup() - defer syscall.Chdir(origDir) - - if err := syscall.Chdir(dir); err != nil { - return fmt.Errorf("chdir: %w", err) - } - return nil -} - -// dirGetcwd changes into a temp directory and calls getcwd(2) directly. -func dirGetcwd() error { - origDir, err := os.Getwd() - if err != nil { - return fmt.Errorf("getwd: %w", err) - } - - dir, cleanup, err := makeTempDir("dir-getcwd") - if err != nil { - return err - } - defer cleanup() - defer syscall.Chdir(origDir) - - if err := syscall.Chdir(dir); err != nil { - return fmt.Errorf("chdir: %w", err) - } - - buf := make([]byte, 4096) - _, _, errno := syscall.Syscall(syscall.SYS_GETCWD, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0) - runtime.KeepAlive(buf) - if errno != 0 { - return fmt.Errorf("getcwd: %w", errno) - } - // Keep cwd unchanged long enough for ior to process enter/exit pairing. - time.Sleep(300 * time.Millisecond) - return nil -} - -// dirGetdents opens a directory and reads its entries via getdents64(2). -func dirGetdents() error { - dir, cleanup, err := makeTempDir("dir-getdents") - if err != nil { - return err - } - defer cleanup() - - // Create a file so getdents has something to return. - filePath := filepath.Join(dir, "getdents-file.txt") - fd, err := syscall.Open(filePath, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open file: %w", err) - } - syscall.Close(fd) - - dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return fmt.Errorf("open dir: %w", err) - } - defer syscall.Close(dirFD) - - buf := make([]byte, 4096) - _, _, errno := syscall.Syscall(syscall.SYS_GETDENTS64, uintptr(dirFD), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf))) - runtime.KeepAlive(buf) - if errno != 0 { - return fmt.Errorf("getdents64: %w", errno) - } - return nil -} - -// dirMkdirEexist attempts to create a directory that already exists via raw -// SYS_MKDIR. The syscall fails with EEXIST, but ior captures the tracepoint -// on entry. -func dirMkdirEexist() error { - dir, cleanup, err := makeTempDir("dir-mkdir-eexist") - if err != nil { - return err - } - defer cleanup() - - subDir := filepath.Join(dir, "mkdir-eexist-subdir") - pathBytes, err := syscall.BytePtrFromString(subDir) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - - // Create the directory first so the second attempt fails. - _, _, errno := syscall.Syscall(syscall.SYS_MKDIR, uintptr(unsafe.Pointer(pathBytes)), 0o755, 0) - runtime.KeepAlive(pathBytes) - if errno != 0 { - return fmt.Errorf("first mkdir: %w", errno) - } - - // Second mkdir on the same path should fail with EEXIST. - pathBytes2, err := syscall.BytePtrFromString(subDir) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - _, _, errno = syscall.Syscall(syscall.SYS_MKDIR, uintptr(unsafe.Pointer(pathBytes2)), 0o755, 0) - runtime.KeepAlive(pathBytes2) - if errno == 0 { - return fmt.Errorf("expected EEXIST, but mkdir succeeded") - } - return nil -} - -// dirChdirEnoent attempts to change to a nonexistent directory via raw -// SYS_CHDIR. The syscall fails with ENOENT, but ior captures the tracepoint -// on entry. -func dirChdirEnoent() error { - dir, cleanup, err := makeTempDir("dir-chdir-enoent") - if err != nil { - return err - } - defer cleanup() - - badPath := filepath.Join(dir, "chdir-enoent-missing") - pathBytes, err := syscall.BytePtrFromString(badPath) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - // Retry a few times to reduce dropped-event flakiness under high load. - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall(syscall.SYS_CHDIR, uintptr(unsafe.Pointer(pathBytes)), 0, 0) - runtime.KeepAlive(pathBytes) - if errno == 0 { - return fmt.Errorf("expected ENOENT, but chdir succeeded") - } - } - return nil -} - -// dirGetdentsEbadf calls getdents64(2) with an invalid file descriptor. -// The syscall fails with EBADF, but ior captures the tracepoint on entry. -func dirGetdentsEbadf() error { - buf := make([]byte, 4096) - // Keep issuing the syscall for a short window so ior has enough time to - // attach under high parallel integration load. - for i := 0; i < 40; i++ { - _, _, errno := syscall.Syscall(syscall.SYS_GETDENTS64, uintptr(9999), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf))) - runtime.KeepAlive(buf) - if errno == 0 { - return fmt.Errorf("expected EBADF, but getdents64 succeeded") - } - time.Sleep(25 * time.Millisecond) - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_dup.go b/integrationtests/cmd/ioworkload/scenario_dup.go deleted file mode 100644 index 6a89970..0000000 --- a/integrationtests/cmd/ioworkload/scenario_dup.go +++ /dev/null @@ -1,151 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "syscall" -) - -// dupBasic opens a file, dups the fd, writes via the dup, closes both. -func dupBasic() error { - dir, cleanup, err := makeTempDir("dup-basic") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "dupfile.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) - - newFd, err := syscall.Dup(fd) - if err != nil { - return fmt.Errorf("dup: %w", err) - } - defer syscall.Close(newFd) - - if _, err := syscall.Write(newFd, []byte("via dup")); err != nil { - return fmt.Errorf("write via dup: %w", err) - } - return nil -} - -// dupDup2 opens a file and duplicates the fd onto a specific target fd via dup2. -func dupDup2() error { - dir, cleanup, err := makeTempDir("dup-dup2") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "dup2file.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 high fd number to avoid collisions. - targetFd := 500 - if err := syscall.Dup2(fd, targetFd); err != nil { - return fmt.Errorf("dup2: %w", err) - } - defer syscall.Close(targetFd) - - if _, err := syscall.Write(targetFd, []byte("via dup2")); err != nil { - return fmt.Errorf("write via dup2: %w", err) - } - return nil -} - -// dupDup3 opens a file and duplicates the fd onto a specific target fd via dup3 -// with O_CLOEXEC flag. -func dupDup3() error { - dir, cleanup, err := makeTempDir("dup-dup3") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "dup3file.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 high fd number to avoid collisions. - targetFd := 501 - if err := syscall.Dup3(fd, targetFd, syscall.O_CLOEXEC); err != nil { - return fmt.Errorf("dup3: %w", err) - } - defer syscall.Close(targetFd) - - if _, err := syscall.Write(targetFd, []byte("via dup3")); err != nil { - return fmt.Errorf("write via dup3: %w", err) - } - return nil -} - -// dupInvalidFd attempts to dup a very high invalid fd number. -// The syscall fails with EBADF, but ior should capture the enter_dup -// tracepoint because arguments are read on syscall entry. -func dupInvalidFd() error { - _, err := syscall.Dup(99999) - if err == nil { - return fmt.Errorf("expected dup of invalid fd to fail") - } - return nil -} - -// dup2SameFd calls dup2 with the same fd for both oldfd and newfd. -// Per POSIX, dup2(fd, fd) is a no-op that returns fd without closing -// and reopening. ior should capture the enter_dup2 tracepoint. -func dup2SameFd() error { - dir, cleanup, err := makeTempDir("dup2-same-fd") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "dup2samefile.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) - - if err := syscall.Dup2(fd, fd); err != nil { - return fmt.Errorf("dup2 same fd: %w", err) - } - return nil -} - -// dup3InvalidFlags calls dup3 with an invalid flags value. -// dup3 only accepts O_CLOEXEC; any other flag causes EINVAL. -// ior should capture the enter_dup3 tracepoint. -func dup3InvalidFlags() error { - dir, cleanup, err := makeTempDir("dup3-invalid-flags") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "dup3flagsfile.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) - - targetFd := 502 - _, _, errno := syscall.Syscall(syscall.SYS_DUP3, uintptr(fd), uintptr(targetFd), 0xBAD) - if errno == 0 { - syscall.Close(targetFd) - return fmt.Errorf("expected dup3 with invalid flags to fail") - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_fcntl.go b/integrationtests/cmd/ioworkload/scenario_fcntl.go deleted file mode 100644 index 0c97002..0000000 --- a/integrationtests/cmd/ioworkload/scenario_fcntl.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "syscall" -) - -// fcntlDupfd uses fcntl F_DUPFD to duplicate a file descriptor. -func fcntlDupfd() error { - dir, cleanup, err := makeTempDir("fcntl-dupfd") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "fcntlfile.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) - - newFd, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_DUPFD, 0) - if errno != 0 { - return fmt.Errorf("fcntl F_DUPFD: %w", errno) - } - defer syscall.Close(int(newFd)) - - if _, err := syscall.Write(int(newFd), []byte("via fcntl")); err != nil { - return fmt.Errorf("write via fcntl dup: %w", err) - } - return nil -} - -// fcntlSetfl uses fcntl F_GETFL/F_SETFL to read and modify file status flags. -func fcntlSetfl() error { - dir, cleanup, err := makeTempDir("fcntl-setfl") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "fcntlsetflfile.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) - - flags, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_GETFL, 0) - if errno != 0 { - return fmt.Errorf("fcntl F_GETFL: %w", errno) - } - - _, _, errno = syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_SETFL, flags|syscall.O_APPEND) - if errno != 0 { - return fmt.Errorf("fcntl F_SETFL: %w", errno) - } - - if _, err := syscall.Write(fd, []byte("appended via fcntl setfl")); err != nil { - return fmt.Errorf("write: %w", err) - } - return nil -} - -// fcntlDupfdCloexec uses fcntl F_DUPFD_CLOEXEC to duplicate a file descriptor -// with the close-on-exec flag set. -func fcntlDupfdCloexec() error { - dir, cleanup, err := makeTempDir("fcntl-dupfd-cloexec") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "fcntlcloexecfile.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) - - newFd, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_DUPFD_CLOEXEC, 0) - if errno != 0 { - return fmt.Errorf("fcntl F_DUPFD_CLOEXEC: %w", errno) - } - defer syscall.Close(int(newFd)) - - if _, err := syscall.Write(int(newFd), []byte("via fcntl dupfd cloexec")); err != nil { - return fmt.Errorf("write via fcntl dup cloexec: %w", err) - } - 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 { - for i := 0; i < 5; i++ { - _, _, 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) - - // Retry the failing fcntl a few times to avoid a single one-shot call - // racing early trace capture under parallel integration load. - for i := 0; i < 5; i++ { - _, _, 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 -} diff --git a/integrationtests/cmd/ioworkload/scenario_iouring.go b/integrationtests/cmd/ioworkload/scenario_iouring.go deleted file mode 100644 index a16d59a..0000000 --- a/integrationtests/cmd/ioworkload/scenario_iouring.go +++ /dev/null @@ -1,133 +0,0 @@ -package main - -import ( - "fmt" - "runtime" - "syscall" - "unsafe" -) - -const ( - sysIoUringSetup = 425 - sysIoUringEnter = 426 - sysIoUringRegister = 427 - - // io_uring_params struct size: 10 x uint32 + io_sqring_offsets(40) + io_cqring_offsets(40) = 120 bytes. - ioUringParamsSize = 120 - - ioringRegisterProbe = 8 // IORING_REGISTER_PROBE -) - -// iouringSetup creates an io_uring instance via io_uring_setup(2) and closes the fd. -func iouringSetup() error { - fd, err := ioUringSetupRing(1) - if err != nil { - return err - } - return syscall.Close(fd) -} - -// iouringEnter creates an io_uring instance, then calls io_uring_enter(2) -// with zero submissions/completions to exercise the enter tracepoint. -func iouringEnter() error { - fd, err := ioUringSetupRing(1) - if err != nil { - return err - } - defer syscall.Close(fd) - - _, _, errno := syscall.Syscall6( - sysIoUringEnter, - uintptr(fd), - 0, // to_submit - 0, // min_complete - 0, // flags - 0, // sig - 0, // sz - ) - if errno != 0 { - return fmt.Errorf("io_uring_enter: %w", errno) - } - return nil -} - -// iouringRegister creates an io_uring instance, then calls io_uring_register(2) -// with IORING_REGISTER_PROBE to exercise the register tracepoint. -func iouringRegister() error { - fd, err := ioUringSetupRing(1) - if err != nil { - return err - } - defer syscall.Close(fd) - - // io_uring_probe header is 16 bytes; we don't need probe_op entries. - var probeBuf [16]byte - _, _, errno := syscall.Syscall6( - sysIoUringRegister, - uintptr(fd), - ioringRegisterProbe, - uintptr(unsafe.Pointer(&probeBuf[0])), - 0, // nr_args (0 ops requested) - 0, 0, - ) - runtime.KeepAlive(probeBuf) - if errno != 0 { - return fmt.Errorf("io_uring_register: %w", errno) - } - return nil -} - -// iouringEnterEbadf calls io_uring_enter on an invalid fd. -// The syscall fails with EBADF, but ior captures the enter_io_uring_enter tracepoint. -func iouringEnterEbadf() error { - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall6( - sysIoUringEnter, - 99999, // invalid fd - 0, // to_submit - 0, // min_complete - 0, // flags - 0, // sig - 0, // sz - ) - if errno == 0 { - return fmt.Errorf("expected EBADF, but io_uring_enter succeeded") - } - } - return nil -} - -// iouringRegisterEbadf calls io_uring_register on an invalid fd. -// The syscall fails with EBADF, but ior captures the enter_io_uring_register tracepoint. -func iouringRegisterEbadf() error { - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall6( - sysIoUringRegister, - 99999, // invalid fd - ioringRegisterProbe, - 0, // arg (NULL) - 0, // nr_args - 0, 0, - ) - if errno == 0 { - return fmt.Errorf("expected EBADF, but io_uring_register succeeded") - } - } - return nil -} - -// ioUringSetupRing calls io_uring_setup(2) and returns the ring fd. -func ioUringSetupRing(entries uint32) (int, error) { - var params [ioUringParamsSize]byte - fd, _, errno := syscall.Syscall( - sysIoUringSetup, - uintptr(entries), - uintptr(unsafe.Pointer(¶ms[0])), - 0, - ) - runtime.KeepAlive(params) - if errno != 0 { - return 0, fmt.Errorf("io_uring_setup: %w", errno) - } - return int(fd), nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_link.go b/integrationtests/cmd/ioworkload/scenario_link.go deleted file mode 100644 index beb49a0..0000000 --- a/integrationtests/cmd/ioworkload/scenario_link.go +++ /dev/null @@ -1,391 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "runtime" - "syscall" - "time" - "unsafe" -) - -// linkBasic creates a file, hard links it via link(2), symlinks it via -// symlink(2), and reads the symlink via readlink(2). -// Uses raw SYS_LINK, SYS_SYMLINK, SYS_READLINK because Go's syscall wrappers -// delegate to linkat/symlinkat/readlinkat on amd64. -func linkBasic() error { - dir, cleanup, err := makeTempDir("link-basic") - if err != nil { - return err - } - defer cleanup() - - origPath := filepath.Join(dir, "original.txt") - fd, err := syscall.Open(origPath, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - if err := rawLink(origPath, filepath.Join(dir, "hardlink.txt")); err != nil { - return err - } - - symPath := filepath.Join(dir, "symlink.txt") - if err := rawSymlink(origPath, symPath); err != nil { - return err - } - - return rawReadlink(symPath) -} - -// linkLinkat creates a file and hard links it via linkat(2). -func linkLinkat() error { - dir, cleanup, err := makeTempDir("link-linkat") - if err != nil { - return err - } - defer cleanup() - - origName := "linkat-original.txt" - origPath := filepath.Join(dir, origName) - fd, err := syscall.Open(origPath, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return fmt.Errorf("open dir: %w", err) - } - defer syscall.Close(dirFD) - - hardName := "linkat-hard.txt" - oldBytes, err := syscall.BytePtrFromString(origName) - if err != nil { - return fmt.Errorf("old name bytes: %w", err) - } - newBytes, err := syscall.BytePtrFromString(hardName) - if err != nil { - return fmt.Errorf("new name bytes: %w", err) - } - - _, _, errno := syscall.Syscall6( - syscall.SYS_LINKAT, - uintptr(dirFD), - uintptr(unsafe.Pointer(oldBytes)), - uintptr(dirFD), - uintptr(unsafe.Pointer(newBytes)), - 0, // flags - 0, - ) - runtime.KeepAlive(oldBytes) - runtime.KeepAlive(newBytes) - if errno != 0 { - return fmt.Errorf("linkat: %w", errno) - } - return nil -} - -// linkSymlinkat creates a symlink via symlinkat(2). -func linkSymlinkat() error { - dir, cleanup, err := makeTempDir("link-symlinkat") - if err != nil { - return err - } - defer cleanup() - - origPath := filepath.Join(dir, "symlinkat-original.txt") - fd, err := syscall.Open(origPath, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return fmt.Errorf("open dir: %w", err) - } - defer syscall.Close(dirFD) - - targetBytes, err := syscall.BytePtrFromString(origPath) - if err != nil { - return fmt.Errorf("target bytes: %w", err) - } - linkName := "symlinkat-link.txt" - linkBytes, err := syscall.BytePtrFromString(linkName) - if err != nil { - return fmt.Errorf("link name bytes: %w", err) - } - - _, _, errno := syscall.Syscall( - syscall.SYS_SYMLINKAT, - uintptr(unsafe.Pointer(targetBytes)), - uintptr(dirFD), - uintptr(unsafe.Pointer(linkBytes)), - ) - runtime.KeepAlive(targetBytes) - runtime.KeepAlive(linkBytes) - if errno != 0 { - return fmt.Errorf("symlinkat: %w", errno) - } - return nil -} - -// linkReadlinkat creates a symlink, then reads it via readlinkat(2). -func linkReadlinkat() error { - dir, cleanup, err := makeTempDir("link-readlinkat") - if err != nil { - return err - } - defer cleanup() - - origPath := filepath.Join(dir, "readlinkat-original.txt") - fd, err := syscall.Open(origPath, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - // Create symlink using raw SYS_SYMLINK so we don't mix tracepoints. - linkPath := filepath.Join(dir, "readlinkat-link.txt") - if err := rawSymlink(origPath, linkPath); err != nil { - return err - } - - // Read via readlinkat(2). - dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return fmt.Errorf("open dir: %w", err) - } - defer syscall.Close(dirFD) - - linkName := "readlinkat-link.txt" - nameBytes, err := syscall.BytePtrFromString(linkName) - if err != nil { - return fmt.Errorf("link name bytes: %w", err) - } - buf := make([]byte, 256) - _, _, errno := syscall.Syscall6( - syscall.SYS_READLINKAT, - uintptr(dirFD), - uintptr(unsafe.Pointer(nameBytes)), - uintptr(unsafe.Pointer(&buf[0])), - uintptr(len(buf)), - 0, 0, - ) - runtime.KeepAlive(nameBytes) - runtime.KeepAlive(buf) - if errno != 0 { - return fmt.Errorf("readlinkat: %w", errno) - } - return nil -} - -// linkEnoent attempts to hard link a nonexistent source via raw SYS_LINK. -// The syscall fails with ENOENT, but ior captures the enter_link tracepoint -// because arguments are read on syscall entry. -func linkEnoent() error { - dir, cleanup, err := makeTempDir("link-enoent") - if err != nil { - return err - } - defer cleanup() - - srcPath := filepath.Join(dir, "link-enoent-missing.txt") - dstPath := filepath.Join(dir, "link-enoent-dst.txt") - - srcBytes, err := syscall.BytePtrFromString(srcPath) - if err != nil { - return fmt.Errorf("src path bytes: %w", err) - } - dstBytes, err := syscall.BytePtrFromString(dstPath) - if err != nil { - return fmt.Errorf("dst path bytes: %w", err) - } - - // Issue the same failing syscall a few times to make capture robust even - // under heavy parallel integration load. - for i := 0; i < 3; i++ { - _, _, errno := syscall.Syscall( - syscall.SYS_LINK, - uintptr(unsafe.Pointer(srcBytes)), - uintptr(unsafe.Pointer(dstBytes)), - 0, - ) - if errno == 0 { - return fmt.Errorf("expected ENOENT, but link succeeded") - } - time.Sleep(20 * time.Millisecond) - } - runtime.KeepAlive(srcBytes) - runtime.KeepAlive(dstBytes) - return nil -} - -// linkSymlinkEexist creates a regular file, then attempts to create a symlink -// at the same path via raw SYS_SYMLINK. The syscall fails with EEXIST because -// the link path already exists, but ior captures the enter_symlink tracepoint. -func linkSymlinkEexist() error { - dir, cleanup, err := makeTempDir("link-symlink-eexist") - if err != nil { - return err - } - defer cleanup() - - existingPath := filepath.Join(dir, "symlink-eexist.txt") - fd, err := syscall.Open(existingPath, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - targetBytes, err := syscall.BytePtrFromString("/tmp/dummy-target") - if err != nil { - return fmt.Errorf("target bytes: %w", err) - } - linkBytes, err := syscall.BytePtrFromString(existingPath) - if err != nil { - return fmt.Errorf("link path bytes: %w", err) - } - - _, _, errno := syscall.Syscall( - syscall.SYS_SYMLINK, - uintptr(unsafe.Pointer(targetBytes)), - uintptr(unsafe.Pointer(linkBytes)), - 0, - ) - runtime.KeepAlive(targetBytes) - runtime.KeepAlive(linkBytes) - if errno == 0 { - return fmt.Errorf("expected EEXIST, but symlink succeeded") - } - return nil -} - -// linkReadlinkatEinval creates a regular file and calls readlinkat(2) on it. -// The syscall fails with EINVAL because the path is not a symlink, but ior -// captures the enter_readlinkat tracepoint on syscall entry. -func linkReadlinkatEinval() error { - dir, cleanup, err := makeTempDir("link-readlinkat-einval") - if err != nil { - return err - } - defer cleanup() - - regularFile := "readlinkat-einval.txt" - regularPath := filepath.Join(dir, regularFile) - fd, err := syscall.Open(regularPath, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return fmt.Errorf("open dir: %w", err) - } - defer syscall.Close(dirFD) - - nameBytes, err := syscall.BytePtrFromString(regularFile) - if err != nil { - return fmt.Errorf("name bytes: %w", err) - } - buf := make([]byte, 256) - _, _, errno := syscall.Syscall6( - syscall.SYS_READLINKAT, - uintptr(dirFD), - uintptr(unsafe.Pointer(nameBytes)), - uintptr(unsafe.Pointer(&buf[0])), - uintptr(len(buf)), - 0, 0, - ) - runtime.KeepAlive(nameBytes) - runtime.KeepAlive(buf) - if errno == 0 { - return fmt.Errorf("expected EINVAL, but readlinkat succeeded") - } - return nil -} - -// rawLink calls link(2) via raw SYS_LINK. -func rawLink(oldPath, newPath string) error { - oldBytes, err := syscall.BytePtrFromString(oldPath) - if err != nil { - return fmt.Errorf("old path bytes: %w", err) - } - newBytes, err := syscall.BytePtrFromString(newPath) - if err != nil { - return fmt.Errorf("new path bytes: %w", err) - } - _, _, errno := syscall.Syscall( - syscall.SYS_LINK, - uintptr(unsafe.Pointer(oldBytes)), - uintptr(unsafe.Pointer(newBytes)), - 0, - ) - runtime.KeepAlive(oldBytes) - runtime.KeepAlive(newBytes) - if errno != 0 { - return fmt.Errorf("link: %w", errno) - } - return nil -} - -// rawSymlink calls symlink(2) via raw SYS_SYMLINK. -func rawSymlink(target, linkPath string) error { - targetBytes, err := syscall.BytePtrFromString(target) - if err != nil { - return fmt.Errorf("target path bytes: %w", err) - } - linkBytes, err := syscall.BytePtrFromString(linkPath) - if err != nil { - return fmt.Errorf("link path bytes: %w", err) - } - _, _, errno := syscall.Syscall( - syscall.SYS_SYMLINK, - uintptr(unsafe.Pointer(targetBytes)), - uintptr(unsafe.Pointer(linkBytes)), - 0, - ) - runtime.KeepAlive(targetBytes) - runtime.KeepAlive(linkBytes) - if errno != 0 { - return fmt.Errorf("symlink: %w", errno) - } - return nil -} - -// rawReadlink calls readlink(2) via raw SYS_READLINK. -func rawReadlink(path string) error { - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - buf := make([]byte, 256) - _, _, errno := syscall.Syscall( - syscall.SYS_READLINK, - uintptr(unsafe.Pointer(pathBytes)), - uintptr(unsafe.Pointer(&buf[0])), - uintptr(len(buf)), - ) - runtime.KeepAlive(pathBytes) - runtime.KeepAlive(buf) - if errno != 0 { - return fmt.Errorf("readlink: %w", errno) - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_mmap.go b/integrationtests/cmd/ioworkload/scenario_mmap.go deleted file mode 100644 index e7b9f02..0000000 --- a/integrationtests/cmd/ioworkload/scenario_mmap.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "syscall" - "unsafe" -) - -// mmapBasic creates a file-backed shared mapping. -// mmap(2) allows closing the fd after mapping without invalidating the mapping. -func mmapBasic() error { - dir, cleanup, err := makeTempDir("mmap-basic") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "mmapfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - defer syscall.Close(fd) - - data := []byte("mmap shared page data") - if _, err := syscall.Write(fd, data); err != nil { - return fmt.Errorf("write: %w", err) - } - - mapped, err := syscall.Mmap(fd, 0, len(data), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) - if err != nil { - return fmt.Errorf("mmap: %w", err) - } - defer syscall.Munmap(mapped) - - copy(mapped[:4], []byte("MMAP")) - return nil -} - -// mmapMsyncSync maps a file and flushes modifications via msync(2). -// Per msync(2), callers should specify exactly one of MS_SYNC or MS_ASYNC. -func mmapMsyncSync() error { - dir, cleanup, err := makeTempDir("mmap-msync-sync") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "msyncfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - defer syscall.Close(fd) - - data := []byte("msync shared page data") - if _, err := syscall.Write(fd, data); err != nil { - return fmt.Errorf("write: %w", err) - } - - mapped, err := syscall.Mmap(fd, 0, len(data), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) - if err != nil { - return fmt.Errorf("mmap: %w", err) - } - defer syscall.Munmap(mapped) - - copy(mapped[:5], []byte("MSYNC")) - - _, _, errno := syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(&mapped[0])), uintptr(len(mapped)), uintptr(syscall.MS_SYNC)) - if errno != 0 { - return fmt.Errorf("msync: %w", errno) - } - return nil -} - -// mmapMsyncInvalidFlags calls msync(2) with both MS_SYNC and MS_ASYNC. -// The kernel returns EINVAL, but enter_msync should still be captured. -func mmapMsyncInvalidFlags() error { - dir, cleanup, err := makeTempDir("mmap-msync-invalid-flags") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "msyncinvalidfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - defer syscall.Close(fd) - - data := []byte("msync invalid flags data") - if _, err := syscall.Write(fd, data); err != nil { - return fmt.Errorf("write: %w", err) - } - - mapped, err := syscall.Mmap(fd, 0, len(data), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) - if err != nil { - return fmt.Errorf("mmap: %w", err) - } - defer syscall.Munmap(mapped) - - flags := syscall.MS_SYNC | syscall.MS_ASYNC - _, _, errno := syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(&mapped[0])), uintptr(len(mapped)), uintptr(flags)) - if errno != syscall.EINVAL { - return fmt.Errorf("expected EINVAL from msync with both MS_SYNC|MS_ASYNC, got %v", errno) - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_open.go b/integrationtests/cmd/ioworkload/scenario_open.go deleted file mode 100644 index 1aebec1..0000000 --- a/integrationtests/cmd/ioworkload/scenario_open.go +++ /dev/null @@ -1,270 +0,0 @@ -package main - -import ( - "fmt" - "os/exec" - "path/filepath" - "runtime" - "syscall" - "time" - "unsafe" -) - -// openBasic opens a file with O_RDWR|O_CREAT, then closes it. -func openBasic() error { - dir, cleanup, err := makeTempDir("open-basic") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "testfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - return syscall.Close(fd) -} - -// openCreat creates a file via raw SYS_CREAT. -// Go's syscall.Creat wraps Open which delegates to openat on amd64, -// so we use the raw syscall to actually exercise the creat tracepoint. -func openCreat() error { - dir, cleanup, err := makeTempDir("open-creat") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "creatfile.txt") - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - fd, _, errno := syscall.Syscall(syscall.SYS_CREAT, uintptr(unsafe.Pointer(pathBytes)), 0o644, 0) - runtime.KeepAlive(pathBytes) - if errno != 0 { - return fmt.Errorf("creat: %w", errno) - } - return syscall.Close(int(fd)) -} - -// openEnoent attempts to open a nonexistent file path. The openat syscall -// returns ENOENT, but ior should still capture the enter_openat tracepoint -// because the filename is read on entry before the syscall executes. -func openEnoent() error { - dir, cleanup, err := makeTempDir("open-enoent") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "nonexistent", "enoentfile.txt") - for i := 0; i < 5; i++ { - _, err = syscall.Open(path, syscall.O_RDONLY, 0) - if err == nil { - return fmt.Errorf("expected ENOENT, but open succeeded") - } - } - return nil -} - -// openRdonlyWrite opens a file O_RDONLY, then attempts to write to it. -// The write fails with EBADF, but ior should capture both the openat -// tracepoint and the write tracepoint. -func openRdonlyWrite() error { - dir, cleanup, err := makeTempDir("open-rdonly-write") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "rdonlyfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("create file: %w", err) - } - syscall.Close(fd) - - fd, err = syscall.Open(path, syscall.O_RDONLY, 0) - if err != nil { - return fmt.Errorf("open rdonly: %w", err) - } - defer syscall.Close(fd) - - _, err = syscall.Write(fd, []byte("should fail")) - if err == nil { - return fmt.Errorf("expected write to rdonly fd to fail") - } - return nil -} - -// openPidFilter spawns a child process that performs file I/O. Since ior -// filters by the workload PID, the child's I/O should NOT appear in results. -// The parent also performs its own open so the test can verify positive and -// negative expectations simultaneously. -func openPidFilter() error { - dir, cleanup, err := makeTempDir("open-pid-filter") - if err != nil { - return err - } - defer cleanup() - - // Parent opens a file (should be captured by ior). - parentPath := filepath.Join(dir, "parentfile.txt") - fd, err := syscall.Open(parentPath, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("parent open: %w", err) - } - syscall.Close(fd) - - // Spawn a child process that creates a file with a distinctive name. - childPath := filepath.Join(dir, "childfile.txt") - cmd := exec.Command("touch", childPath) - if err := cmd.Run(); err != nil { - return fmt.Errorf("child touch: %w", err) - } - return nil -} - -// openByHandleAt creates a file, resolves its handle via name_to_handle_at, -// then opens it via open_by_handle_at. Requires root (CAP_DAC_READ_SEARCH). -// LockOSThread prevents goroutine migration between the two syscalls so that -// ior sees the same TID for both and can correlate the path. -func openByHandleAt() error { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - dir, cleanup, err := makeTempDir("open-by-handle-at") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "handlefile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - handle, mountFD, err := nameToHandleAt(dir, "handlefile.txt") - if err != nil { - return fmt.Errorf("name_to_handle_at: %w", err) - } - defer syscall.Close(mountFD) - - fd2, err := openByHandleAtSyscall(mountFD, handle, syscall.O_RDONLY) - if err != nil { - return fmt.Errorf("open_by_handle_at: %w", err) - } - return syscall.Close(fd2) -} - -// fileHandle matches the kernel's struct file_handle layout. -type fileHandle struct { - Size uint32 - Type int32 - // Handle bytes follow immediately after. -} - -const ( - sysNameToHandleAt = 303 - sysOpenByHandleAt = 304 -) - -// nameToHandleAt calls name_to_handle_at(2) and returns the file handle -// and the directory fd. The caller can pass this dirFD as the mount_fd -// argument to open_by_handle_at since any fd on the same filesystem works. -func nameToHandleAt(dirPath, name string) ([]byte, int, error) { - dirFD, err := syscall.Open(dirPath, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return nil, 0, fmt.Errorf("open dir: %w", err) - } - - nameBytes, err := syscall.BytePtrFromString(name) - if err != nil { - syscall.Close(dirFD) - return nil, 0, fmt.Errorf("name bytes: %w", err) - } - - // Start with a buffer large enough for most handles. - buf := make([]byte, unsafe.Sizeof(fileHandle{})+128) - fh := (*fileHandle)(unsafe.Pointer(&buf[0])) - fh.Size = uint32(len(buf) - int(unsafe.Sizeof(fileHandle{}))) - - var mountID int32 - _, _, errno := syscall.Syscall6( - sysNameToHandleAt, - uintptr(dirFD), - uintptr(unsafe.Pointer(nameBytes)), - uintptr(unsafe.Pointer(fh)), - uintptr(unsafe.Pointer(&mountID)), - 0, 0, - ) - if errno != 0 { - syscall.Close(dirFD) - return nil, 0, fmt.Errorf("syscall: %w", errno) - } - - handleLen := int(unsafe.Sizeof(fileHandle{})) + int(fh.Size) - handle := make([]byte, handleLen) - copy(handle, buf[:handleLen]) - - return handle, dirFD, nil -} - -// openByHandleAtSyscall calls open_by_handle_at(2). -func openByHandleAtSyscall(mountFD int, handle []byte, flags int) (int, error) { - fd, _, errno := syscall.Syscall( - sysOpenByHandleAt, - uintptr(mountFD), - uintptr(unsafe.Pointer(&handle[0])), - uintptr(flags), - ) - if errno != 0 { - return 0, fmt.Errorf("syscall: %w", errno) - } - return int(fd), nil -} - -// openDurationGap performs two openat syscalls for the same path and flags, -// separated by a deliberate sleep. Integration tests use this to assert that -// durationToPrev captures inter-syscall gaps for the same event key. -func openDurationGap() error { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - dir, cleanup, err := makeTempDir("open-duration-gap") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "gap-shared.txt") - - // Repeat the same open/sleep/open pattern to make the gap observation robust - // under high test parallelism where individual events can occasionally drop. - for i := 0; i < 5; i++ { - fd1, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open first: %w", err) - } - if err := syscall.Close(fd1); err != nil { - return fmt.Errorf("close first: %w", err) - } - - time.Sleep(800 * time.Millisecond) - - fd2, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open second: %w", err) - } - if err := syscall.Close(fd2); err != nil { - return fmt.Errorf("close second: %w", err) - } - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_pidfd.go b/integrationtests/cmd/ioworkload/scenario_pidfd.go deleted file mode 100644 index 2aafced..0000000 --- a/integrationtests/cmd/ioworkload/scenario_pidfd.go +++ /dev/null @@ -1,133 +0,0 @@ -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) { - syscallNr, err := pidfdOpenSyscallNr() - if err != nil { - return 0, err - } - fd, _, errno := syscall.Syscall(syscallNr, uintptr(pid), flags, 0) - if errno != 0 { - return 0, errno - } - return int(fd), nil -} - -func pidfdGetfd(pidfd int, targetFd int, flags uintptr) (int, error) { - syscallNr, err := pidfdGetfdSyscallNr() - if err != nil { - return 0, err - } - fd, _, errno := syscall.Syscall( - syscallNr, - uintptr(pidfd), - uintptr(targetFd), - flags, - ) - if errno != 0 { - return 0, errno - } - return int(fd), nil -} - -func pidfdOpenSyscallNr() (uintptr, error) { - return pidfdOpenSyscallNrForArch(runtime.GOARCH) -} - -func pidfdGetfdSyscallNr() (uintptr, error) { - return pidfdGetfdSyscallNrForArch(runtime.GOARCH) -} - -func pidfdOpenSyscallNrForArch(arch string) (uintptr, error) { - // Go's syscall package does not expose pidfd constants on all toolchains. - switch arch { - case "amd64", "arm64": - return 434, nil - default: - return 0, fmt.Errorf("pidfd_open syscall number not defined for GOARCH=%s", arch) - } -} - -func pidfdGetfdSyscallNrForArch(arch string) (uintptr, error) { - // Go's syscall package does not expose pidfd constants on all toolchains. - switch arch { - case "amd64", "arm64": - return 438, nil - default: - return 0, fmt.Errorf("pidfd_getfd syscall number not defined for GOARCH=%s", arch) - } -} diff --git a/integrationtests/cmd/ioworkload/scenario_pidfd_test.go b/integrationtests/cmd/ioworkload/scenario_pidfd_test.go deleted file mode 100644 index 5ee1002..0000000 --- a/integrationtests/cmd/ioworkload/scenario_pidfd_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import "testing" - -func TestPidfdOpenSyscallNrForArch(t *testing.T) { - for _, tc := range []struct { - name string - arch string - want uintptr - wantErr bool - }{ - {name: "amd64", arch: "amd64", want: 434}, - {name: "arm64", arch: "arm64", want: 434}, - {name: "unsupported", arch: "riscv64", wantErr: true}, - } { - got, err := pidfdOpenSyscallNrForArch(tc.arch) - if tc.wantErr { - if err == nil { - t.Fatalf("%s: expected error", tc.name) - } - continue - } - if err != nil { - t.Fatalf("%s: unexpected error: %v", tc.name, err) - } - if got != tc.want { - t.Fatalf("%s: got %d, want %d", tc.name, got, tc.want) - } - } -} - -func TestPidfdGetfdSyscallNrForArch(t *testing.T) { - for _, tc := range []struct { - name string - arch string - want uintptr - wantErr bool - }{ - {name: "amd64", arch: "amd64", want: 438}, - {name: "arm64", arch: "arm64", want: 438}, - {name: "unsupported", arch: "riscv64", wantErr: true}, - } { - got, err := pidfdGetfdSyscallNrForArch(tc.arch) - if tc.wantErr { - if err == nil { - t.Fatalf("%s: expected error", tc.name) - } - continue - } - if err != nil { - t.Fatalf("%s: unexpected error: %v", tc.name, err) - } - if got != tc.want { - t.Fatalf("%s: got %d, want %d", tc.name, got, tc.want) - } - } -} diff --git a/integrationtests/cmd/ioworkload/scenario_readwrite.go b/integrationtests/cmd/ioworkload/scenario_readwrite.go deleted file mode 100644 index c676b90..0000000 --- a/integrationtests/cmd/ioworkload/scenario_readwrite.go +++ /dev/null @@ -1,263 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "runtime" - "syscall" - "unsafe" -) - -// readwriteBasic opens a file, writes data, seeks to start, reads it back. -func readwriteBasic() error { - dir, cleanup, err := makeTempDir("readwrite-basic") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "rwfile.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) - - data := []byte("hello from ioworkload") - if _, err := syscall.Write(fd, data); err != nil { - return fmt.Errorf("write: %w", err) - } - if _, err := syscall.Seek(fd, 0, 0); err != nil { - return fmt.Errorf("seek: %w", err) - } - - buf := make([]byte, len(data)) - if _, err := syscall.Read(fd, buf); err != nil { - return fmt.Errorf("read: %w", err) - } - return nil -} - -// readwritePread opens a file, writes data, then reads it back via pread64. -func readwritePread() error { - dir, cleanup, err := makeTempDir("readwrite-pread") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "preadfile.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) - - data := []byte("pread test data") - if _, err := syscall.Write(fd, data); err != nil { - return fmt.Errorf("write: %w", err) - } - - buf := make([]byte, len(data)) - if _, err := syscall.Pread(fd, buf, 0); err != nil { - return fmt.Errorf("pread: %w", err) - } - return nil -} - -// readwritePwrite opens a file and writes data via pwrite64. -func readwritePwrite() error { - dir, cleanup, err := makeTempDir("readwrite-pwrite") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "pwritefile.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) - - if _, err := syscall.Pwrite(fd, []byte("pwrite test data"), 0); err != nil { - return fmt.Errorf("pwrite: %w", err) - } - return nil -} - -// readwriteReadv opens a file, writes data, then reads it back via readv. -func readwriteReadv() error { - dir, cleanup, err := makeTempDir("readwrite-readv") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "readvfile.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) - - data := []byte("readv test data here") - if _, err := syscall.Write(fd, data); err != nil { - return fmt.Errorf("write: %w", err) - } - if _, err := syscall.Seek(fd, 0, 0); err != nil { - return fmt.Errorf("seek: %w", err) - } - - buf1 := make([]byte, 5) - buf2 := make([]byte, 15) - iovs := []syscall.Iovec{ - {Base: &buf1[0], Len: uint64(len(buf1))}, - {Base: &buf2[0], Len: uint64(len(buf2))}, - } - _, _, errno := syscall.Syscall(syscall.SYS_READV, uintptr(fd), uintptr(unsafe.Pointer(&iovs[0])), uintptr(len(iovs))) - runtime.KeepAlive(buf1) - runtime.KeepAlive(buf2) - if errno != 0 { - return fmt.Errorf("readv: %w", errno) - } - return nil -} - -// readwriteWritev opens a file and writes data via writev. -func readwriteWritev() error { - dir, cleanup, err := makeTempDir("readwrite-writev") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "writevfile.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) - - buf1 := []byte("writev ") - buf2 := []byte("test data") - iovs := []syscall.Iovec{ - {Base: &buf1[0], Len: uint64(len(buf1))}, - {Base: &buf2[0], Len: uint64(len(buf2))}, - } - _, _, errno := syscall.Syscall(syscall.SYS_WRITEV, uintptr(fd), uintptr(unsafe.Pointer(&iovs[0])), uintptr(len(iovs))) - runtime.KeepAlive(buf1) - runtime.KeepAlive(buf2) - if errno != 0 { - return fmt.Errorf("writev: %w", errno) - } - return nil -} - -// readwriteWronlyRead opens a file O_WRONLY, then attempts to read from it. -// The read fails with EBADF, but ior should capture the enter_read tracepoint -// because arguments are read on syscall entry before the kernel returns an error. -func readwriteWronlyRead() error { - dir, cleanup, err := makeTempDir("readwrite-wronly-read") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "wronlyfile.txt") - fd, err := syscall.Open(path, syscall.O_WRONLY|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - defer syscall.Close(fd) - - buf := make([]byte, 16) - _, err = syscall.Read(fd, buf) - if err == nil { - return fmt.Errorf("expected read from wronly fd to fail") - } - return nil -} - -// readwriteRdonlyWrite opens a file O_RDONLY, then attempts to write to it. -// The write fails with EBADF, but ior should capture the enter_write tracepoint -// because arguments are read on syscall entry before the kernel returns an error. -func readwriteRdonlyWrite() error { - dir, cleanup, err := makeTempDir("readwrite-rdonly-write") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "rdonlywritefile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("create file: %w", err) - } - syscall.Close(fd) - - fd, err = syscall.Open(path, syscall.O_RDONLY, 0) - if err != nil { - return fmt.Errorf("open rdonly: %w", err) - } - defer syscall.Close(fd) - - _, err = syscall.Write(fd, []byte("should fail")) - if err == nil { - return fmt.Errorf("expected write to rdonly fd to fail") - } - return nil -} - -// readwritePreadInvalid calls pread64 with a negative offset (-1). -// The syscall fails with EINVAL, but ior should capture the enter_pread64 -// tracepoint because arguments are read on syscall entry. -func readwritePreadInvalid() error { - dir, cleanup, err := makeTempDir("readwrite-pread-invalid") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "preadinvalid.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) - - if _, err := syscall.Write(fd, []byte("some data")); err != nil { - return fmt.Errorf("write: %w", err) - } - - buf := make([]byte, 16) - _, err = syscall.Pread(fd, buf, -1) - if err == nil { - return fmt.Errorf("expected pread with negative offset to fail") - } - return nil -} - -// readwritePwriteInvalid calls pwrite64 with a negative offset (-1). -// The syscall fails with EINVAL, but ior should capture the enter_pwrite64 -// tracepoint because arguments are read on syscall entry. -func readwritePwriteInvalid() error { - dir, cleanup, err := makeTempDir("readwrite-pwrite-invalid") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "pwriteinvalid.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) - - _, err = syscall.Pwrite(fd, []byte("should fail"), -1) - if err == nil { - return fmt.Errorf("expected pwrite with negative offset to fail") - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_rename.go b/integrationtests/cmd/ioworkload/scenario_rename.go deleted file mode 100644 index 685157b..0000000 --- a/integrationtests/cmd/ioworkload/scenario_rename.go +++ /dev/null @@ -1,253 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "runtime" - "syscall" - "unsafe" -) - -const ( - sysRenameat2 = 316 // SYS_RENAMEAT2 on amd64 - renameNoreplaceFlag = 1 // RENAME_NOREPLACE -) - -// 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 { - dir, cleanup, err := makeTempDir("rename-basic") - if err != nil { - return err - } - defer cleanup() - - oldPath := filepath.Join(dir, "oldname.txt") - newPath := filepath.Join(dir, "newname.txt") - - fd, err := syscall.Open(oldPath, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - oldBytes, err := syscall.BytePtrFromString(oldPath) - if err != nil { - return fmt.Errorf("old path bytes: %w", err) - } - newBytes, err := syscall.BytePtrFromString(newPath) - if err != nil { - return fmt.Errorf("new path bytes: %w", err) - } - - _, _, errno := syscall.Syscall( - syscall.SYS_RENAME, - uintptr(unsafe.Pointer(oldBytes)), - uintptr(unsafe.Pointer(newBytes)), - 0, - ) - runtime.KeepAlive(oldBytes) - runtime.KeepAlive(newBytes) - if errno != 0 { - return fmt.Errorf("rename: %w", errno) - } - return nil -} - -// renameRenameat creates a file and renames it via renameat(2). -func renameRenameat() error { - dir, cleanup, err := makeTempDir("rename-renameat") - if err != nil { - return err - } - defer cleanup() - - oldName := "renameat-old.txt" - newName := "renameat-new.txt" - path := filepath.Join(dir, oldName) - - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return fmt.Errorf("open dir: %w", err) - } - defer syscall.Close(dirFD) - - oldBytes, err := syscall.BytePtrFromString(oldName) - if err != nil { - return fmt.Errorf("old name bytes: %w", err) - } - newBytes, err := syscall.BytePtrFromString(newName) - if err != nil { - return fmt.Errorf("new name bytes: %w", err) - } - - _, _, errno := syscall.Syscall6( - syscall.SYS_RENAMEAT, - uintptr(dirFD), - uintptr(unsafe.Pointer(oldBytes)), - uintptr(dirFD), - uintptr(unsafe.Pointer(newBytes)), - 0, 0, - ) - runtime.KeepAlive(oldBytes) - runtime.KeepAlive(newBytes) - if errno != 0 { - return fmt.Errorf("renameat: %w", errno) - } - return nil -} - -// renameRenameat2 creates a file and renames it via renameat2(2) with no flags. -func renameRenameat2() error { - dir, cleanup, err := makeTempDir("rename-renameat2") - if err != nil { - return err - } - defer cleanup() - - oldName := "renameat2-old.txt" - newName := "renameat2-new.txt" - path := filepath.Join(dir, oldName) - - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return fmt.Errorf("open dir: %w", err) - } - defer syscall.Close(dirFD) - - oldBytes, err := syscall.BytePtrFromString(oldName) - if err != nil { - return fmt.Errorf("old name bytes: %w", err) - } - newBytes, err := syscall.BytePtrFromString(newName) - if err != nil { - return fmt.Errorf("new name bytes: %w", err) - } - - _, _, errno := syscall.Syscall6( - sysRenameat2, - uintptr(dirFD), - uintptr(unsafe.Pointer(oldBytes)), - uintptr(dirFD), - uintptr(unsafe.Pointer(newBytes)), - 0, // flags=0: plain rename - 0, - ) - runtime.KeepAlive(oldBytes) - runtime.KeepAlive(newBytes) - if errno != 0 { - return fmt.Errorf("renameat2: %w", errno) - } - return nil -} - -// renameEnoent attempts to rename a nonexistent file via raw SYS_RENAME. -// The syscall fails with ENOENT, but ior captures the tracepoint on entry. -func renameEnoent() error { - dir, cleanup, err := makeTempDir("rename-enoent") - if err != nil { - return err - } - defer cleanup() - - oldPath := filepath.Join(dir, "rename-enoent-missing.txt") - newPath := filepath.Join(dir, "rename-enoent-new.txt") - - oldBytes, err := syscall.BytePtrFromString(oldPath) - if err != nil { - return fmt.Errorf("old path bytes: %w", err) - } - newBytes, err := syscall.BytePtrFromString(newPath) - if err != nil { - return fmt.Errorf("new path bytes: %w", err) - } - - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall( - syscall.SYS_RENAME, - uintptr(unsafe.Pointer(oldBytes)), - uintptr(unsafe.Pointer(newBytes)), - 0, - ) - runtime.KeepAlive(oldBytes) - runtime.KeepAlive(newBytes) - if errno == 0 { - return fmt.Errorf("expected ENOENT, but rename succeeded") - } - } - return nil -} - -// renameNoreplace creates two files, then attempts renameat2 with -// RENAME_NOREPLACE. Because the target already exists, the syscall fails -// with EEXIST, but ior captures the tracepoint on entry. -func renameNoreplace() error { - dir, cleanup, err := makeTempDir("rename-noreplace") - if err != nil { - return err - } - defer cleanup() - - srcName := "noreplace-src.txt" - dstName := "noreplace-dst.txt" - - for _, name := range []string{srcName, dstName} { - path := filepath.Join(dir, name) - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("create %s: %w", name, err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close %s: %w", name, err) - } - } - - dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return fmt.Errorf("open dir: %w", err) - } - defer syscall.Close(dirFD) - - srcBytes, err := syscall.BytePtrFromString(srcName) - if err != nil { - return fmt.Errorf("src name bytes: %w", err) - } - dstBytes, err := syscall.BytePtrFromString(dstName) - if err != nil { - return fmt.Errorf("dst name bytes: %w", err) - } - - _, _, errno := syscall.Syscall6( - sysRenameat2, - uintptr(dirFD), - uintptr(unsafe.Pointer(srcBytes)), - uintptr(dirFD), - uintptr(unsafe.Pointer(dstBytes)), - renameNoreplaceFlag, - 0, - ) - runtime.KeepAlive(srcBytes) - runtime.KeepAlive(dstBytes) - if errno == 0 { - return fmt.Errorf("expected EEXIST, but renameat2 NOREPLACE succeeded") - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_stat.go b/integrationtests/cmd/ioworkload/scenario_stat.go deleted file mode 100644 index 5d242c7..0000000 --- a/integrationtests/cmd/ioworkload/scenario_stat.go +++ /dev/null @@ -1,286 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "runtime" - "syscall" - "time" - "unsafe" -) - -const ( - sysStatx = 332 - rOK = 0x4 // R_OK - statxBasicMask = 0x07ff // STATX_BASIC_STATS - atFDCwd = -100 // AT_FDCWD - statRetryDelay = 20 * time.Millisecond -) - -// statBasic creates a file and stats it via raw SYS_STAT (newstat). -// We use the raw syscall because Go's syscall.Stat wraps newfstatat on amd64. -func statBasic() error { - dir, cleanup, err := makeTempDir("stat-basic") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "statfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - syscall.Close(fd) - - var stat syscall.Stat_t - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - _, _, errno := syscall.Syscall(syscall.SYS_STAT, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(&stat)), 0) - runtime.KeepAlive(pathBytes) - runtime.KeepAlive(&stat) - if errno != 0 { - return fmt.Errorf("stat: %w", errno) - } - return nil -} - -// statFstat creates a file and stats it via raw SYS_FSTAT (newfstat). -// This is an fd_event, so ior resolves the path via its fd lookup table. -func statFstat() error { - dir, cleanup, err := makeTempDir("stat-fstat") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "fstatfile.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) - - var stat syscall.Stat_t - _, _, errno := syscall.Syscall(syscall.SYS_FSTAT, uintptr(fd), uintptr(unsafe.Pointer(&stat)), 0) - runtime.KeepAlive(&stat) - if errno != 0 { - return fmt.Errorf("fstat: %w", errno) - } - return nil -} - -// statLstat creates a file and stats it via raw SYS_LSTAT (newlstat). -// We use the raw syscall because Go's syscall.Lstat wraps newfstatat on amd64. -func statLstat() error { - dir, cleanup, err := makeTempDir("stat-lstat") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "lstatfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - syscall.Close(fd) - - var stat syscall.Stat_t - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - _, _, errno := syscall.Syscall(syscall.SYS_LSTAT, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(&stat)), 0) - runtime.KeepAlive(pathBytes) - runtime.KeepAlive(&stat) - if errno != 0 { - return fmt.Errorf("lstat: %w", errno) - } - return nil -} - -// statNewfstatat creates a file and stats it via Go's syscall.Stat, which -// wraps SYS_NEWFSTATAT (fstatat with AT_FDCWD) on amd64. -func statNewfstatat() error { - dir, cleanup, err := makeTempDir("stat-newfstatat") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "fstatatfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - syscall.Close(fd) - - var stat syscall.Stat_t - if err := syscall.Stat(path, &stat); err != nil { - return fmt.Errorf("newfstatat: %w", err) - } - return nil -} - -// statStatx creates a file and stats it via raw statx(2) syscall. -func statStatx() error { - dir, cleanup, err := makeTempDir("stat-statx") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "statxfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - syscall.Close(fd) - - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - var buf [256]byte // statx struct is ~256 bytes - _, _, errno := syscall.Syscall6( - sysStatx, - ^uintptr(99), // AT_FDCWD (-100) - uintptr(unsafe.Pointer(pathBytes)), - 0, - statxBasicMask, - uintptr(unsafe.Pointer(&buf[0])), - 0, - ) - runtime.KeepAlive(pathBytes) - runtime.KeepAlive(buf) - if errno != 0 { - return fmt.Errorf("statx: %w", errno) - } - return nil -} - -// statAccess creates a file and checks access via raw SYS_ACCESS. -// We use the raw syscall because Go's syscall.Access wraps faccessat on amd64. -func statAccess() error { - dir, cleanup, err := makeTempDir("stat-access") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "accessfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - syscall.Close(fd) - - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - _, _, errno := syscall.Syscall(syscall.SYS_ACCESS, uintptr(unsafe.Pointer(pathBytes)), rOK, 0) - runtime.KeepAlive(pathBytes) - if errno != 0 { - return fmt.Errorf("access: %w", errno) - } - return nil -} - -// statFaccessat creates a file and checks access via faccessat(2). -// Go's syscall.Faccessat wraps SYS_FACCESSAT. -func statFaccessat() error { - dir, cleanup, err := makeTempDir("stat-faccessat") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "faccessatfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - syscall.Close(fd) - - if err := syscall.Faccessat(atFDCwd, path, uint32(rOK), 0); err != nil { - return fmt.Errorf("faccessat: %w", err) - } - return nil -} - -// statEnoent attempts to stat a nonexistent file via raw SYS_STAT. -// The syscall fails with ENOENT, but ior captures the enter_newstat -// tracepoint because the filename is read on entry. -func statEnoent() error { - dir, cleanup, err := makeTempDir("stat-enoent") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "stat-enoent-missing.txt") - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - var stat syscall.Stat_t - // Retry a few times to reduce dropped-event flakiness under high parallelism. - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall(syscall.SYS_STAT, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(&stat)), 0) - runtime.KeepAlive(pathBytes) - runtime.KeepAlive(&stat) - if errno == 0 { - return fmt.Errorf("expected ENOENT, but stat succeeded") - } - } - return nil -} - -// statAccessEnoent attempts to check access on a nonexistent file via raw -// SYS_ACCESS. The syscall fails with ENOENT, but ior captures the -// enter_access tracepoint because the path is read on entry. -// We use ENOENT instead of EACCES because integration tests run as root, -// which bypasses DAC permission checks. -func statAccessEnoent() error { - dir, cleanup, err := makeTempDir("stat-access-enoent") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "access-enoent-missing.txt") - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall(syscall.SYS_ACCESS, uintptr(unsafe.Pointer(pathBytes)), rOK, 0) - runtime.KeepAlive(pathBytes) - if errno == 0 { - return fmt.Errorf("expected ENOENT, but access succeeded") - } - } - return nil -} - -// statFstatEbadf calls raw SYS_FSTAT on an invalid fd (99999). -// The syscall fails with EBADF, but ior captures the enter_newfstat -// tracepoint because it is recorded on syscall entry. -func statFstatEbadf() error { - var stat syscall.Stat_t - for i := 0; i < 20; i++ { - _, _, errno := syscall.Syscall(syscall.SYS_FSTAT, 99999, uintptr(unsafe.Pointer(&stat)), 0) - runtime.KeepAlive(&stat) - if errno == 0 { - return fmt.Errorf("expected EBADF, but fstat succeeded") - } - if i < 19 { - time.Sleep(statRetryDelay) - } - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_sync.go b/integrationtests/cmd/ioworkload/scenario_sync.go deleted file mode 100644 index df1c59c..0000000 --- a/integrationtests/cmd/ioworkload/scenario_sync.go +++ /dev/null @@ -1,137 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "syscall" -) - -// syncBasic opens a file, writes data, and fsyncs it. -func syncBasic() error { - dir, cleanup, err := makeTempDir("sync-basic") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "syncfile.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) - - if _, err := syscall.Write(fd, []byte("sync me")); err != nil { - return fmt.Errorf("write: %w", err) - } - return syscall.Fsync(fd) -} - -// syncFdatasync opens a file, writes data, and fdatasyncs it. -func syncFdatasync() error { - dir, cleanup, err := makeTempDir("sync-fdatasync") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "fdatasyncfile.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) - - if _, err := syscall.Write(fd, []byte("fdatasync me")); err != nil { - return fmt.Errorf("write: %w", err) - } - return syscall.Fdatasync(fd) -} - -// syncSync calls sync(2) to flush all filesystem caches. -// sync is a null_event with no file arguments. -func syncSync() error { - syscall.Sync() - return nil -} - -// syncSyncFileRange opens a file, writes data, then calls sync_file_range(2). -func syncSyncFileRange() error { - dir, cleanup, err := makeTempDir("sync-sync-file-range") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "syncrangefile.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) - - data := []byte("sync file range data") - if _, err := syscall.Write(fd, data); err != nil { - return fmt.Errorf("write: %w", err) - } - return syscall.SyncFileRange(fd, 0, int64(len(data)), 0) -} - -// syncSyncFileRangeToEOF calls sync_file_range(2) with nbytes=0. -// Per sync_file_range(2), nbytes=0 means "sync from offset through end-of-file". -func syncSyncFileRangeToEOF() error { - dir, cleanup, err := makeTempDir("sync-sync-file-range-to-eof") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "syncrangeeoffile.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) - - if _, err := syscall.Write(fd, []byte("sync file range to eof")); err != nil { - return fmt.Errorf("write: %w", err) - } - - return syscall.SyncFileRange(fd, 0, 0, 0) -} - -// syncFsyncEbadf calls fsync on an invalid fd. -// The syscall fails with EBADF, but ior captures the enter_fsync tracepoint. -func syncFsyncEbadf() error { - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall(syscall.SYS_FSYNC, 99999, 0, 0) - if errno == 0 { - return fmt.Errorf("expected EBADF, but fsync succeeded") - } - } - return nil -} - -// syncFdatasyncEbadf calls fdatasync on an invalid fd. -// The syscall fails with EBADF, but ior captures the enter_fdatasync tracepoint. -func syncFdatasyncEbadf() error { - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall(syscall.SYS_FDATASYNC, 99999, 0, 0) - if errno == 0 { - return fmt.Errorf("expected EBADF, but fdatasync succeeded") - } - } - return nil -} - -// syncFileRangeEbadf calls sync_file_range on an invalid fd. -// The syscall fails with EBADF, but ior captures the enter_sync_file_range tracepoint. -func syncFileRangeEbadf() error { - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall6(syscall.SYS_SYNC_FILE_RANGE, 99999, 0, 0, 0, 0, 0) - if errno == 0 { - return fmt.Errorf("expected EBADF, but sync_file_range succeeded") - } - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_truncate.go b/integrationtests/cmd/ioworkload/scenario_truncate.go deleted file mode 100644 index 04288d5..0000000 --- a/integrationtests/cmd/ioworkload/scenario_truncate.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "runtime" - "syscall" - "unsafe" -) - -// truncateBasic opens a file, writes data, then truncates it via -// syscall.Truncate which uses SYS_TRUNCATE directly on amd64 (path-based). -func truncateBasic() error { - dir, cleanup, err := makeTempDir("truncate-basic") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "truncfile.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - - if _, err := syscall.Write(fd, []byte("truncate this content")); err != nil { - syscall.Close(fd) - return fmt.Errorf("write: %w", err) - } - syscall.Close(fd) - - return syscall.Truncate(path, 5) -} - -// truncateFtruncate opens a file, writes data, then truncates it via -// syscall.Ftruncate which uses SYS_FTRUNCATE directly on amd64 (fd-based). -func truncateFtruncate() error { - dir, cleanup, err := makeTempDir("truncate-ftruncate") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "ftruncfile.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) - - if _, err := syscall.Write(fd, []byte("ftruncate this content")); err != nil { - return fmt.Errorf("write: %w", err) - } - return syscall.Ftruncate(fd, 5) -} - -// truncateEnoent attempts to truncate a nonexistent file via raw SYS_TRUNCATE. -// The syscall fails with ENOENT, but ior captures the enter_truncate -// tracepoint because the path is read on entry. -func truncateEnoent() error { - dir, cleanup, err := makeTempDir("truncate-enoent") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "truncate-enoent-missing.txt") - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - // Retry a few times to make this test resilient under high integration - // parallelism where a single failed syscall event can be dropped. - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall(syscall.SYS_TRUNCATE, uintptr(unsafe.Pointer(pathBytes)), 0, 0) - runtime.KeepAlive(pathBytes) - if errno == 0 { - return fmt.Errorf("expected ENOENT, but truncate succeeded") - } - } - return nil -} - -// truncateFtruncateEbadf calls raw SYS_FTRUNCATE on an invalid fd (99999). -// The syscall fails with EBADF, but ior captures the enter_ftruncate -// tracepoint because it is recorded on syscall entry. -func truncateFtruncateEbadf() error { - _, _, errno := syscall.Syscall(syscall.SYS_FTRUNCATE, 99999, 0, 0) - if errno == 0 { - return fmt.Errorf("expected EBADF, but ftruncate succeeded") - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenario_unlink.go b/integrationtests/cmd/ioworkload/scenario_unlink.go deleted file mode 100644 index ea73b10..0000000 --- a/integrationtests/cmd/ioworkload/scenario_unlink.go +++ /dev/null @@ -1,193 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "runtime" - "syscall" - "unsafe" -) - -// unlinkBasic creates a file and unlinks it via raw SYS_UNLINK. -// We use the raw syscall because Go's syscall.Unlink wraps unlinkat on amd64. -func unlinkBasic() error { - dir, cleanup, err := makeTempDir("unlink-basic") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "unlinkme.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - _, _, errno := syscall.Syscall(syscall.SYS_UNLINK, uintptr(unsafe.Pointer(pathBytes)), 0, 0) - runtime.KeepAlive(pathBytes) - if errno != 0 { - return fmt.Errorf("unlink: %w", errno) - } - return nil -} - -// unlinkUnlinkat creates a file and unlinks it via unlinkat(2). -func unlinkUnlinkat() error { - dir, cleanup, err := makeTempDir("unlink-unlinkat") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "unlinkat-file.txt") - fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("open: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close: %w", err) - } - - dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return fmt.Errorf("open dir: %w", err) - } - defer syscall.Close(dirFD) - - nameBytes, err := syscall.BytePtrFromString("unlinkat-file.txt") - if err != nil { - return fmt.Errorf("name bytes: %w", err) - } - _, _, errno := syscall.Syscall(syscall.SYS_UNLINKAT, uintptr(dirFD), uintptr(unsafe.Pointer(nameBytes)), 0) - runtime.KeepAlive(nameBytes) - if errno != 0 { - return fmt.Errorf("unlinkat: %w", errno) - } - return nil -} - -// unlinkRmdir creates a directory and removes it via raw SYS_RMDIR. -// We use the raw syscall because Go's syscall.Rmdir wraps unlinkat on amd64. -func unlinkRmdir() error { - dir, cleanup, err := makeTempDir("unlink-rmdir") - if err != nil { - return err - } - defer cleanup() - - // Retry with fresh paths to avoid a single one-shot syscall that can race - // tracepoint attach during parallel integration test startup. - for i := 0; i < 5; i++ { - subDir := filepath.Join(dir, fmt.Sprintf("rmdir-me-%d", i)) - if err := syscall.Mkdir(subDir, 0o755); err != nil { - return fmt.Errorf("mkdir: %w", err) - } - - pathBytes, err := syscall.BytePtrFromString(subDir) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - _, _, errno := syscall.Syscall(syscall.SYS_RMDIR, uintptr(unsafe.Pointer(pathBytes)), 0, 0) - runtime.KeepAlive(pathBytes) - if errno != 0 { - return fmt.Errorf("rmdir: %w", errno) - } - } - return nil -} - -// unlinkEnoent attempts to unlink a nonexistent file via raw SYS_UNLINK. -// The syscall fails with ENOENT, but ior captures the tracepoint on entry. -func unlinkEnoent() error { - dir, cleanup, err := makeTempDir("unlink-enoent") - if err != nil { - return err - } - defer cleanup() - - path := filepath.Join(dir, "unlink-enoent-missing.txt") - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall(syscall.SYS_UNLINK, uintptr(unsafe.Pointer(pathBytes)), 0, 0) - runtime.KeepAlive(pathBytes) - if errno == 0 { - return fmt.Errorf("expected ENOENT, but unlink succeeded") - } - } - return nil -} - -// unlinkRmdirNotempty attempts to rmdir a non-empty directory via raw SYS_RMDIR. -// The syscall fails with ENOTEMPTY, but ior captures the tracepoint on entry. -func unlinkRmdirNotempty() error { - dir, cleanup, err := makeTempDir("unlink-rmdir-notempty") - if err != nil { - return err - } - defer cleanup() - - subDir := filepath.Join(dir, "rmdir-notempty") - if err := syscall.Mkdir(subDir, 0o755); err != nil { - return fmt.Errorf("mkdir: %w", err) - } - - // Create a file inside so the directory is non-empty. - filePath := filepath.Join(subDir, "blocker.txt") - fd, err := syscall.Open(filePath, syscall.O_RDWR|syscall.O_CREAT, 0o644) - if err != nil { - return fmt.Errorf("create blocker: %w", err) - } - if err := syscall.Close(fd); err != nil { - return fmt.Errorf("close blocker: %w", err) - } - - pathBytes, err := syscall.BytePtrFromString(subDir) - if err != nil { - return fmt.Errorf("path bytes: %w", err) - } - _, _, errno := syscall.Syscall(syscall.SYS_RMDIR, uintptr(unsafe.Pointer(pathBytes)), 0, 0) - runtime.KeepAlive(pathBytes) - if errno == 0 { - return fmt.Errorf("expected ENOTEMPTY, but rmdir succeeded") - } - return nil -} - -// unlinkUnlinkatEnoent attempts to unlinkat a nonexistent file. -// The syscall fails with ENOENT, but ior captures the tracepoint on entry. -func unlinkUnlinkatEnoent() error { - dir, cleanup, err := makeTempDir("unlink-unlinkat-enoent") - if err != nil { - return err - } - defer cleanup() - - dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0) - if err != nil { - return fmt.Errorf("open dir: %w", err) - } - defer syscall.Close(dirFD) - - nameBytes, err := syscall.BytePtrFromString("unlinkat-enoent-missing.txt") - if err != nil { - return fmt.Errorf("name bytes: %w", err) - } - for i := 0; i < 5; i++ { - _, _, errno := syscall.Syscall(syscall.SYS_UNLINKAT, uintptr(dirFD), uintptr(unsafe.Pointer(nameBytes)), 0) - runtime.KeepAlive(nameBytes) - if errno == 0 { - return fmt.Errorf("expected ENOENT, but unlinkat succeeded") - } - } - return nil -} diff --git a/integrationtests/cmd/ioworkload/scenarios.go b/integrationtests/cmd/ioworkload/scenarios.go deleted file mode 100644 index 6910314..0000000 --- a/integrationtests/cmd/ioworkload/scenarios.go +++ /dev/null @@ -1,118 +0,0 @@ -package main - -import ( - "fmt" - "os" -) - -// scenarios maps scenario names to their execution functions. -var scenarios = map[string]func() error{ - "crash": crash, - "open-basic": openBasic, - "open-creat": openCreat, - "open-by-handle-at": openByHandleAt, - "open-duration-gap": openDurationGap, - "open-enoent": openEnoent, - "open-rdonly-write": openRdonlyWrite, - "open-pid-filter": openPidFilter, - "readwrite-basic": readwriteBasic, - "readwrite-pread": readwritePread, - "readwrite-pwrite": readwritePwrite, - "readwrite-readv": readwriteReadv, - "readwrite-writev": readwriteWritev, - "readwrite-wronly-read": readwriteWronlyRead, - "readwrite-rdonly-write": readwriteRdonlyWrite, - "readwrite-pread-invalid": readwritePreadInvalid, - "readwrite-pwrite-invalid": readwritePwriteInvalid, - "close-basic": closeBasic, - "close-range": closeRange, - "close-invalid-fd": closeInvalidFd, - "close-double-close": closeDoubleClose, - "close-range-empty": closeRangeEmpty, - "dup-basic": dupBasic, - "dup-dup2": dupDup2, - "dup-dup3": dupDup3, - "dup-invalid-fd": dupInvalidFd, - "dup2-same-fd": dup2SameFd, - "dup3-invalid-flags": dup3InvalidFlags, - "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, - "rename-enoent": renameEnoent, - "rename-noreplace": renameNoreplace, - "link-basic": linkBasic, - "link-linkat": linkLinkat, - "link-symlinkat": linkSymlinkat, - "link-readlinkat": linkReadlinkat, - "link-enoent": linkEnoent, - "link-symlink-eexist": linkSymlinkEexist, - "link-readlinkat-einval": linkReadlinkatEinval, - "unlink-basic": unlinkBasic, - "unlink-unlinkat": unlinkUnlinkat, - "unlink-rmdir": unlinkRmdir, - "unlink-enoent": unlinkEnoent, - "unlink-rmdir-notempty": unlinkRmdirNotempty, - "unlink-unlinkat-enoent": unlinkUnlinkatEnoent, - "dir-basic": dirBasic, - "dir-mkdirat": dirMkdirat, - "dir-chdir": dirChdir, - "dir-getcwd": dirGetcwd, - "dir-getdents": dirGetdents, - "dir-mkdir-eexist": dirMkdirEexist, - "dir-chdir-enoent": dirChdirEnoent, - "dir-getdents-ebadf": dirGetdentsEbadf, - "stat-basic": statBasic, - "stat-fstat": statFstat, - "stat-lstat": statLstat, - "stat-newfstatat": statNewfstatat, - "stat-statx": statStatx, - "stat-access": statAccess, - "stat-faccessat": statFaccessat, - "stat-enoent": statEnoent, - "stat-access-enoent": statAccessEnoent, - "stat-fstat-ebadf": statFstatEbadf, - "sync-basic": syncBasic, - "sync-fdatasync": syncFdatasync, - "sync-sync": syncSync, - "sync-sync-file-range": syncSyncFileRange, - "sync-sync-file-range-to-eof": syncSyncFileRangeToEOF, - "sync-fsync-ebadf": syncFsyncEbadf, - "sync-fdatasync-ebadf": syncFdatasyncEbadf, - "sync-file-range-ebadf": syncFileRangeEbadf, - "mmap-basic": mmapBasic, - "mmap-msync-sync": mmapMsyncSync, - "mmap-msync-invalid-flags": mmapMsyncInvalidFlags, - "copy-file-range-basic": copyFileRangeBasic, - "copy-file-range-bad-dst-fd": copyFileRangeBadDstFd, - "truncate-basic": truncateBasic, - "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, - "iouring-enter-ebadf": iouringEnterEbadf, - "iouring-register-ebadf": iouringRegisterEbadf, -} - -func makeTempDir(prefix string) (string, func(), error) { - dir, err := os.MkdirTemp("", fmt.Sprintf("ioworkload-%s-", prefix)) - if err != nil { - return "", nil, fmt.Errorf("create temp dir: %w", err) - } - cleanup := func() { os.RemoveAll(dir) } - return dir, cleanup, nil -} - -// crash simulates a workload that fails with a non-zero exit code. -// Used to verify the test harness handles workload failures gracefully. -func crash() error { - return fmt.Errorf("intentional crash for testing") -} |
