summaryrefslogtreecommitdiff
path: root/integrationtests
diff options
context:
space:
mode:
Diffstat (limited to 'integrationtests')
-rw-r--r--integrationtests/xattr_test.go240
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)
+}