diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-21 21:25:27 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-21 21:25:27 +0200 |
| commit | b443f001cce8662a7fdccd4e0e7f707f7b2b47b8 (patch) | |
| tree | 017138e189d77bd537a9d4a0d22f28fb2aa5147e /integrationtests/cmd | |
| parent | 69395ffff024254b114eeba543af68cc6ca77f0c (diff) | |
Add negative integration tests for unlink syscalls (task 348)
Add three negative test scenarios for unlink syscall family:
- unlink-enoent: SYS_UNLINK on nonexistent file (ENOENT)
- unlink-rmdir-notempty: SYS_RMDIR on non-empty directory (ENOTEMPTY)
- unlink-unlinkat-enoent: SYS_UNLINKAT on nonexistent file (ENOENT)
All scenarios use raw syscalls to hit exact tracepoints and verify
ior captures them on entry even when the kernel returns an error.
Amp-Thread-ID: https://ampcode.com/threads/T-019c81a6-6612-7247-9d54-6da5b63a38b4
Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'integrationtests/cmd')
| -rw-r--r-- | integrationtests/cmd/ioworkload/scenarios.go | 92 |
1 files changed, 90 insertions, 2 deletions
diff --git a/integrationtests/cmd/ioworkload/scenarios.go b/integrationtests/cmd/ioworkload/scenarios.go index 4a96a8b..b4a153b 100644 --- a/integrationtests/cmd/ioworkload/scenarios.go +++ b/integrationtests/cmd/ioworkload/scenarios.go @@ -57,8 +57,11 @@ var scenarios = map[string]func() error{ "link-symlink-eexist": linkSymlinkEexist, "link-readlinkat-einval": linkReadlinkatEinval, "unlink-basic": unlinkBasic, - "unlink-unlinkat": unlinkUnlinkat, - "unlink-rmdir": unlinkRmdir, + "unlink-unlinkat": unlinkUnlinkat, + "unlink-rmdir": unlinkRmdir, + "unlink-enoent": unlinkEnoent, + "unlink-rmdir-notempty": unlinkRmdirNotempty, + "unlink-unlinkat-enoent": unlinkUnlinkatEnoent, "dir-basic": dirBasic, "dir-mkdirat": dirMkdirat, "dir-chdir": dirChdir, @@ -1539,6 +1542,91 @@ func unlinkRmdir() error { 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 +} + // dirBasic creates a directory via raw SYS_MKDIR, checks access, then removes it // via raw SYS_RMDIR. We use raw syscalls because Go's syscall.Mkdir wraps mkdirat // and syscall.Rmdir wraps unlinkat on amd64. |
