1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
package integrationtests
import "testing"
// xattrTraceArgs restricts tracing to the getxattrat tracepoints so the test is
// not perturbed by unrelated xattr/open traffic.
var xattrTraceArgs = []string{"-trace-syscalls", "getxattrat,setxattr,openat"}
// xattrListTraceArgs restricts tracing to the listxattrat tracepoints so the
// test is not perturbed by unrelated xattr/open traffic.
var xattrListTraceArgs = []string{"-trace-syscalls", "listxattrat,setxattr,openat"}
// xattrRemoveTraceArgs restricts tracing to the removexattrat tracepoints so
// the test is not perturbed by unrelated xattr/open traffic.
var xattrRemoveTraceArgs = []string{"-trace-syscalls", "removexattrat,setxattr,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
// captured. The path is read on syscall entry, so enter_getxattrat must carry
// the file path "xattrfile.txt" and never the xattr name "user.ior".
func TestXattrGetxattrat(t *testing.T) {
result, _ := runScenarioResultWithIorArgs(t, "xattr-getxattrat", []ExpectedEvent{
{
PathContains: "xattrfile.txt",
Tracepoint: "enter_getxattrat",
Comm: "ioworkload",
MinCount: 1,
},
}, xattrTraceArgs)
// The captured path must be the filesystem path, never the xattr name.
for _, rec := range result.Records {
if rec.TraceID.String() == "enter_getxattrat" && rec.Path == "user.ior" {
t.Errorf("getxattrat captured xattr name %q as path instead of file path", rec.Path)
}
}
// getxattrat returns the xattr value size in bytes; ior READ-classifies the
// exit, so the recorded byte count must reflect the 16-byte value written by
// the workload (consistent with getxattr/lgetxattr/fgetxattr).
exp := ExpectedEvent{Tracepoint: "enter_getxattrat", Comm: "ioworkload"}
assertEventBytesAtLeast(t, result, exp, uint64(len("getxattrat-value")))
assertEventDurationPositive(t, result, exp)
}
// TestXattrSetxattr verifies ior traces the PATH-based setxattr(2) end-to-end.
// setxattr(const char *path, const char *name, const void *value, size_t size,
// int flags) takes a real filesystem PATH at args[0] (kind=pathname) and the
// xattr NAME at args[1]; only the path must be captured on entry, so
// enter_setxattr must carry the file path "xattrfile.txt" and never the xattr
// name "user.ior". Crucially, setxattr returns 0 on success / -1 on error — its
// `size` argument is the INPUT value length, NOT a byte count returned by the
// call. The exit is therefore UNCLASSIFIED (contrast getxattr/listxattr, which
// DO return byte counts and are READ-classified), so the recorded byte total
// must be exactly zero. This reuses the xattr-getxattrat scenario, whose
// workload performs syscall.Setxattr(path, "user.ior", ...) and is traced via
// xattrTraceArgs ("getxattrat,setxattr,openat").
func TestXattrSetxattr(t *testing.T) {
result, _ := runScenarioResultWithIorArgs(t, "xattr-getxattrat", []ExpectedEvent{
{
PathContains: "xattrfile.txt",
Tracepoint: "enter_setxattr",
Comm: "ioworkload",
MinCount: 1,
},
}, xattrTraceArgs)
// The captured path must be the filesystem path, never the xattr name.
for _, rec := range result.Records {
if rec.TraceID.String() == "enter_setxattr" && rec.Path == "user.ior" {
t.Errorf("setxattr captured xattr name %q as path instead of file path", rec.Path)
}
}
// setxattr is UNCLASSIFIED: its return is a 0/-1 status, never a byte count
// (the `size` arg is the input value length). The accounted bytes for the
// setxattr events must therefore be exactly zero — guarding against the
// msgsnd-style bug of treating a status return as bytes written.
exp := ExpectedEvent{Tracepoint: "enter_setxattr", Comm: "ioworkload"}
assertEventBytesEqual(t, result, exp, 0)
assertEventDurationPositive(t, result, exp)
}
// TestXattrListxattrat verifies ior traces listxattrat(2) (Linux 6.13+)
// end-to-end. listxattrat takes a dirfd plus a real filesystem path at args[1]
// (NOT args[0]=dfd); only the path must be captured. The path is read on
// syscall entry, so enter_listxattrat must carry the file path "xattrfile.txt".
func TestXattrListxattrat(t *testing.T) {
result, _ := runScenarioResultWithIorArgs(t, "xattr-listxattrat", []ExpectedEvent{
{
PathContains: "xattrfile.txt",
Tracepoint: "enter_listxattrat",
Comm: "ioworkload",
MinCount: 1,
},
}, xattrListTraceArgs)
// listxattrat returns the size in bytes of the xattr name list; ior
// READ-classifies the exit, so the recorded byte count must reflect at least
// the NUL-terminated "user.ior" name set by the workload (consistent with
// listxattr/llistxattr/flistxattr).
exp := ExpectedEvent{Tracepoint: "enter_listxattrat", Comm: "ioworkload"}
assertEventBytesAtLeast(t, result, exp, uint64(len("user.ior")+1))
assertEventDurationPositive(t, result, exp)
}
// TestXattrRemovexattrat verifies ior traces removexattrat(2) (Linux 6.13+)
// end-to-end. removexattrat takes a dirfd plus a real filesystem path at
// args[1] (NOT args[0]=dfd); the xattr name is at args[3] and must NOT be
// captured. The path is read on syscall entry, so enter_removexattrat must
// carry the file path "xattrfile.txt", never the name "user.ior".
func TestXattrRemovexattrat(t *testing.T) {
result, _ := runScenarioResultWithIorArgs(t, "xattr-removexattrat", []ExpectedEvent{
{
PathContains: "xattrfile.txt",
Tracepoint: "enter_removexattrat",
Comm: "ioworkload",
MinCount: 1,
},
}, xattrRemoveTraceArgs)
// The captured path must be the filesystem path, never the xattr name.
for _, rec := range result.Records {
if rec.TraceID.String() == "enter_removexattrat" && rec.Path == "user.ior" {
t.Errorf("removexattrat captured xattr name %q as path instead of file path", rec.Path)
}
}
// removexattrat is UNCLASSIFIED: it REMOVES an attribute and returns a 0/-1
// status, never a byte count (contrast getxattrat/listxattrat, which return
// value/name-list sizes). The accounted bytes must therefore be exactly
// zero — matching removexattr/lremovexattr/fremovexattr, and guarding
// against wrongly READ-classifying it like its getxattrat/listxattrat
// siblings.
exp := ExpectedEvent{Tracepoint: "enter_removexattrat", Comm: "ioworkload"}
assertEventBytesEqual(t, result, exp, 0)
assertEventDurationPositive(t, result, exp)
}
|