Skip to content

Commit abc6869

Browse files
committed
fix(utils/process): ensure runBashOrBatch runs in background on Windows and fix quoting
Add Windows-specific quoting helper `quoteWindowsArg()` and use Windows-aware quoting for venv, script and output paths to avoid cmd.exe quoting issues. Write batch runner files with CRLF endings, avoid blocking exec by invoking `start "" /B` via `popen`/`pclose`, and ensure Unix background invocation uses proper redirection. These changes prevent `runBashOrBatch` from hanging on Windows and improve path/argument escaping.
1 parent 2a8585b commit abc6869

File tree

1 file changed

+37
-16
lines changed

1 file changed

+37
-16
lines changed

src/utils/process/runBashOrBatch.php

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ function createBatchOrBashRunner($filename, $command)
3030
return $runnerPath;
3131
}
3232

33+
/**
34+
* Quote an argument for Windows cmd.exe (double-quote and double internal quotes).
35+
*/
36+
function quoteWindowsArg(string $str): string
37+
{
38+
// convert forward slashes to backslashes for Windows paths
39+
$s = str_replace('/', '\\', $str);
40+
// double any internal double-quotes and wrap in double-quotes
41+
return '"' . str_replace('"', '""', $s) . '"';
42+
}
43+
3344
/**
3445
* Executes a Bash or Batch script asynchronously with optional arguments.
3546
*
@@ -67,7 +78,11 @@ function runBashOrBatch($scriptPath, $commandArgs = [], $identifier = null, $red
6778
// Convert arguments to command line string
6879
$commandArgsString = '';
6980
foreach ($commandArgs as $key => $value) {
70-
$escapedValue = escapeshellarg($value);
81+
if ($isWin) {
82+
$escapedValue = quoteWindowsArg((string)$value);
83+
} else {
84+
$escapedValue = escapeshellarg((string)$value);
85+
}
7186
$commandArgsString .= "--$key=$escapedValue ";
7287
}
7388
$commandArgsString = trim($commandArgsString);
@@ -95,30 +110,30 @@ function runBashOrBatch($scriptPath, $commandArgs = [], $identifier = null, $red
95110
$cmdParts = [];
96111
if ($venv) {
97112
// Escape the venv path so it's safe for shells
98-
$cmdParts[] = $isWin ? 'call ' . escapeshellarg($venv) : 'source ' . escapeshellarg($venv);
113+
$cmdParts[] = $isWin ? 'call ' . quoteWindowsArg($venv) : 'source ' . escapeshellarg($venv);
99114
}
100115

101116
// Ensure output file is escaped
102-
$escapedOutput = escapeshellarg($output_file);
117+
$escapedOutput = $isWin ? quoteWindowsArg($output_file) : escapeshellarg($output_file);
103118

104119
// Decide how to invoke the target runner script.
105120
// Always treat the provided $scriptPath as a runner script (bat/sh).
106121
// On Windows we use `call` to run the .bat; on Unix we use `bash`.
107122
if ($isWin) {
108-
$invoke = 'call ' . escapeshellarg($scriptPath);
123+
$invoke = 'call ' . quoteWindowsArg($scriptPath);
109124
} else {
110125
$invoke = 'bash ' . escapeshellarg($scriptPath);
111126
}
112127

113128
if ($redirectOutput) {
114129
$cmdParts[] = $invoke . ' > ' . $escapedOutput . ' 2>&1';
115130
} else {
116-
if ($isWin) {
117-
$redirect_cmd = ' > NUL 2>&1';
118-
} else {
119-
$redirect_cmd = ' > /dev/null 2>&1 &';
120-
}
121-
$cmdParts[] = $invoke . $redirect_cmd;
131+
// if ($isWin) {
132+
// $redirect_cmd = ' > NUL 2>&1';
133+
// } else {
134+
// $redirect_cmd = ' > /dev/null 2>&1 &';
135+
// }
136+
$cmdParts[] = $invoke; // . $redirect_cmd;
122137
}
123138

124139
$cmd = trim(implode(' && ', $cmdParts));
@@ -127,19 +142,25 @@ function runBashOrBatch($scriptPath, $commandArgs = [], $identifier = null, $red
127142
truncateFile($output_file);
128143
truncateFile($runner_file);
129144

130-
// Write command to runner file
131-
write_file($runner_file, $cmd);
145+
// Write command to runner file. On Windows prefer CRLF endings for batch files.
146+
if ($isWin) {
147+
$cmdToWrite = preg_replace("/\r?\n/", "\r\n", $cmd);
148+
} else {
149+
$cmdToWrite = $cmd;
150+
}
151+
write_file($runner_file, $cmdToWrite);
132152

133153
// Change current working directory
134154
chdir($cwd);
135155

136156
// Execute the runner script in background; runner already redirects output
137157
if ($isWin) {
138-
$window_name = 'window_name';
139-
$startCmd = 'start /B "' . $window_name . '" ' . escapeshellarg(unixPath($runner_file));
140-
exec($startCmd);
158+
// Use empty title ("") so start doesn't treat the first quoted string as the title.
159+
$startCmd = 'start "" /B ' . quoteWindowsArg(unixPath($runner_file));
160+
// Use pclose popen to avoid waiting on exec on some PHP builds.
161+
pclose(popen($startCmd, 'r'));
141162
} else {
142-
exec('bash ' . escapeshellarg($runner_file));
163+
exec('bash ' . escapeshellarg($runner_file) . ' > /dev/null 2>&1 &');
143164
}
144165

145166
return [

0 commit comments

Comments
 (0)