1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0-only 3# 4# Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com> 5# 6# Generator for runtime verification kunit files 7 8import re 9from pathlib import Path 10from . import generator 11 12 13class KUnitError(Exception): 14 """Exception raised for errors in KUnit generation and file handling.""" 15 16 17class KUnit(generator.RVGenerator): 18 template_dir = "" 19 20 def __init__(self, extra_params={}): 21 super().__init__(extra_params) 22 self.local = extra_params.get("local", False) 23 self.kunit_c = self._read_template_file("kunit.c") 24 if not self.local: 25 self._fill_rv_kernel_dir() 26 try: 27 self.monitor_path = self.__find_monitor_c_file() 28 with open(self.monitor_path, 'r') as f: 29 self.content = f.read() 30 except OSError as e: 31 raise KUnitError(e) from e 32 self.monitor_class = self.__detect_monitor_class() 33 34 def _read_template_file(self, file): 35 if file in ("main.c", "Kconfig"): 36 return "" 37 return super()._read_template_file(file) 38 39 def __find_monitor_c_file(self) -> str: 40 """Look for the monitor file in the kernel tree or in the current folder.""" 41 if not self.local: 42 path = Path(self.rv_dir) / "monitors" / self.name / f"{self.name}.c" 43 if path.exists(): 44 return str(path) 45 46 path = Path(self.name) / f"{self.name}.c" 47 if path.exists(): 48 return str(path) 49 50 raise FileNotFoundError(f"Could not find monitor C file for '{self.name}'") 51 52 def __extract_function_args(self, handler_name: str) -> str: 53 pattern = re.compile( 54 r'^\s*(.*?)\b' + re.escape(handler_name) + r'\(([^)]*)\)', 55 re.MULTILINE | re.DOTALL 56 ) 57 match = pattern.search(self.content) 58 if not match: 59 return "/* XXX: fill handlers argument. */" 60 61 return match.group(2).strip() 62 63 def __parse_attach_handlers(self) -> list[str]: 64 """Find handlers by parsing when they are attached to tracepoints.""" 65 probe_pattern = re.compile( 66 r'rv_attach_trace_probe\(.*, ([a-zA-Z0-9_]+)\)' 67 ) 68 handlers = [] 69 for match in probe_pattern.finditer(self.content): 70 handler = match.group(1) 71 if handler not in handlers: 72 handlers.append(handler) 73 return handlers 74 75 def __detect_monitor_class(self) -> str: 76 for c in ("da", "ha", "ltl"): 77 if f"{c}_monitor.h" in self.content: 78 return c 79 return "da" 80 81 def __fill_kunit_c(self, struct_name: str) -> str: 82 kunit_c = self.kunit_c 83 kunit_c = kunit_c.replace("%%MODEL_NAME%%", self.name) 84 kunit_c = kunit_c.replace("%%MODEL_NAME_UP%%", self.name.upper()) 85 kunit_c = kunit_c.replace("%%MONITOR_CLASS%%", self.monitor_class) 86 kunit_c = kunit_c.replace("%%STRUCT_NAME%%", struct_name) 87 return kunit_c 88 89 def __fill_kunit_h(self, struct_name, prototypes) -> str: 90 return f"""/* SPDX-License-Identifier: GPL-2.0-only */ 91/* 92 * Automatically generated by rvgen kunit. 93 * May need manual intervention for function prototypes that couldn't be 94 * found (e.g. are in another file) or variables to be exported. 95 */ 96 97#ifndef __{self.name.upper()}_KUNIT_H 98#define __{self.name.upper()}_KUNIT_H 99 100#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) 101 102#include <linux/rv.h> 103#include <rv/kunit.h> 104 105extern const struct {struct_name} {{ 106\tstruct rv_kunit_mon mon; 107\t{"\n\t".join(prototypes)} 108}} {struct_name}; 109#endif 110 111#endif /* __{self.name.upper()}_KUNIT_H */ 112""" 113 114 def __fill_monitor_handlers(self, struct_name, assignments): 115 struct_definition = f"""#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) 116#include <kunit/visibility.h> 117#include "{self.name}_kunit.h" 118 119const struct {struct_name} {struct_name} = {{ 120\t.mon = RV_MON_OPS_INIT(), 121\t{"\n\t".join(assignments)} 122}}; 123EXPORT_SYMBOL_IF_KUNIT({struct_name}); 124#endif""" 125 126 if self.auto_patch: 127 try: 128 with open(self.monitor_path, 'w') as f: 129 f.write(f"{self.content}\n{struct_definition}\n") 130 except OSError as e: 131 raise KUnitError(f"Error patching monitor file {self.monitor_path}: {e}") from e 132 else: 133 print(f"Append the following to {self.name}.c:\n") 134 print(struct_definition) 135 print("Now complete the test and add it to rv_monitors_test.c") 136 137 def print_files(self): 138 139 handlers = self.__parse_attach_handlers() 140 141 if not handlers: 142 raise KUnitError(f"No handlers found in {self.monitor_path}") 143 144 prototypes = [] 145 assignments = [] 146 for handler in handlers: 147 arguments = self.__extract_function_args(handler) 148 149 prototypes.append(f"void (*{handler})({arguments});") 150 assignments.append(f".{handler} = {handler},") 151 152 struct_name = f"rv_{self.name}_ops" 153 154 self.__fill_monitor_handlers(struct_name, assignments) 155 156 dir_path = Path(self.monitor_path).parent 157 158 header_file_path = dir_path / f"{self.name}_kunit.h" 159 kunit_c_file_path = dir_path / f"{self.name}_kunit.c" 160 161 use_backup = True 162 if header_file_path.exists() or kunit_c_file_path.exists(): 163 try: 164 response = input("KUnit file(s) already exist. Backup? [Y/n] ") 165 if response.strip().lower() in ("n", "no"): 166 use_backup = False 167 except EOFError: 168 print("Non-interactive session detected, backing up existing files.") 169 else: 170 use_backup = False 171 172 if use_backup: 173 for path in (header_file_path, kunit_c_file_path): 174 if path.exists(): 175 try: 176 path.rename(path.with_suffix(path.suffix + ".old")) 177 except OSError as e: 178 raise KUnitError(f"Error backing up file {path}: {e}") from e 179 180 header_content = self.__fill_kunit_h(struct_name, prototypes) 181 try: 182 with open(header_file_path, 'w') as f: 183 f.write(header_content) 184 print(f"Successfully created KUnit header file: {header_file_path}") 185 except OSError as e: 186 raise KUnitError(f"Error writing to file {header_file_path}: {e}") from e 187 188 kunit_c_content = self.__fill_kunit_c(struct_name) 189 try: 190 with open(kunit_c_file_path, 'w') as f: 191 f.write(kunit_c_content) 192 print(f"Successfully created KUnit C file: {kunit_c_file_path}") 193 except OSError as e: 194 raise KUnitError(f"Error writing to file {kunit_c_file_path}: {e}") from e 195