summaryrefslogtreecommitdiff
path: root/integrationtests/cmd/ioworkload/scenario_stat.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-21 22:03:57 +0200
committerPaul Buetow <paul@buetow.org>2026-02-21 22:03:57 +0200
commit3ec3c117bb280a377fea1a3eef84a70e2a3d4150 (patch)
treeb017e330eeaa1cafe95d2a730675b46342afd92a /integrationtests/cmd/ioworkload/scenario_stat.go
parent311b827599251d8d68526293815e8d4dcba632c8 (diff)
Split ioworkload scenarios.go into per-category files
Split the 2494-line scenarios.go monolith into 14 focused files by syscall category: open, readwrite, close, dup, fcntl, rename, link, unlink, dir, stat, sync, truncate, iouring, plus the slimmed-down scenarios.go containing only the registry map, makeTempDir, and crash. Extracted rawLink, rawSymlink, rawReadlink helpers in scenario_link.go to reduce code duplication in linkBasic. Task: #349 (Go best practices: split oversized scenarios file) Amp-Thread-ID: https://ampcode.com/threads/T-019c81c6-e1b6-747a-9144-40f6be997e60 Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'integrationtests/cmd/ioworkload/scenario_stat.go')
-rw-r--r--integrationtests/cmd/ioworkload/scenario_stat.go274
1 files changed, 274 insertions, 0 deletions
diff --git a/integrationtests/cmd/ioworkload/scenario_stat.go b/integrationtests/cmd/ioworkload/scenario_stat.go
new file mode 100644
index 0000000..ce9807d
--- /dev/null
+++ b/integrationtests/cmd/ioworkload/scenario_stat.go
@@ -0,0 +1,274 @@
+package main
+
+import (
+ "fmt"
+ "path/filepath"
+ "runtime"
+ "syscall"
+ "unsafe"
+)
+
+const (
+ sysStatx = 332
+ rOK = 0x4 // R_OK
+ statxBasicMask = 0x07ff // STATX_BASIC_STATS
+ atFDCwd = -100 // AT_FDCWD
+)
+
+// 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
+ _, _, 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)
+ }
+ _, _, 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
+ _, _, 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")
+ }
+ return nil
+}