1#!/usr/bin/env python 2# @lint-avoid-python-3-compatibility-imports 3# 4# filelife Trace the lifespan of short-lived files. 5# For Linux, uses BCC, eBPF. Embedded C. 6# 7# This traces the creation and deletion of files, providing information 8# on who deleted the file, the file age, and the file name. The intent is to 9# provide information on short-lived files, for debugging or performance 10# analysis. 11# 12# USAGE: filelife [-h] [-p PID] 13# 14# Copyright 2016 Netflix, Inc. 15# Licensed under the Apache License, Version 2.0 (the "License") 16# 17# 08-Feb-2015 Brendan Gregg Created this. 18# 17-Feb-2016 Allan McAleavy updated for BPF_PERF_OUTPUT 19# 13-Nov-2022 Rong Tao Check btf struct field for CO-RE and add vfs_open() 20 21from __future__ import print_function 22from bcc import BPF 23import argparse 24from time import strftime 25 26# arguments 27examples = """examples: 28 ./filelife # trace lifecycle of file(create->remove) 29 ./filelife -p 181 # only trace PID 181 30""" 31parser = argparse.ArgumentParser( 32 description="Trace lifecycle of file", 33 formatter_class=argparse.RawDescriptionHelpFormatter, 34 epilog=examples) 35parser.add_argument("-p", "--pid", 36 help="trace this PID only") 37parser.add_argument("--ebpf", action="store_true", 38 help=argparse.SUPPRESS) 39args = parser.parse_args() 40debug = 0 41 42# define BPF program 43bpf_text = """ 44#include <uapi/linux/ptrace.h> 45#include <linux/fs.h> 46#include <linux/sched.h> 47 48struct data_t { 49 u32 pid; 50 u64 delta; 51 char comm[TASK_COMM_LEN]; 52 char fname[DNAME_INLINE_LEN]; 53}; 54 55BPF_HASH(birth, struct dentry *); 56BPF_PERF_OUTPUT(events); 57 58static int probe_dentry(struct pt_regs *ctx, struct dentry *dentry) 59{ 60 u32 pid = bpf_get_current_pid_tgid() >> 32; 61 FILTER 62 63 u64 ts = bpf_ktime_get_ns(); 64 birth.update(&dentry, &ts); 65 66 return 0; 67} 68 69// trace file creation time 70TRACE_CREATE_FUNC 71{ 72 return probe_dentry(ctx, dentry); 73}; 74 75// trace file security_inode_create time 76int trace_security_inode_create(struct pt_regs *ctx, struct inode *dir, 77 struct dentry *dentry) 78{ 79 return probe_dentry(ctx, dentry); 80}; 81 82// trace file open time 83int trace_open(struct pt_regs *ctx, struct path *path, struct file *file) 84{ 85 struct dentry *dentry = path->dentry; 86 87 if (!(file->f_mode & FMODE_CREATED)) { 88 return 0; 89 } 90 91 return probe_dentry(ctx, dentry); 92}; 93 94// trace file deletion and output details 95TRACE_UNLINK_FUNC 96{ 97 struct data_t data = {}; 98 u32 pid = bpf_get_current_pid_tgid() >> 32; 99 100 FILTER 101 102 u64 *tsp, delta; 103 tsp = birth.lookup(&dentry); 104 if (tsp == 0) { 105 return 0; // missed create 106 } 107 108 delta = (bpf_ktime_get_ns() - *tsp) / 1000000; 109 birth.delete(&dentry); 110 111 struct qstr d_name = dentry->d_name; 112 if (d_name.len == 0) 113 return 0; 114 115 if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) { 116 data.pid = pid; 117 data.delta = delta; 118 bpf_probe_read_kernel(&data.fname, sizeof(data.fname), d_name.name); 119 } 120 121 events.perf_submit(ctx, &data, sizeof(data)); 122 123 return 0; 124} 125""" 126 127trace_create_text_1=""" 128int trace_create(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) 129""" 130trace_create_text_2=""" 131int trace_create(struct pt_regs *ctx, struct user_namespace *mnt_userns, 132 struct inode *dir, struct dentry *dentry) 133""" 134trace_create_text_3=""" 135int trace_create(struct pt_regs *ctx, struct mnt_idmap *idmap, 136 struct inode *dir, struct dentry *dentry) 137""" 138 139trace_unlink_text_1=""" 140int trace_unlink(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) 141""" 142trace_unlink_text_2=""" 143int trace_unlink(struct pt_regs *ctx, struct user_namespace *mnt_userns, 144 struct inode *dir, struct dentry *dentry) 145""" 146trace_unlink_text_3=""" 147int trace_unlink(struct pt_regs *ctx, struct mnt_idmap *idmap, 148 struct inode *dir, struct dentry *dentry) 149""" 150 151if args.pid: 152 bpf_text = bpf_text.replace('FILTER', 153 'if (pid != %s) { return 0; }' % args.pid) 154else: 155 bpf_text = bpf_text.replace('FILTER', '') 156if debug or args.ebpf: 157 print(bpf_text) 158 if args.ebpf: 159 exit() 160 161if BPF.kernel_struct_has_field(b'renamedata', b'new_mnt_idmap') == 1: 162 bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_3) 163 bpf_text = bpf_text.replace('TRACE_UNLINK_FUNC', trace_unlink_text_3) 164elif BPF.kernel_struct_has_field(b'renamedata', b'old_mnt_userns') == 1: 165 bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_2) 166 bpf_text = bpf_text.replace('TRACE_UNLINK_FUNC', trace_unlink_text_2) 167else: 168 bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_1) 169 bpf_text = bpf_text.replace('TRACE_UNLINK_FUNC', trace_unlink_text_1) 170 171# initialize BPF 172b = BPF(text=bpf_text) 173b.attach_kprobe(event="vfs_create", fn_name="trace_create") 174# newer kernels may don't fire vfs_create, call vfs_open instead: 175b.attach_kprobe(event="vfs_open", fn_name="trace_open") 176# newer kernels (say, 4.8) may don't fire vfs_create, so record (or overwrite) 177# the timestamp in security_inode_create(): 178if BPF.get_kprobe_functions(b"security_inode_create"): 179 b.attach_kprobe(event="security_inode_create", fn_name="trace_security_inode_create") 180b.attach_kprobe(event="vfs_unlink", fn_name="trace_unlink") 181 182# header 183print("%-8s %-7s %-16s %-7s %s" % ("TIME", "PID", "COMM", "AGE(s)", "FILE")) 184 185# process event 186def print_event(cpu, data, size): 187 event = b["events"].event(data) 188 print("%-8s %-7d %-16s %-7.2f %s" % (strftime("%H:%M:%S"), event.pid, 189 event.comm.decode('utf-8', 'replace'), float(event.delta) / 1000, 190 event.fname.decode('utf-8', 'replace'))) 191 192b["events"].open_perf_buffer(print_event) 193while 1: 194 try: 195 b.perf_buffer_poll() 196 except KeyboardInterrupt: 197 exit() 198