summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-28 10:43:37 +0300
committerPaul Buetow <paul@buetow.org>2026-05-28 10:43:37 +0300
commitff8774b5ce3f6b37e5152d0dc06ae46b7a36d1da (patch)
tree7224ccb001a0945216d6e30b5b9c326396ceba76 /cmd
parent99e99c6ea35ae97e84d727449f9ad7c4c0a9fa23 (diff)
close_range: honor last bound and CLOSE_RANGE_CLOEXEC flag
close_range was captured as a single-fd fd_event carrying only first, so the runtime evicted every tracked fd >= first, ignoring the last upper bound and the flags. Bounded calls wrongly dropped still-open higher fds, and CLOSE_RANGE_CLOEXEC (which keeps fds open) was treated as a full close. Reclassify close_range to the two_fd_event kind, mapping fd_a/fd_b/extra to first/last/flags. The runtime now closes only the inclusive [first, last] range (a negative last from ~0U means unbounded) and skips eviction when CLOSE_RANGE_CLOEXEC is set or the syscall fails. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/ioworkload/scenario_close.go46
-rw-r--r--cmd/ioworkload/scenarios.go1
2 files changed, 47 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_close.go b/cmd/ioworkload/scenario_close.go
index fc5044c..1be5376 100644
--- a/cmd/ioworkload/scenario_close.go
+++ b/cmd/ioworkload/scenario_close.go
@@ -64,6 +64,52 @@ func closeRange() error {
return nil
}
+// closeRangeBounded opens a contiguous block of low fds plus one higher fd,
+// then closes only the low block via close_range(first, last, 0) where last is
+// strictly below the higher fd. It writes to the higher fd afterwards to prove
+// it stayed open. This exercises close_range's upper-bound handling end to end:
+// ior must keep the higher fd tracked rather than evicting everything >= first.
+func closeRangeBounded() error {
+ dir, cleanup, err := makeTempDir("close-range-bounded")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ var lowFds []int
+ for i := range 3 {
+ path := filepath.Join(dir, fmt.Sprintf("closerangelow-%d.txt", i))
+ fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644)
+ if err != nil {
+ return fmt.Errorf("open low %d: %w", i, err)
+ }
+ lowFds = append(lowFds, fd)
+ }
+
+ highPath := filepath.Join(dir, "closerangehigh.txt")
+ highFd, err := syscall.Open(highPath, syscall.O_RDWR|syscall.O_CREAT, 0o644)
+ if err != nil {
+ return fmt.Errorf("open high: %w", err)
+ }
+ defer syscall.Close(highFd)
+
+ if highFd <= lowFds[len(lowFds)-1] {
+ return fmt.Errorf("high fd %d not above low fds %v", highFd, lowFds)
+ }
+
+ first := uintptr(lowFds[0])
+ last := uintptr(lowFds[len(lowFds)-1])
+ if _, _, errno := syscall.Syscall(sysCloseRange, first, last, 0); errno != 0 {
+ return fmt.Errorf("close_range: %w", errno)
+ }
+
+ // highFd is above last, so it must still be open and usable.
+ if _, err := syscall.Write(highFd, []byte("still-open")); err != nil {
+ return fmt.Errorf("write high fd: %w", err)
+ }
+ return nil
+}
+
// closeInvalidFd attempts to close a very high fd number that is not open.
// The close fails with EBADF, but ior should capture the enter_close tracepoint
// because arguments are read on syscall entry before the kernel returns an error.
diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go
index ba444ef..534c9b4 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -43,6 +43,7 @@ var scenarios = map[string]func() error{
"family-mixed": familyMixed,
"close-basic": closeBasic,
"close-range": closeRange,
+ "close-range-bounded": closeRangeBounded,
"close-invalid-fd": closeInvalidFd,
"close-double-close": closeDoubleClose,
"close-range-empty": closeRangeEmpty,