From 739be1d7f7a9bec6a1d07f45396e4d6167127d51 Mon Sep 17 00:00:00 2001 From: Leonmmcoset Date: Sun, 19 Apr 2026 16:07:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0Wine+=E6=9B=B4=E6=96=B0menuco?= =?UTF-8?q?nfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 21 +- clks/kernel/userland.c | 2 + configs/menuconfig/clks_features.json | 105 +- .../__pycache__/menuconfig.cpython-312.pyc | Bin 56306 -> 0 bytes .../__pycache__/menuconfig.cpython-313.pyc | Bin 0 -> 91113 bytes scripts/menuconfig.py | 936 ++++++++++++++---- wine/README.md | 6 +- .../__pycache__/constants.cpython-313.pyc | Bin 2970 -> 3938 bytes .../__pycache__/runner.cpython-313.pyc | Bin 56594 -> 80042 bytes .../__pycache__/state.cpython-313.pyc | Bin 13532 -> 24061 bytes wine/cleonos_wine_lib/constants.py | 42 + wine/cleonos_wine_lib/runner.py | 604 ++++++++++- wine/cleonos_wine_lib/state.py | 155 ++- 13 files changed, 1642 insertions(+), 229 deletions(-) delete mode 100644 scripts/__pycache__/menuconfig.cpython-312.pyc create mode 100644 scripts/__pycache__/menuconfig.cpython-313.pyc diff --git a/CMakeLists.txt b/CMakeLists.txt index 93afb8a..b4d9011 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,10 +125,27 @@ if(EXISTS "${CLEONOS_MENUCONFIG_CMAKE}") endif() function(cl_set_bool_cache VAR_NAME DEFAULT_VALUE DOC_TEXT) - if(NOT DEFINED ${VAR_NAME}) + set(_raw_value "") + set(_has_value OFF) + + if(DEFINED ${VAR_NAME}) + set(_raw_value "${${VAR_NAME}}") + set(_has_value ON) + elseif(DEFINED ${VAR_NAME}_IS_ENABLED) + set(_raw_value "${${VAR_NAME}_IS_ENABLED}") + set(_has_value ON) + endif() + + if(NOT _has_value) set(${VAR_NAME} ${DEFAULT_VALUE} CACHE BOOL "${DOC_TEXT}") else() - set(${VAR_NAME} ${${VAR_NAME}} CACHE BOOL "${DOC_TEXT}" FORCE) + string(TOUPPER "${_raw_value}" _raw_upper) + if(_raw_upper STREQUAL "Y" OR _raw_upper STREQUAL "M" OR _raw_upper STREQUAL "ON" OR _raw_upper STREQUAL "TRUE" OR _raw_upper STREQUAL "1") + set(_bool_value ON) + else() + set(_bool_value OFF) + endif() + set(${VAR_NAME} ${_bool_value} CACHE BOOL "${DOC_TEXT}" FORCE) endif() endfunction() diff --git a/clks/kernel/userland.c b/clks/kernel/userland.c index d891088..6869b27 100644 --- a/clks/kernel/userland.c +++ b/clks/kernel/userland.c @@ -53,6 +53,7 @@ static clks_bool clks_userland_probe_elf(const char *path, const char *tag) { return CLKS_TRUE; } +#if CLKS_CFG_USER_INIT_SCRIPT_PROBE static void clks_userland_probe_init_script(void) { const void *data; u64 size = 0ULL; @@ -67,6 +68,7 @@ static void clks_userland_probe_init_script(void) { clks_log(CLKS_LOG_INFO, "USER", "INIT SCRIPT READY /SHELL/INIT.CMD"); clks_log_hex(CLKS_LOG_INFO, "USER", "INIT_SCRIPT_SIZE", size); } +#endif static clks_bool clks_userland_request_shell_exec(void) { u64 status = (u64)-1; diff --git a/configs/menuconfig/clks_features.json b/configs/menuconfig/clks_features.json index 32d4bdc..5715d31 100644 --- a/configs/menuconfig/clks_features.json +++ b/configs/menuconfig/clks_features.json @@ -4,193 +4,256 @@ "key": "CLEONOS_CLKS_ENABLE_AUDIO", "title": "Audio Driver Init", "description": "Initialize kernel audio subsystem during boot.", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_MOUSE", "title": "PS/2 Mouse Input", "description": "Initialize kernel PS/2 mouse input subsystem.", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_DESKTOP", "title": "TTY2 Desktop", "description": "Enable desktop compositor tick/update path on TTY2.", - "default": true + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_MOUSE" }, { "key": "CLEONOS_CLKS_ENABLE_DRIVER_MANAGER", "title": "Driver Manager", "description": "Initialize kernel ELF driver manager.", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_KELF", "title": "KELF Executor", "description": "Enable kernel ELF app dispatcher and kelfd task.", - "default": true + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_DRIVER_MANAGER && CLEONOS_CLKS_ENABLE_ELFRUNNER_INIT", + "imply": [ + "CLEONOS_CLKS_ENABLE_ELFRUNNER_PROBE", + "CLEONOS_CLKS_ENABLE_USER_SYSTEM_APP_PROBE" + ] }, { "key": "CLEONOS_CLKS_ENABLE_USERLAND_AUTO_EXEC", "title": "Auto Enter User Shell", "description": "Auto-exec /shell/shell.elf after kernel boot.", - "default": true + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USRD_TASK && CLEONOS_CLKS_ENABLE_KEYBOARD", + "select": [ + "CLEONOS_CLKS_ENABLE_USRD_TASK", + "CLEONOS_CLKS_ENABLE_KEYBOARD" + ], + "imply": [ + "CLEONOS_CLKS_ENABLE_USER_INIT_SCRIPT_PROBE", + "CLEONOS_CLKS_ENABLE_SHELL_MODE_LOG" + ] }, { "key": "CLEONOS_CLKS_ENABLE_HEAP_SELFTEST", "title": "Heap Selftest", "description": "Run kmalloc/kfree selftest during kernel boot.", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_EXTERNAL_PSF", "title": "Load External PSF Font", "description": "Load /system/tty.psf and apply it to framebuffer TTY.", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_KEYBOARD", "title": "PS/2 Keyboard Input", "description": "Initialize PS/2 keyboard input subsystem.", - "default": true + "type": "bool", + "default": true, + "imply": [ + "CLEONOS_CLKS_ENABLE_KBD_TTY_SWITCH_HOTKEY", + "CLEONOS_CLKS_ENABLE_KBD_CTRL_SHORTCUTS" + ] }, { "key": "CLEONOS_CLKS_ENABLE_ELFRUNNER_PROBE", "title": "ELFRUNNER Probe", "description": "Probe kernel ELF runtime metadata after ELFRUNNER init.", - "default": true + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_ELFRUNNER_INIT && CLEONOS_CLKS_ENABLE_KELF" }, { "key": "CLEONOS_CLKS_ENABLE_KLOGD_TASK", "title": "Scheduler Task: klogd", "description": "Enable periodic klogd maintenance task.", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_KWORKER_TASK", "title": "Scheduler Task: kworker", "description": "Enable periodic kernel worker service-heartbeat task.", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_USRD_TASK", "title": "Scheduler Task: usrd", "description": "Enable user/runtime dispatch task (shell tick, tty tick, exec tick).", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_BOOT_VIDEO_LOG", "title": "Boot Video Geometry Logs", "description": "Print framebuffer width/height/pitch/bpp logs at boot.", - "default": true + "type": "tristate", + "default": "y" }, { "key": "CLEONOS_CLKS_ENABLE_PMM_STATS_LOG", "title": "PMM Stats Logs", "description": "Print PMM managed/free/used/dropped pages at boot.", - "default": true + "type": "tristate", + "default": "y" }, { "key": "CLEONOS_CLKS_ENABLE_HEAP_STATS_LOG", "title": "Heap Stats Logs", "description": "Print heap total/free bytes at boot.", - "default": true + "type": "tristate", + "default": "y" }, { "key": "CLEONOS_CLKS_ENABLE_FS_ROOT_LOG", "title": "FS Root Children Log", "description": "Print root directory children count during FS init.", - "default": true + "type": "tristate", + "default": "y", + "depends_on": "CLEONOS_CLKS_ENABLE_SYSTEM_DIR_CHECK" }, { "key": "CLEONOS_CLKS_ENABLE_SYSTEM_DIR_CHECK", "title": "FS /SYSTEM Sanity Check", "description": "Require /system directory check during boot.", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_ELFRUNNER_INIT", "title": "ELFRUNNER Init", "description": "Initialize ELFRUNNER framework in kernel boot path.", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_SYSCALL_TICK_QUERY", "title": "SYSCALL Tick Query", "description": "Query timer ticks via syscall and log result during boot.", - "default": true + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_PROCFS" }, { "key": "CLEONOS_CLKS_ENABLE_TTY_READY_LOG", "title": "TTY Ready Logs", "description": "Print TTY count/active/cursor ready logs.", - "default": true + "type": "tristate", + "default": "y" }, { "key": "CLEONOS_CLKS_ENABLE_IDLE_DEBUG_LOG", "title": "Idle Loop Debug Log", "description": "Print debug log before entering kernel idle loop.", - "default": true + "type": "tristate", + "default": "y" }, { "key": "CLEONOS_CLKS_ENABLE_PROCFS", "title": "Virtual /proc", "description": "Enable virtual procfs paths (/proc, /proc/list, /proc/self, /proc/) in syscall FS layer.", + "type": "bool", "default": true }, { "key": "CLEONOS_CLKS_ENABLE_EXEC_SERIAL_LOG", "title": "EXEC Serial Logs", "description": "Print EXEC run/return/path logs to serial output.", - "default": true + "type": "tristate", + "default": "y" }, { "key": "CLEONOS_CLKS_ENABLE_KBD_TTY_SWITCH_HOTKEY", "title": "Keyboard TTY Switch Hotkey", "description": "Enable ALT+F1..F4 keyboard hotkey for active TTY switching.", - "default": true + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_KEYBOARD" }, { "key": "CLEONOS_CLKS_ENABLE_KBD_CTRL_SHORTCUTS", "title": "Keyboard Ctrl Shortcuts", "description": "Enable Ctrl+A/C/V shortcuts for input selection/copy/paste.", - "default": true + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_KEYBOARD", + "imply": [ + "CLEONOS_CLKS_ENABLE_KBD_TTY_SWITCH_HOTKEY" + ] }, { "key": "CLEONOS_CLKS_ENABLE_KBD_FORCE_STOP_HOTKEY", "title": "Keyboard Force-Stop Hotkey", "description": "Enable Ctrl+Alt+C force-stop for current running user process.", - "default": true + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_KEYBOARD && CLEONOS_CLKS_ENABLE_PROCFS" }, { "key": "CLEONOS_CLKS_ENABLE_USER_INIT_SCRIPT_PROBE", "title": "User Init Script Probe", "description": "Probe and log /shell/init.cmd presence during userland init.", - "default": true + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_USERLAND_AUTO_EXEC" }, { "key": "CLEONOS_CLKS_ENABLE_USER_SYSTEM_APP_PROBE", "title": "User System App Probe", "description": "Probe /system/elfrunner.elf and /system/memc.elf during userland init.", - "default": true + "type": "bool", + "default": true, + "depends_on": "CLEONOS_CLKS_ENABLE_KELF" }, { "key": "CLEONOS_CLKS_ENABLE_SCHED_TASK_COUNT_LOG", "title": "Scheduler Task Count Log", "description": "Print scheduler task count after scheduler initialization.", - "default": true + "type": "tristate", + "default": "y", + "depends_on": "CLEONOS_CLKS_ENABLE_KLOGD_TASK || CLEONOS_CLKS_ENABLE_KWORKER_TASK || CLEONOS_CLKS_ENABLE_USRD_TASK" }, { "key": "CLEONOS_CLKS_ENABLE_INTERRUPT_READY_LOG", "title": "Interrupt Ready Log", "description": "Print IDT/PIC initialized log after interrupt setup.", - "default": true + "type": "tristate", + "default": "y" }, { "key": "CLEONOS_CLKS_ENABLE_SHELL_MODE_LOG", "title": "Shell Mode Log", "description": "Print whether boot default mode is user shell or kernel shell.", - "default": true + "type": "tristate", + "default": "y" } ] } diff --git a/scripts/__pycache__/menuconfig.cpython-312.pyc b/scripts/__pycache__/menuconfig.cpython-312.pyc deleted file mode 100644 index 33898a018d87ec4b1f7aa36a74092e1c5391df13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56306 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 literal 0 HcmV?d00001 diff --git a/scripts/menuconfig.py b/scripts/menuconfig.py index 6d18ad3..1e6baff 100644 --- a/scripts/menuconfig.py +++ b/scripts/menuconfig.py @@ -21,7 +21,7 @@ import sys import textwrap from dataclasses import dataclass from pathlib import Path -from typing import Dict, Iterable, List, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple try: import curses @@ -51,21 +51,239 @@ class OptionItem: key: str title: str description: str - default: bool + kind: str + default: int + depends_on: str + selects: Tuple[str, ...] + implies: Tuple[str, ...] + + +@dataclass +class EvalResult: + effective: Dict[str, int] + visible: Dict[str, bool] + min_required: Dict[str, int] + max_selectable: Dict[str, int] + selected_by: Dict[str, List[str]] + implied_by: Dict[str, List[str]] + depends_symbols: Dict[str, List[str]] + + +TRI_N = 0 +TRI_M = 1 +TRI_Y = 2 + + +def tri_char(value: int) -> str: + if value >= TRI_Y: + return "y" + if value >= TRI_M: + return "m" + return "n" + + +def tri_text(value: int) -> str: + ch = tri_char(value) + if ch == "y": + return "Y" + if ch == "m": + return "M" + return "N" + + +def normalize_kind(raw: object) -> str: + text = str(raw or "bool").strip().lower() + if text in {"tristate", "tri"}: + return "tristate" + return "bool" + + +def _tri_from_bool(value: bool) -> int: + return TRI_Y if value else TRI_N + + +def normalize_tri(raw: object, default: int, kind: str) -> int: + if isinstance(raw, bool): + value = TRI_Y if raw else TRI_N + elif isinstance(raw, (int, float)): + iv = int(raw) + if iv <= 0: + value = TRI_N + elif iv == 1: + value = TRI_M + else: + value = TRI_Y + elif isinstance(raw, str): + text = raw.strip().lower() + if text in {"1", "on", "true", "yes", "y"}: + value = TRI_Y + elif text in {"m", "mod", "module"}: + value = TRI_M + elif text in {"0", "off", "false", "no", "n"}: + value = TRI_N + else: + value = default + else: + value = default + + if kind == "bool": + return TRI_Y if value == TRI_Y else TRI_N + if value < TRI_N: + return TRI_N + if value > TRI_Y: + return TRI_Y + return value + + +def _tokenize_dep_expr(expr: str) -> List[str]: + tokens: List[str] = [] + i = 0 + n = len(expr) + + while i < n: + ch = expr[i] + if ch.isspace(): + i += 1 + continue + if ch in "()!": + tokens.append(ch) + i += 1 + continue + if i + 1 < n: + pair = expr[i : i + 2] + if pair in {"&&", "||"}: + tokens.append(pair) + i += 2 + continue + m = re.match(r"[A-Za-z_][A-Za-z0-9_]*", expr[i:]) + if m: + tok = m.group(0) + tokens.append(tok) + i += len(tok) + continue + raise RuntimeError(f"invalid token in depends expression: {expr!r}") + return tokens + + +class _DepExprParser: + def __init__(self, tokens: List[str], resolver: Callable[[str], int]): + self.tokens = tokens + self.pos = 0 + self.resolver = resolver + + def _peek(self) -> Optional[str]: + if self.pos >= len(self.tokens): + return None + return self.tokens[self.pos] + + def _take(self) -> str: + if self.pos >= len(self.tokens): + raise RuntimeError("unexpected end of expression") + tok = self.tokens[self.pos] + self.pos += 1 + return tok + + def parse(self) -> int: + value = self._parse_or() + if self._peek() is not None: + raise RuntimeError("unexpected token in expression") + return value + + def _parse_or(self) -> int: + value = self._parse_and() + while self._peek() == "||": + self._take() + value = max(value, self._parse_and()) + return value + + def _parse_and(self) -> int: + value = self._parse_unary() + while self._peek() == "&&": + self._take() + value = min(value, self._parse_unary()) + return value + + def _parse_unary(self) -> int: + tok = self._peek() + if tok == "!": + self._take() + value = self._parse_unary() + if value == TRI_Y: + return TRI_N + if value == TRI_N: + return TRI_Y + return TRI_M + return self._parse_primary() + + def _parse_primary(self) -> int: + tok = self._take() + if tok == "(": + value = self._parse_or() + if self._take() != ")": + raise RuntimeError("missing ')' in expression") + return value + + lowered = tok.lower() + if lowered == "y": + return TRI_Y + if lowered == "m": + return TRI_M + if lowered == "n": + return TRI_N + return self.resolver(tok) + + +def eval_dep_expr(expr: str, resolver: Callable[[str], int]) -> int: + text = (expr or "").strip() + if not text: + return TRI_Y + tokens = _tokenize_dep_expr(text) + parser = _DepExprParser(tokens, resolver) + return parser.parse() + + +def extract_dep_symbols(expr: str) -> List[str]: + text = (expr or "").strip() + if not text: + return [] + + out: List[str] = [] + seen: Set[str] = set() + for tok in _tokenize_dep_expr(text): + if tok in {"&&", "||", "!", "(", ")"}: + continue + low = tok.lower() + if low in {"y", "m", "n"}: + continue + if tok in seen: + continue + seen.add(tok) + out.append(tok) + return out def normalize_bool(raw: object, default: bool) -> bool: - if isinstance(raw, bool): - return raw - if isinstance(raw, (int, float)): - return raw != 0 + tri_default = TRI_Y if default else TRI_N + return normalize_tri(raw, tri_default, "bool") == TRI_Y + + +def _normalize_key_list(raw: object) -> Tuple[str, ...]: + items: List[str] = [] + if isinstance(raw, str): - text = raw.strip().lower() - if text in {"1", "on", "true", "yes", "y"}: - return True - if text in {"0", "off", "false", "no", "n"}: - return False - return default + source = re.split(r"[,\s]+", raw.strip()) + elif isinstance(raw, (list, tuple)): + source = [str(x) for x in raw] + else: + return () + + for item in source: + token = str(item).strip() + if not token: + continue + items.append(token) + + return tuple(items) def sanitize_token(name: str) -> str: @@ -89,10 +307,25 @@ def load_clks_options() -> List[OptionItem]: key = str(entry.get("key", "")).strip() title = str(entry.get("title", key)).strip() description = str(entry.get("description", "")).strip() - default = normalize_bool(entry.get("default", True), True) + kind = normalize_kind(entry.get("type", "bool")) + default = normalize_tri(entry.get("default", TRI_Y), TRI_Y, kind) + depends_on = str(entry.get("depends_on", entry.get("depends", ""))).strip() + selects = _normalize_key_list(entry.get("select", entry.get("selects", ()))) + implies = _normalize_key_list(entry.get("imply", entry.get("implies", ()))) if not key: continue - options.append(OptionItem(key=key, title=title, description=description, default=default)) + options.append( + OptionItem( + key=key, + title=title, + description=description, + kind=kind, + default=default, + depends_on=depends_on, + selects=selects, + implies=implies, + ) + ) if not options: raise RuntimeError(f"no CLKS feature options in {CLKS_FEATURES_PATH}") @@ -135,12 +368,23 @@ def discover_user_apps() -> List[OptionItem]: key = f"CLEONOS_USER_APP_{sanitize_token(app)}" title = f"{app}.elf [{section}]" description = f"Build and package user app '{app}' into ramdisk/{section}." - options.append(OptionItem(key=key, title=title, description=description, default=True)) + options.append( + OptionItem( + key=key, + title=title, + description=description, + kind="bool", + default=TRI_Y, + depends_on="", + selects=(), + implies=(), + ) + ) return options -def load_previous_values() -> Dict[str, bool]: +def load_previous_values() -> Dict[str, int]: if not CONFIG_JSON_PATH.exists(): return {} try: @@ -151,54 +395,62 @@ def load_previous_values() -> Dict[str, bool]: if not isinstance(raw, dict): return {} - out: Dict[str, bool] = {} + out: Dict[str, int] = {} for key, value in raw.items(): if not isinstance(key, str): continue - out[key] = normalize_bool(value, False) + out[key] = normalize_tri(value, TRI_N, "tristate") return out -def init_values(options: Iterable[OptionItem], previous: Dict[str, bool], use_defaults: bool) -> Dict[str, bool]: - values: Dict[str, bool] = {} +def init_values(options: Iterable[OptionItem], previous: Dict[str, int], use_defaults: bool) -> Dict[str, int]: + values: Dict[str, int] = {} for item in options: if not use_defaults and item.key in previous: - values[item.key] = previous[item.key] + values[item.key] = normalize_tri(previous[item.key], item.default, item.kind) else: values[item.key] = item.default return values -def _set_option_if_exists(values: Dict[str, bool], key: str, enabled: bool) -> None: +def _build_index(options: Iterable[OptionItem]) -> Dict[str, OptionItem]: + return {item.key: item for item in options} + + +def _set_option_if_exists(values: Dict[str, int], option_index: Dict[str, OptionItem], key: str, level: int) -> None: if key in values: - values[key] = enabled + item = option_index.get(key) + if item is None: + return + values[key] = normalize_tri(level, item.default, item.kind) -def _set_all_options(values: Dict[str, bool], options: List[OptionItem], enabled: bool) -> None: +def _set_all_options(values: Dict[str, int], options: List[OptionItem], level: int) -> None: for item in options: - values[item.key] = enabled + values[item.key] = normalize_tri(level, item.default, item.kind) -def apply_preset(preset: str, clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, bool]) -> None: +def apply_preset(preset: str, clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, int]) -> None: + option_index = _build_index(clks_options + user_options) preset_name = preset.strip().lower() if preset_name == "full": - _set_all_options(values, clks_options, True) - _set_all_options(values, user_options, True) + _set_all_options(values, clks_options, TRI_Y) + _set_all_options(values, user_options, TRI_Y) return if preset_name == "dev": - _set_all_options(values, clks_options, True) - _set_all_options(values, user_options, True) - _set_option_if_exists(values, "CLEONOS_CLKS_ENABLE_USERLAND_AUTO_EXEC", False) - _set_option_if_exists(values, "CLEONOS_CLKS_ENABLE_EXEC_SERIAL_LOG", True) - _set_option_if_exists(values, "CLEONOS_CLKS_ENABLE_PROCFS", True) - _set_option_if_exists(values, "CLEONOS_CLKS_ENABLE_IDLE_DEBUG_LOG", True) + _set_all_options(values, clks_options, TRI_Y) + _set_all_options(values, user_options, TRI_Y) + _set_option_if_exists(values, option_index, "CLEONOS_CLKS_ENABLE_USERLAND_AUTO_EXEC", TRI_N) + _set_option_if_exists(values, option_index, "CLEONOS_CLKS_ENABLE_EXEC_SERIAL_LOG", TRI_Y) + _set_option_if_exists(values, option_index, "CLEONOS_CLKS_ENABLE_PROCFS", TRI_Y) + _set_option_if_exists(values, option_index, "CLEONOS_CLKS_ENABLE_IDLE_DEBUG_LOG", TRI_Y) return if preset_name == "minimal": - _set_all_options(values, clks_options, True) - _set_all_options(values, user_options, False) + _set_all_options(values, clks_options, TRI_Y) + _set_all_options(values, user_options, TRI_N) clks_disable = [ "CLEONOS_CLKS_ENABLE_AUDIO", @@ -222,7 +474,7 @@ def apply_preset(preset: str, clks_options: List[OptionItem], user_options: List "CLEONOS_CLKS_ENABLE_SCHED_TASK_COUNT_LOG", ] for key in clks_disable: - _set_option_if_exists(values, key, False) + _set_option_if_exists(values, option_index, key, TRI_N) clks_enable = [ "CLEONOS_CLKS_ENABLE_KEYBOARD", @@ -240,7 +492,7 @@ def apply_preset(preset: str, clks_options: List[OptionItem], user_options: List "CLEONOS_CLKS_ENABLE_SHELL_MODE_LOG", ] for key in clks_enable: - _set_option_if_exists(values, key, True) + _set_option_if_exists(values, option_index, key, TRI_Y) user_enable_tokens = [ "SHELL", @@ -265,24 +517,271 @@ def apply_preset(preset: str, clks_options: List[OptionItem], user_options: List "TTYDRV", ] for token in user_enable_tokens: - _set_option_if_exists(values, f"CLEONOS_USER_APP_{token}", True) + _set_option_if_exists(values, option_index, f"CLEONOS_USER_APP_{token}", TRI_Y) return raise RuntimeError(f"unknown preset: {preset}") -def print_section(title: str, options: List[OptionItem], values: Dict[str, bool]) -> None: +def _allowed_values(item: OptionItem, min_required: int, max_selectable: int) -> List[int]: + upper = normalize_tri(max_selectable, TRI_N, item.kind) + lower = normalize_tri(min_required, TRI_N, item.kind) + if lower > upper: + lower = upper + + if item.kind == "tristate": + base = [TRI_N, TRI_M, TRI_Y] + else: + base = [TRI_N, TRI_Y] + values = [v for v in base if lower <= v <= upper] + if values: + return values + return [lower] + + +def _choose_select_level(src_value: int, dst_kind: str, is_imply: bool) -> int: + if src_value <= TRI_N: + return TRI_N + if dst_kind == "bool": + if is_imply: + return TRI_Y if src_value == TRI_Y else TRI_N + return TRI_Y + return src_value + + +def evaluate_config(options: List[OptionItem], values: Dict[str, int]) -> EvalResult: + option_index = _build_index(options) + depends_symbols = {item.key: extract_dep_symbols(item.depends_on) for item in options} + + effective: Dict[str, int] = {} + for item in options: + effective[item.key] = normalize_tri(values.get(item.key, item.default), item.default, item.kind) + + visible = {item.key: True for item in options} + min_required = {item.key: TRI_N for item in options} + max_selectable = {item.key: TRI_Y for item in options} + selected_by = {item.key: [] for item in options} + implied_by = {item.key: [] for item in options} + + max_rounds = max(1, len(options) * 8) + for _ in range(max_rounds): + dep_value: Dict[str, int] = {} + for item in options: + def _resolver(symbol: str) -> int: + return effective.get(symbol, TRI_N) + + try: + dep_value[item.key] = normalize_tri(eval_dep_expr(item.depends_on, _resolver), TRI_Y, "tristate") + except Exception: + dep_value[item.key] = TRI_N + + new_visible: Dict[str, bool] = {} + new_max: Dict[str, int] = {} + for item in options: + dep = dep_value[item.key] + new_visible[item.key] = dep > TRI_N + if item.kind == "bool": + new_max[item.key] = TRI_Y if dep == TRI_Y else TRI_N + else: + new_max[item.key] = dep + + new_min = {item.key: TRI_N for item in options} + new_selected_by = {item.key: [] for item in options} + new_implied_by = {item.key: [] for item in options} + + for src in options: + src_value = effective[src.key] + if src_value <= TRI_N: + continue + + for dst_key in src.selects: + dst = option_index.get(dst_key) + if dst is None: + continue + level = _choose_select_level(src_value, dst.kind, is_imply=False) + if level > new_min[dst_key]: + new_min[dst_key] = level + new_selected_by[dst_key].append(f"{src.key}={tri_char(src_value)}") + + for dst_key in src.implies: + dst = option_index.get(dst_key) + if dst is None: + continue + new_implied_by[dst_key].append(f"{src.key}={tri_char(src_value)}") + + changed = False + next_effective: Dict[str, int] = {} + for item in options: + req = normalize_tri(values.get(item.key, item.default), item.default, item.kind) + upper = normalize_tri(new_max[item.key], TRI_N, item.kind) + lower = normalize_tri(new_min[item.key], TRI_N, item.kind) + if lower > upper: + lower = upper + eff = req + if eff < lower: + eff = lower + if eff > upper: + eff = upper + eff = normalize_tri(eff, item.default, item.kind) + next_effective[item.key] = eff + if eff != effective[item.key]: + changed = True + + effective = next_effective + visible = new_visible + min_required = new_min + max_selectable = new_max + selected_by = new_selected_by + implied_by = new_implied_by + if not changed: + break + + for key in selected_by: + selected_by[key].sort() + implied_by[key].sort() + + return EvalResult( + effective=effective, + visible=visible, + min_required=min_required, + max_selectable=max_selectable, + selected_by=selected_by, + implied_by=implied_by, + depends_symbols=depends_symbols, + ) + + +def _option_on(value: int) -> bool: + return value > TRI_N + + +def _set_all(values: Dict[str, int], options: List[OptionItem], level: int) -> None: + for item in options: + values[item.key] = normalize_tri(level, item.default, item.kind) + + +def _set_option_value(values: Dict[str, int], item: OptionItem, level: int) -> None: + values[item.key] = normalize_tri(level, item.default, item.kind) + + +def _cycle_option_value(values: Dict[str, int], item: OptionItem, evaluation: EvalResult, step: int = 1) -> None: + allowed = _allowed_values(item, evaluation.min_required[item.key], evaluation.max_selectable[item.key]) + if not allowed: + return + current = normalize_tri(values.get(item.key, item.default), item.default, item.kind) + if current not in allowed: + current = evaluation.effective.get(item.key, item.default) + if current not in allowed: + current = allowed[0] + pos = allowed.index(current) + values[item.key] = allowed[(pos + step) % len(allowed)] + + +def _detail_lines(item: OptionItem, values: Dict[str, int], ev: EvalResult) -> List[str]: + req = normalize_tri(values.get(item.key, item.default), item.default, item.kind) + eff = ev.effective.get(item.key, item.default) + visible = ev.visible.get(item.key, True) + floor_val = ev.min_required.get(item.key, TRI_N) + ceil_val = ev.max_selectable.get(item.key, TRI_Y) + allowed = ",".join(tri_char(v) for v in _allowed_values(item, floor_val, ceil_val)) + + lines = [ + f"kind: {item.kind}", + f"requested: {tri_char(req)}", + f"effective: {tri_char(eff)}", + f"visible: {'yes' if visible else 'no'}", + f"allowed: {allowed}", + f"depends: {item.depends_on or ''}", + ] + + symbols = ev.depends_symbols.get(item.key, []) + if symbols: + parts = [f"{sym}={tri_char(ev.effective.get(sym, TRI_N))}" for sym in symbols] + lines.append("depends values: " + ", ".join(parts)) + else: + lines.append("depends values: ") + + sel = ev.selected_by.get(item.key, []) + imp = ev.implied_by.get(item.key, []) + lines.append("selected by: " + (", ".join(sel) if sel else "")) + lines.append("implied by: " + (", ".join(imp) if imp else "")) + return lines + + +def _tri_word(value: int) -> str: + if value >= TRI_Y: + return "Enabled" + if value >= TRI_M: + return "Module" + return "Disabled" + + +def _detail_lines_human(item: OptionItem, values: Dict[str, int], ev: EvalResult) -> List[str]: + req = normalize_tri(values.get(item.key, item.default), item.default, item.kind) + eff = ev.effective.get(item.key, item.default) + visible = ev.visible.get(item.key, True) + floor_val = ev.min_required.get(item.key, TRI_N) + ceil_val = ev.max_selectable.get(item.key, TRI_Y) + allowed_vals = _allowed_values(item, floor_val, ceil_val) + + symbols = ev.depends_symbols.get(item.key, []) + if symbols: + dep_values = ", ".join(f"{sym}={tri_char(ev.effective.get(sym, TRI_N))}" for sym in symbols) + else: + dep_values = "" + + selected_chain = ", ".join(ev.selected_by.get(item.key, [])) or "" + implied_chain = ", ".join(ev.implied_by.get(item.key, [])) or "" + allowed_text = "/".join(f"{tri_char(v)}({_tri_word(v)})" for v in allowed_vals) + + return [ + "State:", + f" Running now : {tri_char(eff)} ({_tri_word(eff)})", + f" Your choice : {tri_char(req)} ({_tri_word(req)})", + f" Type : {item.kind}", + f" Visible : {'yes' if visible else 'no'}", + f" Allowed now : {allowed_text}", + "Why:", + f" depends on : {item.depends_on or ''}", + f" depends value : {dep_values}", + f" selected by : {selected_chain}", + f" implied by : {implied_chain}", + "Notes:", + " [a/b] in list means [effective/requested].", + f" {item.description}", + ] + + +def print_section(title: str, section_options: List[OptionItem], all_options: List[OptionItem], values: Dict[str, int]) -> None: + ev = evaluate_config(all_options, values) print() print(f"== {title} ==") - for idx, item in enumerate(options, start=1): - mark = "x" if values.get(item.key, item.default) else " " - print(f"{idx:3d}. [{mark}] {item.title}") - print("Commands: toggle, a enable-all, n disable-all, i info, b back") + for idx, item in enumerate(section_options, start=1): + req = normalize_tri(values.get(item.key, item.default), item.default, item.kind) + eff = ev.effective.get(item.key, item.default) + visible = ev.visible.get(item.key, True) + floor_val = ev.min_required.get(item.key, TRI_N) + ceil_val = ev.max_selectable.get(item.key, TRI_Y) + locked = len(_allowed_values(item, floor_val, ceil_val)) <= 1 + flags: List[str] = [] + if not visible: + flags.append("hidden") + if locked: + flags.append("locked") + if ev.selected_by.get(item.key): + flags.append("selected") + if ev.implied_by.get(item.key): + flags.append("implied") + flag_text = f" ({', '.join(flags)})" if flags else "" + print(f"{idx:3d}. [{tri_char(eff)}|{tri_char(req)}] {item.title}{flag_text}") + print("Commands: cycle, a all->y, n all->n, m all->m, i info, b back") + print("Legend: [effective|requested], states: y/m/n") -def section_loop(title: str, options: List[OptionItem], values: Dict[str, bool]) -> None: +def section_loop(title: str, section_options: List[OptionItem], all_options: List[OptionItem], values: Dict[str, int]) -> None: while True: - print_section(title, options, values) + ev = evaluate_config(all_options, values) + print_section(title, section_options, all_options, values) raw = input(f"{title}> ").strip() if not raw: continue @@ -291,24 +790,28 @@ def section_loop(title: str, options: List[OptionItem], values: Dict[str, bool]) if lower in {"b", "back", "q", "quit"}: return if lower in {"a", "all", "on"}: - for item in options: - values[item.key] = True + for item in section_options: + _set_option_value(values, item, TRI_Y) continue if lower in {"n", "none", "off"}: - for item in options: - values[item.key] = False + for item in section_options: + _set_option_value(values, item, TRI_N) + continue + if lower in {"m", "mod", "module"}: + for item in section_options: + _set_option_value(values, item, TRI_M) continue if lower.startswith("i "): token = lower[2:].strip() if token.isdigit(): idx = int(token) - if 1 <= idx <= len(options): - item = options[idx - 1] - state = "ON" if values.get(item.key, item.default) else "OFF" + if 1 <= idx <= len(section_options): + item = section_options[idx - 1] print() print(f"[{idx}] {item.title}") print(f"key: {item.key}") - print(f"state: {state}") + for line in _detail_lines(item, values, ev): + print(line) print(f"desc: {item.description}") continue print("invalid info index") @@ -316,9 +819,9 @@ def section_loop(title: str, options: List[OptionItem], values: Dict[str, bool]) if raw.isdigit(): idx = int(raw) - if 1 <= idx <= len(options): - item = options[idx - 1] - values[item.key] = not values.get(item.key, item.default) + if 1 <= idx <= len(section_options): + item = section_options[idx - 1] + _cycle_option_value(values, item, ev) else: print("invalid index") continue @@ -476,13 +979,23 @@ def _draw_progress_bar( _safe_addnstr(stdscr, y, x + bar_w + 1, f"{enabled_count:>3}/{total_count:<3}", off_attr | curses.A_BOLD) -def _option_enabled(values: Dict[str, bool], item: OptionItem) -> bool: - return values.get(item.key, item.default) +def _option_enabled(ev: EvalResult, item: OptionItem) -> bool: + return ev.effective.get(item.key, item.default) > TRI_N -def _set_all(values: Dict[str, bool], options: List[OptionItem], enabled: bool) -> None: - for item in options: - values[item.key] = enabled +def _option_flags(item: OptionItem, ev: EvalResult) -> str: + flags: List[str] = [] + if not ev.visible.get(item.key, True): + flags.append("hidden") + if len(_allowed_values(item, ev.min_required.get(item.key, TRI_N), ev.max_selectable.get(item.key, TRI_Y))) <= 1: + flags.append("locked") + if ev.selected_by.get(item.key): + flags.append("selected") + if ev.implied_by.get(item.key): + flags.append("implied") + if not flags: + return "-" + return ",".join(flags) def _draw_scrollbar(stdscr, y: int, x: int, height: int, total: int, top: int, visible: int, track_attr: int, thumb_attr: int) -> None: @@ -508,11 +1021,19 @@ def _draw_scrollbar(stdscr, y: int, x: int, height: int, total: int, top: int, v _safe_addch(stdscr, y + thumb_y + r, x, "#", thumb_attr) -def _run_ncurses_section(stdscr, theme: Dict[str, int], title: str, options: List[OptionItem], values: Dict[str, bool]) -> None: +def _run_ncurses_section( + stdscr, + theme: Dict[str, int], + title: str, + section_options: List[OptionItem], + all_options: List[OptionItem], + values: Dict[str, int], +) -> None: selected = 0 top = 0 while True: + ev = evaluate_config(all_options, values) stdscr.erase() h, w = stdscr.getmaxyx() @@ -541,12 +1062,13 @@ def _run_ncurses_section(stdscr, theme: Dict[str, int], title: str, options: Lis detail_box_w = w - left_w _safe_addnstr(stdscr, 0, 0, f" CLeonOS menuconfig / {title} ", theme["header"]) - enabled_count = sum(1 for item in options if _option_enabled(values, item)) + enabled_count = sum(1 for item in section_options if _option_enabled(ev, item)) + module_count = sum(1 for item in section_options if ev.effective.get(item.key, TRI_N) == TRI_M) _safe_addnstr( stdscr, 1, 0, - f" {enabled_count}/{len(options)} enabled | Arrow/jk move Space toggle a/n all PgUp/PgDn Enter/ESC back ", + f" on:{enabled_count} mod:{module_count} total:{len(section_options)} | Space cycle a/n/m all Enter/ESC back ", theme["subtitle"], ) @@ -561,8 +1083,8 @@ def _run_ncurses_section(stdscr, theme: Dict[str, int], title: str, options: Lis if selected < 0: selected = 0 - if selected >= len(options): - selected = max(0, len(options) - 1) + if selected >= len(section_options): + selected = max(0, len(section_options) - 1) if selected < top: top = selected @@ -573,14 +1095,17 @@ def _run_ncurses_section(stdscr, theme: Dict[str, int], title: str, options: Lis for row in range(visible): idx = top + row - if idx >= len(options): + if idx >= len(section_options): break - item = options[idx] - enabled = _option_enabled(values, item) - mark = "x" if enabled else " " + item = section_options[idx] + req = normalize_tri(values.get(item.key, item.default), item.default, item.kind) + eff = ev.effective.get(item.key, item.default) + flags = _option_flags(item, ev) prefix = ">" if idx == selected else " " - line = f"{prefix} {idx + 1:03d} [{mark}] {item.title}" - base_attr = theme["enabled"] if enabled else theme["disabled"] + line = f"{prefix} {idx + 1:03d} [{tri_char(eff)}|{tri_char(req)}] {item.title}" + if flags != "-": + line += f" [{flags}]" + base_attr = theme["enabled"] if eff > TRI_N else theme["disabled"] attr = theme["selected"] if idx == selected else base_attr _safe_addnstr(stdscr, list_inner_y + row, list_inner_x, line, attr) @@ -589,22 +1114,24 @@ def _run_ncurses_section(stdscr, theme: Dict[str, int], title: str, options: Lis list_inner_y, list_box_x + list_box_w - 2, list_inner_h, - len(options), + len(section_options), top, visible, theme["scroll_track"], theme["scroll_thumb"], ) - if options: - cur = options[selected] + if section_options: + cur = section_options[selected] detail_inner_y = detail_box_y + 1 detail_inner_x = detail_box_x + 2 detail_inner_w = detail_box_w - 4 detail_inner_h = detail_box_h - 2 - state_text = "ENABLED" if _option_enabled(values, cur) else "DISABLED" - state_attr = theme["status_ok"] if _option_enabled(values, cur) else theme["status_warn"] + eff = ev.effective.get(cur.key, cur.default) + req = normalize_tri(values.get(cur.key, cur.default), cur.default, cur.kind) + state_text = f"effective={tri_char(eff)} requested={tri_char(req)} kind={cur.kind}" + state_attr = theme["status_ok"] if eff > TRI_N else theme["status_warn"] _safe_addnstr(stdscr, detail_inner_y + 0, detail_inner_x, cur.title, theme["value_label"]) _safe_addnstr(stdscr, detail_inner_y + 1, detail_inner_x, cur.key, theme["value_key"]) @@ -613,7 +1140,7 @@ def _run_ncurses_section(stdscr, theme: Dict[str, int], title: str, options: Lis stdscr, detail_inner_y + 3, detail_inner_x, - f"Item: {selected + 1}/{len(options)}", + f"Item: {selected + 1}/{len(section_options)} flags: {_option_flags(cur, ev)}", theme["value_label"], ) _draw_progress_bar( @@ -622,19 +1149,30 @@ def _run_ncurses_section(stdscr, theme: Dict[str, int], title: str, options: Lis detail_inner_x, max(12, detail_inner_w), enabled_count, - max(1, len(options)), + max(1, len(section_options)), theme["progress_on"], theme["progress_off"], ) desc_title_y = detail_inner_y + 6 - _safe_addnstr(stdscr, desc_title_y, detail_inner_x, "Description:", theme["value_label"]) - wrapped = textwrap.wrap(cur.description, max(12, detail_inner_w)) + _safe_addnstr(stdscr, desc_title_y, detail_inner_x, "Details:", theme["value_label"]) + raw_lines = _detail_lines_human(cur, values, ev) + wrapped_lines: List[str] = [] + wrap_width = max(12, detail_inner_w) + for raw_line in raw_lines: + if not raw_line: + wrapped_lines.append("") + continue + chunks = textwrap.wrap(raw_line, wrap_width) + if chunks: + wrapped_lines.extend(chunks) + else: + wrapped_lines.append(raw_line) max_desc_lines = max(1, detail_inner_h - 8) - for i, part in enumerate(wrapped[:max_desc_lines]): + for i, part in enumerate(wrapped_lines[:max_desc_lines]): _safe_addnstr(stdscr, desc_title_y + 1 + i, detail_inner_x, part, 0) - _safe_addnstr(stdscr, h - 1, 0, " Space:toggle a:all-on n:all-off Enter/ESC:back ", theme["help"]) + _safe_addnstr(stdscr, h - 1, 0, " Space:cycle a:all-y n:all-n m:all-m Enter/ESC:back ", theme["help"]) stdscr.refresh() key = stdscr.getch() @@ -657,23 +1195,27 @@ def _run_ncurses_section(stdscr, theme: Dict[str, int], title: str, options: Lis selected = 0 continue if key == curses.KEY_END: - selected = max(0, len(options) - 1) + selected = max(0, len(section_options) - 1) continue if key == ord(" "): - if options: - item = options[selected] - values[item.key] = not _option_enabled(values, item) + if section_options: + item = section_options[selected] + _cycle_option_value(values, item, ev) continue if key in (ord("a"), ord("A")): - _set_all(values, options, True) + _set_all(values, section_options, TRI_Y) continue if key in (ord("n"), ord("N")): - _set_all(values, options, False) + _set_all(values, section_options, TRI_N) + continue + if key in (ord("m"), ord("M")): + _set_all(values, section_options, TRI_M) continue -def _run_ncurses_main(stdscr, clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, bool]) -> bool: +def _run_ncurses_main(stdscr, clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, int]) -> bool: theme = _curses_theme() + all_options = clks_options + user_options try: curses.curs_set(0) except Exception: @@ -685,8 +1227,9 @@ def _run_ncurses_main(stdscr, clks_options: List[OptionItem], user_options: List stdscr.erase() h, w = stdscr.getmaxyx() - clks_on = sum(1 for item in clks_options if _option_enabled(values, item)) - user_on = sum(1 for item in user_options if _option_enabled(values, item)) + ev = evaluate_config(all_options, values) + clks_on = sum(1 for item in clks_options if _option_enabled(ev, item)) + user_on = sum(1 for item in user_options if _option_enabled(ev, item)) total_items = len(clks_options) + len(user_options) total_on = clks_on + user_on @@ -745,9 +1288,9 @@ def _run_ncurses_main(stdscr, clks_options: List[OptionItem], user_options: List continue if key in (curses.KEY_ENTER, 10, 13): if selected == 0: - _run_ncurses_section(stdscr, theme, "CLKS", clks_options, values) + _run_ncurses_section(stdscr, theme, "CLKS", clks_options, all_options, values) elif selected == 1: - _run_ncurses_section(stdscr, theme, "USER", user_options, values) + _run_ncurses_section(stdscr, theme, "USER", user_options, all_options, values) elif selected == 2: return True else: @@ -755,7 +1298,7 @@ def _run_ncurses_main(stdscr, clks_options: List[OptionItem], user_options: List continue -def interactive_menu_ncurses(clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, bool]) -> bool: +def interactive_menu_ncurses(clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, int]) -> bool: if curses is None: raise RuntimeError("python curses module unavailable (install python3-curses / ncurses)") if "TERM" not in os.environ or not os.environ["TERM"]: @@ -763,7 +1306,7 @@ def interactive_menu_ncurses(clks_options: List[OptionItem], user_options: List[ return bool(curses.wrapper(lambda stdscr: _run_ncurses_main(stdscr, clks_options, user_options, values))) -def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, bool]) -> bool: +def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, int]) -> bool: if QtWidgets is None or QtCore is None: raise RuntimeError("python PySide unavailable (install PySide6, or use --plain)") @@ -777,12 +1320,9 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti app = QtWidgets.QApplication(["menuconfig-gui"]) owns_app = True - qt_checked = getattr(QtCore.Qt, "Checked", QtCore.Qt.CheckState.Checked) - qt_unchecked = getattr(QtCore.Qt, "Unchecked", QtCore.Qt.CheckState.Unchecked) qt_horizontal = getattr(QtCore.Qt, "Horizontal", QtCore.Qt.Orientation.Horizontal) qt_item_enabled = getattr(QtCore.Qt, "ItemIsEnabled", QtCore.Qt.ItemFlag.ItemIsEnabled) qt_item_selectable = getattr(QtCore.Qt, "ItemIsSelectable", QtCore.Qt.ItemFlag.ItemIsSelectable) - qt_item_checkable = getattr(QtCore.Qt, "ItemIsUserCheckable", QtCore.Qt.ItemFlag.ItemIsUserCheckable) resize_to_contents = getattr( QtWidgets.QHeaderView, @@ -833,14 +1373,16 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti tabs = QtWidgets.QTabWidget() root_layout.addWidget(tabs, 1) + all_options = clks_options + user_options def update_summary() -> None: - clks_on = sum(1 for item in clks_options if values.get(item.key, item.default)) - user_on = sum(1 for item in user_options if values.get(item.key, item.default)) + ev = evaluate_config(all_options, values) + clks_on = sum(1 for item in clks_options if ev.effective.get(item.key, item.default) > TRI_N) + user_on = sum(1 for item in user_options if ev.effective.get(item.key, item.default) > TRI_N) total = len(clks_options) + len(user_options) summary_label.setText( - f"CLKS: {clks_on}/{len(clks_options)} enabled " - f"User: {user_on}/{len(user_options)} enabled " + f"CLKS: {clks_on}/{len(clks_options)} on " + f"User: {user_on}/{len(user_options)} on " f"Total: {clks_on + user_on}/{total}" ) @@ -862,11 +1404,17 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti toolbar.addWidget(title_label) toolbar.addStretch(1) - toggle_btn = QtWidgets.QPushButton("Toggle Selected") - enable_all_btn = QtWidgets.QPushButton("Enable All") - disable_all_btn = QtWidgets.QPushButton("Disable All") + toggle_btn = QtWidgets.QPushButton("Cycle Selected") + set_y_btn = QtWidgets.QPushButton("Set Y") + set_m_btn = QtWidgets.QPushButton("Set M") + set_n_btn = QtWidgets.QPushButton("Set N") + enable_all_btn = QtWidgets.QPushButton("All Y") + disable_all_btn = QtWidgets.QPushButton("All N") toolbar.addWidget(enable_all_btn) toolbar.addWidget(disable_all_btn) + toolbar.addWidget(set_m_btn) + toolbar.addWidget(set_y_btn) + toolbar.addWidget(set_n_btn) toolbar.addWidget(toggle_btn) layout.addLayout(toolbar) @@ -876,11 +1424,12 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti left = QtWidgets.QWidget() left_layout = QtWidgets.QVBoxLayout(left) left_layout.setContentsMargins(0, 0, 0, 0) - self.table = QtWidgets.QTableWidget(len(options), 2) - self.table.setHorizontalHeaderLabels(["On", "Option"]) + self.table = QtWidgets.QTableWidget(len(options), 3) + self.table.setHorizontalHeaderLabels(["Value", "Option", "Status"]) self.table.verticalHeader().setVisible(False) self.table.horizontalHeader().setSectionResizeMode(0, resize_to_contents) self.table.horizontalHeader().setSectionResizeMode(1, stretch_mode) + self.table.horizontalHeader().setSectionResizeMode(2, resize_to_contents) self.table.setSelectionBehavior(select_rows) self.table.setSelectionMode(extended_selection) self.table.setAlternatingRowColors(True) @@ -902,10 +1451,13 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti splitter.setStretchFactor(1, 2) toggle_btn.clicked.connect(self.toggle_selected) + set_y_btn.clicked.connect(lambda: self.set_selected(TRI_Y)) + set_m_btn.clicked.connect(lambda: self.set_selected(TRI_M)) + set_n_btn.clicked.connect(lambda: self.set_selected(TRI_N)) enable_all_btn.clicked.connect(self.enable_all) disable_all_btn.clicked.connect(self.disable_all) self.table.itemSelectionChanged.connect(self._on_selection_changed) - self.table.itemChanged.connect(self._on_item_changed) + self.table.itemDoubleClicked.connect(self._on_item_activated) self.refresh(keep_selection=False) if self.options: @@ -935,10 +1487,14 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti return item = self.options[row] - enabled = values.get(item.key, item.default) - self.state_label.setText(f"State: {'ENABLED' if enabled else 'DISABLED'}") + ev = evaluate_config(all_options, values) + eff = ev.effective.get(item.key, item.default) + req = normalize_tri(values.get(item.key, item.default), item.default, item.kind) + self.state_label.setText( + f"State: eff={tri_char(eff)} req={tri_char(req)} kind={item.kind} flags={_option_flags(item, ev)}" + ) self.key_label.setText(f"Key: {item.key}") - self.detail_text.setPlainText(f"{item.title}\n\n{item.description}") + self.detail_text.setPlainText("\n".join([item.title, ""] + _detail_lines(item, values, ev) + ["", item.description])) def _on_selection_changed(self) -> None: rows = self._selected_rows() @@ -950,44 +1506,37 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti if len(rows) > 1: self.state_label.setText(f"State: {len(rows)} items selected") self.key_label.setText("Key: ") - self.detail_text.setPlainText("Multiple options selected.\nUse Toggle Selected to flip all selected entries.") + self.detail_text.setPlainText("Multiple options selected.\nUse Cycle/Set buttons to update selected entries.") return self._show_detail(-1) - def _on_item_changed(self, changed_item) -> None: + def _on_item_activated(self, changed_item) -> None: if self._updating or changed_item is None: return - - if changed_item.column() != 0: - return - row = changed_item.row() - if row < 0 or row >= len(self.options): return - - self.options[row] - values[self.options[row].key] = changed_item.checkState() == qt_checked - self._on_selection_changed() - update_summary() + ev = evaluate_config(all_options, values) + _cycle_option_value(values, self.options[row], ev) + self.refresh(keep_selection=True) def refresh(self, keep_selection: bool = True) -> None: prev_rows = self._selected_rows() if keep_selection else [] self._updating = True self.table.setRowCount(len(self.options)) + ev = evaluate_config(all_options, values) for row, item in enumerate(self.options): - enabled = values.get(item.key, item.default) - check_item = self.table.item(row, 0) - - if check_item is None: - check_item = QtWidgets.QTableWidgetItem("") - check_item.setFlags(qt_item_enabled | qt_item_selectable | qt_item_checkable) - self.table.setItem(row, 0, check_item) - - check_item.setCheckState(qt_checked if enabled else qt_unchecked) + req = normalize_tri(values.get(item.key, item.default), item.default, item.kind) + eff = ev.effective.get(item.key, item.default) + value_item = self.table.item(row, 0) + if value_item is None: + value_item = QtWidgets.QTableWidgetItem("") + value_item.setFlags(qt_item_enabled | qt_item_selectable) + self.table.setItem(row, 0, value_item) + value_item.setText(f"{tri_char(eff)} (req:{tri_char(req)})") title_item = self.table.item(row, 1) if title_item is None: @@ -997,6 +1546,13 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti else: title_item.setText(item.title) + status_item = self.table.item(row, 2) + if status_item is None: + status_item = QtWidgets.QTableWidgetItem("") + status_item.setFlags(qt_item_enabled | qt_item_selectable) + self.table.setItem(row, 2, status_item) + status_item.setText(_option_flags(item, ev)) + self._updating = False self.table.clearSelection() @@ -1013,39 +1569,30 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti if not rows: return - self._updating = True for row in rows: item = self.options[row] - new_state = not values.get(item.key, item.default) - values[item.key] = new_state - check_item = self.table.item(row, 0) - if check_item is not None: - check_item.setCheckState(qt_checked if new_state else qt_unchecked) - self._updating = False - self._on_selection_changed() - update_summary() + ev = evaluate_config(all_options, values) + _cycle_option_value(values, item, ev) + self.refresh(keep_selection=True) + + def set_selected(self, state: int) -> None: + rows = self._selected_rows() + if not rows: + return + for row in rows: + item = self.options[row] + _set_option_value(values, item, state) + self.refresh(keep_selection=True) def enable_all(self) -> None: - self._updating = True - for row, item in enumerate(self.options): - values[item.key] = True - check_item = self.table.item(row, 0) - if check_item is not None: - check_item.setCheckState(qt_checked) - self._updating = False - self._on_selection_changed() - update_summary() + for item in self.options: + _set_option_value(values, item, TRI_Y) + self.refresh(keep_selection=False) def disable_all(self) -> None: - self._updating = True - for row, item in enumerate(self.options): - values[item.key] = False - check_item = self.table.item(row, 0) - if check_item is not None: - check_item.setCheckState(qt_unchecked) - self._updating = False - self._on_selection_changed() - update_summary() + for item in self.options: + _set_option_value(values, item, TRI_N) + self.refresh(keep_selection=False) clks_panel = _SectionPanel("CLKS Features", clks_options) user_panel = _SectionPanel("User Apps", user_options) @@ -1054,7 +1601,7 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti update_summary() footer = QtWidgets.QHBoxLayout() - footer.addWidget(QtWidgets.QLabel("Tip: select rows and click Toggle Selected.")) + footer.addWidget(QtWidgets.QLabel("Tip: double-click a row to cycle, or use Set/Cycle buttons.")) footer.addStretch(1) save_btn = QtWidgets.QPushButton("Save and Exit") @@ -1082,11 +1629,18 @@ def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[Opti return result["save"] -def write_outputs(all_values: Dict[str, bool], ordered_options: List[OptionItem]) -> None: +def write_outputs(all_values: Dict[str, int], ordered_options: List[OptionItem]) -> None: MENUCONFIG_DIR.mkdir(parents=True, exist_ok=True) - ordered_keys = [item.key for item in ordered_options] - output_values: Dict[str, bool] = {key: all_values[key] for key in ordered_keys if key in all_values} + output_values: Dict[str, object] = {} + for item in ordered_options: + if item.key not in all_values: + continue + value = normalize_tri(all_values[item.key], item.default, item.kind) + if item.kind == "bool": + output_values[item.key] = value == TRI_Y + else: + output_values[item.key] = tri_char(value) CONFIG_JSON_PATH.write_text( json.dumps(output_values, ensure_ascii=True, indent=2, sort_keys=True) + "\n", @@ -1099,32 +1653,46 @@ def write_outputs(all_values: Dict[str, bool], ordered_options: List[OptionItem] 'set(CLEONOS_MENUCONFIG_LOADED ON CACHE BOOL "CLeonOS menuconfig loaded" FORCE)', ] for item in ordered_options: - value = "ON" if all_values.get(item.key, item.default) else "OFF" - lines.append(f'set({item.key} {value} CACHE BOOL "{item.title}" FORCE)') + value = normalize_tri(all_values.get(item.key, item.default), item.default, item.kind) + if item.kind == "bool": + cmake_value = "ON" if value == TRI_Y else "OFF" + lines.append(f'set({item.key} {cmake_value} CACHE BOOL "{item.title}" FORCE)') + else: + cmake_value = tri_char(value).upper() + lines.append(f'set({item.key} "{cmake_value}" CACHE STRING "{item.title}" FORCE)') + lines.append( + f'set({item.key}_IS_ENABLED {"ON" if value > TRI_N else "OFF"} ' + f'CACHE BOOL "{item.title} enabled(y|m)" FORCE)' + ) CONFIG_CMAKE_PATH.write_text("\n".join(lines) + "\n", encoding="utf-8") -def show_summary(clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, bool]) -> None: - clks_on = sum(1 for item in clks_options if values.get(item.key, item.default)) - user_on = sum(1 for item in user_options if values.get(item.key, item.default)) +def show_summary(clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, int]) -> None: + all_options = clks_options + user_options + ev = evaluate_config(all_options, values) + clks_on = sum(1 for item in clks_options if ev.effective.get(item.key, item.default) > TRI_N) + user_on = sum(1 for item in user_options if ev.effective.get(item.key, item.default) > TRI_N) + clks_m = sum(1 for item in clks_options if ev.effective.get(item.key, item.default) == TRI_M) + user_m = sum(1 for item in user_options if ev.effective.get(item.key, item.default) == TRI_M) print() print("========== CLeonOS menuconfig ==========") - print(f"1) CLKS features : {clks_on}/{len(clks_options)} enabled") - print(f"2) User features : {user_on}/{len(user_options)} enabled") + print(f"1) CLKS features : on={clks_on} m={clks_m} total={len(clks_options)}") + print(f"2) User features : on={user_on} m={user_m} total={len(user_options)}") print("s) Save and exit") print("q) Quit without saving") -def interactive_menu(clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, bool]) -> bool: +def interactive_menu(clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, int]) -> bool: + all_options = clks_options + user_options while True: show_summary(clks_options, user_options, values) choice = input("Select> ").strip().lower() if choice == "1": - section_loop("CLKS", clks_options, values) + section_loop("CLKS", clks_options, all_options, values) continue if choice == "2": - section_loop("USER", user_options, values) + section_loop("USER", user_options, all_options, values) continue if choice in {"s", "save"}: return True @@ -1133,15 +1701,19 @@ def interactive_menu(clks_options: List[OptionItem], user_options: List[OptionIt print("unknown selection") -def parse_set_overrides(values: Dict[str, bool], kv_pairs: List[str]) -> None: +def parse_set_overrides(values: Dict[str, int], option_index: Dict[str, OptionItem], kv_pairs: List[str]) -> None: for pair in kv_pairs: if "=" not in pair: - raise RuntimeError(f"invalid --set entry: {pair!r}, expected KEY=ON|OFF") + raise RuntimeError(f"invalid --set entry: {pair!r}, expected KEY=Y|M|N") key, raw = pair.split("=", 1) key = key.strip() if not key: raise RuntimeError(f"invalid --set entry: {pair!r}, empty key") - values[key] = normalize_bool(raw, False) + item = option_index.get(key) + if item is None: + values[key] = normalize_tri(raw, TRI_N, "tristate") + else: + values[key] = normalize_tri(raw, item.default, item.kind) def parse_args() -> argparse.Namespace: @@ -1159,7 +1731,7 @@ def parse_args() -> argparse.Namespace: "--set", action="append", default=[], - metavar="KEY=ON|OFF", + metavar="KEY=Y|M|N", help="override one option before save (can be repeated)", ) return parser.parse_args() @@ -1181,7 +1753,8 @@ def main() -> int: if args.preset: apply_preset(args.preset, clks_options, user_options, values) - parse_set_overrides(values, args.set) + option_index = _build_index(all_options) + parse_set_overrides(values, option_index, args.set) should_save = args.non_interactive if not args.non_interactive: @@ -1199,7 +1772,8 @@ def main() -> int: print("menuconfig: no changes saved") return 0 - write_outputs(values, all_options) + final_eval = evaluate_config(all_options, values) + write_outputs(final_eval.effective, all_options) print(f"menuconfig: wrote {CONFIG_JSON_PATH}") print(f"menuconfig: wrote {CONFIG_CMAKE_PATH}") return 0 diff --git a/wine/README.md b/wine/README.md index 64cacaf..0aaaff7 100644 --- a/wine/README.md +++ b/wine/README.md @@ -37,13 +37,17 @@ python wine/cleonos_wine.py build/x86_64/ramdisk_root/shell/shell.elf --rootfs b ## 支持 - ELF64 (x86_64) PT_LOAD 段装载 -- CLeonOS `int 0x80` syscall 0..60 +- CLeonOS `int 0x80` syscall 0..80(含 `FD_*`、`PROC_*`、`STATS_*`、`EXEC_PATHV_IO`) - TTY 输出与键盘输入队列 - rootfs 文件/目录访问(`FS_*`) - `/temp` 写入限制(`FS_MKDIR/WRITE/APPEND/REMOVE`) - `EXEC_PATH/EXEC_PATHV` 执行 ELF(带深度限制) +- `EXEC_PATHV_IO`(支持 stdio fd 继承/重定向) - `SPAWN_PATH/SPAWN_PATHV/WAITPID/EXIT/SLEEP_TICKS/YIELD` - 进程 `argv/env` 查询(`PROC_ARGC/PROC_ARGV/PROC_ENVC/PROC_ENV`) +- 进程枚举与快照(`PROC_COUNT/PROC_PID_AT/PROC_SNAPSHOT/PROC_KILL`) +- syscall 统计(`STATS_TOTAL/STATS_ID_COUNT/STATS_RECENT_*`) +- 文件描述符(`FD_OPEN/FD_READ/FD_WRITE/FD_CLOSE/FD_DUP`) - 异常退出状态编码与故障元信息(`PROC_LAST_SIGNAL/PROC_FAULT_*`) ## 参数 diff --git a/wine/cleonos_wine_lib/__pycache__/constants.cpython-313.pyc b/wine/cleonos_wine_lib/__pycache__/constants.cpython-313.pyc index a68ee7945c5a67399518db55fe13f4f43947ac5f..ede999193f2dd809b9b74566b7473275463bb289 100644 GIT binary patch delta 1391 zcmZXT-A?016vv(5CE$>R1VYfNUG;87?gPkyZY`6&NSE@x=; z)V}{~JTQL9ucvIO5ZHt|+R(sFXkrU&Y{MpYpoI>!(S;6r;NVMe z(FYF!USa?~b^&k;0_;H-`=H?fGPn&3xC4tg1Os;=k9)9+5-Ru#)^VRa2jn><&(TwA zI)W`ch8~^_)A$;8q5n`j)o~1aFi_DcNZ3}I^yv^;M4IKyc zj_1bbSMfZKuCDPM$E0ke@swUktmEi?d`Eo~8&V!$(~yyrILD4YU%|GLesxQ_o^WP~FRE6uE~@l= zI}o$XY`Y+0gQu!n6KQq5WR%rTrck?~p~ zSaIo&L`$-4r*Tj7yN<(}8ah#X7gF?TiTB4s;Cdcu8L%7bm^lc9-%*O`(zjxTZSjID zeam&)1CDyWiso7C`EkaTCLIU4T^UewlRZ_M+OcB8q~o!jrl1o-zW?fKt2?XiAnvht z=P&T|&Bf96+2}d`LIu9Nm!0&DVSJUEM8%abAC*?aLbSSes}-Z=QkaVh#c(OgFNfJA zRpoN!Rx3phKf2W_(c1d0wvrf>OZgRYh0sU}A5t~(-}72oHDH#agsY;=Jyk-6>{6JC jjC&w;sT3|m*&<=F5Y9)&GMgAm%auc8`dzlH^LGCQdt#IY delta 414 zcmaDPH%pxFGcPX}0}xn$c#z4&K9Ns?(Pg8$9HX{-u#*%+7E6(9ut%^nnB^Ai8SDaP zxdU0QV3tR^o2J($rZ~pQXPA3f-GSzKO^#)8Hgf{9ikyMON`@j25Zet%+~O!TGl|bj zO*bs^26Fv?xR@PCG%(!Z7H;q_@|b*$B~}cqlB*ywJvBZpCqKWaCnNE9;y2?dDxoM2tashK%NAwbcv$@f{+qd?aCX#zb0q;D~2=9S!H zD@rXXEy^nj1&U}g6@>vQ2%DuS5XMFl^}NLuAD>nV)RGz>U&IVj;Q}K3Hz%;=vse~^ zd=2&x*ri~bi@ZQ$NFKMz%}*)KNwq7Q36uc2xVV#XGaqj`qvRJhMn;)09E^-2AVT>o JkT3=71ON#tUyuL* diff --git a/wine/cleonos_wine_lib/__pycache__/runner.cpython-313.pyc b/wine/cleonos_wine_lib/__pycache__/runner.cpython-313.pyc index 34881e1bab3c2941c5fe705e42651c2484ab1c74..dea5b2abc12654fa5c2bc30b99359901a697a170 100644 GIT binary patch literal 80042 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 PnU;Uh;WjmkW6J*lrZoFT delta 15762 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 diff --git a/wine/cleonos_wine_lib/__pycache__/state.cpython-313.pyc b/wine/cleonos_wine_lib/__pycache__/state.cpython-313.pyc index 04393efd4c48385213552aa6a7955a7aa6aba841..9359408f6ef6d1f1aa77fe14007922ea1c74de36 100644 GIT binary patch literal 24061 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 L1%FQk^t}HM37h1F delta 3420 zcma)8YfPKh8UD`a8`y*rY%T!|B!s|cn>)ssI|d2?0wpJu5Fm~PAJmy+oX;i~)CE#) z)4C~5$ceV8OSVjvwz2FQE9tUnS?RP&*{^o{Q+>;%byN3aKh|_XiyCR&-uIZ;#%0nh z@$j7Ieb0N|^Pb!H?_ZLK{>;a_b{Pfu9eea=|F;U(GV{rWwF>Xr*Gfp%+TBS)n3oia zZ)u`0lN@m+TdAa)=``x7fF9JKNW4{rt!eE(VcJGTH&N`g8hmO}R0pjEwJt@iqV=FQ zq^Q+gZA?*XR(N1%id@UprWCb~tF9Eao~zBM(lP2T5Kd~926~vbaMnmEZRN}f);7+X z=n>k^nTxKB(GE_V={W7=%uQdUU7WSh3EItBD|Gg7)<%!g9?sh7B<hvA<_j5+}Q8-V-g%eT@uMf>G5#8Vat2V#B5(v=|D=^ARNjB|A!bUnnF8 z=M)DtBvBbeP4hYVd{_iliqZd(ClPlvKmRr<$C$BjJU|sTUqCnr5Krf$Jr$&zZTgGz#xE8t=Nz*Ino&18lLdf<4SCV8g}f>}Q3)mqx%@(y0bT4h4L(vZ@P*0{)1? zW4JNU7lV=#`yoTM+ zP4Ej%`G%MaVeggZnYQ@2a5$T-m73!=UuCi_H(?ux@mrf#aIfNw6%&Azp|m33jnIa` z7qdw03qMb5C>!~ z97Mb=u)2B&>pEm)=SxlO`%Vk9?J=`=ZTYOx?kqb6o{~0cd|ods6=a3QP$KN6-2$8D zj=e^I0fcZ1`?M53FM8jwJ8q5%Df6d;VrrAg{cIO(RnYC`RkFNx;I$sVRc@-nX zi^}|U68R!yb=S#2w6Qx?vs8aa4I z)xyPdDy#z)7LA|yj7GP94F~-Kr0rm58$E}fq(k0cKK{u+^+H+aC+gldE?k0X;3aTv zx5upcu@>NVjwfBeVXy$*D9rI>>Nje$fWMt?L^K;cdkwRCEONBLd4srk%4vy|W1EoA zdZ)dne^y?Ds}{=J>~fNCvY_it_D0)D_H@9^t`8a6z1GeuegSNif}iOw$WqlT$dT9x z=W%v;0{DDC8no-ahYEZ9U}N@UT$oGM-8#*N&e&}>#5M)%82Yd;xx!uGrFj+@gy(yV_S z;w3VxNzN=K1fTzPa8J%*ZL~qWE3kJ)kCN}O_vr5`d4S|i&Fjw-*`CAhu^Qd4AlhTL z(QaYC?kXd%?;L>N6FUTGKin^F2<(1)9vkZXpS}47vqSHTw9oWg7+$%Bz=y8-OzwCw z#nxt9?x3m`_UMR(Jv>q=K8WRD>n~fuV#4vTW49^<2LfIz{6ZX&L{+cI5qPrsV#DD- zj5faTcYvwdk;R}4uLU-ica4T3{zZ7*@%^3w=UH@q6#?zaT>wxQk@FDZ9f7?yZdnP( z74C;N@WUG|F;uL#K1sc}gFUs^@^eR);N?$zP)KP>5xxQuo~944z>CK}QIxM@$e$x5 z63$5Ka3wzETAW>tGZtrw-l+-2>X5WbD@ z2Eum{))9V$@Kc1Sz)nvVlr$oDBH$~mWIhADTEk;v^aqnpVzRCdUn%>8*7`wdIde_* zvG=E5V#lV7$}GCPO^2wf+DsF5)thNLUCU;sQMYe1KTX%ZX))@YoB8a6>B>qTA45yK zRioECzXT^l_Iee3t765zkF1~7%?wxa6t<_OrnQ|~oLdUW9m+f4NZ>E*X7^^S`#%&4 Z9_a int: diff --git a/wine/cleonos_wine_lib/runner.py b/wine/cleonos_wine_lib/runner.py index 0b7601b..57b1384 100644 --- a/wine/cleonos_wine_lib/runner.py +++ b/wine/cleonos_wine_lib/runner.py @@ -6,40 +6,48 @@ import sys import time from dataclasses import dataclass from pathlib import Path -from typing import List, Optional, Tuple +from typing import Dict, List, Optional, Tuple from .constants import ( DEFAULT_MAX_EXEC_DEPTH, + FD_INHERIT, FS_NAME_MAX, MAX_CSTR, MAX_IO_READ, + O_APPEND, + O_CREAT, + O_RDONLY, + O_RDWR, + O_TRUNC, + O_WRONLY, PAGE_SIZE, + PROC_STATE_EXITED, + PROC_STATE_PENDING, + PROC_STATE_RUNNING, + PROC_STATE_STOPPED, + SIGCONT, + SIGKILL, + SIGSTOP, + SIGTERM, SYS_AUDIO_AVAILABLE, SYS_AUDIO_PLAY_TONE, SYS_AUDIO_STOP, SYS_CONTEXT_SWITCHES, SYS_CUR_TASK, + SYS_DL_CLOSE, + SYS_DL_OPEN, + SYS_DL_SYM, SYS_EXEC_PATH, SYS_EXEC_PATHV, + SYS_EXEC_PATHV_IO, SYS_EXEC_REQUESTS, SYS_EXEC_SUCCESS, SYS_EXIT, - SYS_GETPID, - SYS_PROC_ARGC, - SYS_PROC_ARGV, - SYS_PROC_ENVC, - SYS_PROC_ENV, - SYS_PROC_FAULT_ERROR, - SYS_PROC_FAULT_RIP, - SYS_PROC_FAULT_VECTOR, - SYS_PROC_LAST_SIGNAL, - SYS_SLEEP_TICKS, - SYS_SPAWN_PATH, - SYS_SPAWN_PATHV, - SYS_WAITPID, - SYS_YIELD, - SYS_SHUTDOWN, - SYS_RESTART, + SYS_FD_CLOSE, + SYS_FD_DUP, + SYS_FD_OPEN, + SYS_FD_READ, + SYS_FD_WRITE, SYS_FS_APPEND, SYS_FS_CHILD_COUNT, SYS_FS_GET_CHILD_NAME, @@ -50,6 +58,7 @@ from .constants import ( SYS_FS_STAT_SIZE, SYS_FS_STAT_TYPE, SYS_FS_WRITE, + SYS_GETPID, SYS_KBD_BUFFERED, SYS_KBD_DROPPED, SYS_KBD_GET_CHAR, @@ -61,8 +70,29 @@ from .constants import ( SYS_LOG_JOURNAL_COUNT, SYS_LOG_JOURNAL_READ, SYS_LOG_WRITE, + SYS_PROC_ARGC, + SYS_PROC_ARGV, + SYS_PROC_COUNT, + SYS_PROC_ENVC, + SYS_PROC_ENV, + SYS_PROC_FAULT_ERROR, + SYS_PROC_FAULT_RIP, + SYS_PROC_FAULT_VECTOR, + SYS_PROC_KILL, + SYS_PROC_LAST_SIGNAL, + SYS_PROC_PID_AT, + SYS_PROC_SNAPSHOT, + SYS_RESTART, SYS_SERVICE_COUNT, SYS_SERVICE_READY_COUNT, + SYS_SHUTDOWN, + SYS_SLEEP_TICKS, + SYS_SPAWN_PATH, + SYS_SPAWN_PATHV, + SYS_STATS_ID_COUNT, + SYS_STATS_RECENT_ID, + SYS_STATS_RECENT_WINDOW, + SYS_STATS_TOTAL, SYS_TASK_COUNT, SYS_TIMER_TICKS, SYS_TTY_ACTIVE, @@ -75,6 +105,8 @@ from .constants import ( SYS_USER_LAUNCH_OK, SYS_USER_LAUNCH_TRIES, SYS_USER_SHELL_READY, + SYS_WAITPID, + SYS_YIELD, page_ceil, page_floor, u64, @@ -124,6 +156,15 @@ class ELFImage: segments: List[ELFSegment] +@dataclass +class FDEntry: + kind: str + flags: int + offset: int = 0 + path: str = "" + tty_index: int = 0 + + EXEC_PATH_MAX = 192 EXEC_ARG_LINE_MAX = 256 EXEC_ENV_LINE_MAX = 512 @@ -131,6 +172,8 @@ EXEC_MAX_ARGS = 24 EXEC_MAX_ENVS = 24 EXEC_ITEM_MAX = 128 EXEC_STATUS_SIGNAL_FLAG = 1 << 63 +PROC_PATH_MAX = 192 +FD_MAX = 64 class CLeonOSWineNative: @@ -150,6 +193,7 @@ class CLeonOSWineNative: ppid: int = 0, argv_items: Optional[List[str]] = None, env_items: Optional[List[str]] = None, + inherited_fds: Optional[Dict[int, FDEntry]] = None, ) -> None: self.elf_path = elf_path self.rootfs = rootfs @@ -175,9 +219,97 @@ class CLeonOSWineNative: self._stack_size = 0x0000000000020000 self._ret_sentinel = 0x00007FFF10000000 self._mapped_ranges: List[Tuple[int, int]] = [] + self._tty_index = int(self.state.tty_active) + self._fds: Dict[int, FDEntry] = {} + self._fd_inherited = inherited_fds if inherited_fds is not None else {} default_path = self._normalize_guest_path(self.guest_path_hint or f"/{self.elf_path.name}") self.argv_items, self.env_items = self._prepare_exec_items(default_path, self.argv_items, self.env_items) + self._init_default_fds() + + @staticmethod + def _clone_fd_entry(entry: FDEntry) -> FDEntry: + return FDEntry(kind=entry.kind, flags=int(entry.flags), offset=int(entry.offset), path=str(entry.path), tty_index=int(entry.tty_index)) + + @staticmethod + def _fd_access_mode(flags: int) -> int: + return int(flags) & 0x3 + + @classmethod + def _fd_access_mode_valid(cls, flags: int) -> bool: + mode = cls._fd_access_mode(flags) + return mode in (O_RDONLY, O_WRONLY, O_RDWR) + + @classmethod + def _fd_can_read(cls, flags: int) -> bool: + mode = cls._fd_access_mode(flags) + return mode in (O_RDONLY, O_RDWR) + + @classmethod + def _fd_can_write(cls, flags: int) -> bool: + mode = cls._fd_access_mode(flags) + return mode in (O_WRONLY, O_RDWR) + + def _init_default_fds(self) -> None: + self._fds = { + 0: FDEntry(kind="tty", flags=O_RDONLY, offset=0, tty_index=self._tty_index), + 1: FDEntry(kind="tty", flags=O_WRONLY, offset=0, tty_index=self._tty_index), + 2: FDEntry(kind="tty", flags=O_WRONLY, offset=0, tty_index=self._tty_index), + } + + for target in (0, 1, 2): + inherited = self._fd_inherited.get(target) + if inherited is not None: + self._fds[target] = self._clone_fd_entry(inherited) + + for target in (0, 1, 2): + entry = self._fds.get(target) + if entry is not None and entry.kind == "tty": + self._tty_index = int(entry.tty_index) + break + + def _fd_lookup(self, fd: int) -> Optional[FDEntry]: + if fd < 0 or fd >= FD_MAX: + return None + return self._fds.get(int(fd)) + + def _fd_find_free(self) -> int: + for fd in range(FD_MAX): + if fd not in self._fds: + return fd + return -1 + + def _stdio_entry_for_child(self, target_fd: int, override_fd: int, require_read: bool, require_write: bool) -> Optional[FDEntry]: + if override_fd == FD_INHERIT: + src = self._fd_lookup(target_fd) + else: + src = self._fd_lookup(override_fd) + + if src is None: + return None + + if require_read and not self._fd_can_read(src.flags): + return None + + if require_write and not self._fd_can_write(src.flags): + return None + + return self._clone_fd_entry(src) + + def _build_child_stdio_map(self, stdin_fd: int, stdout_fd: int, stderr_fd: int) -> Optional[Dict[int, FDEntry]]: + child_map: Dict[int, FDEntry] = {} + + in_entry = self._stdio_entry_for_child(0, stdin_fd, require_read=True, require_write=False) + out_entry = self._stdio_entry_for_child(1, stdout_fd, require_read=False, require_write=True) + err_entry = self._stdio_entry_for_child(2, stderr_fd, require_read=False, require_write=True) + + if in_entry is None or out_entry is None or err_entry is None: + return None + + child_map[0] = in_entry + child_map[1] = out_entry + child_map[2] = err_entry + return child_map def run(self) -> Optional[int]: if self.pid == 0: @@ -187,6 +319,7 @@ class CLeonOSWineNative: self.state.set_current_pid(self.pid) self.state.set_proc_cmdline(self.pid, self.argv_items, self.env_items) self.state.set_proc_fault(self.pid, 0, 0, 0, 0) + self.state.set_proc_running(self.pid, self.argv_items[0] if self.argv_items else self.guest_path_hint, self._tty_index) uc = Uc(UC_ARCH_X86, UC_MODE_64) self._install_hooks(uc) @@ -252,6 +385,7 @@ class CLeonOSWineNative: arg1 = self._reg_read(uc, UC_X86_REG_RCX) arg2 = self._reg_read(uc, UC_X86_REG_RDX) + self.state.record_syscall(syscall_id) self.state.context_switches = u64(self.state.context_switches + 1) ret = self._dispatch_syscall(uc, syscall_id, arg0, arg1, arg2) self._reg_write(uc, UC_X86_REG_RAX, u64(ret)) @@ -291,6 +425,8 @@ class CLeonOSWineNative: return self._exec_path(uc, arg0) if sid == SYS_EXEC_PATHV: return self._exec_pathv(uc, arg0, arg1, arg2) + if sid == SYS_EXEC_PATHV_IO: + return self._exec_pathv_io(uc, arg0, arg1, arg2) if sid == SYS_SPAWN_PATH: return self._spawn_path(uc, arg0) if sid == SYS_SPAWN_PATHV: @@ -315,6 +451,14 @@ class CLeonOSWineNative: return self._proc_fault_error() if sid == SYS_PROC_FAULT_RIP: return self._proc_fault_rip() + if sid == SYS_PROC_COUNT: + return self._proc_count() + if sid == SYS_PROC_PID_AT: + return self._proc_pid_at(uc, arg0, arg1) + if sid == SYS_PROC_SNAPSHOT: + return self._proc_snapshot(uc, arg0, arg1, arg2) + if sid == SYS_PROC_KILL: + return self._proc_kill(uc, arg0, arg1) if sid == SYS_EXIT: return self._request_exit(uc, arg0) if sid == SYS_SLEEP_TICKS: @@ -400,6 +544,30 @@ class CLeonOSWineNative: return self.state.kbd_drop_count if sid == SYS_KBD_HOTKEY_SWITCHES: return self.state.kbd_hotkey_switches + if sid == SYS_STATS_TOTAL: + return self.state.stats_total() + if sid == SYS_STATS_ID_COUNT: + return self.state.stats_id_count(arg0) + if sid == SYS_STATS_RECENT_WINDOW: + return self.state.stats_recent_window() + if sid == SYS_STATS_RECENT_ID: + return self.state.stats_recent_id_count(arg0) + if sid == SYS_FD_OPEN: + return self._fd_open(uc, arg0, arg1, arg2) + if sid == SYS_FD_READ: + return self._fd_read(uc, arg0, arg1, arg2) + if sid == SYS_FD_WRITE: + return self._fd_write(uc, arg0, arg1, arg2) + if sid == SYS_FD_CLOSE: + return self._fd_close(arg0) + if sid == SYS_FD_DUP: + return self._fd_dup(arg0) + if sid == SYS_DL_OPEN: + return u64_neg1() + if sid == SYS_DL_CLOSE: + return 0 + if sid == SYS_DL_SYM: + return 0 return u64_neg1() @@ -409,6 +577,15 @@ class CLeonOSWineNative: sys.stdout.write(text) sys.stdout.flush() + def _host_write_bytes(self, data: bytes) -> None: + if not data: + return + if hasattr(sys.stdout, "buffer"): + sys.stdout.buffer.write(data) + sys.stdout.flush() + return + self._host_write(data.decode("utf-8", errors="replace")) + def _load_segments(self, uc: Uc) -> None: for seg in self.image.segments: start = page_floor(seg.vaddr) @@ -504,6 +681,30 @@ class CLeonOSWineNative: except UcError: return b"" + def _read_guest_bytes_exact(self, uc: Uc, addr: int, size: int) -> Optional[bytes]: + if size < 0 or addr == 0: + return None + if size == 0: + return b"" + + out = bytearray() + cursor = int(addr) + left = int(size) + + while left > 0: + chunk = min(left, MAX_IO_READ) + try: + data = uc.mem_read(cursor, chunk) + except UcError: + return None + if len(data) != chunk: + return None + out.extend(data) + cursor += chunk + left -= chunk + + return bytes(out) + def _write_guest_bytes(self, uc: Uc, addr: int, data: bytes) -> bool: if addr == 0: return False @@ -743,16 +944,86 @@ class CLeonOSWineNative: return 1 if self._write_guest_bytes(uc, out_ptr, encoded + b"\x00") else 0 def _exec_path(self, uc: Uc, path_ptr: int) -> int: - return self._spawn_path_common(uc, path_ptr, 0, 0, return_pid=False) + return self._spawn_path_common( + uc, + path_ptr, + 0, + 0, + return_pid=False, + env_line_override=None, + stdin_fd=FD_INHERIT, + stdout_fd=FD_INHERIT, + stderr_fd=FD_INHERIT, + ) def _exec_pathv(self, uc: Uc, path_ptr: int, argv_ptr: int, env_ptr: int) -> int: - return self._spawn_path_common(uc, path_ptr, argv_ptr, env_ptr, return_pid=False) + return self._spawn_path_common( + uc, + path_ptr, + argv_ptr, + env_ptr, + return_pid=False, + env_line_override=None, + stdin_fd=FD_INHERIT, + stdout_fd=FD_INHERIT, + stderr_fd=FD_INHERIT, + ) + + def _exec_pathv_io(self, uc: Uc, path_ptr: int, argv_ptr: int, req_ptr: int) -> int: + req_data: Optional[bytes] + env_ptr: int + stdin_fd: int + stdout_fd: int + stderr_fd: int + env_line: str + + if req_ptr == 0: + return u64_neg1() + + req_data = self._read_guest_bytes_exact(uc, req_ptr, 32) + if req_data is None or len(req_data) != 32: + return u64_neg1() + + env_ptr, stdin_fd, stdout_fd, stderr_fd = struct.unpack(" int: - return self._spawn_path_common(uc, path_ptr, 0, 0, return_pid=True) + return self._spawn_path_common( + uc, + path_ptr, + 0, + 0, + return_pid=True, + env_line_override=None, + stdin_fd=FD_INHERIT, + stdout_fd=FD_INHERIT, + stderr_fd=FD_INHERIT, + ) def _spawn_pathv(self, uc: Uc, path_ptr: int, argv_ptr: int, env_ptr: int) -> int: - return self._spawn_path_common(uc, path_ptr, argv_ptr, env_ptr, return_pid=True) + return self._spawn_path_common( + uc, + path_ptr, + argv_ptr, + env_ptr, + return_pid=True, + env_line_override=None, + stdin_fd=FD_INHERIT, + stdout_fd=FD_INHERIT, + stderr_fd=FD_INHERIT, + ) def _spawn_path_common( self, @@ -762,17 +1033,30 @@ class CLeonOSWineNative: env_ptr: int, *, return_pid: bool, + env_line_override: Optional[str], + stdin_fd: int, + stdout_fd: int, + stderr_fd: int, ) -> int: path = self._read_guest_cstring(uc, path_ptr, EXEC_PATH_MAX) guest_path = self._normalize_guest_path(path) argv_line = self._read_guest_cstring(uc, argv_ptr, EXEC_ARG_LINE_MAX) if argv_ptr != 0 else "" - env_line = self._read_guest_cstring(uc, env_ptr, EXEC_ENV_LINE_MAX) if env_ptr != 0 else "" + env_line = ( + env_line_override + if env_line_override is not None + else (self._read_guest_cstring(uc, env_ptr, EXEC_ENV_LINE_MAX) if env_ptr != 0 else "") + ) host_path = self._guest_to_host(guest_path, must_exist=True) + child_stdio = self._build_child_stdio_map(int(stdin_fd), int(stdout_fd), int(stderr_fd)) self.state.exec_requests = u64(self.state.exec_requests + 1) self.state.user_exec_requested = 1 self.state.user_launch_tries = u64(self.state.user_launch_tries + 1) + if child_stdio is None: + self.state.user_launch_fail = u64(self.state.user_launch_fail + 1) + return u64_neg1() + if host_path is None or not host_path.is_file(): self.state.user_launch_fail = u64(self.state.user_launch_fail + 1) return u64_neg1() @@ -802,6 +1086,7 @@ class CLeonOSWineNative: ppid=parent_pid, argv_items=argv_items, env_items=env_items, + inherited_fds=child_stdio, ) child_ret = child.run() @@ -854,6 +1139,97 @@ class CLeonOSWineNative: def _proc_fault_rip(self) -> int: return self.state.proc_fault_rip_value(self.state.get_current_pid()) + def _proc_count(self) -> int: + return self.state.proc_count() + + def _proc_pid_at(self, uc: Uc, index: int, out_ptr: int) -> int: + if out_ptr == 0: + return 0 + + pid = self.state.proc_pid_at(int(index)) + if pid is None: + return 0 + + return 1 if self._write_guest_bytes(uc, out_ptr, struct.pack(" int: + if out_ptr == 0 or out_size < (13 * 8 + PROC_PATH_MAX): + return 0 + + target = int(pid) + state_value = self.state.proc_state_value(target) + if state_value == 0: + return 0 + + path = self.state.proc_path_value(target) + encoded_path = path.encode("utf-8", errors="replace") + if len(encoded_path) >= PROC_PATH_MAX: + encoded_path = encoded_path[: PROC_PATH_MAX - 1] + path_buf = encoded_path + b"\x00" + (b"\x00" * (PROC_PATH_MAX - len(encoded_path) - 1)) + + blob = struct.pack( + "<13Q", + u64(target), + u64(self.state.proc_ppid(target)), + u64(state_value), + u64(self.state.proc_started_tick_value(target)), + u64(self.state.proc_exited_tick_value(target)), + u64(self.state.proc_exit_status_value(target)), + u64(self.state.proc_runtime_ticks(target)), + u64(self.state.proc_mem_bytes_value(target)), + u64(self.state.proc_tty_index_value(target)), + u64(self.state.proc_signal(target)), + u64(self.state.proc_fault_vector_value(target)), + u64(self.state.proc_fault_error_value(target)), + u64(self.state.proc_fault_rip_value(target)), + ) + path_buf + + return 1 if self._write_guest_bytes(uc, out_ptr, blob) else 0 + + def _proc_kill(self, uc: Uc, pid: int, signal: int) -> int: + target = int(pid) + if target <= 0: + return u64_neg1() + + current_state = self.state.proc_state_value(target) + if current_state == 0: + return u64_neg1() + + effective_signal = int(signal) & 0xFF + if effective_signal == 0: + effective_signal = SIGTERM + + self.state.set_proc_fault(target, effective_signal, 0, 0, 0) + + if current_state == PROC_STATE_EXITED: + return 1 + + if effective_signal == SIGCONT: + if current_state == PROC_STATE_STOPPED: + self.state.set_proc_pending(target) + return 1 + + if effective_signal == SIGSTOP and current_state in (PROC_STATE_PENDING, PROC_STATE_STOPPED): + self.state.set_proc_stopped(target) + return 1 + + status = self._encode_signal_status(effective_signal, 0, 0) + + if target == self.state.get_current_pid(): + self._exit_requested = True + self._exit_status = u64(status) + if effective_signal == SIGSTOP: + self.state.set_proc_stopped(target) + uc.emu_stop() + return 1 + + if effective_signal == SIGSTOP: + self.state.set_proc_stopped(target) + return 1 + + self.state.mark_exited(target, status) + return 1 + def _wait_pid(self, uc: Uc, pid: int, out_ptr: int) -> int: wait_ret, status = self.state.wait_pid(int(pid)) @@ -885,6 +1261,190 @@ class CLeonOSWineNative: time.sleep(0) return self.state.timer_ticks() + def _fd_open(self, uc: Uc, path_ptr: int, flags: int, mode: int) -> int: + _ = mode + guest_path = self._normalize_guest_path(self._read_guest_cstring(uc, path_ptr, EXEC_PATH_MAX)) + open_flags = int(u64(flags)) + lower_path = guest_path.lower() + fd_slot: int + entry: FDEntry + + if not guest_path.startswith("/"): + return u64_neg1() + + if not self._fd_access_mode_valid(open_flags): + return u64_neg1() + + if ((open_flags & O_TRUNC) != 0 or (open_flags & O_APPEND) != 0) and not self._fd_can_write(open_flags): + return u64_neg1() + + fd_slot = self._fd_find_free() + if fd_slot < 0: + return u64_neg1() + + if lower_path == "/dev/tty": + entry = FDEntry(kind="tty", flags=open_flags, offset=0, tty_index=self._tty_index) + self._fds[fd_slot] = entry + return fd_slot + + if lower_path == "/dev/null": + entry = FDEntry(kind="dev_null", flags=open_flags, offset=0, tty_index=self._tty_index) + self._fds[fd_slot] = entry + return fd_slot + + if lower_path == "/dev/zero": + entry = FDEntry(kind="dev_zero", flags=open_flags, offset=0, tty_index=self._tty_index) + self._fds[fd_slot] = entry + return fd_slot + + if lower_path == "/dev/random": + entry = FDEntry(kind="dev_random", flags=open_flags, offset=0, tty_index=self._tty_index) + self._fds[fd_slot] = entry + return fd_slot + + host_path = self._guest_to_host(guest_path, must_exist=False) + if host_path is None: + return u64_neg1() + + try: + if not host_path.exists(): + if (open_flags & O_CREAT) == 0 or not self._fd_can_write(open_flags): + return u64_neg1() + host_path.parent.mkdir(parents=True, exist_ok=True) + host_path.write_bytes(b"") + + if host_path.is_dir(): + return u64_neg1() + + if (open_flags & O_TRUNC) != 0: + host_path.write_bytes(b"") + + offset = int(host_path.stat().st_size) if (open_flags & O_APPEND) != 0 else 0 + except Exception: + return u64_neg1() + + entry = FDEntry(kind="file", flags=open_flags, offset=offset, path=str(host_path), tty_index=self._tty_index) + self._fds[fd_slot] = entry + return fd_slot + + def _fd_read(self, uc: Uc, fd: int, out_ptr: int, size: int) -> int: + req = int(u64(size)) + entry = self._fd_lookup(int(fd)) + data: bytes + + if req == 0: + return 0 + + if out_ptr == 0: + return u64_neg1() + + if entry is None or not self._fd_can_read(entry.flags): + return u64_neg1() + + req = min(req, MAX_IO_READ) + + if entry.kind == "tty": + out = bytearray() + while len(out) < req: + key = self.state.pop_key() + if key is None: + break + out.append(key & 0xFF) + data = bytes(out) + elif entry.kind == "dev_null": + return 0 + elif entry.kind == "dev_zero": + data = b"\x00" * req + elif entry.kind == "dev_random": + data = os.urandom(req) + elif entry.kind == "file": + try: + with open(entry.path, "rb") as fh: + fh.seek(entry.offset) + data = fh.read(req) + except Exception: + return u64_neg1() + else: + return u64_neg1() + + if len(data) == 0: + return 0 + + if not self._write_guest_bytes(uc, int(out_ptr), data): + return u64_neg1() + + entry.offset += len(data) + return len(data) + + def _fd_write(self, uc: Uc, fd: int, buf_ptr: int, size: int) -> int: + req = int(u64(size)) + entry = self._fd_lookup(int(fd)) + data: Optional[bytes] + write_pos: int + + if req == 0: + return 0 + + if buf_ptr == 0: + return u64_neg1() + + if entry is None or not self._fd_can_write(entry.flags): + return u64_neg1() + + req = min(req, MAX_IO_READ) + data = self._read_guest_bytes_exact(uc, int(buf_ptr), req) + if data is None: + return u64_neg1() + + if entry.kind == "tty": + self._host_write_bytes(data) + entry.offset += len(data) + return len(data) + + if entry.kind in ("dev_null", "dev_zero", "dev_random"): + entry.offset += len(data) + return len(data) + + if entry.kind != "file": + return u64_neg1() + + try: + host_path = Path(entry.path) + if not host_path.exists(): + if (entry.flags & O_CREAT) == 0 or not self._fd_can_write(entry.flags): + return u64_neg1() + host_path.parent.mkdir(parents=True, exist_ok=True) + host_path.write_bytes(b"") + + with open(host_path, "r+b") as fh: + write_pos = int(entry.offset) + fh.seek(write_pos) + fh.write(data) + except Exception: + return u64_neg1() + + entry.offset += len(data) + return len(data) + + def _fd_close(self, fd: int) -> int: + key = int(fd) + if key not in self._fds: + return u64_neg1() + del self._fds[key] + return 0 + + def _fd_dup(self, fd: int) -> int: + src = self._fd_lookup(int(fd)) + if src is None: + return u64_neg1() + + slot = self._fd_find_free() + if slot < 0: + return u64_neg1() + + self._fds[slot] = self._clone_fd_entry(src) + return slot + @staticmethod def _truncate_item_text(text: str, max_bytes: int = EXEC_ITEM_MAX) -> str: if max_bytes <= 1: diff --git a/wine/cleonos_wine_lib/state.py b/wine/cleonos_wine_lib/state.py index 3739786..7c5ce38 100644 --- a/wine/cleonos_wine_lib/state.py +++ b/wine/cleonos_wine_lib/state.py @@ -6,7 +6,7 @@ import time from dataclasses import dataclass, field from typing import Deque, Dict, List, Optional, Tuple -from .constants import u64 +from .constants import PROC_STATE_EXITED, PROC_STATE_PENDING, PROC_STATE_RUNNING, PROC_STATE_STOPPED, u64 @dataclass @@ -37,13 +37,27 @@ class SharedKernelState: kbd_hotkey_switches: int = 0 log_journal_cap: int = 256 log_journal: Deque[str] = field(default_factory=lambda: collections.deque(maxlen=256)) - fs_write_max: int = 65536 + fs_write_max: int = 16 * 1024 * 1024 + # syscall stats + stats_lock: threading.Lock = field(default_factory=threading.Lock) + stats_total_calls: int = 0 + stats_id_total: Dict[int, int] = field(default_factory=dict) + stats_recent_window_cap: int = 256 + stats_recent_ring: Deque[int] = field(default_factory=lambda: collections.deque(maxlen=256)) + + # process table proc_lock: threading.Lock = field(default_factory=threading.Lock) proc_next_pid: int = 1 proc_current_pid: int = 0 proc_parents: Dict[int, int] = field(default_factory=dict) proc_status: Dict[int, Optional[int]] = field(default_factory=dict) + proc_state: Dict[int, int] = field(default_factory=dict) + proc_started_tick: Dict[int, int] = field(default_factory=dict) + proc_exited_tick: Dict[int, int] = field(default_factory=dict) + proc_mem_bytes: Dict[int, int] = field(default_factory=dict) + proc_tty_index: Dict[int, int] = field(default_factory=dict) + proc_path: Dict[int, str] = field(default_factory=dict) proc_argv: Dict[int, List[str]] = field(default_factory=dict) proc_env: Dict[int, List[str]] = field(default_factory=dict) proc_last_signal: Dict[int, int] = field(default_factory=dict) @@ -54,6 +68,32 @@ class SharedKernelState: def timer_ticks(self) -> int: return (time.monotonic_ns() - self.start_ns) // 1_000_000 + def record_syscall(self, sid: int) -> None: + key = int(u64(sid)) + + with self.stats_lock: + self.stats_total_calls = int(u64(self.stats_total_calls + 1)) + self.stats_id_total[key] = int(u64(self.stats_id_total.get(key, 0) + 1)) + self.stats_recent_ring.append(key) + + def stats_total(self) -> int: + with self.stats_lock: + return int(self.stats_total_calls) + + def stats_id_count(self, sid: int) -> int: + key = int(u64(sid)) + with self.stats_lock: + return int(self.stats_id_total.get(key, 0)) + + def stats_recent_window(self) -> int: + with self.stats_lock: + return len(self.stats_recent_ring) + + def stats_recent_id_count(self, sid: int) -> int: + key = int(u64(sid)) + with self.stats_lock: + return sum(1 for item in self.stats_recent_ring if item == key) + def push_key(self, key: int) -> None: with self.kbd_lock: if len(self.kbd_queue) >= self.kbd_queue_cap: @@ -94,6 +134,8 @@ class SharedKernelState: return list(self.log_journal)[index_from_oldest] def alloc_pid(self, ppid: int) -> int: + now = self.timer_ticks() + with self.proc_lock: pid = int(self.proc_next_pid) @@ -107,6 +149,12 @@ class SharedKernelState: self.proc_parents[pid] = int(ppid) self.proc_status[pid] = None + self.proc_state[pid] = PROC_STATE_PENDING + self.proc_started_tick[pid] = now + self.proc_exited_tick[pid] = 0 + self.proc_mem_bytes[pid] = 0 + self.proc_tty_index[pid] = int(self.tty_active) + self.proc_path[pid] = "" self.proc_argv[pid] = [] self.proc_env[pid] = [] self.proc_last_signal[pid] = 0 @@ -123,12 +171,51 @@ class SharedKernelState: with self.proc_lock: return int(self.proc_current_pid) + def set_proc_running(self, pid: int, path: Optional[str], tty_index: int) -> None: + if pid <= 0: + return + + now = self.timer_ticks() + + with self.proc_lock: + if pid not in self.proc_status: + return + + self.proc_state[pid] = PROC_STATE_RUNNING + if self.proc_started_tick.get(pid, 0) == 0: + self.proc_started_tick[pid] = now + self.proc_tty_index[pid] = int(tty_index) + if path: + self.proc_path[pid] = str(path) + def mark_exited(self, pid: int, status: int) -> None: if pid <= 0: return with self.proc_lock: self.proc_status[int(pid)] = int(u64(status)) + self.proc_state[int(pid)] = PROC_STATE_EXITED + self.proc_exited_tick[int(pid)] = self.timer_ticks() + + def set_proc_stopped(self, pid: int) -> None: + if pid <= 0: + return + + with self.proc_lock: + if pid not in self.proc_status: + return + if self.proc_state.get(pid, PROC_STATE_PENDING) != PROC_STATE_EXITED: + self.proc_state[pid] = PROC_STATE_STOPPED + + def set_proc_pending(self, pid: int) -> None: + if pid <= 0: + return + + with self.proc_lock: + if pid not in self.proc_status: + return + if self.proc_state.get(pid, PROC_STATE_PENDING) != PROC_STATE_EXITED: + self.proc_state[pid] = PROC_STATE_PENDING def wait_pid(self, pid: int) -> Tuple[int, int]: with self.proc_lock: @@ -151,6 +238,8 @@ class SharedKernelState: return self.proc_argv[pid] = [str(item) for item in argv] self.proc_env[pid] = [str(item) for item in env] + if argv: + self.proc_path[pid] = str(argv[0]) def set_proc_fault(self, pid: int, signal: int, vector: int, error_code: int, rip: int) -> None: if pid <= 0: @@ -164,6 +253,68 @@ class SharedKernelState: self.proc_fault_error[pid] = int(u64(error_code)) self.proc_fault_rip[pid] = int(u64(rip)) + def proc_count(self) -> int: + with self.proc_lock: + return len(self.proc_status) + + def proc_pid_at(self, index: int) -> Optional[int]: + if index < 0: + return None + + with self.proc_lock: + ordered = sorted(self.proc_status.keys()) + if index >= len(ordered): + return None + return int(ordered[index]) + + def proc_state_value(self, pid: int) -> int: + with self.proc_lock: + return int(self.proc_state.get(int(pid), 0)) + + def proc_ppid(self, pid: int) -> int: + with self.proc_lock: + return int(self.proc_parents.get(int(pid), 0)) + + def proc_started_tick_value(self, pid: int) -> int: + with self.proc_lock: + return int(self.proc_started_tick.get(int(pid), 0)) + + def proc_exited_tick_value(self, pid: int) -> int: + with self.proc_lock: + return int(self.proc_exited_tick.get(int(pid), 0)) + + def proc_exit_status_value(self, pid: int) -> int: + with self.proc_lock: + value = self.proc_status.get(int(pid)) + return int(value) if value is not None else 0 + + def proc_runtime_ticks(self, pid: int) -> int: + with self.proc_lock: + start = int(self.proc_started_tick.get(int(pid), 0)) + state = int(self.proc_state.get(int(pid), 0)) + end = int(self.proc_exited_tick.get(int(pid), 0)) + + if start == 0: + return 0 + + if state == PROC_STATE_EXITED and end >= start: + return end - start + + now = self.timer_ticks() + return 0 if now < start else now - start + + def proc_mem_bytes_value(self, pid: int) -> int: + with self.proc_lock: + return int(self.proc_mem_bytes.get(int(pid), 0)) + + def proc_tty_index_value(self, pid: int) -> int: + with self.proc_lock: + return int(self.proc_tty_index.get(int(pid), 0)) + + def proc_path_value(self, pid: int) -> str: + with self.proc_lock: + return str(self.proc_path.get(int(pid), "")) + def proc_argc(self, pid: int) -> int: with self.proc_lock: return len(self.proc_argv.get(int(pid), []))