summaryrefslogtreecommitdiff
path: root/integrationtests/cmd/ioworkload/scenario_unlink.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_unlink.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_unlink.go')
-rw-r--r--integrationtests/cmd/ioworkload/scenario_unlink.go185
1 files changed, 185 insertions, 0 deletions
diff --git a/integrationtests/cmd/ioworkload/scenario_unlink.go b/integrationtests/cmd/ioworkload/scenario_unlink.go
new file mode 100644
index 0000000..0d45710
--- /dev/null
+++ b/integrationtests/cmd/ioworkload/scenario_unlink.go
@@ -0,0 +1,185 @@
+package main
+
+import (
+ "fmt"
+ "path/filepath"
+ "runtime"
+ "syscall"
+ "unsafe"
+)
+
+// unlinkBasic creates a file and unlinks it via raw SYS_UNLINK.
+// We use the raw syscall because Go's syscall.Unlink wraps unlinkat on amd64.
+func unlinkBasic() error {
+ dir, cleanup, err := makeTempDir("unlink-basic")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "unlinkme.txt")
+ fd, err := syscall.Open(path, 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)
+ }
+
+ pathBytes, err := syscall.BytePtrFromString(path)
+ if err != nil {
+ return fmt.Errorf("path bytes: %w", err)
+ }
+ _, _, errno := syscall.Syscall(syscall.SYS_UNLINK, uintptr(unsafe.Pointer(pathBytes)), 0, 0)
+ runtime.KeepAlive(pathBytes)
+ if errno != 0 {
+ return fmt.Errorf("unlink: %w", errno)
+ }
+ return nil
+}
+
+// unlinkUnlinkat creates a file and unlinks it via unlinkat(2).
+func unlinkUnlinkat() error {
+ dir, cleanup, err := makeTempDir("unlink-unlinkat")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "unlinkat-file.txt")
+ fd, err := syscall.Open(path, 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("unlinkat-file.txt")
+ if err != nil {
+ return fmt.Errorf("name bytes: %w", err)
+ }
+ _, _, errno := syscall.Syscall(syscall.SYS_UNLINKAT, uintptr(dirFD), uintptr(unsafe.Pointer(nameBytes)), 0)
+ runtime.KeepAlive(nameBytes)
+ if errno != 0 {
+ return fmt.Errorf("unlinkat: %w", errno)
+ }
+ return nil
+}
+
+// unlinkRmdir creates a directory and removes it via raw SYS_RMDIR.
+// We use the raw syscall because Go's syscall.Rmdir wraps unlinkat on amd64.
+func unlinkRmdir() error {
+ dir, cleanup, err := makeTempDir("unlink-rmdir")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ subDir := filepath.Join(dir, "rmdir-me")
+ if err := syscall.Mkdir(subDir, 0o755); err != nil {
+ return fmt.Errorf("mkdir: %w", err)
+ }
+
+ pathBytes, err := syscall.BytePtrFromString(subDir)
+ if err != nil {
+ return fmt.Errorf("path bytes: %w", err)
+ }
+ _, _, errno := syscall.Syscall(syscall.SYS_RMDIR, uintptr(unsafe.Pointer(pathBytes)), 0, 0)
+ runtime.KeepAlive(pathBytes)
+ if errno != 0 {
+ return fmt.Errorf("rmdir: %w", errno)
+ }
+ return nil
+}
+
+// unlinkEnoent attempts to unlink a nonexistent file via raw SYS_UNLINK.
+// The syscall fails with ENOENT, but ior captures the tracepoint on entry.
+func unlinkEnoent() error {
+ dir, cleanup, err := makeTempDir("unlink-enoent")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "unlink-enoent-missing.txt")
+ pathBytes, err := syscall.BytePtrFromString(path)
+ if err != nil {
+ return fmt.Errorf("path bytes: %w", err)
+ }
+ _, _, errno := syscall.Syscall(syscall.SYS_UNLINK, uintptr(unsafe.Pointer(pathBytes)), 0, 0)
+ runtime.KeepAlive(pathBytes)
+ if errno == 0 {
+ return fmt.Errorf("expected ENOENT, but unlink succeeded")
+ }
+ return nil
+}
+
+// unlinkRmdirNotempty attempts to rmdir a non-empty directory via raw SYS_RMDIR.
+// The syscall fails with ENOTEMPTY, but ior captures the tracepoint on entry.
+func unlinkRmdirNotempty() error {
+ dir, cleanup, err := makeTempDir("unlink-rmdir-notempty")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ subDir := filepath.Join(dir, "rmdir-notempty")
+ if err := syscall.Mkdir(subDir, 0o755); err != nil {
+ return fmt.Errorf("mkdir: %w", err)
+ }
+
+ // Create a file inside so the directory is non-empty.
+ filePath := filepath.Join(subDir, "blocker.txt")
+ fd, err := syscall.Open(filePath, syscall.O_RDWR|syscall.O_CREAT, 0o644)
+ if err != nil {
+ return fmt.Errorf("create blocker: %w", err)
+ }
+ if err := syscall.Close(fd); err != nil {
+ return fmt.Errorf("close blocker: %w", err)
+ }
+
+ pathBytes, err := syscall.BytePtrFromString(subDir)
+ if err != nil {
+ return fmt.Errorf("path bytes: %w", err)
+ }
+ _, _, errno := syscall.Syscall(syscall.SYS_RMDIR, uintptr(unsafe.Pointer(pathBytes)), 0, 0)
+ runtime.KeepAlive(pathBytes)
+ if errno == 0 {
+ return fmt.Errorf("expected ENOTEMPTY, but rmdir succeeded")
+ }
+ return nil
+}
+
+// unlinkUnlinkatEnoent attempts to unlinkat a nonexistent file.
+// The syscall fails with ENOENT, but ior captures the tracepoint on entry.
+func unlinkUnlinkatEnoent() error {
+ dir, cleanup, err := makeTempDir("unlink-unlinkat-enoent")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ 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("unlinkat-enoent-missing.txt")
+ if err != nil {
+ return fmt.Errorf("name bytes: %w", err)
+ }
+ _, _, errno := syscall.Syscall(syscall.SYS_UNLINKAT, uintptr(dirFD), uintptr(unsafe.Pointer(nameBytes)), 0)
+ runtime.KeepAlive(nameBytes)
+ if errno == 0 {
+ return fmt.Errorf("expected ENOENT, but unlinkat succeeded")
+ }
+ return nil
+}