diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-21 21:16:01 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-21 21:16:01 +0200 |
| commit | 04500a92433396ca17cb879f4a40e4399dc06ff9 (patch) | |
| tree | 2b1de6720fb6929919dc01d32a9fcb2d025f0321 /integrationtests | |
| parent | 603972340549cd3265f184457ee072fd8915e27b (diff) | |
Add negative integration tests for rename syscalls (task 348)
- rename-enoent: rename nonexistent file via SYS_RENAME (ENOENT)
- rename-noreplace: renameat2 with RENAME_NOREPLACE on existing target (EEXIST)
Both verify ior captures tracepoints on entry even when syscall fails.
Amp-Thread-ID: https://ampcode.com/threads/T-019c819e-cdae-7777-9be6-992ca8a7b96b
Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'integrationtests')
| -rw-r--r-- | integrationtests/cmd/ioworkload/scenarios.go | 95 | ||||
| -rw-r--r-- | integrationtests/rename_test.go | 22 |
2 files changed, 117 insertions, 0 deletions
diff --git a/integrationtests/cmd/ioworkload/scenarios.go b/integrationtests/cmd/ioworkload/scenarios.go index 4275fb6..a186b08 100644 --- a/integrationtests/cmd/ioworkload/scenarios.go +++ b/integrationtests/cmd/ioworkload/scenarios.go @@ -47,6 +47,8 @@ var scenarios = map[string]func() error{ "rename-basic": renameBasic, "rename-renameat": renameRenameat, "rename-renameat2": renameRenameat2, + "rename-enoent": renameEnoent, + "rename-noreplace": renameNoreplace, "link-basic": linkBasic, "link-linkat": linkLinkat, "link-symlinkat": linkSymlinkat, @@ -975,6 +977,99 @@ func renameRenameat2() error { return nil } +// renameEnoent attempts to rename a nonexistent file via raw SYS_RENAME. +// The syscall fails with ENOENT, but ior captures the tracepoint on entry. +func renameEnoent() error { + dir, cleanup, err := makeTempDir("rename-enoent") + if err != nil { + return err + } + defer cleanup() + + oldPath := filepath.Join(dir, "rename-enoent-missing.txt") + newPath := filepath.Join(dir, "rename-enoent-new.txt") + + 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_RENAME, + uintptr(unsafe.Pointer(oldBytes)), + uintptr(unsafe.Pointer(newBytes)), + 0, + ) + runtime.KeepAlive(oldBytes) + runtime.KeepAlive(newBytes) + if errno == 0 { + return fmt.Errorf("expected ENOENT, but rename succeeded") + } + return nil +} + +const renameNoreplaceFlag = 1 // RENAME_NOREPLACE + +// renameNoreplace creates two files, then attempts renameat2 with +// RENAME_NOREPLACE. Because the target already exists, the syscall fails +// with EEXIST, but ior captures the tracepoint on entry. +func renameNoreplace() error { + dir, cleanup, err := makeTempDir("rename-noreplace") + if err != nil { + return err + } + defer cleanup() + + srcName := "noreplace-src.txt" + dstName := "noreplace-dst.txt" + + for _, name := range []string{srcName, dstName} { + path := filepath.Join(dir, name) + fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) + if err != nil { + return fmt.Errorf("create %s: %w", name, err) + } + if err := syscall.Close(fd); err != nil { + return fmt.Errorf("close %s: %w", name, 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) + + srcBytes, err := syscall.BytePtrFromString(srcName) + if err != nil { + return fmt.Errorf("src name bytes: %w", err) + } + dstBytes, err := syscall.BytePtrFromString(dstName) + if err != nil { + return fmt.Errorf("dst name bytes: %w", err) + } + + _, _, errno := syscall.Syscall6( + sysRenameat2, + uintptr(dirFD), + uintptr(unsafe.Pointer(srcBytes)), + uintptr(dirFD), + uintptr(unsafe.Pointer(dstBytes)), + renameNoreplaceFlag, + 0, + ) + runtime.KeepAlive(srcBytes) + runtime.KeepAlive(dstBytes) + if errno == 0 { + return fmt.Errorf("expected EEXIST, but renameat2 NOREPLACE succeeded") + } + return nil +} + // 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 diff --git a/integrationtests/rename_test.go b/integrationtests/rename_test.go index 0ae1d47..50d80cb 100644 --- a/integrationtests/rename_test.go +++ b/integrationtests/rename_test.go @@ -34,3 +34,25 @@ func TestRenameRenameat2(t *testing.T) { }, }) } + +func TestRenameEnoent(t *testing.T) { + runScenario(t, "rename-enoent", []ExpectedEvent{ + { + PathContains: "rename-enoent-missing.txt", + Tracepoint: "enter_rename", + Comm: "ioworkload", + MinCount: 1, + }, + }) +} + +func TestRenameNoreplace(t *testing.T) { + runScenario(t, "rename-noreplace", []ExpectedEvent{ + { + PathContains: "noreplace-dst.txt", + Tracepoint: "enter_renameat2", + Comm: "ioworkload", + MinCount: 1, + }, + }) +} |
