summaryrefslogtreecommitdiff
path: root/integrationtests
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-21 21:16:01 +0200
committerPaul Buetow <paul@buetow.org>2026-02-21 21:16:01 +0200
commit04500a92433396ca17cb879f4a40e4399dc06ff9 (patch)
tree2b1de6720fb6929919dc01d32a9fcb2d025f0321 /integrationtests
parent603972340549cd3265f184457ee072fd8915e27b (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.go95
-rw-r--r--integrationtests/rename_test.go22
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,
+ },
+ })
+}