Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libbpf-tools: add BPF CO-RE filegone #4978

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libbpf-tools/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
/f2fsdist
/f2fsslower
/filelife
/filegone
/filetop
/fsdist
/fsslower
Expand Down
1 change: 1 addition & 0 deletions libbpf-tools/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ APPS = \
execsnoop \
exitsnoop \
filelife \
filegone \
filetop \
fsdist \
fsslower \
Expand Down
133 changes: 133 additions & 0 deletions libbpf-tools/filegone.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright 2024 Sony Group Corporation

#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_tracing.h>
#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";
210 changes: 210 additions & 0 deletions libbpf-tools/filegone.c
Original file line number Diff line number Diff line change
@@ -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 <argp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#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);
}
19 changes: 19 additions & 0 deletions libbpf-tools/filegone.h
Original file line number Diff line number Diff line change
@@ -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 */
Loading