diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-29 17:22:56 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-29 17:22:56 +0300 |
| commit | 4df620818f603564b2e6f4def6052baa72b2fbb1 (patch) | |
| tree | 133ea6c3d95304dc823319aaecab8dca8d8866da /cmd | |
| parent | 8e524f9ca7f8c105f395bfa111f1b052206bc836 (diff) | |
utime/utimes: classify as FS family (fix Misc misclassification)
utime(2) and utimes(2) change a file's access/modification times by a real
filesystem path (filename at args[0]). The path was already captured
(KindPathname), but both syscalls fell through to FamilyMisc instead of
joining their siblings utimensat/futimesat in FamilyFS. Add them to
fsSyscalls and regenerate; the only generated change is trace IDs
1034-1037 flipping FamilyMisc -> FamilyFS.
Lock-in coverage:
- family_test.go asserts utime/utimes/utimensat/futimesat are all FamilyFS.
- classify_test.go + FormatUtime fixture assert utime is KindPathname with
PathnameField "filename" (path captured even though it is a char* string,
unlike domain/host name args).
- New ioworkload scenarios utime-basic/utimes/enoent and integration tests
TestUtimeBasic/Utimes/Enoent verify the path is captured at runtime,
including on the ENOENT error path.
Docs updated: moved utime/utimes from Misc to FS in
docs/syscall-tracing-plan.md to keep the drift tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/ioworkload/scenario_utime.go | 138 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 3 |
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(×)), + 0, + ) + runtime.KeepAlive(pathBytes) + runtime.KeepAlive(×) + 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(×[0])), + 0, + ) + runtime.KeepAlive(pathBytes) + runtime.KeepAlive(×) + 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(×)), + 0, + ) + runtime.KeepAlive(pathBytes) + runtime.KeepAlive(×) + 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, |
