Updates to vendors etc

This commit is contained in:
Chris Hunt
2025-07-11 15:57:48 +01:00
parent d972cbcd0a
commit 8fb6438254
8043 changed files with 248005 additions and 189479 deletions

View File

@@ -15,6 +15,7 @@ use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Exception\ProcessStartFailedException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Pipes\UnixPipes;
@@ -76,19 +77,21 @@ class Process implements \IteratorAggregate
private bool $tty = false;
private bool $pty;
private array $options = ['suppress_errors' => true, 'bypass_shell' => true];
private array $ignoredSignals = [];
private WindowsPipes|UnixPipes $processPipes;
private ?int $latestSignal = null;
private static ?bool $sigchild = null;
private static array $executables = [];
/**
* Exit codes translation table.
*
* User-defined errors must use exit codes in the 64-113 range.
*/
public static $exitCodes = [
public static array $exitCodes = [
0 => 'OK',
1 => 'General error',
2 => 'Misuse of shell builtins',
@@ -199,10 +202,7 @@ class Process implements \IteratorAggregate
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
/**
* @return void
*/
public function __wakeup()
public function __wakeup(): void
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
@@ -236,11 +236,11 @@ class Process implements \IteratorAggregate
*
* @return int The exit status code
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws ProcessTimedOutException When process timed out
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException In case a callback is provided and output has been disabled
* @throws ProcessStartFailedException When process can't be launched
* @throws RuntimeException When process is already running
* @throws ProcessTimedOutException When process timed out
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException In case a callback is provided and output has been disabled
*
* @final
*/
@@ -287,13 +287,11 @@ class Process implements \IteratorAggregate
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return void
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws LogicException In case a callback is provided and output has been disabled
* @throws ProcessStartFailedException When process can't be launched
* @throws RuntimeException When process is already running
* @throws LogicException In case a callback is provided and output has been disabled
*/
public function start(?callable $callback = null, array $env = [])
public function start(?callable $callback = null, array $env = []): void
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running.');
@@ -311,12 +309,7 @@ class Process implements \IteratorAggregate
$env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv();
if (\is_array($commandline = $this->commandline)) {
$commandline = implode(' ', array_map($this->escapeArgument(...), $commandline));
if ('\\' !== \DIRECTORY_SEPARATOR) {
// exec is mandatory to deal with sending a signal to the process
$commandline = 'exec '.$commandline;
}
$commandline = array_values(array_map(strval(...), $commandline));
} else {
$commandline = $this->replacePlaceholders($commandline, $env);
}
@@ -327,6 +320,11 @@ class Process implements \IteratorAggregate
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
$descriptors[3] = ['pipe', 'w'];
if (\is_array($commandline)) {
// exec is mandatory to deal with sending a signal to the process
$commandline = 'exec '.$this->buildShellCommandline($commandline);
}
// See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
$commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
$commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code';
@@ -340,13 +338,42 @@ class Process implements \IteratorAggregate
}
if (!is_dir($this->cwd)) {
throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
throw new RuntimeException(\sprintf('The provided cwd "%s" does not exist.', $this->cwd));
}
$process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
$lastError = null;
set_error_handler(function ($type, $msg) use (&$lastError) {
$lastError = $msg;
if (!\is_resource($process)) {
throw new RuntimeException('Unable to launch a new process.');
return true;
});
$oldMask = [];
if ($this->ignoredSignals && \function_exists('pcntl_sigprocmask')) {
// we block signals we want to ignore, as proc_open will use fork / posix_spawn which will copy the signal mask this allow to block
// signals in the child process
pcntl_sigprocmask(\SIG_BLOCK, $this->ignoredSignals, $oldMask);
}
try {
$process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
// Ensure array vs string commands behave the same
if (!$process && \is_array($commandline)) {
$process = @proc_open('exec '.$this->buildShellCommandline($commandline), $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
}
} finally {
if ($this->ignoredSignals && \function_exists('pcntl_sigprocmask')) {
// we restore the signal mask here to avoid any side effects
pcntl_sigprocmask(\SIG_SETMASK, $oldMask);
}
restore_error_handler();
}
if (!$process) {
throw new ProcessStartFailedException($this, $lastError);
}
$this->process = $process;
$this->status = self::STATUS_STARTED;
@@ -371,8 +398,8 @@ class Process implements \IteratorAggregate
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws ProcessStartFailedException When process can't be launched
* @throws RuntimeException When process is already running
*
* @see start()
*
@@ -948,7 +975,7 @@ class Process implements \IteratorAggregate
*/
public function getCommandLine(): string
{
return \is_array($this->commandline) ? implode(' ', array_map($this->escapeArgument(...), $this->commandline)) : $this->commandline;
return $this->buildShellCommandline($this->commandline);
}
/**
@@ -1140,11 +1167,9 @@ class Process implements \IteratorAggregate
* In case you run a background process (with the start method), you should
* trigger this method regularly to ensure the process timeout
*
* @return void
*
* @throws ProcessTimedOutException In case the timeout was reached
*/
public function checkTimeout()
public function checkTimeout(): void
{
if (self::STATUS_STARTED !== $this->status) {
return;
@@ -1182,10 +1207,8 @@ class Process implements \IteratorAggregate
*
* Enabling the "create_new_console" option allows a subprocess to continue
* to run after the main process exited, on both Windows and *nix
*
* @return void
*/
public function setOptions(array $options)
public function setOptions(array $options): void
{
if ($this->isRunning()) {
throw new RuntimeException('Setting options while the process is running is not possible.');
@@ -1197,12 +1220,26 @@ class Process implements \IteratorAggregate
foreach ($options as $key => $value) {
if (!\in_array($key, $existingOptions)) {
$this->options = $defaultOptions;
throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
throw new LogicException(\sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
}
$this->options[$key] = $value;
}
}
/**
* Defines a list of posix signals that will not be propagated to the process.
*
* @param list<\SIG*> $signals
*/
public function setIgnoredSignals(array $signals): void
{
if ($this->isRunning()) {
throw new RuntimeException('Setting ignored signals while the process is running is not possible.');
}
$this->ignoredSignals = $signals;
}
/**
* Returns whether TTY is supported on the current operating system.
*/
@@ -1210,7 +1247,7 @@ class Process implements \IteratorAggregate
{
static $isTtySupported;
return $isTtySupported ??= ('/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT));
return $isTtySupported ??= ('/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT) && @is_writable('/dev/tty'));
}
/**
@@ -1279,16 +1316,16 @@ class Process implements \IteratorAggregate
* Updates the status of the process, reads pipes.
*
* @param bool $blocking Whether to use a blocking read call
*
* @return void
*/
protected function updateStatus(bool $blocking)
protected function updateStatus(bool $blocking): void
{
if (self::STATUS_STARTED !== $this->status) {
return;
}
$this->processInformation = proc_get_status($this->process);
if ($this->processInformation['running'] ?? true) {
$this->processInformation = proc_get_status($this->process);
}
$running = $this->processInformation['running'];
$this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
@@ -1386,8 +1423,9 @@ class Process implements \IteratorAggregate
private function close(): int
{
$this->processPipes->close();
if (\is_resource($this->process)) {
if ($this->process) {
proc_close($this->process);
$this->process = null;
}
$this->exitcode = $this->processInformation['exitcode'];
$this->status = self::STATUS_TERMINATED;
@@ -1441,6 +1479,11 @@ class Process implements \IteratorAggregate
*/
private function doSignal(int $signal, bool $throwException): bool
{
// Signal seems to be send when sigchild is enable, this allow blocking the signal correctly in this case
if ($this->isSigchildEnabled() && \in_array($signal, $this->ignoredSignals)) {
return false;
}
if (null === $pid = $this->getPid()) {
if ($throwException) {
throw new LogicException('Cannot send signal on a non running process.');
@@ -1450,10 +1493,10 @@ class Process implements \IteratorAggregate
}
if ('\\' === \DIRECTORY_SEPARATOR) {
exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
exec(\sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
if ($exitCode && $this->isRunning()) {
if ($throwException) {
throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
throw new RuntimeException(\sprintf('Unable to kill the process (%s).', implode(' ', $output)));
}
return false;
@@ -1463,12 +1506,12 @@ class Process implements \IteratorAggregate
$ok = @proc_terminate($this->process, $signal);
} elseif (\function_exists('posix_kill')) {
$ok = @posix_kill($pid, $signal);
} elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
} elseif ($ok = proc_open(\sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
$ok = false === fgets($pipes[2]);
}
if (!$ok) {
if ($throwException) {
throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
throw new RuntimeException(\sprintf('Error while sending signal "%s".', $signal));
}
return false;
@@ -1483,9 +1526,25 @@ class Process implements \IteratorAggregate
return true;
}
private function prepareWindowsCommandLine(string $cmd, array &$env): string
private function buildShellCommandline(string|array $commandline): string
{
$uid = uniqid('', true);
if (\is_string($commandline)) {
return $commandline;
}
if ('\\' === \DIRECTORY_SEPARATOR && isset($commandline[0][0]) && \strlen($commandline[0]) === strcspn($commandline[0], ':/\\')) {
// On Windows, we don't rely on the OS to find the executable if possible to avoid lookups
// in the current directory which could be untrusted. Instead we use the ExecutableFinder.
$commandline[0] = (self::$executables[$commandline[0]] ??= (new ExecutableFinder())->find($commandline[0])) ?? $commandline[0];
}
return implode(' ', array_map($this->escapeArgument(...), $commandline));
}
private function prepareWindowsCommandLine(string|array $cmd, array &$env): string
{
$cmd = $this->buildShellCommandline($cmd);
$uid = bin2hex(random_bytes(4));
$cmd = preg_replace_callback(
'/"(?:(
[^"%!^]*+
@@ -1521,7 +1580,14 @@ class Process implements \IteratorAggregate
$cmd
);
$cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
static $comSpec;
if (!$comSpec && $comSpec = (new ExecutableFinder())->find('cmd.exe')) {
// Escape according to CommandLineToArgvW rules
$comSpec = '"'.preg_replace('{(\\\\*+)"}', '$1$1\"', $comSpec).'"';
}
$cmd = ($comSpec ?? 'cmd').' /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
foreach ($this->processPipes->getFiles() as $offset => $filename) {
$cmd .= ' '.$offset.'>"'.$filename.'"';
}
@@ -1537,7 +1603,7 @@ class Process implements \IteratorAggregate
private function requireProcessIsStarted(string $functionName): void
{
if (!$this->isStarted()) {
throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName));
throw new LogicException(\sprintf('Process must be started before calling "%s()".', $functionName));
}
}
@@ -1549,7 +1615,7 @@ class Process implements \IteratorAggregate
private function requireProcessIsTerminated(string $functionName): void
{
if (!$this->isTerminated()) {
throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName));
throw new LogicException(\sprintf('Process must be terminated before calling "%s()".', $functionName));
}
}
@@ -1567,7 +1633,7 @@ class Process implements \IteratorAggregate
if (str_contains($argument, "\0")) {
$argument = str_replace("\0", '?', $argument);
}
if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
if (!preg_match('/[()%!^"<>&|\s]/', $argument)) {
return $argument;
}
$argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
@@ -1579,7 +1645,7 @@ class Process implements \IteratorAggregate
{
return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline);
throw new InvalidArgumentException(\sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline);
}
return $this->escapeArgument($env[$matches[1]]);