On Tue, Sep 01, 2026 at 08:35:31PM +0200, Pavel Sanda wrote:
> On Mon, Aug 31, 2026 at 09:54:20PM +0200, Pavel Sanda wrote:
> > The first batch of patches tries to sanitize runCommand() calls.
> 
> The next one uses new startscriptArgv to close kpsewhich hole
> in a less fragile way.

The third one in our march for ditching runCommand from the source
code kicks it from python probes. Jose, you might want to have a look.

Pavel
>From 4e1d0b76f5020afca4110579619cf4ef15af8293 Mon Sep 17 00:00:00 2001
From: Pavel Sanda <[email protected]>
Date: Tue, 8 Sep 2026 21:42:02 +0200
Subject: [PATCH 05/11] Probe the python interpreter without a shell

Reroute python_call() and python_info() to startscriptArgv() so
os.cpp is left with no runCommand().

Adds new os::pythonArgvForm() for the argv-form callers.

Tier 01 DiD / argv 03a: python argv probe

Assisted-by: Claude Opus 5
---
 src/support/os.cpp | 47 +++++++++++++++++++++++++++++++++++++++-------
 src/support/os.h   |  5 +++++
 2 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/src/support/os.cpp b/src/support/os.cpp
index d34adee420..8fe6c8932a 100644
--- a/src/support/os.cpp
+++ b/src/support/os.cpp
@@ -19,6 +19,7 @@
 #include "support/debug.h"
 #include "support/filetools.h"
 #include "support/qstring_helpers.h"
+#include "support/Systemcall.h"
 
 #include <QDir>
 
@@ -58,20 +59,33 @@ int timeout_ms()
 
 static string const python_call(string const & binary, bool verbose = false)
 {
-	const string version_info = " -c \"import sys; print(sys.version_info[:2], end='')\"";
+	const string version_script = "import sys; print(sys.version_info[:2], end='')";
 	// Default to "python3" if no binary is given.
 	if (binary.empty())
 		return "python3";
 
 	if (verbose)
 		lyxerr << "Examining " << binary << "\n";
-	// Check whether this is a python 2 or 3 binary.
-	cmd_ret const out = runCommand(binary + version_info);
+	// Check whether this is a python 2 or 3 binary. The candidate may be
+	// a bare name ("python3"), a name plus argument ("py -3") or a quoted
+	// absolute path found with walking-path fallback; split it into argv
+	// so the probe never goes through a shell.
+	vector<string> argv = splitCommandTokens(binary);
+	if (argv.empty())
+		return string();
+	string const program = argv.front();
+	argv.erase(argv.begin());
+	argv.push_back("-c");
+	argv.push_back(version_script);
+
+	Systemcall one;
+	string result;
+	int const ret = one.startscriptArgv(program, argv, &result);
 
 	smatch sm;
 	try {
 		static regex const python_reg("\\((\\d*), (\\d*)\\)");
-		if (!out.valid || !regex_match(out.result, sm, python_reg))
+		if (ret != Systemcall::OK || !regex_match(result, sm, python_reg))
 			return string();
 	} catch(regex_error const & /*e*/) {
 		LYXERR0("Regex error! This should not happen.");
@@ -84,7 +98,7 @@ static string const python_call(string const & binary, bool verbose = false)
 		return string();
 
 	if (verbose)
-		lyxerr << "Found Python " << out.result << "\n";
+		lyxerr << "Found Python " << result << "\n";
 	return binary;
 }
 
@@ -158,12 +172,32 @@ bool hasPython()
 	return !(python_call(python()).empty());
 }
 
+vector<string> pythonArgvForm()
+{
+	return splitCommandTokens(python());
+}
+
+
+// consumers: About dialog & GraphicsConverter version check on "2"
 string const python_info()
 {
-	const string info_version = " -c \"import sys; print('{} ({})'.format(sys.version.split()[0],sys.executable), end='')\"";
+	const string info_script = "import sys; print('{} ({})'.format(sys.version.split()[0],sys.executable), end='')";
 	if (!hasPython())
 		return("None");
-	return (runCommand(python() + info_version).result);
+
+	vector<string> argv = pythonArgvForm();
+	if (argv.empty())
+		return("None");
+	string const program = argv.front();
+	argv.erase(argv.begin());
+	argv.push_back("-c");
+	argv.push_back(info_script);
+
+	Systemcall one;
+	string result;
+	if (one.startscriptArgv(program, argv, &result) != Systemcall::OK)
+		return("None");
+	return result;
 }
 
 } // namespace os
diff --git a/src/support/os.h b/src/support/os.h
index 1b664e4444..94c243abf3 100644
--- a/src/support/os.h
+++ b/src/support/os.h
@@ -16,6 +16,7 @@
 
 #include "support/docstring.h"
 #include <cstddef>
+#include <vector>
 
 
 namespace lyx {
@@ -62,6 +63,10 @@ int timeout_ms();
 /// @param reset True if the python path should be recomputed
 std::string const python(bool reset = false);
 
+/// Take python() output and split it into program + leading arguments, for invocation
+/// via Systemcall::startscriptArgv (no shell, no command-line re-parsing).
+std::vector<std::string> pythonArgvForm();
+
 /// Check for availability of the python interpreter
 bool hasPython();
 
-- 
2.39.5

-- 
lyx-devel mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-devel

Reply via email to