summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/ioworkload/scenario_utime.go138
-rw-r--r--cmd/ioworkload/scenarios.go3
2 files changed, 141 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_utime.go b/cmd/ioworkload/scenario_utime.go
new file mode 100644
index 0000000..3f86a54
--- /dev/null
+++ b/cmd/ioworkload/scenario_utime.go
@@ -0,0 +1,138 @@
+package main
+
+import (
+ "fmt"
+ "path/filepath"
+ "runtime"
+ "syscall"
+ "time"
+ "unsafe"
+)
+
+// utimbuf mirrors struct utimbuf from <utime.h>: the actime/modtime pair that
+// utime(2) reads from userspace. It is passed by pointer as the second arg.
+type utimbuf struct {
+ actime int64 // access time (seconds)
+ modtime int64 // modification time (seconds)
+}
+
+// timeval mirrors struct timeval used by utimes(2): a 2-element array of
+// {tv_sec, tv_usec} for the new access and modification times.
+type timeval struct {
+ tvSec int64
+ tvUsec int64
+}
+
+// utimeBasic creates a file and changes its timestamps via raw SYS_UTIME.
+// utime(2) takes a real filesystem path at args[0] ("filename"), so ior must
+// capture it as a path event (KindPathname) and tag it FamilyFS, matching its
+// siblings utimensat/futimesat. We use the raw syscall because Go's os.Chtimes
+// wraps utimensat on amd64, not utime.
+func utimeBasic() error {
+ dir, cleanup, err := makeTempDir("utime-basic")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "utimefile.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)
+ }
+ times := utimbuf{actime: 1000000000, modtime: 1000000000}
+ _, _, errno := syscall.Syscall(
+ syscall.SYS_UTIME,
+ uintptr(unsafe.Pointer(pathBytes)),
+ uintptr(unsafe.Pointer(&times)),
+ 0,
+ )
+ runtime.KeepAlive(pathBytes)
+ runtime.KeepAlive(&times)
+ if errno != 0 {
+ return fmt.Errorf("utime: %w", errno)
+ }
+ return nil
+}
+
+// utimeUtimes creates a file and changes its timestamps via raw SYS_UTIMES.
+// utimes(2) is utime's microsecond-resolution sibling and likewise takes a
+// real path at args[0] ("filename"), so it is path-classified and FamilyFS.
+func utimeUtimes() error {
+ dir, cleanup, err := makeTempDir("utime-utimes")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "utimesfile.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)
+ }
+ times := [2]timeval{
+ {tvSec: 1000000000, tvUsec: 0},
+ {tvSec: 1000000000, tvUsec: 0},
+ }
+ _, _, errno := syscall.Syscall(
+ syscall.SYS_UTIMES,
+ uintptr(unsafe.Pointer(pathBytes)),
+ uintptr(unsafe.Pointer(&times[0])),
+ 0,
+ )
+ runtime.KeepAlive(pathBytes)
+ runtime.KeepAlive(&times)
+ if errno != 0 {
+ return fmt.Errorf("utimes: %w", errno)
+ }
+ return nil
+}
+
+// utimeEnoent calls raw SYS_UTIME on a nonexistent path. The syscall fails
+// with ENOENT, but ior still captures the enter_utime tracepoint because the
+// filename path is read on syscall entry. This locks in that the path is
+// captured even on the error path.
+func utimeEnoent() error {
+ dir, cleanup, err := makeTempDir("utime-enoent")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "utime-enoent-missing.txt")
+ pathBytes, err := syscall.BytePtrFromString(path)
+ if err != nil {
+ return fmt.Errorf("path bytes: %w", err)
+ }
+ times := utimbuf{actime: 1000000000, modtime: 1000000000}
+ // Retry a few times to reduce dropped-event flakiness under high parallelism.
+ for i := 0; i < 5; i++ {
+ _, _, errno := syscall.Syscall(
+ syscall.SYS_UTIME,
+ uintptr(unsafe.Pointer(pathBytes)),
+ uintptr(unsafe.Pointer(&times)),
+ 0,
+ )
+ runtime.KeepAlive(pathBytes)
+ runtime.KeepAlive(&times)
+ if errno == 0 {
+ return fmt.Errorf("expected ENOENT, but utime succeeded")
+ }
+ if i < 4 {
+ time.Sleep(statRetryDelay)
+ }
+ }
+ return nil
+}
diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go
index 534c9b4..b37f272 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -94,6 +94,9 @@ var scenarios = map[string]func() error{
"stat-enoent": statEnoent,
"stat-access-enoent": statAccessEnoent,
"stat-fstat-ebadf": statFstatEbadf,
+ "utime-basic": utimeBasic,
+ "utime-utimes": utimeUtimes,
+ "utime-enoent": utimeEnoent,
"sync-basic": syncBasic,
"sync-fdatasync": syncFdatasync,
"sync-sync": syncSync,