Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

helpers.linux.fs: add dentry_path_first_mount() #426

Merged
merged 1 commit into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 45 additions & 7 deletions drgn/helpers/linux/fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,54 @@ def d_path(vfsmnt: Object, dentry: Object) -> bytes:
...


@overload
def d_path(dentry: Object) -> bytes:
"""
Return the full path of a dentry.

Since a mount is not provided, this arbitrarily selects a mount to determine
the path.

:param dentry: ``struct dentry *``
"""
...


def d_path( # type: ignore # Need positional-only arguments.
path_or_vfsmnt: Object, dentry: Optional[Object] = None
arg1: Object, arg2: Optional[Object] = None
) -> bytes:
if dentry is None:
vfsmnt = path_or_vfsmnt.mnt
dentry = path_or_vfsmnt.dentry.read_()
if arg2 is None:
try:
mnt = container_of(arg1.mnt, "struct mount", "mnt")
dentry = arg1.dentry.read_()
except AttributeError:
# Select an arbitrary mount from this dentry's super block. We
# choose the first non-internal mount. Internal mounts exist for
# kernel filesystems (e.g. debugfs) and they are mounted at "/".
# Paths from these mounts aren't usable in userspace and they're
# confusing. If there's no other option, we will use the first
# internal mount we encountered.
#
# The MNT_INTERNAL flag is defined as a macro in the kernel source.
# Introduced in 2.6.34 and has not been modified since.
MNT_INTERNAL = 0x4000
internal_mnt = None
dentry = arg1
for mnt in list_for_each_entry(
"struct mount", dentry.d_sb.s_mounts.address_of_(), "mnt_instance"
):
if mnt.mnt.mnt_flags & MNT_INTERNAL:
internal_mnt = internal_mnt or mnt
continue
break
else:
if internal_mnt is not None:
mnt = internal_mnt
else:
raise ValueError("Could not find a mount for this dentry")
else:
vfsmnt = path_or_vfsmnt
dentry = dentry.read_()
mnt = container_of(vfsmnt, "struct mount", "mnt")
mnt = container_of(arg1, "struct mount", "mnt")
dentry = arg2.read_()

d_op = dentry.d_op.read_()
if d_op and d_op.d_dname:
Expand Down
14 changes: 14 additions & 0 deletions tests/linux_kernel/helpers/test_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ def test_d_path(self):
task = find_task(self.prog, os.getpid())
self.assertEqual(d_path(task.fs.pwd.address_of_()), os.fsencode(os.getcwd()))

def test_d_path_dentry_only(self):
# This test could fail if we are running inside a container or if we are
# in a bind mount.
task = find_task(self.prog, os.getpid())
self.assertEqual(d_path(task.fs.pwd.dentry), os.fsencode(os.getcwd()))

def test_d_path_no_internal_mount(self):
if not os.path.isdir("/sys/kernel/tracing"):
self.skipTest("The /sys/kernel/tracing directory is not mounted")
path = path_lookup(self.prog, "/sys/kernel/tracing/trace_pipe")
# The first mount for this super block is usually MNT_INTERNAL, but we
# don't want that one. Ensure we skip it.
self.assertEqual(d_path(path.dentry), b"/sys/kernel/tracing/trace_pipe")

def test_dentry_path(self):
pwd = os.fsencode(os.getcwd())
task = find_task(self.prog, os.getpid())
Expand Down