summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/generate/classify_test.go30
-rw-r--r--internal/generate/family_test.go7
-rw-r--r--internal/generate/retclassify_test.go12
-rw-r--r--internal/generate/testdata.go41
4 files changed, 89 insertions, 1 deletions
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go
index 7dddab7..d150bdf 100644
--- a/internal/generate/classify_test.go
+++ b/internal/generate/classify_test.go
@@ -40,6 +40,35 @@ func TestClassifyFdWrite(t *testing.T) {
}
}
+// TestClassifyFdLseek pins lseek(2) as a single-fd KindFd event. lseek's
+// tracepoint exposes a generic "fd" field of an fd-like type at args[0], so it
+// classifies via classifyByField exactly like read/write — the fd is captured
+// from args[0], while the off_t offset and whence args are ignored. The return
+// value (resulting file offset) is asserted UNCLASSIFIED separately in
+// retclassify_test.go (TestClassifyRetUnclassified) and end-to-end in
+// TestClassifyRetExitLseek below.
+func TestClassifyFdLseek(t *testing.T) {
+ r := classifyFromData(t, FormatLseek)
+ if r.Kind != KindFd {
+ t.Errorf("lseek: got kind %d, want KindFd", r.Kind)
+ }
+}
+
+// TestClassifyRetExitLseek locks in that sys_exit_lseek is a plain ret_event
+// (KindRet) and that ClassifyRet keeps it UNCLASSIFIED. lseek returns the new
+// file OFFSET (bytes-from-start), not a transferred byte count, so it must
+// never be classified as READ/WRITE/TRANSFER — doing so would inflate I/O byte
+// accounting.
+func TestClassifyRetExitLseek(t *testing.T) {
+ r := classifyFromData(t, FormatExitLseek)
+ if r.Kind != KindRet {
+ t.Errorf("lseek exit: got kind %d, want KindRet", r.Kind)
+ }
+ if got := ClassifyRet("sys_exit_lseek"); got != Unclassified {
+ t.Errorf("lseek exit: ClassifyRet = %q, want UNCLASSIFIED", got)
+ }
+}
+
func TestClassifyFdPidfdGetfd(t *testing.T) {
r := classifyFromData(t, FormatPidfdGetfd)
if r.Kind != KindFd {
@@ -2072,6 +2101,7 @@ func TestClassifySyscallPairAccepted(t *testing.T) {
enterKind TracepointKind
}{
{"read", FormatRead, FormatExitRead, KindFd},
+ {"lseek", FormatLseek, FormatExitLseek, KindFd},
{"openat", FormatOpenat, FormatExitOpenat, KindOpen},
{"rename", FormatRename, FormatExitRename, KindName},
{"close", FormatClose, FormatExitClose, KindFd},
diff --git a/internal/generate/family_test.go b/internal/generate/family_test.go
index 9dd9a8b..9fe3ecf 100644
--- a/internal/generate/family_test.go
+++ b/internal/generate/family_test.go
@@ -92,6 +92,13 @@ func TestClassifySyscallFamily(t *testing.T) {
{"sys_exit_times", FamilyTime},
{"sys_enter_sched_yield", FamilySched},
{"sys_enter_openat", FamilyFS},
+ // lseek(2) repositions the file offset of an open fd; it is a per-file
+ // positioning syscall and shares FamilyFS with its fd-based I/O siblings
+ // read/write/fsync (also FamilyFS). Assert both enter and exit so a stray
+ // reclassification trips this test. Keep in sync with the FS list in
+ // docs/syscall-tracing-plan.md.
+ {"sys_enter_lseek", FamilyFS},
+ {"sys_exit_lseek", FamilyFS},
// access(2) checks the calling process's permissions for a file named by
// a real filesystem path (pathname at args[0]; no dirfd). Its siblings
// faccessat(2)/faccessat2(2) perform the same check relative to a dirfd
diff --git a/internal/generate/retclassify_test.go b/internal/generate/retclassify_test.go
index ad548b2..25c5e71 100644
--- a/internal/generate/retclassify_test.go
+++ b/internal/generate/retclassify_test.go
@@ -42,8 +42,18 @@ func TestClassifyRetTransfer(t *testing.T) {
func TestClassifyRetUnclassified(t *testing.T) {
unclassified := []string{
"openat", "close", "rename", "unlink", "fcntl", "dup", "dup2", "dup3",
- "mkdir", "rmdir", "chmod", "chown", "chdir", "stat", "lseek",
+ "mkdir", "rmdir", "chmod", "chown", "chdir", "stat",
"truncate", "fallocate", "mmap", "fsync", "flock", "recvmmsg", "sendmmsg",
+ // lseek(2) repositions the file offset of args[0]'s fd and returns the
+ // RESULTING file OFFSET (off_t, bytes from the start of the file) on
+ // success, or -1 on error. That return is a file POSITION, NOT a count of
+ // bytes transferred — so its exit must stay UNCLASSIFIED (plain
+ // ret_event). Classifying it as READ/WRITE/TRANSFER would wrongly add the
+ // absolute file position into I/O byte totals and grossly inflate them
+ // (e.g. an lseek to offset 1 GiB would look like a 1 GiB transfer). lseek
+ // is a KindFd FamilyFS syscall like its read/write/fsync siblings; only
+ // read/write actually move bytes and carry a byte-count return.
+ "lseek",
// syncfs(2) returns int 0/-1 (no byte count); it commits the filesystem
// containing args[0]'s fd and transfers no bytes, so its exit must stay
// UNCLASSIFIED (plain ret_event), like its fsync/fdatasync siblings.
diff --git a/internal/generate/testdata.go b/internal/generate/testdata.go
index 3a5920f..c2d47ba 100644
--- a/internal/generate/testdata.go
+++ b/internal/generate/testdata.go
@@ -124,6 +124,47 @@ format:
print fmt: "0x%lx", REC->ret
`
+// FormatLseek mirrors the real sys_enter_lseek tracepoint
+// (lseek(unsigned int fd, off_t offset, unsigned int whence)). The first
+// field is a generic "fd" of an fd-like type, so ClassifyFormat resolves it
+// to KindFd via classifyByField (fd at args[0]) — exactly like read/write.
+// The off_t offset and whence args are not captured by an fd_event.
+const FormatLseek = `name: sys_enter_lseek
+ID: 853
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:unsigned int fd; offset:16; size:8; signed:0;
+ field:off_t offset; offset:24; size:8; signed:0;
+ field:unsigned int whence; offset:32; size:8; signed:0;
+
+print fmt: "fd: 0x%08lx, offset: 0x%08lx, whence: 0x%08lx", ((unsigned long)(REC->fd)), ((unsigned long)(REC->offset)), ((unsigned long)(REC->whence))
+`
+
+// FormatExitLseek mirrors sys_exit_lseek. lseek returns the RESULTING FILE
+// OFFSET (bytes from the start of the file), or -1 on error — a file
+// position, NOT a count of bytes transferred. So its exit is a plain
+// ret_event and stays UNCLASSIFIED; it must never be ReadClassified /
+// WriteClassified / TransferClassified, which would wrongly inflate I/O byte
+// totals.
+const FormatExitLseek = `name: sys_exit_lseek
+ID: 852
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+
+print fmt: "0x%lx", REC->ret
+`
+
const FormatClose = `name: sys_enter_close
ID: 778
format: