diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-29 17:53:10 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-29 17:53:10 +0300 |
| commit | b86b817594ac8a4dc7fe2b80649df2c7a62f1b59 (patch) | |
| tree | bfe09e28771102384cb3f04cae68f7bc907d8eab /internal | |
| parent | 2d87d048eb0c629990445eac81229424fe50c215 (diff) | |
test(fallocate): lock in FS family, KindFd enter, UNCLASSIFIED ret
Audit of fallocate(2) found the tracing correct and consistent with its
fd-based siblings, so add lock-in tests rather than fixing anything:
- fallocate(int fd, int mode, off_t offset, off_t len) returns int 0/-1
(a status code, NOT a transferred byte count). Its exit must stay a
plain ret_event with ret_type UNCLASSIFIED so it is never mistaken for
a READ/WRITE/TRANSFER byte count.
- The enter tracepoint carries a leading fd field (args[0]); only fd is
captured into a fd_event (KindFd), matching fadvise64/ftruncate/
sync_file_range which likewise drop their trailing offset/len/advice
args.
- fallocate belongs to FamilyFS alongside fadvise64/ftruncate/
sync_file_range.
TestClassifyFallocateEnterFd and TestClassifyExitFallocateUnclassifiedRet
assert the per-syscall behavior; TestClassifySyscallFamily now also
covers fallocate/fadvise64/ftruncate so a stray reclassification trips a
test. No classification logic or generated artifacts changed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/generate/classify_test.go | 55 | ||||
| -rw-r--r-- | internal/generate/family_test.go | 12 |
2 files changed, 67 insertions, 0 deletions
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go index 41e1bf8..92b6e58 100644 --- a/internal/generate/classify_test.go +++ b/internal/generate/classify_test.go @@ -368,6 +368,61 @@ func TestClassifyExitSyncfs(t *testing.T) { } } +// TestClassifyFallocateEnterFd locks in that the fallocate enter tracepoint is +// classified as KindFd with the fd captured at args[0]. +// +// int fallocate(int fd, int mode, off_t offset, off_t len) +// +// fallocate(2) manipulates the allocated disk space for the file referred to +// by fd (args[0]); the remaining mode/offset/len args are NOT captured, exactly +// like its fd-based siblings fadvise64(2)/ftruncate(2)/sync_file_range(2) which +// also carry trailing offset/len/advice args but only record args[0]. The +// leading "fd" external field must select KindFd so the generated +// handle_sys_enter_fallocate emits ev->fd = ctx->args[0] into a fd_event. +func TestClassifyFallocateEnterFd(t *testing.T) { + f := &Format{ + Name: "sys_enter_fallocate", + ExternalFields: []Field{ + {Type: "long", Name: "__syscall_nr"}, + {Type: "int", Name: "fd"}, + {Type: "int", Name: "mode"}, + {Type: "loff_t", Name: "offset"}, + {Type: "loff_t", Name: "len"}, + }, + } + r := ClassifyFormat(f) + if r.Kind != KindFd { + t.Fatalf("enter_fallocate: got kind %d, want KindFd", r.Kind) + } + // fd is the first real argument (args[0]); FieldNumber skips __syscall_nr. + if got := f.FieldNumber("fd"); got != 0 { + t.Errorf("enter_fallocate: fd field number = %d, want 0 (args[0])", got) + } +} + +// TestClassifyExitFallocateUnclassifiedRet locks in that the fallocate exit +// tracepoint is classified as KindRet and Unclassified. fallocate(2) returns +// int (0 on success, -1 on error) — that return is a status code, NOT a +// transferred byte count, so its exit format carries a single "ret" field and +// must map to a plain ret_event (KindRet) whose ret_type stays UNCLASSIFIED. +// Misclassifying it as a READ/WRITE/TRANSFER byte count would be a real bug, +// since fallocate allocates space but reports no transferred bytes. +func TestClassifyExitFallocateUnclassifiedRet(t *testing.T) { + r := ClassifyFormat(&Format{ + Name: "sys_exit_fallocate", + ExternalFields: []Field{ + {Type: "long", Name: "__syscall_nr"}, + {Type: "long", Name: "ret"}, + }, + }) + if r.Kind != KindRet { + t.Fatalf("exit_fallocate: got kind %d, want KindRet", r.Kind) + } + if got := ClassifyRet("sys_exit_fallocate"); got != Unclassified { + t.Errorf("ClassifyRet(sys_exit_fallocate) = %q, want UNCLASSIFIED", got) + } +} + // TestClassifyExitGetpeername locks in that the getpeername exit tracepoint is // classified as KindRet. getpeername(2) returns int (0 on success, -1 on // error), so its exit format carries a single "ret" field and must map to a diff --git a/internal/generate/family_test.go b/internal/generate/family_test.go index 827e592..af40e7d 100644 --- a/internal/generate/family_test.go +++ b/internal/generate/family_test.go @@ -86,6 +86,18 @@ func TestClassifySyscallFamily(t *testing.T) { {"sys_enter_fsync", FamilyFS}, {"sys_enter_fdatasync", FamilyFS}, {"sys_enter_sync_file_range", FamilyFS}, + // fallocate(2) manipulates the allocated disk space (preallocate, + // punch-hole, collapse, zero, insert) for the file referred to by its + // args[0] fd; it is a per-file space-management syscall and shares + // FamilyFS with its fd-based siblings fadvise64(2) (access-pattern + // advice), ftruncate(2) (resize by fd), and sync_file_range(2) (flush a + // byte range). Assert the group so a stray reclassification of any one + // trips this test. Keep in sync with the FS list in + // docs/syscall-tracing-plan.md. + {"sys_enter_fallocate", FamilyFS}, + {"sys_exit_fallocate", FamilyFS}, + {"sys_enter_fadvise64", FamilyFS}, + {"sys_enter_ftruncate", FamilyFS}, {"sys_enter_epoll_wait", FamilyPolling}, {"sys_enter_io_uring_enter", FamilyAIO}, {"sys_enter_bpf", FamilySecurity}, |
