From 9af3443d9549d678c8edb79555fa070d5be75d61 Mon Sep 17 00:00:00 2001 From: cnc Date: Sat, 6 Jun 2026 05:34:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8C=89=E6=BA=90=E4=BF=9D=E6=8A=A4=20PythonPl?= =?UTF-8?q?ugin=20=E8=B7=AF=E5=BE=84=E5=88=97=E8=A1=A8=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- check-linuxcnc-wasm-shims-source-map.sh | 21 ++++ core/wasm_shims/pythonplugin/python_plugin.cc | 75 ++++++++++++-- .../pythonplugin/python_plugin_probe_main.cc | 97 +++++++++++++++++++ docs/linuxcnc-wasm-shims-source-map.md | 3 +- 4 files changed, 186 insertions(+), 10 deletions(-) diff --git a/check-linuxcnc-wasm-shims-source-map.sh b/check-linuxcnc-wasm-shims-source-map.sh index fbb5214..b993377 100755 --- a/check-linuxcnc-wasm-shims-source-map.sh +++ b/check-linuxcnc-wasm-shims-source-map.sh @@ -93,6 +93,8 @@ grep -F 'return vsnprintf(buffer, size, fmt, args);' \ grep -F 'class PythonPlugin' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PLUGIN_PYTHON_NOT_INITIALIZED = -12,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PLUGIN_BAD_PATH = -5,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null +grep -F 'PLUGIN_EXCEPTION_DURING_PATH_PREPEND = -9,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null +grep -F 'PLUGIN_EXCEPTION_DURING_PATH_APPEND = -10,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PLUGIN_NO_CALLABLE = 1,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PLUGIN_EXCEPTION = 2' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PythonPlugin::instantiate' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null @@ -102,6 +104,17 @@ grep -F 'if (auto inistring = inifile.findString("TOPLEVEL", section)) {' \ grep -F 'if (realpath(toplevel, real_path) == NULL) {' \ "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null grep -F 'status = PLUGIN_BAD_PATH;' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null +grep -F 'while (auto inistring = inifile.findString(n, "PATH_PREPEND", "PYTHON")) {' \ + "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null +grep -F 'while (auto inistring = inifile.findString(n, "PATH_APPEND", "PYTHON")) {' \ + "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null +grep -F 'status = PLUGIN_EXCEPTION_DURING_PATH_PREPEND;' \ + "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null +grep -F 'status = PLUGIN_EXCEPTION_DURING_PATH_APPEND;' \ + "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null +grep -F 'int IniFile::tildeExpand(const std::string &path, std::string &result)' \ + "$linuxcnc_root/src/emc/ini/inifile.cc" >/dev/null +grep -F "if(!home)" "$linuxcnc_root/src/emc/ini/inifile.cc" >/dev/null grep -F 'retval = bp::exec_file(cmd, main_namespace, main_namespace);' \ "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null grep -F 'retval = bp::exec(cmd, main_namespace, main_namespace);' \ @@ -195,6 +208,7 @@ expected_shims = { "core/wasm_shims/pythonplugin/python_plugin.cc": [ "src/emc/pythonplugin/python_plugin.cc", "src/emc/pythonplugin/python_plugin.hh", + "src/emc/ini/inifile.cc", ], } if shim_paths != list(expected_shims): @@ -326,6 +340,10 @@ for needle in [ 'find_ini_value(iniFilename, section, "TOPLEVEL", toplevel)', "realpath(toplevel.c_str(), resolved_path) == nullptr", "status = PLUGIN_BAD_PATH;", + 'check_python_path_entries(iniFilename,', + "PLUGIN_EXCEPTION_DURING_PATH_PREPEND", + "PLUGIN_EXCEPTION_DURING_PATH_APPEND", + "tilde_expand_path(entry, expanded)", "status = PLUGIN_PYTHON_NOT_INITIALIZED;", "return PLUGIN_NO_CALLABLE;", "status = PLUGIN_EXCEPTION;", @@ -337,6 +355,8 @@ for needle in [ "PLUGIN_NO_SECTION", "PLUGIN_BAD_INIFILE", "PLUGIN_BAD_PATH", + "PLUGIN_EXCEPTION_DURING_PATH_PREPEND", + "PLUGIN_EXCEPTION_DURING_PATH_APPEND", "PLUGIN_PYTHON_NOT_INITIALIZED", "PLUGIN_NO_CALLABLE", "PLUGIN_EXCEPTION", @@ -418,6 +438,7 @@ for phrase in [ "inactive-DB return values", "negative-status short-circuit behavior", "preserve the `TOPLEVEL` missing-path `PLUGIN_BAD_PATH` edge", + "preserve `PATH_PREPEND` and `PATH_APPEND` tilde-expansion failure statuses", "LinuxCNC `rcs_print_error` split entry points", "without native print routing", "LinuxCNC userspace `rtapi_snprintf()` formatting", diff --git a/core/wasm_shims/pythonplugin/python_plugin.cc b/core/wasm_shims/pythonplugin/python_plugin.cc index 62174ee..bb8fe7d 100644 --- a/core/wasm_shims/pythonplugin/python_plugin.cc +++ b/core/wasm_shims/pythonplugin/python_plugin.cc @@ -6,6 +6,7 @@ #include #include #include +#include #include // LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc provides the @@ -31,13 +32,13 @@ std::string trim_ini_value(std::string value) { return std::string(first, last); } -bool find_ini_value(const char *iniFilename, - const char *section, - const char *key, - std::string &value) { +std::vector find_ini_values(const char *iniFilename, + const char *section, + const char *key) { + std::vector values; FILE *file = std::fopen(iniFilename, "r"); if (!file) { - return false; + return values; } bool in_section = false; @@ -59,14 +60,55 @@ bool find_ini_value(const char *iniFilename, continue; } if (trim_ini_value(text.substr(0, equals)) == key) { - value = trim_ini_value(text.substr(equals + 1)); - std::fclose(file); - return true; + values.push_back(trim_ini_value(text.substr(equals + 1))); } } std::fclose(file); - return false; + return values; +} + +bool find_ini_value(const char *iniFilename, + const char *section, + const char *key, + std::string &value) { + const std::vector values = find_ini_values(iniFilename, section, key); + if (values.empty()) { + return false; + } + value = values.front(); + return true; +} + +bool tilde_expand_path(const std::string &path, std::string &expanded) { + // LinuxCNC source basis: IniFile::tildeExpand() copies non-"~/" paths and + // returns failure for "~/" paths when HOME is unavailable. + if (path.size() < 2 || path[0] != '~' || path[1] != '/') { + expanded = path; + return true; + } + const char *home = std::getenv("HOME"); + if (!home) { + return false; + } + expanded = std::string(home) + path.substr(1); + return true; +} + +int check_python_path_entries(const char *iniFilename, + const char *key, + pp_status failure_status) { + for (const std::string &entry : find_ini_values(iniFilename, "PYTHON", key)) { + std::string expanded; + // LinuxCNC source basis: python_plugin.cc configure() runs + // TildeExpansion on [PYTHON] PATH_PREPEND/PATH_APPEND before + // PyRun_SimpleString(); browser-safe probes preserve the expansion + // failure status without executing Python path mutation. + if (!tilde_expand_path(entry, expanded)) { + return failure_status; + } + } + return PLUGIN_OK; } } // namespace @@ -115,6 +157,21 @@ int PythonPlugin::configure(const char *iniFilename, const char *section) { } } + status = check_python_path_entries(iniFilename, + "PATH_PREPEND", + PLUGIN_EXCEPTION_DURING_PATH_PREPEND); + if (status != PLUGIN_OK) { + error_msg = "bad path prepend"; + return status; + } + status = check_python_path_entries(iniFilename, + "PATH_APPEND", + PLUGIN_EXCEPTION_DURING_PATH_APPEND); + if (status != PLUGIN_OK) { + error_msg = "bad path append"; + return status; + } + return initialize(); } diff --git a/core/wasm_shims/pythonplugin/python_plugin_probe_main.cc b/core/wasm_shims/pythonplugin/python_plugin_probe_main.cc index e599854..520547a 100644 --- a/core/wasm_shims/pythonplugin/python_plugin_probe_main.cc +++ b/core/wasm_shims/pythonplugin/python_plugin_probe_main.cc @@ -1,5 +1,6 @@ #include "pythonplugin/python_plugin.hh" +#include #include #include @@ -86,6 +87,102 @@ int main() { return 36; } std::remove(bad_path_ini); + const char *path_prepend_ini = "/tmp/cnc_sim_python_plugin_probe_path_prepend.ini"; + { + FILE *file = std::fopen(path_prepend_ini, "w"); + if (!file) { + std::remove(ini); + return 37; + } + std::fputs("[PYTHON]\nPATH_PREPEND = ~/linuxcnc-python\n", file); + std::fclose(file); + } + const char *saved_home = std::getenv("HOME"); + const std::string saved_home_value = saved_home ? saved_home : ""; + unsetenv("HOME"); + if (plugin->configure(path_prepend_ini, "PYTHON") != PLUGIN_EXCEPTION_DURING_PATH_PREPEND) { + if (saved_home) { + setenv("HOME", saved_home_value.c_str(), 1); + } + std::remove(ini); + std::remove(path_prepend_ini); + return 38; + } + if (plugin->usable()) { + if (saved_home) { + setenv("HOME", saved_home_value.c_str(), 1); + } + std::remove(ini); + std::remove(path_prepend_ini); + return 39; + } + if (plugin->last_errmsg() != "bad path prepend") { + if (saved_home) { + setenv("HOME", saved_home_value.c_str(), 1); + } + std::remove(ini); + std::remove(path_prepend_ini); + return 40; + } + if (saved_home) { + setenv("HOME", saved_home_value.c_str(), 1); + } + std::remove(path_prepend_ini); + const char *path_append_ini = "/tmp/cnc_sim_python_plugin_probe_path_append.ini"; + { + FILE *file = std::fopen(path_append_ini, "w"); + if (!file) { + std::remove(ini); + return 41; + } + std::fputs("[PYTHON]\nPATH_APPEND = ~/linuxcnc-python\n", file); + std::fclose(file); + } + unsetenv("HOME"); + if (plugin->configure(path_append_ini, "PYTHON") != PLUGIN_EXCEPTION_DURING_PATH_APPEND) { + if (saved_home) { + setenv("HOME", saved_home_value.c_str(), 1); + } + std::remove(ini); + std::remove(path_append_ini); + return 42; + } + if (plugin->usable()) { + if (saved_home) { + setenv("HOME", saved_home_value.c_str(), 1); + } + std::remove(ini); + std::remove(path_append_ini); + return 43; + } + if (plugin->last_errmsg() != "bad path append") { + if (saved_home) { + setenv("HOME", saved_home_value.c_str(), 1); + } + std::remove(ini); + std::remove(path_append_ini); + return 44; + } + if (saved_home) { + setenv("HOME", saved_home_value.c_str(), 1); + } + std::remove(path_append_ini); + const char *plain_path_ini = "/tmp/cnc_sim_python_plugin_probe_plain_path.ini"; + { + FILE *file = std::fopen(plain_path_ini, "w"); + if (!file) { + std::remove(ini); + return 45; + } + std::fputs("[PYTHON]\nPATH_PREPEND = /tmp\nPATH_APPEND = /tmp\n", file); + std::fclose(file); + } + if (plugin->configure(plain_path_ini, "PYTHON") != PLUGIN_PYTHON_NOT_INITIALIZED) { + std::remove(ini); + std::remove(plain_path_ini); + return 46; + } + std::remove(plain_path_ini); setenv("INI_FILE_NAME", ini, 1); if (plugin->configure(nullptr, "PYTHON") != PLUGIN_PYTHON_NOT_INITIALIZED) { std::remove(ini); diff --git a/docs/linuxcnc-wasm-shims-source-map.md b/docs/linuxcnc-wasm-shims-source-map.md index 6129e8a..d3a29c8 100644 --- a/docs/linuxcnc-wasm-shims-source-map.md +++ b/docs/linuxcnc-wasm-shims-source-map.md @@ -24,7 +24,7 @@ only; it must not add CNC behavior and must not expand the temporary smoke parse | `core/wasm_shims/rtapi_compat.cc` | `src/rtapi/uspace_common.h`, `src/rtapi/rtapi.h` | Provides LinuxCNC userspace `rtapi_snprintf()` formatting by following the `rtapi.h` declaration and `uspace_common.h` `vsnprintf()` implementation path. | | `core/wasm_shims/tooldata/tooldata_mmap_backend.cc` | `src/emc/tooldata/tooldata_mmap.cc` | Replaces native mmap storage with an in-memory WASM-safe backend for probes. | | `core/wasm_shims/tooldata/tooldata_runtime_stubs.cc` | `src/emc/tooldata/tooldata_nml.cc`, `src/emc/tooldata/tooldata_db.cc` | Satisfies NML and external tool database entry points without native services, preserving inactive-DB return values. | -| `core/wasm_shims/pythonplugin/python_plugin.cc` | `src/emc/pythonplugin/python_plugin.cc`, `src/emc/pythonplugin/python_plugin.hh` | Keeps LinuxCNC PythonPlugin API and negative-status short-circuit behavior available while Python execution is disabled for browser-safe probes. | +| `core/wasm_shims/pythonplugin/python_plugin.cc` | `src/emc/pythonplugin/python_plugin.cc`, `src/emc/pythonplugin/python_plugin.hh`, `src/emc/ini/inifile.cc` | Keeps LinuxCNC PythonPlugin API and negative-status short-circuit behavior available while Python execution is disabled for browser-safe probes. | ## Runtime Shim Boundaries @@ -64,6 +64,7 @@ only; it must not add CNC behavior and must not expand the temporary smoke parse `PLUGIN_BAD_PATH`, `PLUGIN_PYTHON_NOT_INITIALIZED`, `PLUGIN_NO_CALLABLE`, and `PLUGIN_EXCEPTION` while keeping Python execution disabled. - The PythonPlugin shim must preserve the `TOPLEVEL` missing-path `PLUGIN_BAD_PATH` edge from `src/emc/pythonplugin/python_plugin.cc` before reporting the browser-safe disabled Python runtime status. +- The PythonPlugin shim must preserve `PATH_PREPEND` and `PATH_APPEND` tilde-expansion failure statuses from `src/emc/pythonplugin/python_plugin.cc` and `src/emc/ini/inifile.cc` without executing Python path mutation in browser-safe probes. - The PythonPlugin shim must not execute `bp::exec`, `bp::exec_file`, or real Python callable dispatch; `PyObject_Call` is an inert C API symbol only. Browser Python execution remains blocked until implemented as a source-backed