From 2054c3c53f8bc83733c8ef81c2cc57f4e6b27f5a Mon Sep 17 00:00:00 2001 From: Oleksandr Kuchmystyi Date: Thu, 9 Jul 2026 11:16:09 +0300 Subject: kdump: T8868: Implement kernel crash dump support Add new CLI subtree 'system option kdump' with memory reservation and local dump path configuration. Also add 'show system kdump' and 'show system kdump dumps' operation mode commands to get status of the configuration and list of dumps. Extend 'show tech-support report' and 'generate tech-support archive' with kdump service status, active config files and the recently kernel dump file. --- python/vyos/defaults.py | 8 ++++++ python/vyos/utils/boot.py | 1 + python/vyos/utils/kernel.py | 70 +++++++++++++++++++++++++++++++++++++++++++++ python/vyos/utils/memory.py | 28 ++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 python/vyos/utils/memory.py (limited to 'python') diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 5829b859f..854f95ff0 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -49,6 +49,7 @@ systemd_services = { 'openconnect': 'ocserv.service', 'syslog' : 'syslog.service', 'snmpd' : 'snmpd.service', + 'kdump': 'kdump-tools.service', } internal_ports = { @@ -115,3 +116,10 @@ config_sync_exclusion_list = os.path.join( # Sits between the l3mdev rule (1000) and the l3mdev unreachable rule (2000), # ensuring fwmark-tagged tunnel packets are routed into the correct VRF table. wireguard_fwmark_pref = '1998' + +# Tiered auto-sizing based on total system RAM, determined by +# empirical testing across representative VyOS deployments: +# 1G - 8G RAM -> reserve 512M +# 8G - 64G RAM -> reserve 768M +# 64G+ RAM -> reserve 1G +KDUMP_DEFAULT_MEMORY_AUTO = '1G-8G:512M,8G-64G:768M,64G-:1G' diff --git a/python/vyos/utils/boot.py b/python/vyos/utils/boot.py index f804cd94e..dc6fcd408 100644 --- a/python/vyos/utils/boot.py +++ b/python/vyos/utils/boot.py @@ -15,6 +15,7 @@ import os + def boot_configuration_complete() -> bool: """ Check if the boot config loader has completed """ diff --git a/python/vyos/utils/kernel.py b/python/vyos/utils/kernel.py index 726025605..69574cefc 100644 --- a/python/vyos/utils/kernel.py +++ b/python/vyos/utils/kernel.py @@ -17,10 +17,18 @@ import os from typing import Tuple from typing import Optional +from vyos.utils.file import read_file + # A list of used Kernel constants # https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/net/wireguard/messages.h?h=linux-6.6.y#n45 WIREGUARD_REKEY_AFTER_TIME = 120 +CMDLINE_PATH = '/proc/cmdline' + +# Kernel interface files exposing crash kernel state at runtime +KEXEC_CRASH_LOADED = '/sys/kernel/kexec_crash_loaded' +KEXEC_CRASH_SIZE = '/sys/kernel/kexec_crash_size' + def load_module(name: str, quiet: bool = True, dry_run: bool = False) -> int: """Load a kernel module via modprobe. @@ -172,3 +180,65 @@ def get_kernel_serial_console() -> Tuple[Optional[str], Optional[str], Optional[ ) return (None, None, None) + + +def get_kernel_boot_arg(argument=None) -> str | None: + """Read and parse kernel boot arguments from the kernel command line. + + Args: + argument: The name of a specific boot argument to look up (e.g. + 'crashkernel'). If omitted or None, the full raw command + line string is returned. + + Returns: + If argument is None: the full kernel command line string. + If argument is given: the value of that argument (the part after '='), + or None if the argument is not present on the command line. + + Examples: + >>> get_kernel_boot_arg() + 'ro quiet crashkernel=256M console=tty0' + + >>> get_kernel_boot_arg('crashkernel') + '256M' + + >>> get_kernel_boot_arg('quiet') + '' + + >>> get_kernel_boot_arg('nonexistent') + None + """ + cmdline = read_file(CMDLINE_PATH) + if not argument: + return cmdline + + # Kernel arguments with values are formatted as 'key=value' tokens + # separated by spaces. Build the prefix to match against: + key = f'{argument}=' + + # Kernel parses the command line from left to right. + # A later argument will overwrite an earlier one. + for part in reversed(cmdline.split()): + if part.startswith(key): + # Strip the 'key=' prefix and return only the value portion + return part[len(key) :] + elif part == argument: + # Statement without value portion should be return empty string + return '' + + return None + + +def is_crash_kernel_loaded() -> bool: + """Return True when a capture kernel is currently loaded via kexec""" + return read_file(KEXEC_CRASH_LOADED, defaultonfailure='0') == '1' + + +def get_crash_kernel_size() -> int: + """Return the number of bytes reserved for the capture kernel""" + + if not is_crash_kernel_loaded(): + return 0 + + raw = read_file(KEXEC_CRASH_SIZE, defaultonfailure='0') + return int(raw) if raw.isdigit() else 0 diff --git a/python/vyos/utils/memory.py b/python/vyos/utils/memory.py new file mode 100644 index 000000000..c279168b5 --- /dev/null +++ b/python/vyos/utils/memory.py @@ -0,0 +1,28 @@ +# Copyright (C) VyOS Inc. +# +# 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 . + +import re +from vyos.utils.file import read_file + + +def get_memory_info() -> dict: + """Returns system memory information parsed from /proc/meminfo""" + data = read_file('/proc/meminfo') + + result = {} + regex = r'^(?P\S+):\s+(?P[0-9]+)\s+kB\s*$' + for match in re.finditer(regex, data, flags=re.MULTILINE): + result[match['key']] = int(match['value']) + + return result -- cgit v1.2.3 From b1d4de376eb79c932539c5ec2ddb5241fbce8bb7 Mon Sep 17 00:00:00 2001 From: Oleksandr Kuchmystyi Date: Thu, 23 Jul 2026 12:17:36 +0300 Subject: utils: T8868: Refactor kernel command-line arguments and memory info - Replaced direct file reads from `/proc/cmdline` with `get_kernel_boot_arg()` for fetching kernel command-line arguments in multiple modules. - Improved memory information retrieval by utilizing `get_memory_info()`. --- python/vyos/system/image.py | 17 +++++++++++------ python/vyos/utils/kernel.py | 7 +++---- src/helpers/run-config-activation.py | 3 ++- src/helpers/run-config-migration.py | 5 ++--- src/helpers/vyos-boot-config-loader.py | 7 +++---- src/op_mode/memory.py | 19 ++++++------------- 6 files changed, 27 insertions(+), 31 deletions(-) (limited to 'python') diff --git a/python/vyos/system/image.py b/python/vyos/system/image.py index ed8a96fbb..02f2303b2 100644 --- a/python/vyos/system/image.py +++ b/python/vyos/system/image.py @@ -22,6 +22,7 @@ from json import loads from vyos.defaults import directories from vyos.system import disk, grub +from vyos.utils.kernel import get_kernel_boot_arg # Define variables GRUB_DIR_MAIN: str = '/boot/grub' @@ -29,8 +30,12 @@ GRUB_DIR_VYOS: str = f'{GRUB_DIR_MAIN}/grub.cfg.d' CFG_VYOS_VARS: str = f'{GRUB_DIR_VYOS}/20-vyos-defaults-autoload.cfg' GRUB_DIR_VYOS_VERS: str = f'{GRUB_DIR_VYOS}/vyos-versions' # prepare regexes -REGEX_KERNEL_CMDLINE: str = r'^BOOT_IMAGE=/(?Pboot|live)/((?P.+)/)?vmlinuz.*$' -REGEX_SYSTEM_CFG_VER: str = r'(\r\n|\r|\n)SYSTEM_CFG_VER\s*=\s*(?P\d+)(\r\n|\r|\n)' +REGEX_KERNEL_CMDLINE: str = ( + r'^/(?Pboot|live)/((?P.+)/)?vmlinuz.*$' +) +REGEX_SYSTEM_CFG_VER: str = ( + r'(\r\n|\r|\n)SYSTEM_CFG_VER\s*=\s*(?P\d+)(\r\n|\r|\n)' +) # structures definitions @@ -197,8 +202,8 @@ def get_running_image() -> str: """ running_image: str = '' regex_filter = re_compile(REGEX_KERNEL_CMDLINE) - cmdline: str = Path('/proc/cmdline').read_text() - running_image_result = regex_filter.match(cmdline) + cmdline_arg: str = get_kernel_boot_arg('BOOT_IMAGE') or '' + running_image_result = regex_filter.match(cmdline_arg) if running_image_result: running_image: str = running_image_result.groupdict().get( 'image_version', '') @@ -259,8 +264,8 @@ def is_live_boot() -> bool: bool: True if the system currently booted in live mode """ regex_filter = re_compile(REGEX_KERNEL_CMDLINE) - cmdline: str = Path('/proc/cmdline').read_text() - running_image_result = regex_filter.match(cmdline) + cmdline_arg: str = get_kernel_boot_arg('BOOT_IMAGE') or '' + running_image_result = regex_filter.match(cmdline_arg) if running_image_result: boot_type: str = running_image_result.groupdict().get('boot_type', '') if boot_type == 'boot': diff --git a/python/vyos/utils/kernel.py b/python/vyos/utils/kernel.py index 69574cefc..72c7413cf 100644 --- a/python/vyos/utils/kernel.py +++ b/python/vyos/utils/kernel.py @@ -165,14 +165,13 @@ def get_kernel_serial_console() -> Tuple[Optional[str], Optional[str], Optional[ command line which was used during system boot. """ import re - from vyos.utils.file import read_file cmdline_console_re = re.compile( - r'(?:^|\s)console=(?Ptty(?:S|AMA))(?P\d+),(?P\d+)(?=\s|$)' + r'(?Ptty(?:S|AMA))(?P\d+),(?P\d+)' ) - kernel_cmdline = read_file('/proc/cmdline') - if m := cmdline_console_re.search(kernel_cmdline): + console_value = get_kernel_boot_arg('console') or '' + if m := cmdline_console_re.search(console_value): return ( m.group('console_type'), m.group('console_num'), diff --git a/src/helpers/run-config-activation.py b/src/helpers/run-config-activation.py index 7e7a6571e..9e048e193 100755 --- a/src/helpers/run-config-activation.py +++ b/src/helpers/run-config-activation.py @@ -30,6 +30,7 @@ from vyos.utils.activate import set_activation from vyos.utils.activate import is_active from vyos.utils.system import load_as_module from vyos.utils.func import FalseCallable +from vyos.utils.kernel import get_kernel_boot_arg from vyos.defaults import directories from vyos.defaults import activation_list @@ -54,7 +55,7 @@ fh.setFormatter(formatter) logger.addHandler(fh) -if 'vyos-activate-debug' in Path('/proc/cmdline').read_text(): +if get_kernel_boot_arg('vyos-activate-debug') is not None: print(f'\nactivate-debug enabled: file {checkpoint_file}_* on error') debug = checkpoint_file logger.setLevel(logging.DEBUG) diff --git a/src/helpers/run-config-migration.py b/src/helpers/run-config-migration.py index 6329d2979..f538c1e86 100755 --- a/src/helpers/run-config-migration.py +++ b/src/helpers/run-config-migration.py @@ -19,7 +19,7 @@ import sys import time from argparse import ArgumentParser from shutil import copyfile -from vyos.utils.file import read_file +from vyos.utils.kernel import get_kernel_boot_arg from vyos.migrate import ConfigMigrate from vyos.migrate import ConfigMigrateError @@ -80,6 +80,5 @@ if backup is not None and not config_migrate.config_modified: # T1771: add knob on Kernel command-line to simulate failed config migrator run # used to test if the automatic image reboot works. -kernel_cmdline = read_file('/proc/cmdline') -if 'vyos-fail-migration' in kernel_cmdline.split(): +if get_kernel_boot_arg('vyos-fail-migration') is not None: sys.exit(1) diff --git a/src/helpers/vyos-boot-config-loader.py b/src/helpers/vyos-boot-config-loader.py index a3a66eedc..e01b5bff6 100755 --- a/src/helpers/vyos-boot-config-loader.py +++ b/src/helpers/vyos-boot-config-loader.py @@ -28,6 +28,7 @@ from vyos.configsession import ConfigSessionError from vyos.configtree import ConfigTree from vyos.utils.process import cmdl from vyos.utils.file import write_file +from vyos.utils.kernel import get_kernel_boot_arg STATUS_FILE = config_status TRACE_FILE = '/tmp/boot-config-trace' @@ -44,11 +45,9 @@ else: LOG_FILE = LOG_DIR + '/vyos-boot-config-loader.log' try: - with open('/proc/cmdline', 'r') as f: - cmdline = f.read() - if 'vyos-debug' in cmdline: + if get_kernel_boot_arg('vyos-debug') is not None: os.environ['VYOS_DEBUG'] = 'yes' - if 'vyos-config-debug' in cmdline: + if get_kernel_boot_arg('vyos-config-debug') is not None: os.environ['VYOS_DEBUG'] = 'yes' trace_config = True except Exception as e: diff --git a/src/op_mode/memory.py b/src/op_mode/memory.py index 20d937243..47a9c476e 100755 --- a/src/op_mode/memory.py +++ b/src/op_mode/memory.py @@ -18,23 +18,16 @@ import sys import vyos.opmode +from vyos.utils.memory import get_memory_info def _get_raw_data(): - from re import search as re_search + mem_info = get_memory_info() - def find_value(keyword, mem_data): - regex = keyword + ':\s+(\d+)' - res = re_search(regex, mem_data).group(1) - return int(res) - - with open("/proc/meminfo", "r") as f: - mem_data = f.read() - - total = find_value('MemTotal', mem_data) - available = find_value('MemAvailable', mem_data) - buffers = find_value('Buffers', mem_data) - cached = find_value('Cached', mem_data) + total = mem_info['MemTotal'] + available = mem_info['MemAvailable'] + buffers = mem_info['Buffers'] + cached = mem_info['Cached'] used = total - available -- cgit v1.2.3