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
1516from __future__ import print_function
1617from bcc import BPF
2021# arguments
2122examples = """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"""
2527parser = argparse .ArgumentParser (
2830 epilog = examples )
2931parser .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" )
3135parser .add_argument ("--ebpf" , action = "store_true" ,
3236 help = argparse .SUPPRESS )
3337args = parser .parse_args ()
3741bpf_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
4251struct 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
5487TRACE_VFS_UNLINK_FUNC
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
78108TRACE_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
102134int 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):
155225else :
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
164229if 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
175256b = BPF (text = bpf_text )
176257b .attach_kprobe (event = "vfs_unlink" , fn_name = "trace_unlink" )
@@ -187,15 +268,26 @@ def action2str(action):
187268def 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 )
197289while 1 :
198290 try :
199- b .perf_buffer_poll ()
291+ b .ring_buffer_poll ()
200292 except KeyboardInterrupt :
201293 exit ()
0 commit comments