diff options
| -rw-r--r-- | cmd/ioworkload/scenario_readwrite.go | 97 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 2 | ||||
| -rw-r--r-- | integrationtests/readwrite_test.go | 50 |
3 files changed, 148 insertions, 1 deletions
diff --git a/cmd/ioworkload/scenario_readwrite.go b/cmd/ioworkload/scenario_readwrite.go index c676b90..c69e588 100644 --- a/cmd/ioworkload/scenario_readwrite.go +++ b/cmd/ioworkload/scenario_readwrite.go @@ -65,6 +65,103 @@ func readwritePread() error { return nil } +// readwritePreadv opens a file, writes data, then reads it back via preadv +// (positional vectored read). preadv returns the number of bytes read and is +// READ_CLASSIFIED, so the scenario reads a known payload to validate +// end-to-end byte attribution for the positional vectored read variant. +func readwritePreadv() error { + dir, cleanup, err := makeTempDir("readwrite-preadv") + if err != nil { + return err + } + defer cleanup() + + path := filepath.Join(dir, "preadvfile.txt") + fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) + if err != nil { + return fmt.Errorf("open: %w", err) + } + defer syscall.Close(fd) + + data := []byte("preadv test data") + if _, err := syscall.Write(fd, data); err != nil { + return fmt.Errorf("write: %w", err) + } + + buf1 := make([]byte, 6) + buf2 := make([]byte, 10) + iovs := []syscall.Iovec{ + {Base: &buf1[0], Len: uint64(len(buf1))}, + {Base: &buf2[0], Len: uint64(len(buf2))}, + } + // preadv(fd, iov, iovcnt, offset) reads at the given offset (0) without + // changing the file position. + _, _, errno := syscall.Syscall6(syscall.SYS_PREADV, uintptr(fd), uintptr(unsafe.Pointer(&iovs[0])), uintptr(len(iovs)), 0, 0, 0) + runtime.KeepAlive(buf1) + runtime.KeepAlive(buf2) + if errno != 0 { + return fmt.Errorf("preadv: %w", errno) + } + return nil +} + +// readwritePreadv2 opens a file, writes data, then reads it back via preadv2 +// (positional vectored read with flags). Like preadv it returns the bytes read +// and is READ_CLASSIFIED; the scenario reads a known payload to validate +// end-to-end byte attribution. +func readwritePreadv2() error { + dir, cleanup, err := makeTempDir("readwrite-preadv2") + if err != nil { + return err + } + defer cleanup() + + path := filepath.Join(dir, "preadv2file.txt") + fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) + if err != nil { + return fmt.Errorf("open: %w", err) + } + defer syscall.Close(fd) + + data := []byte("preadv2 test data") + if _, err := syscall.Write(fd, data); err != nil { + return fmt.Errorf("write: %w", err) + } + + buf1 := make([]byte, 7) + buf2 := make([]byte, 10) + iovs := []syscall.Iovec{ + {Base: &buf1[0], Len: uint64(len(buf1))}, + {Base: &buf2[0], Len: uint64(len(buf2))}, + } + nr, err := preadv2SyscallNr(runtime.GOARCH) + if err != nil { + return err + } + // preadv2(fd, iov, iovcnt, pos_l, pos_h, flags): offset 0, no flags. + _, _, errno := syscall.Syscall6(nr, uintptr(fd), uintptr(unsafe.Pointer(&iovs[0])), uintptr(len(iovs)), 0, 0, 0) + runtime.KeepAlive(buf1) + runtime.KeepAlive(buf2) + if errno != 0 { + return fmt.Errorf("preadv2: %w", errno) + } + return nil +} + +// preadv2SyscallNr returns the preadv2(2) syscall number for the given GOARCH. +// Go's syscall package lacks a SYS_PREADV2 constant, so the architecture +// numbers are provided explicitly (matching securitySyscallNumbers' pattern). +func preadv2SyscallNr(arch string) (uintptr, error) { + switch arch { + case "amd64": + return 327, nil + case "arm64": + return 286, nil + default: + return 0, fmt.Errorf("preadv2 syscall number not defined for GOARCH=%s", arch) + } +} + // readwritePwrite opens a file and writes data via pwrite64. func readwritePwrite() error { dir, cleanup, err := makeTempDir("readwrite-pwrite") diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go index 821c8b2..08ac7a3 100644 --- a/cmd/ioworkload/scenarios.go +++ b/cmd/ioworkload/scenarios.go @@ -18,6 +18,8 @@ var scenarios = map[string]func() error{ "open-pid-filter": openPidFilter, "readwrite-basic": readwriteBasic, "readwrite-pread": readwritePread, + "readwrite-preadv": readwritePreadv, + "readwrite-preadv2": readwritePreadv2, "readwrite-pwrite": readwritePwrite, "readwrite-readv": readwriteReadv, "readwrite-writev": readwriteWritev, diff --git a/integrationtests/readwrite_test.go b/integrationtests/readwrite_test.go index 69f60ea..7a479b7 100644 --- a/integrationtests/readwrite_test.go +++ b/integrationtests/readwrite_test.go @@ -31,7 +31,11 @@ func TestReadwriteBasic(t *testing.T) { } func TestReadwritePread(t *testing.T) { - runScenario(t, "readwrite-pread", []ExpectedEvent{ + // pread64 returns the number of bytes read (READ_CLASSIFIED), so a + // successful positional read must attribute the payload byte count + // end-to-end, mirroring the pwrite64 byte-count assertion below. + const payloadLen = uint64(len("pread test data")) + result, _ := runScenarioResult(t, "readwrite-pread", []ExpectedEvent{ { PathContains: "preadfile.txt", Tracepoint: "enter_pread64", @@ -39,6 +43,11 @@ func TestReadwritePread(t *testing.T) { MinCount: 1, }, }) + assertEventBytesAtLeast(t, result, ExpectedEvent{ + PathContains: "preadfile.txt", + Tracepoint: "enter_pread64", + Comm: "ioworkload", + }, payloadLen) } func TestReadwritePwrite(t *testing.T) { @@ -57,6 +66,45 @@ func TestReadwritePwrite(t *testing.T) { }, 1) } +func TestReadwritePreadv(t *testing.T) { + // preadv is the positional vectored read sibling of pread64/readv and is + // READ_CLASSIFIED, so a successful read must attribute the payload bytes + // end-to-end. + const payloadLen = uint64(len("preadv test data")) + result, _ := runScenarioResult(t, "readwrite-preadv", []ExpectedEvent{ + { + PathContains: "preadvfile.txt", + Tracepoint: "enter_preadv", + Comm: "ioworkload", + MinCount: 1, + }, + }) + assertEventBytesAtLeast(t, result, ExpectedEvent{ + PathContains: "preadvfile.txt", + Tracepoint: "enter_preadv", + Comm: "ioworkload", + }, payloadLen) +} + +func TestReadwritePreadv2(t *testing.T) { + // preadv2 is the positional vectored read variant with flags; like preadv + // it is READ_CLASSIFIED and must attribute the payload bytes end-to-end. + const payloadLen = uint64(len("preadv2 test data")) + result, _ := runScenarioResult(t, "readwrite-preadv2", []ExpectedEvent{ + { + PathContains: "preadv2file.txt", + Tracepoint: "enter_preadv2", + Comm: "ioworkload", + MinCount: 1, + }, + }) + assertEventBytesAtLeast(t, result, ExpectedEvent{ + PathContains: "preadv2file.txt", + Tracepoint: "enter_preadv2", + Comm: "ioworkload", + }, payloadLen) +} + func TestReadwriteReadv(t *testing.T) { result, _ := runScenarioResult(t, "readwrite-readv", []ExpectedEvent{ { |
