diff options
-rw-r--r-- | op-mode-definitions/show-kernel-modules.xml.in | 20 | ||||
-rw-r--r-- | python/vyos/utils/kernel.py | 77 | ||||
-rwxr-xr-x | src/op_mode/kernel_modules.py | 82 |
3 files changed, 178 insertions, 1 deletions
diff --git a/op-mode-definitions/show-kernel-modules.xml.in b/op-mode-definitions/show-kernel-modules.xml.in new file mode 100644 index 000000000..28eb28212 --- /dev/null +++ b/op-mode-definitions/show-kernel-modules.xml.in @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="kernel"> + <properties> + <help>Show kernel information</help> + </properties> + <children> + <node name="modules"> + <properties> + <help>Show kernel modules</help> + </properties> + <command>sudo ${vyos_op_scripts_dir}/kernel_modules.py show</command> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/python/vyos/utils/kernel.py b/python/vyos/utils/kernel.py index 1f3bbdffe..847f80108 100644 --- a/python/vyos/utils/kernel.py +++ b/python/vyos/utils/kernel.py @@ -1,4 +1,4 @@ -# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -36,3 +36,78 @@ def unload_kmod(k_mod): if os.path.exists(f'/sys/module/{module}'): if call(f'rmmod {module}') != 0: raise ConfigError(f'Unloading Kernel module {module} failed') + +def list_loaded_modules(): + """ Returns the list of currently loaded kernel modules """ + from os import listdir + return listdir('/sys/module/') + +def get_module_data(module: str): + """ Retrieves information about a module """ + from os import listdir + from os.path import isfile, dirname, basename, join + from vyos.utils.file import read_file + + def _get_file(path): + # Some files inside some modules are not readable at all, + # we just skip them. + try: + return read_file(path) + except PermissionError: + return None + + mod_path = join('/sys/module', module) + mod_data = {"name": module, "fields": {}, "parameters": {}} + + for f in listdir(mod_path): + if f in ["sections", "notes", "uevent"]: + # The uevent file is not readable + # and module build info and memory layout + # in notes and sections generally aren't useful + # for anything but kernel debugging. + pass + elif f == "drivers": + # Drivers are dir symlinks, + # we just list them + drivers = listdir(join(mod_path, f)) + if drivers: + mod_data["drivers"] = drivers + elif f == "holders": + # Holders (module that use this one) + # are always symlink to other modules. + # We only need the list. + holders = listdir(join(mod_path, f)) + if holders: + mod_data["holders"] = holders + elif f == "parameters": + # Many modules keep their configuration + # in the "parameters" subdir. + ppath = join(mod_path, "parameters") + ps = listdir(ppath) + for p in ps: + data = _get_file(join(ppath, p)) + if data: + mod_data["parameters"][p] = data + else: + # Everything else... + # There are standard fields like refcount and initstate, + # but many modules also keep custom information or settings + # in top-level fields. + # For now we don't separate well-known and custom fields. + if isfile(join(mod_path, f)): + data = _get_file(join(mod_path, f)) + if data: + mod_data["fields"][f] = data + else: + raise RuntimeError(f"Unexpected directory inside module {module}: {f}") + + return mod_data + +def lsmod(): + """ Returns information about all loaded modules. + Like lsmod(8), but more detailed. + """ + mods_data = [] + for m in list_loaded_modules(): + mods_data.append(get_module_data(m)) + return mods_data diff --git a/src/op_mode/kernel_modules.py b/src/op_mode/kernel_modules.py new file mode 100755 index 000000000..e381a1df7 --- /dev/null +++ b/src/op_mode/kernel_modules.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Purpose: +# Provides commands for retrieving information about kernel modules. + +import sys +import typing + +import vyos.opmode + + +lsmod_tmpl = """ +{% for m in modules -%} +Module: {{m.name}} + +{% if m.holders -%} +Holders: {{m.holders | join(", ")}} +{%- endif %} + +{% if m.drivers -%} +Drivers: {{m.drivers | join(", ")}} +{%- endif %} + +{% for k in m.fields -%} +{{k}}: {{m["fields"][k]}} +{% endfor %} +{% if m.parameters %} + +Parameters: + +{% for p in m.parameters -%} +{{p}}: {{m["parameters"][p]}} +{% endfor -%} +{% endif -%} + +------------- + +{% endfor %} +""" + +def _get_raw_data(module=None): + from vyos.utils.kernel import get_module_data, lsmod + + if module: + return [get_module_data(module)] + else: + return lsmod() + +def show(raw: bool, module: typing.Optional[str]): + from jinja2 import Template + + data = _get_raw_data(module=module) + + if raw: + return data + else: + t = Template(lsmod_tmpl) + output = t.render({"modules": data}) + return output + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) |