diff options
Diffstat (limited to 'integrationtests')
| -rw-r--r-- | integrationtests/xattr_test.go | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/integrationtests/xattr_test.go b/integrationtests/xattr_test.go index f99629d..73e74c7 100644 --- a/integrationtests/xattr_test.go +++ b/integrationtests/xattr_test.go @@ -14,6 +14,25 @@ var xattrListTraceArgs = []string{"-trace-syscalls", "listxattrat,setxattr,opena // the test is not perturbed by unrelated xattr/open traffic. var xattrRemoveTraceArgs = []string{"-trace-syscalls", "removexattrat,setxattr,openat"} +// The trace selectors below scope tracing to exactly the path/fd-based xattr +// variant under test (plus setxattr/openat used to prime the attribute), so the +// substring-based assertion matcher cannot pick up sibling tracepoints. The +// syscall filter is exact-match, so e.g. tracing "getxattr" does NOT enable +// lgetxattr/fgetxattr/getxattrat. +var ( + xattrGetTraceArgs = []string{"-trace-syscalls", "getxattr,setxattr,openat"} + xattrLgetTraceArgs = []string{"-trace-syscalls", "lgetxattr,setxattr,openat"} + xattrListPathTraceArgs = []string{"-trace-syscalls", "listxattr,setxattr,openat"} + xattrLlistTraceArgs = []string{"-trace-syscalls", "llistxattr,setxattr,openat"} + xattrLsetTraceArgs = []string{"-trace-syscalls", "lsetxattr,setxattr,openat"} + xattrSetatTraceArgs = []string{"-trace-syscalls", "setxattrat,setxattr,openat"} + xattrRemovePathTraceArgs = []string{"-trace-syscalls", "removexattr,setxattr,openat"} + xattrLremoveTraceArgs = []string{"-trace-syscalls", "lremovexattr,setxattr,openat"} + // The fd family runs all four fd-based syscalls in one scenario, so trace + // them together. + xattrFdTraceArgs = []string{"-trace-syscalls", "fsetxattr,fgetxattr,flistxattr,fremovexattr,openat"} +) + // TestXattrGetxattrat verifies ior traces getxattrat(2) (Linux 6.13+) // end-to-end. getxattrat takes a dirfd plus a real filesystem path at args[1] // (NOT args[0]=dfd) and an xattr NAME at args[3]; only the path must be @@ -137,3 +156,224 @@ func TestXattrRemovexattrat(t *testing.T) { assertEventBytesEqual(t, result, exp, 0) assertEventDurationPositive(t, result, exp) } + +// assertNoXattrNameAsPath fails if any record for the given enter tracepoint +// captured the xattr NAME ("user.ior") as its path instead of the file path. +func assertNoXattrNameAsPath(t *testing.T, result TestResult, tracepoint string) { + t.Helper() + for _, rec := range result.Records { + if rec.TraceID.String() == tracepoint && rec.Path == "user.ior" { + t.Errorf("%s captured xattr name %q as path instead of file path", tracepoint, rec.Path) + } + } +} + +// TestXattrGetxattr verifies ior traces the PATH-based getxattr(2) end-to-end. +// getxattr(const char *path, const char *name, void *value, size_t size) takes +// the filesystem PATH at args[0] and the xattr NAME at args[1]; only the path +// must be captured on entry. getxattr returns the xattr value size in bytes, so +// ior READ-classifies the exit and the recorded byte count must reflect at +// least the value written by the workload (consistent with +// lgetxattr/fgetxattr/getxattrat). +func TestXattrGetxattr(t *testing.T) { + result, _ := runScenarioResultWithIorArgs(t, "xattr-getxattr", []ExpectedEvent{ + { + PathContains: "xattrfile.txt", + Tracepoint: "enter_getxattr", + Comm: "ioworkload", + MinCount: 1, + }, + }, xattrGetTraceArgs) + + assertNoXattrNameAsPath(t, result, "enter_getxattr") + + exp := ExpectedEvent{Tracepoint: "enter_getxattr", Comm: "ioworkload"} + assertEventBytesAtLeast(t, result, exp, 1) + assertEventDurationPositive(t, result, exp) +} + +// TestXattrLgetxattr verifies ior traces the no-follow PATH-based lgetxattr(2) +// end-to-end. lgetxattr has getxattr's signature but does NOT follow symlinks; +// the workload targets a regular file, so it returns the value size (user.* +// xattrs on a bare symlink are kernel-restricted). The PATH is at args[0]; the +// exit is READ-classified. +func TestXattrLgetxattr(t *testing.T) { + result, _ := runScenarioResultWithIorArgs(t, "xattr-lgetxattr", []ExpectedEvent{ + { + PathContains: "xattrfile.txt", + Tracepoint: "enter_lgetxattr", + Comm: "ioworkload", + MinCount: 1, + }, + }, xattrLgetTraceArgs) + + assertNoXattrNameAsPath(t, result, "enter_lgetxattr") + + exp := ExpectedEvent{Tracepoint: "enter_lgetxattr", Comm: "ioworkload"} + assertEventBytesAtLeast(t, result, exp, 1) + assertEventDurationPositive(t, result, exp) +} + +// TestXattrListxattr verifies ior traces the PATH-based listxattr(2) end-to-end. +// listxattr(const char *path, char *list, size_t size) takes the PATH at +// args[0]; it returns the size in bytes of the NUL-separated xattr name list, +// so ior READ-classifies the exit (consistent with llistxattr/flistxattr/ +// listxattrat). +func TestXattrListxattr(t *testing.T) { + result, _ := runScenarioResultWithIorArgs(t, "xattr-listxattr", []ExpectedEvent{ + { + PathContains: "xattrfile.txt", + Tracepoint: "enter_listxattr", + Comm: "ioworkload", + MinCount: 1, + }, + }, xattrListPathTraceArgs) + + exp := ExpectedEvent{Tracepoint: "enter_listxattr", Comm: "ioworkload"} + assertEventBytesAtLeast(t, result, exp, 1) + assertEventDurationPositive(t, result, exp) +} + +// TestXattrLlistxattr verifies ior traces the no-follow PATH-based llistxattr(2) +// end-to-end. As with lgetxattr the target is a regular file, so it returns the +// name-list size. The PATH is at args[0]; the exit is READ-classified. +func TestXattrLlistxattr(t *testing.T) { + result, _ := runScenarioResultWithIorArgs(t, "xattr-llistxattr", []ExpectedEvent{ + { + PathContains: "xattrfile.txt", + Tracepoint: "enter_llistxattr", + Comm: "ioworkload", + MinCount: 1, + }, + }, xattrLlistTraceArgs) + + exp := ExpectedEvent{Tracepoint: "enter_llistxattr", Comm: "ioworkload"} + assertEventBytesAtLeast(t, result, exp, 1) + assertEventDurationPositive(t, result, exp) +} + +// TestXattrLsetxattr verifies ior traces the no-follow PATH-based lsetxattr(2) +// end-to-end. lsetxattr(const char *path, const char *name, const void *value, +// size_t size, int flags) takes the PATH at args[0] and the xattr NAME at +// args[1]; only the path must be captured. lsetxattr returns 0/-1 (its `size` +// arg is the INPUT value length), so the exit is UNCLASSIFIED — the accounted +// bytes must be exactly zero (contrast getxattr/lgetxattr, which ARE READ- +// classified), matching setxattr/setxattrat/fsetxattr. +func TestXattrLsetxattr(t *testing.T) { + result, _ := runScenarioResultWithIorArgs(t, "xattr-lsetxattr", []ExpectedEvent{ + { + PathContains: "xattrfile.txt", + Tracepoint: "enter_lsetxattr", + Comm: "ioworkload", + MinCount: 1, + }, + }, xattrLsetTraceArgs) + + assertNoXattrNameAsPath(t, result, "enter_lsetxattr") + + exp := ExpectedEvent{Tracepoint: "enter_lsetxattr", Comm: "ioworkload"} + assertEventBytesEqual(t, result, exp, 0) + assertEventDurationPositive(t, result, exp) +} + +// TestXattrSetxattrat verifies ior traces the -at variant setxattrat(2) (Linux +// 6.13+) end-to-end. setxattrat takes a dirfd plus a real filesystem PATH at +// args[1] (NOT args[0]=dfd) and the xattr NAME at args[3]; only the path must be +// captured. setxattrat SETS an attribute and returns 0/-1 (the value size is an +// INPUT field of struct xattr_args), so the exit is UNCLASSIFIED — the accounted +// bytes must be exactly zero (guarding against accidental READ-classification +// like its getxattrat/listxattrat siblings), matching setxattr/lsetxattr/ +// fsetxattr. +func TestXattrSetxattrat(t *testing.T) { + result, _ := runScenarioResultWithIorArgs(t, "xattr-setxattrat", []ExpectedEvent{ + { + PathContains: "xattrfile.txt", + Tracepoint: "enter_setxattrat", + Comm: "ioworkload", + MinCount: 1, + }, + }, xattrSetatTraceArgs) + + assertNoXattrNameAsPath(t, result, "enter_setxattrat") + + exp := ExpectedEvent{Tracepoint: "enter_setxattrat", Comm: "ioworkload"} + assertEventBytesEqual(t, result, exp, 0) + assertEventDurationPositive(t, result, exp) +} + +// TestXattrRemovexattr verifies ior traces the PATH-based removexattr(2) +// end-to-end. removexattr(const char *path, const char *name) takes the PATH at +// args[0] and the xattr NAME at args[1]; only the path must be captured. +// removexattr returns 0/-1, never a byte count, so the exit is UNCLASSIFIED — +// the accounted bytes must be exactly zero, matching lremovexattr/fremovexattr/ +// removexattrat. +func TestXattrRemovexattr(t *testing.T) { + result, _ := runScenarioResultWithIorArgs(t, "xattr-removexattr", []ExpectedEvent{ + { + PathContains: "xattrfile.txt", + Tracepoint: "enter_removexattr", + Comm: "ioworkload", + MinCount: 1, + }, + }, xattrRemovePathTraceArgs) + + assertNoXattrNameAsPath(t, result, "enter_removexattr") + + exp := ExpectedEvent{Tracepoint: "enter_removexattr", Comm: "ioworkload"} + assertEventBytesEqual(t, result, exp, 0) + assertEventDurationPositive(t, result, exp) +} + +// TestXattrLremovexattr verifies ior traces the no-follow PATH-based +// lremovexattr(2) end-to-end. As with the other l* variants the target is a +// regular file, so it removes the user.* attr. The PATH is at args[0]; the exit +// is UNCLASSIFIED. +func TestXattrLremovexattr(t *testing.T) { + result, _ := runScenarioResultWithIorArgs(t, "xattr-lremovexattr", []ExpectedEvent{ + { + PathContains: "xattrfile.txt", + Tracepoint: "enter_lremovexattr", + Comm: "ioworkload", + MinCount: 1, + }, + }, xattrLremoveTraceArgs) + + assertNoXattrNameAsPath(t, result, "enter_lremovexattr") + + exp := ExpectedEvent{Tracepoint: "enter_lremovexattr", Comm: "ioworkload"} + assertEventBytesEqual(t, result, exp, 0) + assertEventDurationPositive(t, result, exp) +} + +// TestXattrFd verifies ior traces the entire fd-based xattr family end-to-end: +// fsetxattr/fgetxattr/flistxattr/fremovexattr. Each takes the FD at args[0] +// (kind=fd, no path) rather than a pathname. fgetxattr and flistxattr return +// value/name-list sizes and are READ-classified (bytes>0); fsetxattr and +// fremovexattr return 0/-1 status and are UNCLASSIFIED (bytes==0). All four +// fire on the single open fd in the xattr-fd scenario. +func TestXattrFd(t *testing.T) { + result, _ := runScenarioResultWithIorArgs(t, "xattr-fd", []ExpectedEvent{ + {Tracepoint: "enter_fsetxattr", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_fgetxattr", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_flistxattr", Comm: "ioworkload", MinCount: 1}, + {Tracepoint: "enter_fremovexattr", Comm: "ioworkload", MinCount: 1}, + }, xattrFdTraceArgs) + + // READ-classified fd reads: returned value/name-list sizes are accounted. + fget := ExpectedEvent{Tracepoint: "enter_fgetxattr", Comm: "ioworkload"} + assertEventBytesAtLeast(t, result, fget, 1) + assertEventDurationPositive(t, result, fget) + + flist := ExpectedEvent{Tracepoint: "enter_flistxattr", Comm: "ioworkload"} + assertEventBytesAtLeast(t, result, flist, 1) + assertEventDurationPositive(t, result, flist) + + // UNCLASSIFIED fd ops: 0/-1 status returns, never a byte count. + fset := ExpectedEvent{Tracepoint: "enter_fsetxattr", Comm: "ioworkload"} + assertEventBytesEqual(t, result, fset, 0) + assertEventDurationPositive(t, result, fset) + + fremove := ExpectedEvent{Tracepoint: "enter_fremovexattr", Comm: "ioworkload"} + assertEventBytesEqual(t, result, fremove, 0) + assertEventDurationPositive(t, result, fremove) +} |
