diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-21 22:03:57 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-21 22:03:57 +0200 |
| commit | 3ec3c117bb280a377fea1a3eef84a70e2a3d4150 (patch) | |
| tree | b017e330eeaa1cafe95d2a730675b46342afd92a /integrationtests/cmd/ioworkload/scenario_link.go | |
| parent | 311b827599251d8d68526293815e8d4dcba632c8 (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_link.go')
| -rw-r--r-- | integrationtests/cmd/ioworkload/scenario_link.go | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/integrationtests/cmd/ioworkload/scenario_link.go b/integrationtests/cmd/ioworkload/scenario_link.go new file mode 100644 index 0000000..bb16984 --- /dev/null +++ b/integrationtests/cmd/ioworkload/scenario_link.go @@ -0,0 +1,385 @@ +package main + +import ( + "fmt" + "path/filepath" + "runtime" + "syscall" + "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) + } + + _, _, errno := syscall.Syscall( + syscall.SYS_LINK, + uintptr(unsafe.Pointer(srcBytes)), + uintptr(unsafe.Pointer(dstBytes)), + 0, + ) + runtime.KeepAlive(srcBytes) + runtime.KeepAlive(dstBytes) + if errno == 0 { + return fmt.Errorf("expected ENOENT, but link succeeded") + } + 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 +} |
