From 33dfe4ee3cf948444571554aa35508605fee0474 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 6 Jun 2026 09:05:36 +0300 Subject: test(retbytes): assert getdents64 READ_CLASSIFIED byte count end-to-end getdents64 was listed in retbytesTraceArgs but the retbytes workload never issued it and TestRetbytesPhaseA never asserted its exit byte count, leaving the READ_CLASSIFIED ctx->ret path for getdents/getdents64 unverified end-to-end (unlike sibling read/recvfrom/getxattrat). Add retbytesGetdents to the phase-A workload: it populates a temp directory with several files, opens it O_DIRECTORY, and re-issues getdents64 in a short window (lseek-rewind each iteration) so ior can attach and capture an enter/exit pair under parallel load. A non-empty directory guarantees ctx->ret > 0. Assert enter_getdents64 presence (MinCount) plus bytes>=1 via assertEventBytesAtLeast and a positive duration, mirroring the existing READ-classified siblings. Bytes>=1 (not an exact payload length) because dirent size varies with filename length and alignment. Coverage hardening only; classify.go getdents/getdents64=ReadClassified is correct. Co-Authored-By: Claude Opus 4.8 --- cmd/ioworkload/scenario_retbytes.go | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'cmd') diff --git a/cmd/ioworkload/scenario_retbytes.go b/cmd/ioworkload/scenario_retbytes.go index d9b2984..0382db2 100644 --- a/cmd/ioworkload/scenario_retbytes.go +++ b/cmd/ioworkload/scenario_retbytes.go @@ -6,6 +6,7 @@ import ( "path/filepath" "runtime" "syscall" + "time" "unsafe" ) @@ -39,9 +40,61 @@ func retbytesPhaseA() error { if err := retbytesTee(); err != nil { return err } + if err := retbytesGetdents(); err != nil { + return err + } return retbytesProcessVM() } +// retbytesGetdents opens a non-empty directory and reads its entries via +// getdents64(2). getdents/getdents64 are READ_CLASSIFIED, so a successful call +// on a populated directory returns ctx->ret > 0 (bytes filled into the dirent +// buffer). This drives the exit byte-count assertion in TestRetbytesPhaseA. +func retbytesGetdents() error { + dir, cleanup, err := makeTempDir("retbytes-getdents") + if err != nil { + return err + } + defer cleanup() + + // Create several files so getdents64 has substantial dirent data to return, + // guaranteeing a strictly positive byte count. + for i := 0; i < 4; i++ { + filePath := filepath.Join(dir, fmt.Sprintf("getdents-file-%d.txt", i)) + fd, openErr := syscall.Open(filePath, syscall.O_RDWR|syscall.O_CREAT, 0o644) + if openErr != nil { + return fmt.Errorf("open file: %w", openErr) + } + syscall.Close(fd) + } + + 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) + + // Re-issue getdents64 in a short window so ior has enough time to attach and + // capture an enter/exit pair under high parallel integration load. Each call + // rewinds via lseek so the directory keeps returning entries. + buf := make([]byte, 4096) + for i := 0; i < 40; i++ { + if _, err := syscall.Seek(dirFD, 0, 0); err != nil { + return fmt.Errorf("seek dir: %w", err) + } + n, _, errno := syscall.Syscall(syscall.SYS_GETDENTS64, uintptr(dirFD), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf))) + runtime.KeepAlive(buf) + if errno != 0 { + return fmt.Errorf("getdents64: %w", errno) + } + if n == 0 { + return fmt.Errorf("getdents64 returned 0 bytes on a non-empty directory") + } + time.Sleep(25 * time.Millisecond) + } + return nil +} + func retbytesSocketIO() error { fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) if err != nil { -- cgit v1.2.3