-
Notifications
You must be signed in to change notification settings - Fork 165
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
helper: partially follow link in path_lookup #218
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -70,6 +70,37 @@ def _follow_dotdot( | |||||
mnt = mnt_parent | ||||||
return _follow_mount(mnt, dentry) | ||||||
|
||||||
S_IFMT = 0o170000 | ||||||
S_IFLNK = 0o120000 | ||||||
def is_inode_symlink( | ||||||
inode: Object) -> bool: | ||||||
return (inode.i_mode & S_IFMT) == S_IFLNK | ||||||
|
||||||
XFS_IFINLINE = 0x01 | ||||||
XFS_DINODE_FMT_LOCAL = 0x01 | ||||||
def xfs_get_link( | ||||||
inode: Object) -> bytes: | ||||||
xfs_inode = container_of(inode, "struct xfs_inode", "i_vnode") | ||||||
ifork = xfs_inode.i_df | ||||||
# xfs_ifork_t has different members in different versions. | ||||||
if hasattr(ifork, "if_format") and ifork.if_format == XFS_DINODE_FMT_LOCAL: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a |
||||||
return xfs_inode.i_df.if_u1.if_data.string_() | ||||||
elif hasattr(ifork, "if_flags") and ifork.if_flags == XFS_IFINLINE: | ||||||
return xfs_inode.i_df.if_u1.if_data.string_() | ||||||
|
||||||
# doesn't support long symbol path yet as the path is not stored in inode. | ||||||
return b"" | ||||||
|
||||||
# caller makes sure this is a symbol link inode | ||||||
def get_link( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This deserves to be a public helper. Could you please name this Then, we can figure out how to write some tests for it. |
||||||
inode: Object) -> bytes: | ||||||
if inode.i_link: | ||||||
return inode.i_link | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be:
Suggested change
|
||||||
|
||||||
if inode.i_sb.s_type.name.string_() == b"xfs": | ||||||
return xfs_get_link(inode) | ||||||
|
||||||
return b"" | ||||||
|
||||||
def path_lookup( | ||||||
prog_or_root: Union[Program, Object], path: Path, allow_negative: bool = False | ||||||
|
@@ -120,6 +151,19 @@ def path_lookup( | |||||
failed_path = os.fsdecode(b"/".join(components[: i + 1])) | ||||||
raise Exception(f"could not find {failed_path!r} in dcache") | ||||||
mnt, dentry = _follow_mount(mnt, dentry) | ||||||
if dentry.d_inode and is_inode_symlink(dentry.d_inode): | ||||||
s = get_link(dentry.d_inode) | ||||||
if not s: | ||||||
raise Exception(f"could not get link from inode") | ||||||
|
||||||
# full symbol link target path | ||||||
if s[0:1] == b'/': | ||||||
link_target = s | ||||||
else: | ||||||
solved_path = b"/" + b"/".join(components[0:i+1]) | ||||||
link_target = solved_path + "/" + s | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will redo the entire lookup up to the symlink, right? That sounds pretty inefficient (and racy on live systems), especially if there are multiple symlinks in the path traversal. There are probably multiple ways to avoid this, but the one that comes to mind is to keep a stack of names being resolved and avoid recursion. Something like: class PathLookupName:
def __init__(self, name: str) -> None:
self.components = name.split(b"/")
self.index = 0
name_stack = [PathLookupName(os.fsencode(path))]
while name_stack:
name = name_stack[-1]
if name.index >= len(name.components):
name_stack.pop()
continue
component = name_stack.components[name.index]
name.index += 1
... handle component ...
if is_symlink(...):
link_target = ...
name_stack.push(PathLookupName(link_target))
if link_target.startswith(b"/"):
# Restart at the root.
mnt = root_mnt
dentry = root_dentry I think that general idea will work. We also need to be careful about cycles (the kernel handles this by returning This is going to need lots of test cases 😄 |
||||||
link_target = link_target + b"/" + b"/".join(components[i+1:]) | ||||||
return path_lookup(prog_or_root, link_target, allow_negative) | ||||||
if not allow_negative and not dentry.d_inode: | ||||||
failed_path = os.fsdecode(b"/".join(components)) | ||||||
raise Exception(f"{failed_path!r} dentry is negative") | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document which commit changed this. See
drgn.helpers.linux.sched.task_state_to_char()
for an example:drgn/drgn/helpers/linux/sched.py
Lines 59 to 67 in 220c1c7