|
1 | | -# Copyright (c) 2023, Oracle and/or its affiliates. |
| 1 | +# Copyright (c) 2024, Oracle and/or its affiliates. |
2 | 2 | # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ |
3 | 3 | """ |
4 | 4 | Helpers for dentries. |
|
12 | 12 | import drgn |
13 | 13 | from drgn import Object |
14 | 14 | from drgn import Program |
| 15 | +from drgn.helpers.linux.fs import path_lookup |
| 16 | +from drgn.helpers.linux.list import hlist_for_each_entry |
15 | 17 | from drgn.helpers.linux.list import list_for_each_entry |
16 | 18 |
|
17 | 19 | from drgn_tools.corelens import CorelensModule |
|
24 | 26 | MNT_INTERNAL = 0x4000 |
25 | 27 |
|
26 | 28 |
|
| 29 | +def dentry_for_each_child(dentry: Object) -> Iterator[Object]: |
| 30 | + """ |
| 31 | + Iterate over every child of a dentry |
| 32 | + """ |
| 33 | + # Commit da549bdd15c29 ("dentry: switch the lists of children to hlist") |
| 34 | + # changes the list names and types. Try the older names first since all UEK |
| 35 | + # versions have the older names. |
| 36 | + try: |
| 37 | + return list_for_each_entry( |
| 38 | + "struct dentry", |
| 39 | + dentry.d_subdirs.address_of_(), |
| 40 | + "d_child", |
| 41 | + ) |
| 42 | + except AttributeError: |
| 43 | + return hlist_for_each_entry( |
| 44 | + "struct dentry", |
| 45 | + dentry.d_children.address_of_(), |
| 46 | + "d_sib", |
| 47 | + ) |
| 48 | + |
| 49 | + |
27 | 50 | def sb_first_mount_point(sb: Object) -> Optional[Object]: |
28 | 51 | """ |
29 | 52 | Return the first mountpoint of the superblock |
@@ -358,6 +381,52 @@ def __file_type(mode: Object) -> str: |
358 | 381 | return "UNKN" |
359 | 382 |
|
360 | 383 |
|
| 384 | +def ls(prog: Program, directory: str, count: bool = False) -> None: |
| 385 | + """ |
| 386 | + Print dentry children, like the ls command |
| 387 | + :param directory: directory to print children of |
| 388 | + :param count: when true, only print counts (not the full contents) |
| 389 | + """ |
| 390 | + dentries = dentry_for_each_child(path_lookup(prog, directory).dentry) |
| 391 | + |
| 392 | + pos = neg = 0 |
| 393 | + for i, dentry in enumerate(dentries): |
| 394 | + path = dentry_path_any_mount(dentry).decode() |
| 395 | + if dentry_is_negative(dentry): |
| 396 | + neg += 1 |
| 397 | + else: |
| 398 | + pos += 1 |
| 399 | + if not count: |
| 400 | + print(f"{i:05d} {path}") |
| 401 | + print(f"{pos} positive, {neg} negative dentries") |
| 402 | + |
| 403 | + |
| 404 | +class Ls(CorelensModule): |
| 405 | + """List or count child dentries given a file path""" |
| 406 | + |
| 407 | + name = "ls" |
| 408 | + |
| 409 | + # This module shouldn't run for corelens reports, because it has a required |
| 410 | + # argument. It's quite useful to run it interactively though. |
| 411 | + run_when = "never" |
| 412 | + |
| 413 | + def add_args(self, parser: argparse.ArgumentParser) -> None: |
| 414 | + parser.add_argument( |
| 415 | + "directory", |
| 416 | + type=str, |
| 417 | + help="directory to list", |
| 418 | + ) |
| 419 | + parser.add_argument( |
| 420 | + "--count", |
| 421 | + "-c", |
| 422 | + action="store_true", |
| 423 | + help="only print counts, rather than every element", |
| 424 | + ) |
| 425 | + |
| 426 | + def run(self, prog: Program, args: argparse.Namespace) -> None: |
| 427 | + ls(prog, args.directory, count=args.count) |
| 428 | + |
| 429 | + |
361 | 430 | class DentryCache(CorelensModule): |
362 | 431 | """List dentries from the dentry hash table""" |
363 | 432 |
|
|
0 commit comments