Skip to content

Commit 8af1b95

Browse files
committed
tools/filegone: support full-path
1. use ring-buffer instead of perf-buffer; 2. get the full-path of unlink file and rename old,new file; For example: $ touch a.out && mv a.out ../b.out && rm ../b.out $ sudo ./filegone.py -F TIME PID COMM ACTION FILE 14:37:09 96022 mv RENAME /home/sda/git-repos/iovisor/a.out > /home/sda/git-repos/iovisor/b.out 14:37:09 96023 rm DELETE /home/sda/git-repos/iovisor/b.out 14:40:16 96914 mv RENAME a.out > b.out 14:40:16 96915 rm DELETE b.out Signed-off-by: Rong Tao <[email protected]>
1 parent 63eb74f commit 8af1b95

File tree

2 files changed

+136
-42
lines changed

2 files changed

+136
-42
lines changed

tools/filegone.py

Lines changed: 130 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# 08-Nov-2022 Curu. modified from filelife
1212
# 19-Nov-2022 Rong Tao Check btf struct field instead of KERNEL_VERSION macro.
1313
# 05-Nov-2023 Rong Tao Support rename/unlink failed situation.
14+
# 01-Jul-2025 Rong Tao Support full-path
1415

1516
from __future__ import print_function
1617
from bcc import BPF
@@ -20,6 +21,7 @@
2021
# arguments
2122
examples = """examples:
2223
./filegone # trace all file gone events
24+
./filegone -F # show full path of file
2325
./filegone -p 181 # only trace PID 181
2426
"""
2527
parser = argparse.ArgumentParser(
@@ -28,6 +30,8 @@
2830
epilog=examples)
2931
parser.add_argument("-p", "--pid",
3032
help="trace this PID only")
33+
parser.add_argument("-F", "--full-path", action="store_true",
34+
help="show full path")
3135
parser.add_argument("--ebpf", action="store_true",
3236
help=argparse.SUPPRESS)
3337
args = parser.parse_args()
@@ -37,18 +41,47 @@
3741
bpf_text = """
3842
#include <uapi/linux/ptrace.h>
3943
#include <linux/fs.h>
44+
#include <linux/fs_struct.h>
4045
#include <linux/sched.h>
46+
#ifdef FULLPATH
47+
INCLUDE_FULL_PATH_H
48+
INCLUDE_PATH_HELPERS_BPF_H
49+
#endif
4150
4251
struct data_t {
4352
u32 pid;
4453
u8 action;
4554
char comm[TASK_COMM_LEN];
46-
char fname[DNAME_INLINE_LEN];
47-
char fname2[DNAME_INLINE_LEN];
55+
56+
u32 path_depth, path_depth2;
57+
#ifdef FULLPATH
58+
FULL_PATH_FIELD(fname);
59+
FULL_PATH_FIELD(fname2);
60+
#else
61+
char fname[NAME_MAX];
62+
char fname2[NAME_MAX];
63+
#endif
64+
};
65+
66+
struct entry_t {
67+
u32 pid;
68+
u8 action;
69+
struct {
70+
char name[DNAME_INLINE_LEN];
71+
struct dentry *dentry;
72+
} old, new;
4873
};
4974
50-
BPF_PERF_OUTPUT(events);
51-
BPF_HASH(currdata, u32, struct data_t);
75+
BPF_RINGBUF_OUTPUT(events, 64);
76+
BPF_HASH(currentry, u32, struct entry_t);
77+
78+
static inline void get_dentry_name(char **name, struct dentry *dentry)
79+
{
80+
struct qstr d_name = dentry->d_name;
81+
if (d_name.len == 0)
82+
return;
83+
bpf_probe_read(name, DNAME_INLINE_LEN, d_name.name);
84+
}
5285
5386
// trace file deletion and output details
5487
TRACE_VFS_UNLINK_FUNC
@@ -59,63 +92,100 @@
5992
6093
FILTER
6194
62-
struct data_t data = {};
63-
struct qstr d_name = dentry->d_name;
64-
if (d_name.len == 0)
65-
return 0;
95+
struct entry_t entry = {};
6696
67-
bpf_get_current_comm(&data.comm, sizeof(data.comm));
68-
data.pid = pid;
69-
data.action = 'D';
70-
bpf_probe_read(&data.fname, sizeof(data.fname), d_name.name);
97+
entry.pid = pid;
98+
entry.action = 'D';
99+
entry.old.dentry = dentry;
100+
get_dentry_name((char **)&entry.old.name, dentry);
71101
72-
currdata.update(&tid, &data);
102+
currentry.update(&tid, &entry);
73103
74104
return 0;
75105
}
76106
77107
// trace file rename
78108
TRACE_VFS_RENAME_FUNC
79109
110+
struct entry_t entry = {};
80111
u64 pid_tgid = bpf_get_current_pid_tgid();
81112
u32 pid = pid_tgid >> 32;
82113
u32 tid = (u32)pid_tgid;
83114
84115
FILTER
85116
86-
struct data_t data = {};
87-
struct qstr s_name = old_dentry->d_name;
88-
struct qstr d_name = new_dentry->d_name;
89-
if (s_name.len == 0 || d_name.len == 0)
90-
return 0;
117+
entry.pid = pid;
118+
entry.action = 'R';
91119
92-
bpf_get_current_comm(&data.comm, sizeof(data.comm));
93-
data.pid = pid;
94-
data.action = 'R';
95-
bpf_probe_read(&data.fname, sizeof(data.fname), s_name.name);
96-
bpf_probe_read(&data.fname2, sizeof(data.fname), d_name.name);
97-
currdata.update(&tid, &data);
120+
/**
121+
* Couldn't get new and old dentry name in trace_return(), because you'll
122+
* get new-name for old.
123+
*/
124+
entry.old.dentry = old_dentry;
125+
get_dentry_name((char **)&entry.old.name, old_dentry);
126+
entry.new.dentry = new_dentry;
127+
get_dentry_name((char **)&entry.new.name, new_dentry);
128+
129+
currentry.update(&tid, &entry);
98130
99131
return 0;
100132
}
101133
102134
int trace_return(struct pt_regs *ctx)
103135
{
104136
struct data_t *data;
137+
struct entry_t *entry;
105138
u32 tid = (u32)bpf_get_current_pid_tgid();
106139
int ret = PT_REGS_RC(ctx);
107140
108-
data = currdata.lookup(&tid);
109-
if (data == 0)
141+
entry = currentry.lookup(&tid);
142+
if (entry == 0)
110143
return 0;
111144
112-
currdata.delete(&tid);
145+
currentry.delete(&tid);
113146
114147
/* Skip failed */
115148
if (ret)
116149
return 0;
117150
118-
events.perf_submit(ctx, data, sizeof(*data));
151+
data = events.ringbuf_reserve(sizeof(struct data_t));
152+
if (!data)
153+
return 0;
154+
155+
data->pid = entry->pid;
156+
data->action = entry->action;
157+
bpf_get_current_comm(&data->comm, sizeof(data->comm));
158+
159+
data->path_depth = data->path_depth2 = 0;
160+
bpf_probe_read(&data->fname, sizeof(data->fname), entry->old.name);
161+
162+
if (entry->action == 'R')
163+
bpf_probe_read(&data->fname2, sizeof(data->fname2), entry->new.name);
164+
165+
#ifdef FULLPATH
166+
struct task_struct *task;
167+
struct fs_struct *fs;
168+
struct vfsmount *cwd_vfsmnt;
169+
170+
task = (struct task_struct *)bpf_get_current_task_btf();
171+
bpf_probe_read_kernel(&fs, sizeof(fs), &task->fs);
172+
bpf_probe_read_kernel(&cwd_vfsmnt, sizeof(cwd_vfsmnt), &fs->pwd.mnt);
173+
if (data->fname[0] != '/') {
174+
data->path_depth = 1;
175+
bpf_dentry_full_path(data->fname + NAME_MAX, NAME_MAX, MAX_ENTRIES - 1,
176+
entry->old.dentry->d_parent, cwd_vfsmnt,
177+
&data->path_depth);
178+
}
179+
180+
if (entry->action == 'R' && data->fname2[0] != '/') {
181+
data->path_depth2 = 1;
182+
bpf_dentry_full_path(data->fname2 + NAME_MAX, NAME_MAX, MAX_ENTRIES - 1,
183+
entry->new.dentry->d_parent, cwd_vfsmnt,
184+
&data->path_depth2);
185+
}
186+
#endif
187+
188+
events.ringbuf_submit(data, sizeof(*data));
119189
return 0;
120190
}
121191
"""
@@ -155,11 +225,6 @@ def action2str(action):
155225
else:
156226
bpf_text = bpf_text.replace('FILTER', '')
157227

158-
if debug or args.ebpf:
159-
print(bpf_text)
160-
if args.ebpf:
161-
exit()
162-
163228
# check 'struct renamedata' exist or not
164229
if BPF.kernel_struct_has_field(b'renamedata', b'new_mnt_idmap') == 1:
165230
bpf_text = bpf_text.replace('TRACE_VFS_RENAME_FUNC', bpf_vfs_rename_text_new)
@@ -171,6 +236,22 @@ def action2str(action):
171236
bpf_text = bpf_text.replace('TRACE_VFS_RENAME_FUNC', bpf_vfs_rename_text_old)
172237
bpf_text = bpf_text.replace('TRACE_VFS_UNLINK_FUNC', bpf_vfs_unlink_text_1)
173238

239+
if args.full_path:
240+
bpf_text = "#define FULLPATH\n" + bpf_text
241+
242+
with open(BPF._find_file("full_path.h".encode("utf-8"))) as fileobj:
243+
progtxt = fileobj.read()
244+
bpf_text = bpf_text.replace('INCLUDE_FULL_PATH_H', progtxt)
245+
246+
with open(BPF._find_file("path_helpers.bpf.c".encode("utf-8"))) as fileobj:
247+
progtxt = fileobj.read()
248+
bpf_text = bpf_text.replace('INCLUDE_PATH_HELPERS_BPF_H', progtxt)
249+
250+
if debug or args.ebpf:
251+
print(bpf_text)
252+
if args.ebpf:
253+
exit()
254+
174255
# initialize BPF
175256
b = BPF(text=bpf_text)
176257
b.attach_kprobe(event="vfs_unlink", fn_name="trace_unlink")
@@ -187,15 +268,26 @@ def action2str(action):
187268
def print_event(cpu, data, size):
188269
event = b["events"].event(data)
189270
action_str = action2str(event.action)
190-
file_str = event.fname.decode('utf-8', 'replace')
191-
if action_str == "RENAME":
192-
file_str = "%s > %s" % (file_str, event.fname2.decode('utf-8', 'replace'))
271+
if args.full_path:
272+
import os
273+
import sys
274+
sys.path.append(os.path.dirname(sys.argv[0]))
275+
from path_helpers import get_full_path
276+
file_str = get_full_path(event.fname, event.path_depth)
277+
if action_str == "RENAME":
278+
file2_str = get_full_path(event.fname2, event.path_depth2)
279+
file_str = "%s > %s" % (file_str, file2_str)
280+
else:
281+
file_str = event.fname.decode('utf-8', 'replace')
282+
if action_str == "RENAME":
283+
file2_str = event.fname2.decode('utf-8', 'replace')
284+
file_str = "%s > %s" % (file_str, file2_str)
193285
print("%-8s %-7d %-16s %6s %s" % (strftime("%H:%M:%S"), event.pid,
194286
event.comm.decode('utf-8', 'replace'), action_str, file_str))
195287

196-
b["events"].open_perf_buffer(print_event)
288+
b["events"].open_ring_buffer(print_event)
197289
while 1:
198290
try:
199-
b.perf_buffer_poll()
291+
b.ring_buffer_poll()
200292
except KeyboardInterrupt:
201293
exit()

tools/filegone_example.txt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ For example:
1313

1414
USAGE message:
1515

16-
usage: filegone.py [-h] [-p PID]
16+
usage: filegone.py [-h] [-p PID] [-F]
1717

1818
Trace why file gone (deleted or renamed)
1919

20-
optional arguments:
21-
-h, --help show this help message and exit
22-
-p PID, --pid PID trace this PID only
20+
options:
21+
-h, --help show this help message and exit
22+
-p, --pid PID trace this PID only
23+
-F, --full-path show full path
2324

2425
examples:
2526
./filegone # trace all file gone events
27+
./filegone -F # show full path of file
2628
./filegone -p 181 # only trace PID 181

0 commit comments

Comments
 (0)