|
20 | 20 | import argparse |
21 | 21 | import os |
22 | 22 | import stat |
| 23 | +import re |
23 | 24 | from subprocess import call |
24 | 25 |
|
| 26 | +MAX_DENTRY_DEPTH_VAL = 32 |
| 27 | + |
25 | 28 | # arguments |
26 | | -examples = """examples: |
27 | | - ./filetop # file I/O top, 1 second refresh |
28 | | - ./filetop -C # don't clear the screen |
29 | | - ./filetop -p 181 # PID 181 only |
30 | | - ./filetop -d /home/user # trace files in /home/user directory only |
31 | | - ./filetop 5 # 5 second summaries |
32 | | - ./filetop 5 10 # 5 second summaries, 10 times only |
33 | | - ./filetop 5 --read-only # 5 second summaries, only read operations traced |
34 | | - ./filetop 5 --write-only # 5 second summaries, only write operations traced |
| 29 | +examples = f"""examples: |
| 30 | + ./filetop # file I/O top, 1 second refresh |
| 31 | + ./filetop -C # don't clear the screen |
| 32 | + ./filetop -p 181 # PID 181 only |
| 33 | + ./filetop -d /home/user # trace files in /home/user directory only |
| 34 | + ./filetop -d /home/user -R # trace files in /home/user and subdirectories (max depth {MAX_DENTRY_DEPTH_VAL}) |
| 35 | + ./filetop 5 # 5 second summaries |
| 36 | + ./filetop 5 10 # 5 second summaries, 10 times only |
| 37 | + ./filetop 5 --read-only # 5 second summaries, only read operations traced |
| 38 | + ./filetop 5 --write-only # 5 second summaries, only write operations traced |
35 | 39 | """ |
36 | 40 | parser = argparse.ArgumentParser( |
37 | 41 | description="File reads and writes by process", |
|
60 | 64 | help=argparse.SUPPRESS) |
61 | 65 | parser.add_argument("-d", "--directory", type=str, |
62 | 66 | help="trace this directory only") |
| 67 | +parser.add_argument("-R", "--recursive", action="store_true", |
| 68 | + help=f"when used with -d, also trace files in subdirectories (max depth {MAX_DENTRY_DEPTH_VAL})") |
63 | 69 |
|
64 | 70 | args = parser.parse_args() |
65 | 71 | interval = int(args.interval) |
|
80 | 86 | #undef DNAME_INLINE_LEN |
81 | 87 | #define DNAME_INLINE_LEN __BCC_DNAME_INLINE_LEN |
82 | 88 |
|
| 89 | +// Limit dentry parent walk depth to satisfy eBPF verifier loop |
| 90 | +#ifndef MAX_DENTRY_DEPTH |
| 91 | +#define MAX_DENTRY_DEPTH MAX_DENTRY_DEPTH_VALUE |
| 92 | +#endif |
| 93 | +
|
| 94 | +static __always_inline int not_under_inode(struct dentry *de, unsigned long target_ino) |
| 95 | +{ |
| 96 | + struct dentry *pde = de; |
| 97 | + int found = 0; |
| 98 | + #pragma unroll |
| 99 | + for (int i = 0; i < MAX_DENTRY_DEPTH; i++) { |
| 100 | + if (!pde->d_parent) |
| 101 | + break; |
| 102 | + pde = pde->d_parent; |
| 103 | + if (pde->d_inode && pde->d_inode->i_ino == target_ino) { |
| 104 | + found = 1; |
| 105 | + break; |
| 106 | + } |
| 107 | + } |
| 108 | + return !found; |
| 109 | +} |
| 110 | +
|
83 | 111 | // the key for the output summary |
84 | 112 | struct info_t { |
85 | 113 | unsigned long inode; |
|
169 | 197 | } |
170 | 198 |
|
171 | 199 | """ |
| 200 | +bpf_text = bpf_text.replace('MAX_DENTRY_DEPTH_VALUE', str(MAX_DENTRY_DEPTH_VAL)) |
| 201 | +m = re.search(r'#define\s+MAX_DENTRY_DEPTH\s+(\d+)', bpf_text) |
| 202 | +MAX_DENTRY_DEPTH = int(m.group(1)) if m else 32 |
172 | 203 | if args.tgid: |
173 | 204 | bpf_text = bpf_text.replace('TGID_FILTER', 'tgid != %d' % args.tgid) |
174 | 205 | else: |
|
180 | 211 | if args.directory: |
181 | 212 | try: |
182 | 213 | directory_inode = os.lstat(args.directory)[stat.ST_INO] |
183 | | - print(f'Tracing directory: {args.directory} (Inode: {directory_inode})') |
184 | | - bpf_text = bpf_text.replace('DIRECTORY_FILTER', 'file->f_path.dentry->d_parent->d_inode->i_ino != %d' % directory_inode) |
| 214 | + if args.recursive: |
| 215 | + print( |
| 216 | + f'Tracing directory recursively: {args.directory} ' |
| 217 | + f'(Inode: {directory_inode}, max depth: {MAX_DENTRY_DEPTH_VAL})' |
| 218 | + ) |
| 219 | + directory_filter = "not_under_inode(de, %d)" % directory_inode |
| 220 | + else: |
| 221 | + print(f'Tracing directory: {args.directory} (Inode: {directory_inode})') |
| 222 | + directory_filter = "de->d_parent->d_inode->i_ino != %d" % directory_inode |
| 223 | + |
| 224 | + bpf_text = bpf_text.replace('DIRECTORY_FILTER', directory_filter) |
185 | 225 | except (FileNotFoundError, PermissionError) as e: |
186 | 226 | print(f'Error accessing directory {args.directory}: {e}') |
187 | 227 | exit(1) |
188 | 228 | else: |
| 229 | + if args.recursive: |
| 230 | + print("Error: --recursive can only be used with -d/--directory option") |
| 231 | + exit(1) |
189 | 232 | bpf_text = bpf_text.replace('DIRECTORY_FILTER', '0') |
190 | 233 |
|
191 | 234 | if debug or args.ebpf: |
|
0 commit comments