From 1f80063eb2fb3e2e785647a34bde1dc6c2abbb75 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Thu, 26 Jun 2025 08:41:49 +0200 Subject: [PATCH] feat(revpiconfig): Add `CmdLineTxt` class for managing cmdline.txt Introduced the `CmdLineTxt` class to handle parsing, modifying, and writing operations on the `cmdline.txt` file with thread safety. Implemented methods for setting, removing keys, and ensuring safe file operations. Signed-off-by: Sven Sager --- .../system_config/revpi_config.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/revpi_middleware/dbus_middleware1/system_config/revpi_config.py b/src/revpi_middleware/dbus_middleware1/system_config/revpi_config.py index b61ae20..f578e5c 100644 --- a/src/revpi_middleware/dbus_middleware1/system_config/revpi_config.py +++ b/src/revpi_middleware/dbus_middleware1/system_config/revpi_config.py @@ -25,6 +25,7 @@ ConfigVariable = namedtuple("ConfigVariable", ["name", "value", "line_index"]) LINUX_BT_CLASS_PATH = "/sys/class/bluetooth" LINUX_WLAN_CLASS_PATH = "/sys/class/ieee80211" CONFIG_TXT_LOCATIONS = ("/boot/firmware/config.txt", "/boot/config.txt") +CMDLINE_TXT_LOCK = Lock() CONFIG_TXT_LOCK = Lock() @@ -221,6 +222,87 @@ class RevPiConfig: return bool(self._wlan_class_path) +class CmdLineTxt: + """ + Represents operations on a `cmdline.txt` configuration file. + + This class provides functionality to read, modify, and save the + `cmdline.txt` file commonly used for system configurations. It allows + setting key-value pairs, removing keys, and manages file locking to ensure + thread-safe modifications. + """ + + # Value is optional, "?:=" non-capturing the "=" + re_name_value = re.compile(r"(?P[^\s=]+)(?:=(?P\S+))?") + + def __init__(self): + self._cmdline_txt_path = "" + for path in CONFIG_TXT_LOCATIONS: + if exists(path): + self._cmdline_txt_path = path + break + + if not self._cmdline_txt_path: + raise FileNotFoundError("no config.txt found") + + def _get_cmdline_dict(self) -> dict: + with CMDLINE_TXT_LOCK: + with open(self._cmdline_txt_path, "r") as file: + cmdline = file.read() + + return { + match.group("key"): match.group("value") + for match in self.re_name_value.finditer(cmdline) + } + + def _write_cmdline_dict(self, cmdline_dict: dict) -> None: + with CMDLINE_TXT_LOCK: + + tmp_path = f"{self._cmdline_txt_path}.tmp" + with open(tmp_path, "w") as file: + str_cmdline = "" + for key, value in cmdline_dict.items(): + if value is None: + str_cmdline += f"{key} " + else: + str_cmdline += f"{key}={value} " + + str_cmdline = str_cmdline.strip() + file.write(str_cmdline + "\n") + + shutil.move(tmp_path, self._cmdline_txt_path) + + def remove_key(self, key: str) -> None: + """ + Removes a specified key from the config.txt file. + + Parameters: + key: str + The key to be removed from the config.txt file. + """ + dc_cmdline = self._get_cmdline_dict() + if key in dc_cmdline: + del dc_cmdline[key] + self._write_cmdline_dict(dc_cmdline) + + def set_key_value(self, key: str, value: Optional[str] = None) -> None: + """ + Sets a given key-value pair in the config.txt file. If the key does not + exist or the value differs from the current one, the pair is updated. + If the value is None, just the key is set without a value. + + Parameters: + key: str + The key to set in the config.txt file. + value: Optional[str], default = None + The value to associate with the key, defaulting to None. + """ + dc_cmdline = self._get_cmdline_dict() + if key not in dc_cmdline or dc_cmdline.get(key, value) != value: + dc_cmdline[key] = value + self._write_cmdline_dict(dc_cmdline) + + class ConfigTxt: """ Configuration file handler for managing 'config.txt'.