diff --git a/libbpf-tools/.gitignore b/libbpf-tools/.gitignore index 9da5a5553322..5a6f751c8941 100644 --- a/libbpf-tools/.gitignore +++ b/libbpf-tools/.gitignore @@ -22,6 +22,7 @@ /f2fsdist /f2fsslower /filelife +/filegone /filetop /fsdist /fsslower diff --git a/libbpf-tools/Makefile b/libbpf-tools/Makefile index 0ae0d8611d8d..a970a8d5defa 100644 --- a/libbpf-tools/Makefile +++ b/libbpf-tools/Makefile @@ -55,6 +55,7 @@ APPS = \ execsnoop \ exitsnoop \ filelife \ + filegone \ filetop \ fsdist \ fsslower \ diff --git a/libbpf-tools/filegone.bpf.c b/libbpf-tools/filegone.bpf.c new file mode 100644 index 000000000000..fb267ab8158a --- /dev/null +++ b/libbpf-tools/filegone.bpf.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2024 Sony Group Corporation + +#include +#include +#include +#include +#include "filegone.h" +#include "core_fixes.bpf.h" + +#define FMODE_CREATED 0x100000 + +const volatile pid_t targ_tgid = 0; + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(u32)); + __uint(value_size, sizeof(u32)); +} events SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 8192); + __type(key, u32); /* tid */ + __type(value, struct event); +} currevent SEC(".maps"); + +/* In different kernel versions, function vfs_unlink() has three declarations, + * and their parameter lists are as follows: + * + * int vfs_unlink(struct inode *dir, struct dentry *dentry, + * struct inode **delegated_inode); + * int vfs_unlink(struct user_namespace *mnt_userns, struct inode *dir, + * struct dentry *dentry, struct inode **delegated_inode); + * int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir, + * struct dentry *dentry, struct inode **delegated_inode); + */ +SEC("kprobe/vfs_unlink") +int BPF_KPROBE(vfs_unlink, void *arg0, void *arg1, void *arg2) +{ + u64 id = bpf_get_current_pid_tgid(); + u32 tid = (u32)id; + struct event event = {}; + const u8 *qs_name_ptr; + u32 tgid = id >> 32; + + if (targ_tgid && targ_tgid != tgid) + return 0; + + bool has_arg = renamedata_has_old_mnt_userns_field() + || renamedata_has_new_mnt_idmap_field(); + qs_name_ptr = has_arg + ? BPF_CORE_READ((struct dentry *)arg2, d_name.name) + : BPF_CORE_READ((struct dentry *)arg1, d_name.name); + bpf_probe_read_kernel_str(&event.fname, sizeof(event.fname), qs_name_ptr); + bpf_get_current_comm(&event.task, sizeof(event.task)); + event.action = 'D'; + event.tgid = tgid; + + bpf_map_update_elem(&currevent, &tid, &event, BPF_ANY); + return 0; +} + +/* vfs_rename() has two declarations in different kernel versions with the following parameter lists- + * int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, + struct dentry *new_dentry, struct inode **delegated_inode, unsigned int flags); + * int vfs_rename(struct renamedata *); + */ +SEC("kprobe/vfs_rename") +int BPF_KPROBE(vfs_rename, void *arg0, void *arg1, void *arg2, void *arg3) +{ + u64 id = bpf_get_current_pid_tgid(); + u32 tid = (u32)id; + struct event event = {}; + struct qstr qs_name_ptr; + struct qstr qd_name_ptr; + u32 tgid = id >> 32; + + if (targ_tgid && targ_tgid != tgid) + return 0; + + bool has_arg = renamedata_has_old_mnt_userns_field() + || renamedata_has_new_mnt_idmap_field(); + qs_name_ptr = has_arg + ? BPF_CORE_READ((struct renamedata *)arg0, old_dentry, d_name) + : BPF_CORE_READ((struct dentry *)arg1, d_name); + qd_name_ptr = has_arg + ? BPF_CORE_READ((struct renamedata *)arg0, new_dentry, d_name) + : BPF_CORE_READ((struct dentry *)arg3, d_name); + bpf_get_current_comm(&event.task, sizeof(event.task)); + bpf_probe_read_kernel_str(&event.fname, sizeof(event.fname), qs_name_ptr.name); + bpf_probe_read_kernel_str(&event.fname2, sizeof(event.fname2), qd_name_ptr.name); + event.action = 'R'; + event.tgid = tgid; + + bpf_map_update_elem(&currevent, &tid, &event, BPF_ANY); + return 0; +} + +static int handle_kretprobe(struct pt_regs *ctx) +{ + u64 id = bpf_get_current_pid_tgid(); + u32 tid = (u32)id; + int ret = PT_REGS_RC(ctx); + struct event *event; + + event = bpf_map_lookup_elem(&currevent, &tid); + if (!event) + return 0; + + bpf_map_delete_elem(&currevent, &tid); + + /* Skip failed unlink or rename */ + if (ret) + return 0; + + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, event, sizeof(*event)); + return 0; +} + +SEC("kretprobe/vfs_unlink") +int BPF_KRETPROBE(vfs_unlink_ret) +{ + return handle_kretprobe(ctx); +} + +SEC("kretprobe/vfs_rename") +int BPF_KRETPROBE(vfs_rename_ret) +{ + return handle_kretprobe(ctx); +} + +char LICENSE[] SEC("license") = "GPL"; diff --git a/libbpf-tools/filegone.c b/libbpf-tools/filegone.c new file mode 100644 index 000000000000..f41733d0983a --- /dev/null +++ b/libbpf-tools/filegone.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +// filegone Trace why a file has vanished (either deleted or renamed). +// Copyright 2024 Sony Group Corporation +// +// Based on filegone from BCC by Curu. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "filegone.h" +#include "filegone.skel.h" +#include "btf_helpers.h" +#include "trace_helpers.h" + +#define PERF_BUFFER_PAGES 16 +#define PERF_POLL_TIMEOUT_MS 100 + +static volatile sig_atomic_t exiting = 0; + +static struct env { + pid_t pid; + bool verbose; +} env = {}; + +const char *argp_program_version = "filegone 0.1"; +const char *argp_program_bug_address = +"https://github.com/iovisor/bcc/tree/master/libbpf-tools"; +const char argp_program_doc[] = +"Trace why a file has vanished (either deleted or renamed).\n" +"\n" +"USAGE: filegone [--help] [-p PID]\n" +"\n" +"EXAMPLES:\n" +" filegone # trace all events\n" +" filegone -p 123 # trace pid 123\n"; + +static const struct argp_option opts[] = { + {"pid", 'p', "PID", 0, "Process PID to trace"}, + {"verbose", 'v', NULL, 0, "Verbose debug output"}, + {NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help"}, + {}, +}; + +static error_t parse_arg(int key, char *arg, struct argp_state *state) +{ + int pid; + + switch (key) { + case 'h': + argp_state_help(state, stderr, ARGP_HELP_STD_HELP); + break; + case 'v': + env.verbose = true; + break; + case 'p': + errno = 0; + pid = strtol(arg, NULL, 10); + if (errno || pid <= 0) { + fprintf(stderr, "invalid PID: %s\n", arg); + argp_usage(state); + } + env.pid = pid; + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) +{ + if (level == LIBBPF_DEBUG && !env.verbose) + return 0; + return vfprintf(stderr, format, args); +} + +static void sig_int(int signo) +{ + exiting = 1; +} + +const char *action2str(char action) +{ + return (action == 'D') ? "DELETE" : "RENAME"; +} + +void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) +{ + const char *action_str; + char file_str[96]; + struct event e; + struct tm *tm; + char ts[32]; + time_t t; + + if (data_sz < sizeof(e)) { + printf("Error: packet too small\n"); + return; + } + + /* Copy data as alignment in the perf buffer isn't guaranteed. */ + memcpy(&e, data, sizeof(e)); + action_str = action2str(e.action); + + if (strcmp(action_str, "RENAME") == 0) { + strncpy(file_str, e.fname, sizeof(file_str) - 1); + strncat(file_str, " > ", sizeof(file_str) - strlen(file_str) - 1); + strncat(file_str, e.fname2, sizeof(file_str) - strlen(file_str) - 1); + } else { + strncpy(file_str, e.fname, sizeof(file_str) - 1); + } + + time(&t); + tm = localtime(&t); + strftime(ts, sizeof(ts), "%H:%M:%S", tm); + + printf("%-8s %-6d %-16s %-6s %s\n", + ts, e.tgid, e.task, action_str, + file_str); +} + +void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) +{ + fprintf(stderr, "lost %llu events on CPU #%d\n", lost_cnt, cpu); +} + +int main(int argc, char **argv) +{ + LIBBPF_OPTS(bpf_object_open_opts, open_opts); + static const struct argp argp = { + .options = opts, + .parser = parse_arg, + .doc = argp_program_doc, + }; + struct perf_buffer *pb = NULL; + struct filegone_bpf *obj; + int err; + + err = argp_parse(&argp, argc, argv, 0, NULL, NULL); + if (err) + return err; + + libbpf_set_print(libbpf_print_fn); + + err = ensure_core_btf(&open_opts); + if (err) { + fprintf(stderr, "failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err)); + return 1; + } + + obj = filegone_bpf__open_opts(&open_opts); + if (!obj) { + fprintf(stderr, "failed to open BPF object\n"); + return 1; + } + + /* initialize global data (filtering options) */ + obj->rodata->targ_tgid = env.pid; + + err = filegone_bpf__load(obj); + if (err) { + fprintf(stderr, "failed to load BPF object: %d\n", err); + goto cleanup; + } + + err = filegone_bpf__attach(obj); + if (err) { + fprintf(stderr, "failed to attach BPF programs\n"); + goto cleanup; + } + + printf("Tracing deleted or renamed files ... Hit Ctrl-C to end.\n"); + printf("%-8s %-6s %-16s %-6s %s\n", "TIME", "PID", "COMM", "ACTION", "FILE"); + + pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES, + handle_event, handle_lost_events, NULL, NULL); + if (!pb) { + err = -errno; + fprintf(stderr, "failed to open perf buffer: %d\n", err); + goto cleanup; + } + + if (signal(SIGINT, sig_int) == SIG_ERR) { + fprintf(stderr, "can't set signal handler: %s\n", strerror(errno)); + err = 1; + goto cleanup; + } + + while (!exiting) { + err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS); + if (err < 0 && err != -EINTR) { + fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err)); + goto cleanup; + } + /* Reset err to return 0 if exiting */ + err = 0; + } + +cleanup: + perf_buffer__free(pb); + filegone_bpf__destroy(obj); + cleanup_core_btf(&open_opts); +} diff --git a/libbpf-tools/filegone.h b/libbpf-tools/filegone.h new file mode 100644 index 000000000000..520560ba3f96 --- /dev/null +++ b/libbpf-tools/filegone.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +// +// Copyright 2024 Sony Group Corporation + +#ifndef __FILEGONE_H +#define __FILEGONE_H + +#define DNAME_INLINE_LEN 32 +#define TASK_COMM_LEN 16 + +struct event { + char fname[DNAME_INLINE_LEN]; + char fname2[DNAME_INLINE_LEN]; + char task[TASK_COMM_LEN]; + __u8 action; + pid_t tgid; +}; + +#endif /* __FILEGONE_H */