On Sun, Sep 20, 2026 at 04:00:51PM +0200, Enrico Forestieri wrote:
> Regarding the issue with .bat files on windows, some time ago we were using
> QProcess, which was was also unable to launch batch files. At the time, I
> wrote a wrapper that made it possible to do so simply by giving the
> resulting executable the same name. I am attaching it here. This single
> executable, when renamed appropriately, can launch any .bat file. We could
> therefore distribute a maxima.exe file in LyX's bin folder on Windows to
> resolve the issue. I tested it with your patch and it works.

It seems to me that _popen essentially reintroduces the shell I was trying to
avoid with this whole patch stack. So introducing the wrapper is not worth it - 
shell remains, we "fix" only maxima and not other possible .bats and we have
+1 executable build to care about. 

Anyway, I think I lean to clean runCommand from common API, if you willing
to do some testing.

My proposal is going in two steps - first can you test whether the
following patch works for CAS on windows now with the attached?

Pavel
>From ea557b366a7485547a0d73b91545b1150244bb94 Mon Sep 17 00:00:00 2001
From: Pavel Sanda <[email protected]>
Date: Thu, 17 Sep 2026 02:55:40 +0200
Subject: [PATCH 12/14] Run the CAS through Systemcall instead of runCommand

Rerouting to argv form would not work here (unless we rewrite the API
just for this consumer so it deals with stdin/out).

Fortunately we have startscript() which spawns without a shell and its
parsecmd() already understands the redirections these commands use,
including the 2>&1 that octave is invoked with. startscript() reports
an exit code rather than the output, so collect the output in a second
temp file and read it back.

There is no way how to get rid of shell on windows, because of .bat
invocations (e.g. maxima.bat).

Compatibility change here, runCommand() used to return the raw bytes,
while fileContents() decodes them as UTF-8 and to_utf8() encodes them
back: output which is not valid UTF-8 now comes back with replacement
characters, and output containing a NUL byte is truncated there.
Should not be a problem for CAS exchange though.

Tier 01 DiD / argv 04c: CAS via Systemcall, no shell

Assisted-by: Claude Opus 5
---
 src/mathed/MathExtern.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 44 insertions(+), 4 deletions(-)

diff --git a/src/mathed/MathExtern.cpp b/src/mathed/MathExtern.cpp
index 531138b358..39d79fe440 100644
--- a/src/mathed/MathExtern.cpp
+++ b/src/mathed/MathExtern.cpp
@@ -39,10 +39,12 @@
 
 #include "support/debug.h"
 #include "support/docstream.h"
+#include "support/environment.h"
 #include "support/FileName.h"
 #include "support/filetools.h"
 #include "support/gettext.h"
 #include "support/lstrings.h"
+#include "support/Systemcall.h"
 #include "support/TempFile.h"
 #include "support/textutils.h"
 
@@ -1136,13 +1138,51 @@ namespace {
 		ofstream os(cas_tmpfile.toFilesystemEncoding().c_str());
 		os << data << endl;
 		os.close();
-		string command =  cmd + " < "
-			+ quoteName(cas_tmpfile.toFilesystemEncoding());
+
+		// startscript() reports an exit code, so collect the output of
+		// the CAS in a file as well.
+		unique_ptr<TempFile> outfile(new TempFile("casoutput"));
+		outfile->setAutoRemove(false);
+		FileName const cas_outfile = outfile->name();
+		outfile.reset();
+
+		if (cas_outfile.empty()) {
+			lyxerr << "Warning: cannot create temporary file."
+			       << endl;
+			cas_tmpfile.removeFile();
+			return string();
+		}
+
+		string redirect =
+			" < " + quoteName(cas_tmpfile.toFilesystemEncoding())
+			+ " > " + quoteName(cas_outfile.toFilesystemEncoding());
 		lyxerr << "calling: " << cmd
 		       << "\ninput: '" << data << "'" << endl;
-		cmd_ret const ret = runCommand(command);
+		Systemcall one;
+#if defined(_WIN32)
+		// CreateProcess() can't run .bat, so for windows we need to
+		// stay in realm of shell. Only the command goes to the interpreter.
+		string command = cmd;
+		// Keep redirection out of interpreter.
+		if (suffixIs(command, "2>&1")) {
+			command = rtrim(command.substr(0, command.size() - 4));
+			redirect += " 2>&1";
+		}
+		one.startscript(Systemcall::Wait,
+				quoteName(getEnv("COMSPEC")) + " /d /c "
+				+ quoteName(command) + redirect);
+#else
+		one.startscript(Systemcall::Wait, cmd + redirect);
+#endif
+
+		// Read what the CAS produced whatever its exit code was: a
+		// failing run still prints the diagnostics the callers parse
+		// (octave is invoked with 2>&1 for exactly that reason).
+		string const result = to_utf8(cas_outfile.fileContents("UTF-8"));
+
 		cas_tmpfile.removeFile();
-		return ret.result;
+		cas_outfile.removeFile();
+		return result;
 	}
 
 	size_t get_matching_brace(string const & str, size_t i)
-- 
2.39.5

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

Reply via email to