Skip to content

Commit c73dcf8

Browse files
committed
dentry: add "ls" corelens module
This simple module just lists the children of a dentry, much like ls would list the contents of a directory. Of course, not all children of a dentry are real files (as they may be negative), and not all real contents of a directory are necessarily cached as a child dentry. So it's not an exact comparison. But it's close enough. Signed-off-by: Stephen Brennan <[email protected]>
1 parent 2c85424 commit c73dcf8

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

drgn_tools/dentry.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2023, Oracle and/or its affiliates.
1+
# Copyright (c) 2024, Oracle and/or its affiliates.
22
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
33
"""
44
Helpers for dentries.
@@ -12,6 +12,8 @@
1212
import drgn
1313
from drgn import Object
1414
from drgn import Program
15+
from drgn.helpers.linux.fs import path_lookup
16+
from drgn.helpers.linux.list import hlist_for_each_entry
1517
from drgn.helpers.linux.list import list_for_each_entry
1618

1719
from drgn_tools.corelens import CorelensModule
@@ -24,6 +26,27 @@
2426
MNT_INTERNAL = 0x4000
2527

2628

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+
2750
def sb_first_mount_point(sb: Object) -> Optional[Object]:
2851
"""
2952
Return the first mountpoint of the superblock
@@ -358,6 +381,52 @@ def __file_type(mode: Object) -> str:
358381
return "UNKN"
359382

360383

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+
361430
class DentryCache(CorelensModule):
362431
"""List dentries from the dentry hash table"""
363432

tests/test_dentry.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ def test_for_each_dentry_in_hashtable(prog):
1414

1515
def test_list_dentries_in_hashtable(prog):
1616
dentry.list_dentries_in_hashtable(prog, LIMIT)
17+
18+
19+
def test_ls(prog):
20+
dentry.ls(prog, "/")

0 commit comments

Comments
 (0)