Skip to content

Commit bba69e2

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] #5340 Signed-off-by: Rong Tao <[email protected]>
1 parent 137bd5f commit bba69e2

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
@@ -102,24 +102,10 @@
102102
#include <linux/fcntl.h>
103103
#include <linux/sched.h>
104104
#ifdef FULLPATH
105-
#include <linux/fs_struct.h>
106-
#include <linux/dcache.h>
107-
#include <linux/fs.h>
108-
#include <linux/mount.h>
109-
110-
/* see https://github.com/torvalds/linux/blob/master/fs/mount.h */
111-
struct mount {
112-
struct hlist_node mnt_hash;
113-
struct mount *mnt_parent;
114-
struct dentry *mnt_mountpoint;
115-
struct vfsmount mnt;
116-
/* ... */
117-
};
105+
INCLUDE_FULL_PATH_H
106+
INCLUDE_PATH_HELPERS_BPF_H
118107
#endif
119108
120-
#define NAME_MAX 255
121-
#define MAX_ENTRIES 32
122-
123109
struct val_t {
124110
u64 id;
125111
char comm[TASK_COMM_LEN];
@@ -136,15 +122,7 @@
136122
char comm[TASK_COMM_LEN];
137123
u32 path_depth;
138124
#ifdef FULLPATH
139-
/**
140-
* Example: "/CCCCC/BB/AAAA"
141-
* name[]: "AAAA000000000000BB0000000000CCCCC00000000000"
142-
* |<- NAME_MAX ->|
143-
*
144-
* name[] must be u8, because char [] will be truncated by ctypes.cast(),
145-
* such as above example, will be truncated to "AAAA0".
146-
*/
147-
u8 name[NAME_MAX * MAX_ENTRIES];
125+
FULL_PATH_FIELD(name);
148126
#else
149127
/* If not fullpath, avoid transfer big data */
150128
char name[NAME_MAX];
@@ -187,7 +165,9 @@
187165
data->mode = valp->mode; // EXTENDED_STRUCT_MEMBER
188166
data->ret = PT_REGS_RC(ctx);
189167
190-
SUBMIT_DATA
168+
GET_FULL_PATH
169+
170+
events.ringbuf_submit(data, sizeof(*data));
191171
192172
cleanup:
193173
infotmp.delete(&id);
@@ -347,7 +327,9 @@
347327
data->mode = mode; // EXTENDED_STRUCT_MEMBER
348328
data->ret = ret;
349329
350-
SUBMIT_DATA
330+
GET_FULL_PATH
331+
332+
events.ringbuf_submit(data, sizeof(*data));
351333
352334
return 0;
353335
}
@@ -416,73 +398,22 @@
416398
if 'EXTENDED_STRUCT_MEMBER' not in x)
417399

418400
if args.full_path:
419-
bpf_text = bpf_text.replace('SUBMIT_DATA', """
401+
bpf_text = bpf_text.replace('GET_FULL_PATH', """
420402
if (data->name[0] != '/') { // relative path
421-
struct task_struct *task;
422-
struct dentry *dentry, *parent_dentry, *mnt_root;
423-
struct vfsmount *vfsmnt;
424-
struct fs_struct *fs;
425-
struct path *path;
426-
struct mount *mnt;
427-
size_t filepart_length;
428-
char *payload = data->name;
429-
struct qstr d_name;
430-
int i;
431-
432-
task = (struct task_struct *)bpf_get_current_task_btf();
433-
434-
fs = task->fs;
435-
path = &fs->pwd;
436-
dentry = path->dentry;
437-
vfsmnt = path->mnt;
438-
439-
mnt = container_of(vfsmnt, struct mount, mnt);
440-
441-
for (i = 1, payload += NAME_MAX; i < MAX_ENTRIES; i++) {
442-
bpf_probe_read_kernel(&d_name, sizeof(d_name), &dentry->d_name);
443-
filepart_length =
444-
bpf_probe_read_kernel_str(payload, NAME_MAX, (void *)d_name.name);
445-
446-
if (filepart_length < 0 || filepart_length > NAME_MAX)
447-
break;
448-
449-
bpf_probe_read_kernel(&mnt_root, sizeof(mnt_root), &vfsmnt->mnt_root);
450-
bpf_probe_read_kernel(&parent_dentry, sizeof(parent_dentry), &dentry->d_parent);
451-
452-
if (dentry == parent_dentry || dentry == mnt_root) {
453-
struct mount *mnt_parent;
454-
bpf_probe_read_kernel(&mnt_parent, sizeof(mnt_parent), &mnt->mnt_parent);
455-
456-
if (mnt != mnt_parent) {
457-
bpf_probe_read_kernel(&dentry, sizeof(dentry), &mnt->mnt_mountpoint);
458-
459-
mnt = mnt_parent;
460-
vfsmnt = &mnt->mnt;
461-
462-
bpf_probe_read_kernel(&mnt_root, sizeof(mnt_root), &vfsmnt->mnt_root);
463-
464-
data->path_depth++;
465-
payload += NAME_MAX;
466-
continue;
467-
} else {
468-
/* Real root directory */
469-
break;
470-
}
471-
}
472-
473-
payload += NAME_MAX;
474-
475-
dentry = parent_dentry;
476-
data->path_depth++;
477-
}
403+
bpf_getcwd(data->name + NAME_MAX, NAME_MAX, MAX_ENTRIES - 1,
404+
&data->path_depth);
478405
}
479-
480-
events.ringbuf_submit(data, sizeof(*data));
481406
""")
407+
408+
with open(BPF._find_file("full_path.h".encode("utf-8"))) as fileobj:
409+
progtxt = fileobj.read()
410+
bpf_text = bpf_text.replace('INCLUDE_FULL_PATH_H', progtxt)
411+
412+
with open(BPF._find_file("path_helpers.bpf.c".encode("utf-8"))) as fileobj:
413+
progtxt = fileobj.read()
414+
bpf_text = bpf_text.replace('INCLUDE_PATH_HELPERS_BPF_H', progtxt)
482415
else:
483-
bpf_text = bpf_text.replace('SUBMIT_DATA', """
484-
events.ringbuf_submit(data, sizeof(*data));
485-
""")
416+
bpf_text = bpf_text.replace('GET_FULL_PATH', """""")
486417

487418
if debug or args.ebpf:
488419
print(bpf_text)
@@ -517,12 +448,6 @@
517448

518449
entries = defaultdict(list)
519450

520-
def split_names(str):
521-
NAME_MAX = 255
522-
MAX_ENTRIES = 32
523-
chunks = [str[i:i + NAME_MAX] for i in range(0, NAME_MAX * MAX_ENTRIES, NAME_MAX)]
524-
return [chunk.split(b'\x00', 1)[0] for chunk in chunks]
525-
526451
# process event
527452
def print_event(cpu, data, size):
528453
event = b["events"].event(data)
@@ -569,17 +494,10 @@ def print_event(cpu, data, size):
569494
printb(b"%08o %04o " % (event.flags, event.mode), nl="")
570495

571496
if args.full_path:
572-
# see struct data_t::name field comment.
573-
names = split_names(bytes(event.name))
574-
picked = names[:event.path_depth + 1]
575-
picked_str = []
576-
for x in picked:
577-
s = x.decode('utf-8', 'ignore') if isinstance(x, bytes) else str(x)
578-
# remove mountpoint '/' and empty string
579-
if s != "/" and s != "":
580-
picked_str.append(s)
581-
joined = '/'.join(picked_str[::-1])
582-
result = joined if joined.startswith('/') else '/' + joined
497+
import sys
498+
sys.path.append(os.path.dirname(sys.argv[0]))
499+
from path_helpers import get_full_path
500+
result = get_full_path(event.name, event.path_depth)
583501
printb(b"%s" % result.encode("utf-8"))
584502
else:
585503
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 split_names(str):
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):
20+
names = split_names(bytes(name))
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)