Source code for crash.subsystem.filesystem.mount

# -*- coding: utf-8 -*-
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
"""
The crash.subsystem.filesystem.mount module contains helpers used to
access the file system namespace.

.. _mount_structure:

*NOTE*: Linux v3.3 split ``struct mount`` from ``struct vfsmount``.  Prior
kernels do not have ``struct mount``.  In functions documented as using a
:obj:`gdb.Value` describing a ``struct mount``, a ``struct vfsmount``
will be required and/or returned instead.
"""

from typing import Iterator, Callable, Any

from crash.subsystem.filesystem import super_fstype
from crash.types.list import list_for_each_entry
from crash.util import container_of, decode_flags, struct_has_member
from crash.util.symbols import Types, Symvals, TypeCallbacks, SymbolCallbacks

import gdb

MNT_NOSUID = 0x01
MNT_NODEV = 0x02
MNT_NOEXEC = 0x04
MNT_NOATIME = 0x08
MNT_NODIRATIME = 0x10
MNT_RELATIME = 0x20
MNT_READONLY = 0x40
MNT_SHRINKABLE = 0x100
MNT_WRITE_HOLD = 0x200
MNT_SHARED = 0x1000
MNT_UNBINDABLE = 0x2000

MNT_FLAGS = {
    MNT_NOSUID      : "MNT_NOSUID",
    MNT_NODEV       : "MNT_NODEV",
    MNT_NOEXEC      : "MNT_NOEXEC",
    MNT_NOATIME     : "MNT_NOATIME",
    MNT_NODIRATIME  : "MNT_NODIRATIME",
    MNT_RELATIME    : "MNT_RELATIME",
    MNT_READONLY    : "MNT_READONLY",
}

MNT_FLAGS_HIDDEN = {
    MNT_SHRINKABLE : "[MNT_SHRINKABLE]",
    MNT_WRITE_HOLD : "[MNT_WRITE_HOLD]",
    MNT_SHARED : "[MNT_SHARED]",
    MNT_UNBINDABLE : "[MNT_UNBINDABLE]",
}
MNT_FLAGS_HIDDEN.update(MNT_FLAGS)

types = Types(['struct mount', 'struct vfsmount'])
symvals = Symvals(['init_task'])

[docs]class Mount: _for_each_mount: Callable[[Any, gdb.Value], Iterator[gdb.Value]] _init_fs_root: gdb.Value def _for_each_mount_nsproxy(self, task: gdb.Value) -> Iterator[gdb.Value]: """ An implementation of for_each_mount that uses the task's nsproxy to locate the mount namespace. See :ref:`for_each_mount` for more details. """ return list_for_each_entry(task['nsproxy']['mnt_ns']['list'], types.mount_type, 'mnt_list')
[docs] @classmethod def check_task_interface(cls, init_task: gdb.Symbol) -> None: """ Check which interface to iterating over mount structures is in use Meant to be used as a SymbolCallback. Args: init_task: The ``init_task`` symbol. """ cls._init_fs_root = init_task.value()['fs']['root'] if struct_has_member(init_task, 'nsproxy'): cls._for_each_mount = cls._for_each_mount_nsproxy else: raise NotImplementedError("Mount.for_each_mount is unhandled on this kernel version")
[docs] def for_each_mount(self, task: gdb.Value) -> Iterator[gdb.Value]: return self._for_each_mount(task)
@property def init_fs_root(self) -> gdb.Value: return self._init_fs_root
_Mount = Mount() # pylint: disable=unused-argument def _check_mount_type(gdbtype: gdb.Type) -> None: try: types.mount_type = gdb.lookup_type('struct mount') # type: ignore except gdb.error: # Older kernels didn't separate mount from vfsmount types.mount_type = types.vfsmount_type # type: ignore
[docs]def for_each_mount(task: gdb.Value = None) -> Iterator[gdb.Value]: """ Iterate over each mountpoint in the namespace of the specified task If no task is given, the ``init_task`` symbol is used. The type of the mount structure returned depends on whether ``struct mount`` exists on the kernel version being debugged :ref:`structure <mount_structure>`. Args: task: The task which contains the namespace to iterate. The :obj:`gdb.Value` must describe a ``struct task_struct``. If unspecified, the value for the ``init_task`` symbol will be used. Yields: :obj:`gdb.Value`: A mountpoint attached to the namespace. The value will be of type ``struct mount`` :ref:`structure <mount_structure>` . Raises: :obj:`gdb.NotAvailableError`: The target value is not available. """ if task is None: task = symvals.init_task return _Mount.for_each_mount(task)
[docs]def mount_flags(mnt: gdb.Value, show_hidden: bool = False) -> str: """ Returns the human-readable flags of the ``struct mount`` :ref:`structure <mount_structure>`. Args: mnt: The :ref:`mount structure <mount_structure>` for which to return flags show_hidden: Whether to return hidden flags Returns: :obj:`str`: The mount flags in human-readable form """ if struct_has_member(mnt, 'mnt'): mnt = mnt['mnt'] if show_hidden: return decode_flags(mnt['mnt_flags'], MNT_FLAGS_HIDDEN, ",") return decode_flags(mnt['mnt_flags'], MNT_FLAGS, ",")
[docs]def mount_super(mnt: gdb.Value) -> gdb.Value: """ Returns the struct super_block associated with a mount Args: mnt: The :ref:`mount structure <mount_structure>` for which to return the super_block Returns: :obj:`gdb.Value`: The super_block associated with the mount. The value will be of type ``struct super_block``. """ try: sb = mnt['mnt']['mnt_sb'] except gdb.error: sb = mnt['mnt_sb'] return sb
[docs]def mount_root(mnt: gdb.Value) -> gdb.Value: """ Returns the struct dentry corresponding to the root of a mount Args: mnt: The :ref:`mount structure <mount_structure>` for which to return the root dentry Returns: :obj:`gdb.Value`: The dentry that corresponds to the root of the mount. The value will be of type ``struct dentry``. """ try: mnt = mnt['mnt'] except gdb.error: pass return mnt['mnt_root']
[docs]def mount_fstype(mnt: gdb.Value) -> str: """ Returns the file system type of the mount Args: mnt: The :ref:`mount structure <mount_structure>` for which to return the file system type Returns: :obj:`str`: The file system type of the mount in string form """ return super_fstype(mount_super(mnt))
[docs]def mount_device(mnt: gdb.Value) -> str: """ Returns the device name that this mount is using Args: mnt: The :ref:`mount structure <mount_structure>` for which to get the device name Returns: :obj:`str`: The device name in string form Raises: :obj:`gdb.NotAvailableError`: The target value was not available. """ devname = mnt['mnt_devname'].string() if devname is None: devname = "none" return devname
def _real_mount(vfsmnt: gdb.Value) -> gdb.Value: if (vfsmnt.type == types.mount_type or vfsmnt.type == types.mount_type.pointer()): t = vfsmnt.type if t.code == gdb.TYPE_CODE_PTR: t = t.target() if t is not types.mount_type: types.mount_type = t # type: ignore return vfsmnt return container_of(vfsmnt, types.mount_type, 'mnt')
[docs]def d_path(mnt: gdb.Value, dentry: gdb.Value, root: gdb.Value = None) -> str: """ Returns a file system path described by a mount and dentry Args: mnt: The :ref:`mount structure <mount_structure>` for the start of the path dentry: The dentry for the start of the path. The value must be of type ``struct dentry``. root: The :ref:`mount structure <mount_structure>` at which to stop resolution. If unspecified or ``None``, the current root of the namespace is used. Returns: :obj:`str`: The path in string form Raises: :obj:`gdb.NotAvailableError`: The target value was not available. """ if root is None: root = _Mount.init_fs_root if dentry.type.code != gdb.TYPE_CODE_PTR: dentry = dentry.address if mnt.type.code != gdb.TYPE_CODE_PTR: mnt = mnt.address mount = _real_mount(mnt) if mount.type.code != gdb.TYPE_CODE_PTR: mount = mount.address try: mnt = mnt['mnt'].address except gdb.error: pass name = "" # Gone are the days where finding the root was as simple as # dentry == dentry->d_parent while dentry != root['dentry'] or mnt != root['mnt']: # pylint: disable=consider-using-in if dentry == mnt['mnt_root'] or dentry == dentry['d_parent']: if mount != mount['mnt_parent']: dentry = mount['mnt_mountpoint'] mount = mount['mnt_parent'] try: mnt = mount['mnt'].address except gdb.error: mnt = mount continue break name = "/" + dentry['d_name']['name'].string() + name dentry = dentry['d_parent'] if not name: name = '/' return name
type_cbs = TypeCallbacks([('struct vfsmount', _check_mount_type)]) symbols_cbs = SymbolCallbacks([('init_task', Mount.check_task_interface)])