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 13from __future__ import print_function 14from bcc import BPF 15import argparse 16from time import strftime 17 18# arguments 19examples = """examples: 20 ./filegone # trace all file gone events 21 ./filegone -p 181 # only trace PID 181 22""" 23parser = argparse.ArgumentParser( 24 description="Trace why file gone (deleted or renamed)", 25 formatter_class=argparse.RawDescriptionHelpFormatter, 26 epilog=examples) 27parser.add_argument("-p", "--pid", 28 help="trace this PID only") 29parser.add_argument("--ebpf", action="store_true", 30 help=argparse.SUPPRESS) 31args = parser.parse_args() 32debug = 0 33 34# define BPF program 35bpf_text = """ 36#include <uapi/linux/ptrace.h> 37#include <linux/fs.h> 38#include <linux/sched.h> 39 40struct data_t { 41 u32 pid; 42 u8 action; 43 char comm[TASK_COMM_LEN]; 44 char fname[DNAME_INLINE_LEN]; 45 char fname2[DNAME_INLINE_LEN]; 46}; 47 48BPF_PERF_OUTPUT(events); 49 50// trace file deletion and output details 51#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0) 52int trace_unlink(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) 53#else 54int trace_unlink(struct pt_regs *ctx, struct user_namespace *ns, struct inode *dir, struct dentry *dentry) 55#endif 56{ 57 u32 pid = bpf_get_current_pid_tgid() >> 32; 58 59 FILTER 60 61 struct data_t data = {}; 62 struct qstr d_name = dentry->d_name; 63 if (d_name.len == 0) 64 return 0; 65 66 bpf_get_current_comm(&data.comm, sizeof(data.comm)); 67 data.pid = pid; 68 data.action = 'D'; 69 bpf_probe_read(&data.fname, sizeof(data.fname), d_name.name); 70 71 events.perf_submit(ctx, &data, sizeof(data)); 72 73 return 0; 74} 75 76// trace file rename 77#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0) 78int trace_rename(struct pt_regs *ctx, struct inode *old_dir, struct dentry *old_dentry, 79struct inode *new_dir, struct dentry *new_dentry) 80{ 81#else 82int trace_rename(struct pt_regs *ctx, struct renamedata *rd) 83{ 84 struct dentry *old_dentry = rd->old_dentry; 85 struct dentry *new_dentry = rd->new_dentry; 86#endif 87 88 u32 pid = bpf_get_current_pid_tgid() >> 32; 89 90 FILTER 91 92 struct data_t data = {}; 93 struct qstr s_name = old_dentry->d_name; 94 struct qstr d_name = new_dentry->d_name; 95 if (s_name.len == 0 || d_name.len == 0) 96 return 0; 97 98 bpf_get_current_comm(&data.comm, sizeof(data.comm)); 99 data.pid = pid; 100 data.action = 'R'; 101 bpf_probe_read(&data.fname, sizeof(data.fname), s_name.name); 102 bpf_probe_read(&data.fname2, sizeof(data.fname), d_name.name); 103 events.perf_submit(ctx, &data, sizeof(data)); 104 105 return 0; 106} 107""" 108 109 110def action2str(action): 111 if chr(action) == 'D': 112 action_str = "DELETE" 113 else: 114 action_str = "RENAME" 115 return action_str 116 117if args.pid: 118 bpf_text = bpf_text.replace('FILTER', 119 'if (pid != %s) { return 0; }' % args.pid) 120else: 121 bpf_text = bpf_text.replace('FILTER', '') 122 123if debug or args.ebpf: 124 print(bpf_text) 125 if args.ebpf: 126 exit() 127 128# initialize BPF 129b = BPF(text=bpf_text) 130b.attach_kprobe(event="vfs_unlink", fn_name="trace_unlink") 131b.attach_kprobe(event="vfs_rmdir", fn_name="trace_unlink") 132b.attach_kprobe(event="vfs_rename", fn_name="trace_rename") 133 134# header 135print("%-8s %-7s %-16s %6s %s" % ("TIME", "PID", "COMM", "ACTION", "FILE")) 136 137# process event 138def print_event(cpu, data, size): 139 event = b["events"].event(data) 140 action_str = action2str(event.action) 141 file_str = event.fname.decode('utf-8', 'replace') 142 if action_str == "RENAME": 143 file_str = "%s > %s" % (file_str, event.fname2.decode('utf-8', 'replace')) 144 print("%-8s %-7d %-16s %6s %s" % (strftime("%H:%M:%S"), event.pid, 145 event.comm.decode('utf-8', 'replace'), action_str, file_str)) 146 147b["events"].open_perf_buffer(print_event) 148while 1: 149 try: 150 b.perf_buffer_poll() 151 except KeyboardInterrupt: 152 exit() 153