Skip to content

Commit 63eb74f

Browse files
committed
tools/filelife: support full-path
Same as libbpf-tools/filelife changes [1], introduce -F argument to support full-path. Example: $ touch a.out && sleep 0.2 && rm a.out # Before $ sudo ./filelife.py TIME PID COMM AGE(s) FILE 18:57:25 343907 rm 0.21 a.out # After $ sudo ./filelife.py -F TIME PID COMM AGE(s) FILE 18:57:35 343917 rm 0.21 /home/sda/git-repos/iovisor/bcc/libbpf-tools/a.out ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1] #5340 74bddcbe646e Signed-off-by: Rong Tao <[email protected]>
1 parent b32c7d0 commit 63eb74f

File tree

2 files changed

+116
-43
lines changed

2 files changed

+116
-43
lines changed

tools/filelife.py

Lines changed: 108 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,18 @@
1818
# 17-Feb-2016 Allan McAleavy updated for BPF_PERF_OUTPUT
1919
# 13-Nov-2022 Rong Tao Check btf struct field for CO-RE and add vfs_open()
2020
# 05-Nov-2023 Rong Tao Support unlink failed
21+
# 01-Jul-2025 Rong Tao Support full-path
2122

2223
from __future__ import print_function
2324
from bcc import BPF
25+
from bcc.utils import printb
2426
import argparse
2527
from time import strftime
2628

2729
# arguments
2830
examples = """examples:
2931
./filelife # trace lifecycle of file(create->remove)
32+
./filelife -F # show full path of file
3033
./filelife -p 181 # only trace PID 181
3134
"""
3235
parser = argparse.ArgumentParser(
@@ -35,6 +38,8 @@
3538
epilog=examples)
3639
parser.add_argument("-p", "--pid",
3740
help="trace this PID only")
41+
parser.add_argument("-F", "--full-path", action="store_true",
42+
help="show full path")
3843
parser.add_argument("--ebpf", action="store_true",
3944
help=argparse.SUPPRESS)
4045
args = parser.parse_args()
@@ -45,27 +50,56 @@
4550
#include <uapi/linux/ptrace.h>
4651
#include <linux/fs.h>
4752
#include <linux/sched.h>
53+
#include <linux/fs_struct.h>
54+
#ifdef FULLPATH
55+
INCLUDE_FULL_PATH_H
56+
INCLUDE_PATH_HELPERS_BPF_H
57+
#endif
4858
4959
struct data_t {
5060
u32 pid;
5161
u64 delta;
5262
char comm[TASK_COMM_LEN];
63+
u32 path_depth;
64+
#ifdef FULLPATH
65+
FULL_PATH_FIELD(fname);
66+
#else
5367
char fname[DNAME_INLINE_LEN];
54-
/* private */
55-
void *dentry;
68+
#endif
5669
};
5770
58-
BPF_HASH(birth, struct dentry *);
59-
BPF_HASH(unlink_data, u32, struct data_t);
60-
BPF_PERF_OUTPUT(events);
71+
struct create_arg {
72+
u64 ts;
73+
struct vfsmount *cwd_vfsmnt;
74+
};
75+
76+
struct unlink_event {
77+
u32 tid;
78+
u64 delta;
79+
struct dentry *dentry;
80+
struct vfsmount *cwd_vfsmnt;
81+
};
82+
83+
BPF_HASH(birth, struct dentry *, struct create_arg);
84+
BPF_HASH(unlink_data, u32, struct unlink_event);
85+
BPF_RINGBUF_OUTPUT(events, 64);
6186
6287
static int probe_dentry(struct pt_regs *ctx, struct dentry *dentry)
6388
{
89+
struct task_struct *task;
90+
struct fs_struct *fs;
91+
struct create_arg arg = {};
6492
u32 pid = bpf_get_current_pid_tgid() >> 32;
6593
FILTER
6694
6795
u64 ts = bpf_ktime_get_ns();
68-
birth.update(&dentry, &ts);
96+
task = (struct task_struct *)bpf_get_current_task_btf();
97+
98+
arg.ts = ts;
99+
bpf_probe_read_kernel(&fs, sizeof(fs), &task->fs);
100+
bpf_probe_read_kernel(&arg.cwd_vfsmnt, sizeof(arg.cwd_vfsmnt), &fs->pwd.mnt);
101+
102+
birth.update(&dentry, &arg);
69103
70104
return 0;
71105
}
@@ -98,46 +132,41 @@
98132
// trace file deletion and output details
99133
TRACE_UNLINK_FUNC
100134
{
101-
struct data_t data = {};
135+
struct create_arg *arg;
136+
struct unlink_event event = {};
102137
u64 pid_tgid = bpf_get_current_pid_tgid();
103138
u32 pid = pid_tgid >> 32;
104139
u32 tid = (u32)pid_tgid;
105140
106141
FILTER
107142
108-
u64 *tsp, delta;
109-
tsp = birth.lookup(&dentry);
110-
if (tsp == 0) {
143+
u64 delta;
144+
arg = birth.lookup(&dentry);
145+
if (arg == 0) {
111146
return 0; // missed create
112147
}
113148
114-
delta = (bpf_ktime_get_ns() - *tsp) / 1000000;
115-
116-
struct qstr d_name = dentry->d_name;
117-
if (d_name.len == 0)
118-
return 0;
119-
120-
if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) {
121-
data.pid = pid;
122-
data.delta = delta;
123-
bpf_probe_read_kernel(&data.fname, sizeof(data.fname), d_name.name);
124-
}
149+
delta = (bpf_ktime_get_ns() - arg->ts) / 1000000;
125150
126151
/* record dentry, only delete from birth if unlink successful */
127-
data.dentry = dentry;
152+
event.delta = delta;
153+
event.tid = tid;
154+
event.dentry = dentry;
155+
event.cwd_vfsmnt = arg->cwd_vfsmnt;
128156
129-
unlink_data.update(&tid, &data);
157+
unlink_data.update(&tid, &event);
130158
return 0;
131159
}
132160
133161
int trace_unlink_ret(struct pt_regs *ctx)
134162
{
135163
int ret = PT_REGS_RC(ctx);
164+
struct unlink_event *unlink_event;
136165
struct data_t *data;
137166
u32 tid = (u32)bpf_get_current_pid_tgid();
138167
139-
data = unlink_data.lookup(&tid);
140-
if (!data)
168+
unlink_event = unlink_data.lookup(&tid);
169+
if (!unlink_event)
141170
return 0;
142171
143172
/* delete it any way */
@@ -147,8 +176,30 @@
147176
if (ret)
148177
return 0;
149178
150-
birth.delete((struct dentry **)&data->dentry);
151-
events.perf_submit(ctx, data, sizeof(*data));
179+
data = events.ringbuf_reserve(sizeof(struct data_t));
180+
if (!data)
181+
return 0;
182+
183+
data->pid = unlink_event->tid;
184+
data->delta = unlink_event->delta;
185+
bpf_get_current_comm(&data->comm, sizeof(data->comm));
186+
187+
data->path_depth = 0;
188+
189+
#ifdef FULLPATH
190+
if (data->fname[0] != '/') {
191+
bpf_dentry_full_path(data->fname, NAME_MAX, MAX_ENTRIES,
192+
unlink_event->dentry, unlink_event->cwd_vfsmnt,
193+
&data->path_depth);
194+
}
195+
#else
196+
struct qstr d_name = unlink_event->dentry->d_name;
197+
bpf_probe_read_kernel_str(&data->fname, sizeof(data->fname), d_name.name);
198+
#endif
199+
200+
birth.delete((struct dentry **)&unlink_event->dentry);
201+
202+
events.ringbuf_submit(data, sizeof(*data));
152203
153204
return 0;
154205
}
@@ -183,10 +234,17 @@
183234
'if (pid != %s) { return 0; }' % args.pid)
184235
else:
185236
bpf_text = bpf_text.replace('FILTER', '')
186-
if debug or args.ebpf:
187-
print(bpf_text)
188-
if args.ebpf:
189-
exit()
237+
238+
if args.full_path:
239+
bpf_text = "#define FULLPATH\n" + bpf_text
240+
241+
with open(BPF._find_file("full_path.h".encode("utf-8"))) as fileobj:
242+
progtxt = fileobj.read()
243+
bpf_text = bpf_text.replace('INCLUDE_FULL_PATH_H', progtxt)
244+
245+
with open(BPF._find_file("path_helpers.bpf.c".encode("utf-8"))) as fileobj:
246+
progtxt = fileobj.read()
247+
bpf_text = bpf_text.replace('INCLUDE_PATH_HELPERS_BPF_H', progtxt)
190248

191249
if BPF.kernel_struct_has_field(b'renamedata', b'new_mnt_idmap') == 1:
192250
bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_3)
@@ -198,6 +256,11 @@
198256
bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_1)
199257
bpf_text = bpf_text.replace('TRACE_UNLINK_FUNC', trace_unlink_text_1)
200258

259+
if debug or args.ebpf:
260+
print(bpf_text)
261+
if args.ebpf:
262+
exit()
263+
201264
# initialize BPF
202265
b = BPF(text=bpf_text)
203266
b.attach_kprobe(event="vfs_create", fn_name="trace_create")
@@ -216,13 +279,21 @@
216279
# process event
217280
def print_event(cpu, data, size):
218281
event = b["events"].event(data)
219-
print("%-8s %-7d %-16s %-7.2f %s" % (strftime("%H:%M:%S"), event.pid,
220-
event.comm.decode('utf-8', 'replace'), float(event.delta) / 1000,
221-
event.fname.decode('utf-8', 'replace')))
222-
223-
b["events"].open_perf_buffer(print_event)
282+
printb(b"%-8s %-7d %-16s %-7.2f " % (strftime("%H:%M:%S").encode('utf-8', 'replace'),
283+
event.pid, event.comm, float(event.delta) / 1000), nl="")
284+
if args.full_path:
285+
import os
286+
import sys
287+
sys.path.append(os.path.dirname(sys.argv[0]))
288+
from path_helpers import get_full_path
289+
result = get_full_path(event.fname, event.path_depth)
290+
printb(b"%s" % result.encode("utf-8"))
291+
else:
292+
printb(b"%s" % event.fname)
293+
294+
b["events"].open_ring_buffer(print_event)
224295
while 1:
225296
try:
226-
b.perf_buffer_poll()
297+
b.ring_buffer_poll()
227298
except KeyboardInterrupt:
228299
exit()

tools/filelife_example.txt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,16 @@ optimizations.
3939
USAGE message:
4040

4141
# ./filelife -h
42-
usage: filelife [-h] [-p PID]
42+
usage: filelife.py [-h] [-p PID] [-F]
4343

44-
Trace stat() syscalls
44+
Trace lifecycle of file
4545

46-
optional arguments:
47-
-h, --help show this help message and exit
48-
-p PID, --pid PID trace this PID only
46+
options:
47+
-h, --help show this help message and exit
48+
-p, --pid PID trace this PID only
49+
-F, --full-path show full path
4950

5051
examples:
51-
./filelife # trace all stat() syscalls
52+
./filelife # trace lifecycle of file(create->remove)
53+
./filelife -F # show full path of file
5254
./filelife -p 181 # only trace PID 181

0 commit comments

Comments
 (0)