xref: /aosp_15_r20/external/bcc/tools/filegone.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1#!/usr/bin/python
2# @lint-avoid-python-3-compatibility-imports
3#
4# filegone    Trace why file gone (deleted or renamed).
5#             For Linux, uses BCC, eBPF. Embedded C.
6#
7# USAGE: filegone [-h] [-p PID]
8#
9# Licensed under the Apache License, Version 2.0 (the "License")
10#
11# 08-Nov-2022 Curu. modified from filelife
12# 19-Nov-2022 Rong Tao Check btf struct field instead of KERNEL_VERSION macro.
13
14from __future__ import print_function
15from bcc import BPF
16import argparse
17from time import strftime
18
19# arguments
20examples = """examples:
21    ./filegone           # trace all file gone events
22    ./filegone -p 181    # only trace PID 181
23"""
24parser = argparse.ArgumentParser(
25    description="Trace why file gone (deleted or renamed)",
26    formatter_class=argparse.RawDescriptionHelpFormatter,
27    epilog=examples)
28parser.add_argument("-p", "--pid",
29    help="trace this PID only")
30parser.add_argument("--ebpf", action="store_true",
31    help=argparse.SUPPRESS)
32args = parser.parse_args()
33debug = 0
34
35# define BPF program
36bpf_text = """
37#include <uapi/linux/ptrace.h>
38#include <linux/fs.h>
39#include <linux/sched.h>
40
41struct data_t {
42    u32 pid;
43    u8 action;
44    char comm[TASK_COMM_LEN];
45    char fname[DNAME_INLINE_LEN];
46    char fname2[DNAME_INLINE_LEN];
47};
48
49BPF_PERF_OUTPUT(events);
50
51// trace file deletion and output details
52TRACE_VFS_UNLINK_FUNC
53{
54    u32 pid = bpf_get_current_pid_tgid() >> 32;
55
56    FILTER
57
58    struct data_t data = {};
59    struct qstr d_name = dentry->d_name;
60    if (d_name.len == 0)
61        return 0;
62
63    bpf_get_current_comm(&data.comm, sizeof(data.comm));
64    data.pid = pid;
65    data.action = 'D';
66    bpf_probe_read(&data.fname, sizeof(data.fname), d_name.name);
67
68    events.perf_submit(ctx, &data, sizeof(data));
69
70    return 0;
71}
72
73// trace file rename
74TRACE_VFS_RENAME_FUNC
75
76    u32 pid = bpf_get_current_pid_tgid() >> 32;
77
78    FILTER
79
80    struct data_t data = {};
81    struct qstr s_name = old_dentry->d_name;
82    struct qstr d_name = new_dentry->d_name;
83    if (s_name.len == 0 || d_name.len == 0)
84        return 0;
85
86    bpf_get_current_comm(&data.comm, sizeof(data.comm));
87    data.pid = pid;
88    data.action = 'R';
89    bpf_probe_read(&data.fname, sizeof(data.fname), s_name.name);
90    bpf_probe_read(&data.fname2, sizeof(data.fname), d_name.name);
91    events.perf_submit(ctx, &data, sizeof(data));
92
93    return 0;
94}
95"""
96
97bpf_vfs_rename_text_old="""
98int trace_rename(struct pt_regs *ctx, struct inode *old_dir, struct dentry *old_dentry,
99struct inode *new_dir, struct dentry *new_dentry)
100{
101"""
102bpf_vfs_rename_text_new="""
103int trace_rename(struct pt_regs *ctx, struct renamedata *rd)
104{
105    struct dentry *old_dentry = rd->old_dentry;
106    struct dentry *new_dentry = rd->new_dentry;
107"""
108
109bpf_vfs_unlink_text_1="""
110int trace_unlink(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry)
111"""
112bpf_vfs_unlink_text_2="""
113int trace_unlink(struct pt_regs *ctx, struct user_namespace *ns, struct inode *dir, struct dentry *dentry)
114"""
115bpf_vfs_unlink_text_3="""
116int trace_unlink(struct pt_regs *ctx, struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry)
117"""
118
119def action2str(action):
120    if chr(action) == 'D':
121        action_str = "DELETE"
122    else:
123        action_str = "RENAME"
124    return action_str
125
126if args.pid:
127    bpf_text = bpf_text.replace('FILTER',
128        'if (pid != %s) { return 0; }' % args.pid)
129else:
130    bpf_text = bpf_text.replace('FILTER', '')
131
132if debug or args.ebpf:
133    print(bpf_text)
134    if args.ebpf:
135        exit()
136
137# check 'struct renamedata' exist or not
138if BPF.kernel_struct_has_field(b'renamedata', b'new_mnt_idmap') == 1:
139    bpf_text = bpf_text.replace('TRACE_VFS_RENAME_FUNC', bpf_vfs_rename_text_new)
140    bpf_text = bpf_text.replace('TRACE_VFS_UNLINK_FUNC', bpf_vfs_unlink_text_3)
141elif BPF.kernel_struct_has_field("renamedata", "old_mnt_userns") == 1:
142    bpf_text = bpf_text.replace('TRACE_VFS_RENAME_FUNC', bpf_vfs_rename_text_new)
143    bpf_text = bpf_text.replace('TRACE_VFS_UNLINK_FUNC', bpf_vfs_unlink_text_2)
144else:
145    bpf_text = bpf_text.replace('TRACE_VFS_RENAME_FUNC', bpf_vfs_rename_text_old)
146    bpf_text = bpf_text.replace('TRACE_VFS_UNLINK_FUNC', bpf_vfs_unlink_text_1)
147
148# initialize BPF
149b = BPF(text=bpf_text)
150b.attach_kprobe(event="vfs_unlink", fn_name="trace_unlink")
151b.attach_kprobe(event="vfs_rmdir", fn_name="trace_unlink")
152b.attach_kprobe(event="vfs_rename", fn_name="trace_rename")
153
154# header
155print("%-8s %-7s %-16s %6s %s" % ("TIME", "PID", "COMM", "ACTION", "FILE"))
156
157# process event
158def print_event(cpu, data, size):
159    event = b["events"].event(data)
160    action_str = action2str(event.action)
161    file_str = event.fname.decode('utf-8', 'replace')
162    if action_str == "RENAME":
163        file_str = "%s > %s" % (file_str, event.fname2.decode('utf-8', 'replace'))
164    print("%-8s %-7d %-16s %6s %s" % (strftime("%H:%M:%S"), event.pid,
165        event.comm.decode('utf-8', 'replace'), action_str, file_str))
166
167b["events"].open_perf_buffer(print_event)
168while 1:
169    try:
170        b.perf_buffer_poll()
171    except KeyboardInterrupt:
172        exit()
173