Skip to content

Commit 5fd083e

Browse files
committed
tools: Introduce full-path helpers
In bcc PR [1], add common path helpers for serial of tools, this patch do the same thing. After analysis and experiments, libbpf-tools/c and tools/py codes about path-helpers cannot be reused. Thus, this patch add: 1. full_path.h: defined FULL_PATH_FIELD(name); 2. path_helpers.bpf.c: add bpf_dentry_full_path() and bpf_getcwd() helpers same as commit [1]; 3. path_helpers.py: add get_full_path() to parse full-path in full_path.h; [1] iovisor#5340 Signed-off-by: Rong Tao <[email protected]>
1 parent 8c5c96a commit 5fd083e

File tree

5 files changed

+176
-109
lines changed

5 files changed

+176
-109
lines changed

tools/CMakeLists.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
file(GLOB C_FILES *.c)
1+
file(GLOB C_FILES *.c *.h)
22
file(GLOB PY_FILES *.py)
33
file(GLOB SH_FILES *.sh)
44
file(GLOB TXT_FILES *.txt)
55
list(REMOVE_ITEM TXT_FILES ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt)
66
foreach(FIL ${PY_FILES})
77
get_filename_component(FIL_WE ${FIL} NAME_WE)
8-
install(PROGRAMS ${FIL} DESTINATION share/bcc/tools RENAME ${FIL_WE})
8+
# python helpers for import need suffix .py
9+
if (FIL_WE MATCHES "_helpers")
10+
install(FILES ${FIL} DESTINATION share/bcc/tools)
11+
else()
12+
install(PROGRAMS ${FIL} DESTINATION share/bcc/tools RENAME ${FIL_WE})
13+
endif()
914
endforeach()
1015
foreach(FIL ${SH_FILES})
1116
if(${FIL} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}/reset-trace.sh)

tools/full_path.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2+
/* Copyright (c) 2025 Rong Tao */
3+
/**
4+
* This code only for bcc/tools/, not for bcc/libbpf-tools/
5+
*/
6+
#pragma once
7+
#ifndef __FULL_PATH_H
8+
#define __FULL_PATH_H
9+
10+
#define NAME_MAX 255
11+
#define MAX_ENTRIES 32
12+
13+
/**
14+
* Example: "/CCCCC/BB/AAAA"
15+
* name[]: "AAAA000000000000BB0000000000CCCCC00000000000"
16+
* |<- NAME_MAX ->|
17+
*
18+
* name[] must be u8, because char [] will be truncated by ctypes.cast(),
19+
* such as above example, will be truncated to "AAAA0".
20+
*/
21+
#define FULL_PATH_FIELD(name) u8 name[NAME_MAX * MAX_ENTRIES];
22+
#endif

tools/opensnoop.py

Lines changed: 25 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -116,24 +116,10 @@
116116
#include <linux/fcntl.h>
117117
#include <linux/sched.h>
118118
#ifdef FULLPATH
119-
#include <linux/fs_struct.h>
120-
#include <linux/dcache.h>
121-
#include <linux/fs.h>
122-
#include <linux/mount.h>
123-
124-
/* see https://github.com/torvalds/linux/blob/master/fs/mount.h */
125-
struct mount {
126-
struct hlist_node mnt_hash;
127-
struct mount *mnt_parent;
128-
struct dentry *mnt_mountpoint;
129-
struct vfsmount mnt;
130-
/* ... */
131-
};
119+
INCLUDE_FULL_PATH_H
120+
INCLUDE_PATH_HELPERS_BPF_H
132121
#endif
133122
134-
#define NAME_MAX 255
135-
#define MAX_ENTRIES 32
136-
137123
struct val_t {
138124
u64 id;
139125
char comm[TASK_COMM_LEN];
@@ -150,15 +136,7 @@
150136
char comm[TASK_COMM_LEN];
151137
u32 path_depth;
152138
#ifdef FULLPATH
153-
/**
154-
* Example: "/CCCCC/BB/AAAA"
155-
* name[]: "AAAA000000000000BB0000000000CCCCC00000000000"
156-
* |<- NAME_MAX ->|
157-
*
158-
* name[] must be u8, because char [] will be truncated by ctypes.cast(),
159-
* such as above example, will be truncated to "AAAA0".
160-
*/
161-
u8 name[NAME_MAX * MAX_ENTRIES];
139+
FULL_PATH_FIELD(name);
162140
#else
163141
/* If not fullpath, avoid transfer big data */
164142
char name[NAME_MAX];
@@ -201,7 +179,9 @@
201179
data->mode = valp->mode; // EXTENDED_STRUCT_MEMBER
202180
data->ret = PT_REGS_RC(ctx);
203181
204-
SUBMIT_DATA
182+
GET_FULL_PATH
183+
184+
events.ringbuf_submit(data, sizeof(*data));
205185
206186
cleanup:
207187
infotmp.delete(&id);
@@ -362,7 +342,9 @@
362342
data->mode = mode; // EXTENDED_STRUCT_MEMBER
363343
data->ret = ret;
364344
365-
SUBMIT_DATA
345+
GET_FULL_PATH
346+
347+
events.ringbuf_submit(data, sizeof(*data));
366348
367349
return 0;
368350
}
@@ -448,73 +430,22 @@
448430
if 'EXTENDED_STRUCT_MEMBER' not in x)
449431

450432
if args.full_path:
451-
bpf_text = bpf_text.replace('SUBMIT_DATA', """
433+
bpf_text = bpf_text.replace('GET_FULL_PATH', """
452434
if (data->name[0] != '/') { // relative path
453-
struct task_struct *task;
454-
struct dentry *dentry, *parent_dentry, *mnt_root;
455-
struct vfsmount *vfsmnt;
456-
struct fs_struct *fs;
457-
struct path *path;
458-
struct mount *mnt;
459-
size_t filepart_length;
460-
char *payload = data->name;
461-
struct qstr d_name;
462-
int i;
463-
464-
task = (struct task_struct *)bpf_get_current_task_btf();
465-
466-
fs = task->fs;
467-
path = &fs->pwd;
468-
dentry = path->dentry;
469-
vfsmnt = path->mnt;
470-
471-
mnt = container_of(vfsmnt, struct mount, mnt);
472-
473-
for (i = 1, payload += NAME_MAX; i < MAX_ENTRIES; i++) {
474-
bpf_probe_read_kernel(&d_name, sizeof(d_name), &dentry->d_name);
475-
filepart_length =
476-
bpf_probe_read_kernel_str(payload, NAME_MAX, (void *)d_name.name);
477-
478-
if (filepart_length < 0 || filepart_length > NAME_MAX)
479-
break;
480-
481-
bpf_probe_read_kernel(&mnt_root, sizeof(mnt_root), &vfsmnt->mnt_root);
482-
bpf_probe_read_kernel(&parent_dentry, sizeof(parent_dentry), &dentry->d_parent);
483-
484-
if (dentry == parent_dentry || dentry == mnt_root) {
485-
struct mount *mnt_parent;
486-
bpf_probe_read_kernel(&mnt_parent, sizeof(mnt_parent), &mnt->mnt_parent);
487-
488-
if (mnt != mnt_parent) {
489-
bpf_probe_read_kernel(&dentry, sizeof(dentry), &mnt->mnt_mountpoint);
490-
491-
mnt = mnt_parent;
492-
vfsmnt = &mnt->mnt;
493-
494-
bpf_probe_read_kernel(&mnt_root, sizeof(mnt_root), &vfsmnt->mnt_root);
495-
496-
data->path_depth++;
497-
payload += NAME_MAX;
498-
continue;
499-
} else {
500-
/* Real root directory */
501-
break;
502-
}
503-
}
504-
505-
payload += NAME_MAX;
506-
507-
dentry = parent_dentry;
508-
data->path_depth++;
509-
}
435+
bpf_getcwd(data->name + NAME_MAX, NAME_MAX, MAX_ENTRIES - 1,
436+
&data->path_depth);
510437
}
511-
512-
events.ringbuf_submit(data, sizeof(*data));
513438
""")
439+
440+
with open(BPF._find_file("full_path.h".encode("utf-8"))) as fileobj:
441+
progtxt = fileobj.read()
442+
bpf_text = bpf_text.replace('INCLUDE_FULL_PATH_H', progtxt)
443+
444+
with open(BPF._find_file("path_helpers.bpf.c".encode("utf-8"))) as fileobj:
445+
progtxt = fileobj.read()
446+
bpf_text = bpf_text.replace('INCLUDE_PATH_HELPERS_BPF_H', progtxt)
514447
else:
515-
bpf_text = bpf_text.replace('SUBMIT_DATA', """
516-
events.ringbuf_submit(data, sizeof(*data));
517-
""")
448+
bpf_text = bpf_text.replace('GET_FULL_PATH', """""")
518449

519450
if debug or args.ebpf:
520451
print(bpf_text)
@@ -552,12 +483,6 @@
552483

553484
entries = defaultdict(list)
554485

555-
def split_names(str):
556-
NAME_MAX = 255
557-
MAX_ENTRIES = 32
558-
chunks = [str[i:i + NAME_MAX] for i in range(0, NAME_MAX * MAX_ENTRIES, NAME_MAX)]
559-
return [chunk.split(b'\x00', 1)[0] for chunk in chunks]
560-
561486
# process event
562487
def print_event(cpu, data, size):
563488
event = b["events"].event(data)
@@ -604,17 +529,10 @@ def print_event(cpu, data, size):
604529
printb(b"%08o %04o " % (event.flags, event.mode), nl="")
605530

606531
if args.full_path:
607-
# see struct data_t::name field comment.
608-
names = split_names(bytes(event.name))
609-
picked = names[:event.path_depth + 1]
610-
picked_str = []
611-
for x in picked:
612-
s = x.decode('utf-8', 'ignore') if isinstance(x, bytes) else str(x)
613-
# remove mountpoint '/' and empty string
614-
if s != "/" and s != "":
615-
picked_str.append(s)
616-
joined = '/'.join(picked_str[::-1])
617-
result = joined if joined.startswith('/') else '/' + joined
532+
import sys
533+
sys.path.append(os.path.dirname(sys.argv[0]))
534+
from path_helpers import get_full_path
535+
result = get_full_path(event.name, event.path_depth)
618536
printb(b"%s" % result.encode("utf-8"))
619537
else:
620538
printb(b"%s" % event.name)

tools/path_helpers.bpf.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2+
/* Copyright (c) 2025 Rong Tao */
3+
#ifndef __PATH_HELPERS_BPF_H
4+
#define __PATH_HELPERS_BPF_H 1
5+
6+
#include <linux/fs_struct.h>
7+
#include <linux/dcache.h>
8+
#include <linux/fs.h>
9+
#include <linux/mount.h>
10+
11+
/* see https://github.com/torvalds/linux/blob/master/fs/mount.h */
12+
struct mount {
13+
struct hlist_node mnt_hash;
14+
struct mount *mnt_parent;
15+
struct dentry *mnt_mountpoint;
16+
struct vfsmount mnt;
17+
/* ... */
18+
};
19+
20+
21+
static __always_inline
22+
int bpf_dentry_full_path(char *pathes, int name_len, int max_depth,
23+
struct dentry *dentry, struct vfsmount *vfsmnt,
24+
__u32 *path_depth)
25+
{
26+
struct dentry *parent_dentry, *mnt_root;
27+
struct mount *mnt;
28+
size_t filepart_length;
29+
char *payload = pathes;
30+
struct qstr d_name;
31+
int i;
32+
33+
mnt = container_of(vfsmnt, struct mount, mnt);
34+
35+
for (i = 1, payload += name_len; i < max_depth; i++) {
36+
bpf_probe_read_kernel(&d_name, sizeof(d_name), &dentry->d_name);
37+
filepart_length =
38+
bpf_probe_read_kernel_str(payload, name_len, (void *)d_name.name);
39+
40+
if (filepart_length < 0 || filepart_length > name_len)
41+
break;
42+
43+
bpf_probe_read_kernel(&mnt_root, sizeof(mnt_root), &vfsmnt->mnt_root);
44+
bpf_probe_read_kernel(&parent_dentry, sizeof(parent_dentry), &dentry->d_parent);
45+
46+
if (dentry == parent_dentry || dentry == mnt_root) {
47+
struct mount *mnt_parent;
48+
bpf_probe_read_kernel(&mnt_parent, sizeof(mnt_parent), &mnt->mnt_parent);
49+
50+
if (mnt != mnt_parent) {
51+
bpf_probe_read_kernel(&dentry, sizeof(dentry), &mnt->mnt_mountpoint);
52+
53+
mnt = mnt_parent;
54+
vfsmnt = &mnt->mnt;
55+
56+
bpf_probe_read_kernel(&mnt_root, sizeof(mnt_root), &vfsmnt->mnt_root);
57+
58+
(*path_depth)++;
59+
payload += name_len;
60+
continue;
61+
} else {
62+
/* Real root directory */
63+
break;
64+
}
65+
}
66+
67+
payload += name_len;
68+
69+
dentry = parent_dentry;
70+
(*path_depth)++;
71+
}
72+
73+
return 0;
74+
}
75+
76+
static __always_inline
77+
int bpf_getcwd(char *pathes, int name_len, int max_depth, __u32 *path_depth)
78+
{
79+
struct task_struct *task;
80+
struct fs_struct *fs;
81+
struct dentry *dentry;
82+
struct vfsmount *vfsmnt;
83+
84+
task = (struct task_struct *)bpf_get_current_task_btf();
85+
bpf_probe_read_kernel(&fs, sizeof(fs), &task->fs);
86+
bpf_probe_read_kernel(&dentry, sizeof(dentry), &fs->pwd.dentry);
87+
bpf_probe_read_kernel(&vfsmnt, sizeof(vfsmnt), &fs->pwd.mnt);
88+
89+
return bpf_dentry_full_path(pathes, name_len, max_depth, dentry, vfsmnt,
90+
path_depth);
91+
}
92+
#endif

tools/path_helpers.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env python
2+
# Path related helper functions
3+
#
4+
# Copyright (c) 2025 Rong Tao
5+
# Licensed under the Apache License, Version 2.0 (the "License")
6+
#
7+
# 30-Jun-2025 Rong Tao Created this.
8+
9+
import os
10+
11+
def full_path_split_names(str, name_max=255, max_entries=32):
12+
name_max = 255
13+
max_entries = 32
14+
chunks = [str[i:i + name_max] for i in range(0, name_max * max_entries, name_max)]
15+
return [chunk.split(b'\x00', 1)[0] for chunk in chunks]
16+
17+
18+
# parse full-path, see tools/full_path.h
19+
def get_full_path(name, depth, name_max=255, max_entries=32):
20+
names = full_path_split_names(bytes(name), name_max, max_entries)
21+
picked = names[:depth + 1]
22+
picked_str = []
23+
for x in picked:
24+
s = x.decode('utf-8', 'ignore') if isinstance(x, bytes) else str(x)
25+
# remove mountpoint '/' and empty string
26+
if s != "/" and s != "":
27+
picked_str.append(s)
28+
joined = '/'.join(picked_str[::-1])
29+
result = joined if joined.startswith('/') else '/' + joined
30+
return result

0 commit comments

Comments
 (0)