diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-31 10:27:26 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-31 10:27:26 +0300 |
| commit | 783c551f8e7f293b723e44386e50c4739075e2d4 (patch) | |
| tree | 4e8f752123893483e1813b359e062fec426ea40f /cmd | |
| parent | 7e58a0df8b994e4a3a53aec88dc05fe7f1b079bf (diff) | |
test(openat2): add end-to-end integration coverage for openat2
openat2(2) is the one open-family syscall with a structurally distinct
argument layout: flags/mode live inside the open_how struct (args[2]),
not as a plain int, and args[3] is the struct size. The tracer correctly
reads the path from args[1] and omits flags (ev->flags = -1) rather than
misreading the struct ptr/size, and registers the returned fd->path
mapping via the shared handleOpenExit path. This was verified by
inspection but had no integration scenario, unlike open/openat/creat/
open_by_handle_at.
Add an open-openat2 ioworkload scenario issuing the raw openat2 syscall
(Go has no wrapper and routes Open through openat) and a TestOpenOpenat2
integration test asserting the enter_openat2 tracepoint captures the
path. Verified passing as root.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/ioworkload/scenario_open.go | 59 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 1 |
2 files changed, 60 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_open.go b/cmd/ioworkload/scenario_open.go index 1aebec1..8a18e35 100644 --- a/cmd/ioworkload/scenario_open.go +++ b/cmd/ioworkload/scenario_open.go @@ -127,6 +127,65 @@ func openPidFilter() error { return nil } +// openHow mirrors the kernel's struct open_how (uapi/linux/openat2.h): +// the resolve-time configuration that openat2(2) takes by pointer instead of +// a plain flags int. flags carries the O_* open flags, mode the creation mode +// (only meaningful with O_CREAT/O_TMPFILE), and resolve the RESOLVE_* bits. +type openHow struct { + Flags uint64 + Mode uint64 + Resolve uint64 +} + +// sysOpenat2 is the openat2(2) syscall number on amd64. +const sysOpenat2 = 437 + +// openOpenat2 opens a file via the raw openat2(2) syscall. Go's syscall package +// has no openat2 wrapper and routes Open/Openat through openat, so we must issue +// the raw syscall to actually exercise the openat2 tracepoint. The path lives at +// args[1] (after dirfd at args[0]); the open flags/mode live INSIDE the open_how +// struct pointed to by args[2], not as a plain int argument — ior reads the path +// from args[1] and intentionally does not decode flags out of the struct. +func openOpenat2() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + dir, cleanup, err := makeTempDir("open-openat2") + if err != nil { + return err + } + defer cleanup() + + path := filepath.Join(dir, "openat2file.txt") + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return fmt.Errorf("path bytes: %w", err) + } + + how := openHow{Flags: uint64(syscall.O_RDWR | syscall.O_CREAT), Mode: 0o644} + // Use a runtime int variable so the negative AT_FDCWD survives the uintptr + // conversion: converting the negative constant directly overflows uintptr. + dirfd := _AT_FDCWD + fd, _, errno := syscall.Syscall6( + sysOpenat2, + uintptr(dirfd), + uintptr(unsafe.Pointer(pathBytes)), + uintptr(unsafe.Pointer(&how)), + unsafe.Sizeof(how), + 0, 0, + ) + runtime.KeepAlive(pathBytes) + runtime.KeepAlive(&how) + if errno != 0 { + return fmt.Errorf("openat2: %w", errno) + } + return syscall.Close(int(fd)) +} + +// _AT_FDCWD is the special dirfd value meaning "relative to the current working +// directory"; openat2 with an absolute path ignores it but still requires it. +const _AT_FDCWD int = -100 + // 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 diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go index 7f42106..821c8b2 100644 --- a/cmd/ioworkload/scenarios.go +++ b/cmd/ioworkload/scenarios.go @@ -9,6 +9,7 @@ import ( var scenarios = map[string]func() error{ "crash": crash, "open-basic": openBasic, + "open-openat2": openOpenat2, "open-creat": openCreat, "open-by-handle-at": openByHandleAt, "open-duration-gap": openDurationGap, |
