update patch 1
This commit is contained in:
@@ -20,15 +20,14 @@ use Symfony\Component\Process\Process;
|
||||
*/
|
||||
class ProcessFailedException extends RuntimeException
|
||||
{
|
||||
private Process $process;
|
||||
|
||||
public function __construct(Process $process)
|
||||
{
|
||||
public function __construct(
|
||||
private Process $process,
|
||||
) {
|
||||
if ($process->isSuccessful()) {
|
||||
throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
|
||||
}
|
||||
|
||||
$error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
|
||||
$error = \sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
|
||||
$process->getCommandLine(),
|
||||
$process->getExitCode(),
|
||||
$process->getExitCodeText(),
|
||||
@@ -36,7 +35,7 @@ class ProcessFailedException extends RuntimeException
|
||||
);
|
||||
|
||||
if (!$process->isOutputDisabled()) {
|
||||
$error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
|
||||
$error .= \sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
|
||||
$process->getOutput(),
|
||||
$process->getErrorOutput()
|
||||
);
|
||||
|
||||
@@ -20,13 +20,10 @@ use Symfony\Component\Process\Process;
|
||||
*/
|
||||
final class ProcessSignaledException extends RuntimeException
|
||||
{
|
||||
private Process $process;
|
||||
|
||||
public function __construct(Process $process)
|
||||
{
|
||||
$this->process = $process;
|
||||
|
||||
parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
|
||||
public function __construct(
|
||||
private Process $process,
|
||||
) {
|
||||
parent::__construct(\sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
|
||||
}
|
||||
|
||||
public function getProcess(): Process
|
||||
|
||||
@@ -18,15 +18,15 @@ use Symfony\Component\Process\Process;
|
||||
*/
|
||||
class ProcessStartFailedException extends ProcessFailedException
|
||||
{
|
||||
private Process $process;
|
||||
|
||||
public function __construct(Process $process, ?string $message)
|
||||
{
|
||||
public function __construct(
|
||||
private Process $process,
|
||||
?string $message,
|
||||
) {
|
||||
if ($process->isStarted()) {
|
||||
throw new InvalidArgumentException('Expected a process that failed during startup, but the given process was started successfully.');
|
||||
}
|
||||
|
||||
$error = sprintf('The command "%s" failed.'."\n\nWorking directory: %s\n\nError: %s",
|
||||
$error = \sprintf('The command "%s" failed.'."\n\nWorking directory: %s\n\nError: %s",
|
||||
$process->getCommandLine(),
|
||||
$process->getWorkingDirectory(),
|
||||
$message ?? 'unknown'
|
||||
@@ -34,8 +34,6 @@ class ProcessStartFailedException extends ProcessFailedException
|
||||
|
||||
// Skip parent constructor
|
||||
RuntimeException::__construct($error);
|
||||
|
||||
$this->process = $process;
|
||||
}
|
||||
|
||||
public function getProcess(): Process
|
||||
|
||||
@@ -23,15 +23,11 @@ class ProcessTimedOutException extends RuntimeException
|
||||
public const TYPE_GENERAL = 1;
|
||||
public const TYPE_IDLE = 2;
|
||||
|
||||
private Process $process;
|
||||
private int $timeoutType;
|
||||
|
||||
public function __construct(Process $process, int $timeoutType)
|
||||
{
|
||||
$this->process = $process;
|
||||
$this->timeoutType = $timeoutType;
|
||||
|
||||
parent::__construct(sprintf(
|
||||
public function __construct(
|
||||
private Process $process,
|
||||
private int $timeoutType,
|
||||
) {
|
||||
parent::__construct(\sprintf(
|
||||
'The process "%s" exceeded the timeout of %s seconds.',
|
||||
$process->getCommandLine(),
|
||||
$this->getExceededTimeout()
|
||||
@@ -58,7 +54,7 @@ class ProcessTimedOutException extends RuntimeException
|
||||
return match ($this->timeoutType) {
|
||||
self::TYPE_GENERAL => $this->process->getTimeout(),
|
||||
self::TYPE_IDLE => $this->process->getIdleTimeout(),
|
||||
default => throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)),
|
||||
default => throw new \LogicException(\sprintf('Unknown timeout type "%d".', $this->timeoutType)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -38,7 +38,10 @@ class ExecutableFinder
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new possible suffix to check for executable.
|
||||
* Adds new possible suffix to check for executable, including the dot (.).
|
||||
*
|
||||
* $finder = new ExecutableFinder();
|
||||
* $finder->addSuffix('.foo');
|
||||
*/
|
||||
public function addSuffix(string $suffix): void
|
||||
{
|
||||
@@ -64,10 +67,9 @@ class ExecutableFinder
|
||||
$extraDirs
|
||||
);
|
||||
|
||||
$suffixes = [];
|
||||
$suffixes = $this->suffixes;
|
||||
if ('\\' === \DIRECTORY_SEPARATOR) {
|
||||
$pathExt = getenv('PATHEXT');
|
||||
$suffixes = $this->suffixes;
|
||||
$suffixes = array_merge($suffixes, $pathExt ? explode(\PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com']);
|
||||
}
|
||||
$suffixes = '' !== pathinfo($name, PATHINFO_EXTENSION) ? array_merge([''], $suffixes) : array_merge($suffixes, ['']);
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ class InputStream implements \IteratorAggregate
|
||||
return;
|
||||
}
|
||||
if ($this->isClosed()) {
|
||||
throw new RuntimeException(sprintf('"%s" is closed.', static::class));
|
||||
throw new RuntimeException(\sprintf('"%s" is closed.', static::class));
|
||||
}
|
||||
$this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@ class PhpExecutableFinder
|
||||
$dirs[] = 'C:\xampp\php\\';
|
||||
}
|
||||
|
||||
if ($herdPath = getenv('HERD_HOME')) {
|
||||
$dirs[] = $herdPath.\DIRECTORY_SEPARATOR.'bin';
|
||||
}
|
||||
|
||||
return $this->executableFinder->find('php', false, $dirs);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ class PhpProcess extends Process
|
||||
|
||||
public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static
|
||||
{
|
||||
throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
|
||||
throw new LogicException(\sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
|
||||
}
|
||||
|
||||
public function start(?callable $callback = null, array $env = []): void
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ class PhpSubprocess extends Process
|
||||
|
||||
public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static
|
||||
{
|
||||
throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
|
||||
throw new LogicException(\sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
|
||||
}
|
||||
|
||||
public function start(?callable $callback = null, array $env = []): void
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ abstract class AbstractPipes implements PipesInterface
|
||||
} elseif (!isset($this->inputBuffer[0])) {
|
||||
if (!\is_string($input)) {
|
||||
if (!\is_scalar($input)) {
|
||||
throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input)));
|
||||
throw new InvalidArgumentException(\sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input)));
|
||||
}
|
||||
$input = (string) $input;
|
||||
}
|
||||
|
||||
+6
-10
@@ -22,16 +22,12 @@ use Symfony\Component\Process\Process;
|
||||
*/
|
||||
class UnixPipes extends AbstractPipes
|
||||
{
|
||||
private ?bool $ttyMode;
|
||||
private bool $ptyMode;
|
||||
private bool $haveReadSupport;
|
||||
|
||||
public function __construct(?bool $ttyMode, bool $ptyMode, mixed $input, bool $haveReadSupport)
|
||||
{
|
||||
$this->ttyMode = $ttyMode;
|
||||
$this->ptyMode = $ptyMode;
|
||||
$this->haveReadSupport = $haveReadSupport;
|
||||
|
||||
public function __construct(
|
||||
private ?bool $ttyMode,
|
||||
private bool $ptyMode,
|
||||
mixed $input,
|
||||
private bool $haveReadSupport,
|
||||
) {
|
||||
parent::__construct($input);
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -33,12 +33,11 @@ class WindowsPipes extends AbstractPipes
|
||||
Process::STDOUT => 0,
|
||||
Process::STDERR => 0,
|
||||
];
|
||||
private bool $haveReadSupport;
|
||||
|
||||
public function __construct(mixed $input, bool $haveReadSupport)
|
||||
{
|
||||
$this->haveReadSupport = $haveReadSupport;
|
||||
|
||||
public function __construct(
|
||||
mixed $input,
|
||||
private bool $haveReadSupport,
|
||||
) {
|
||||
if ($this->haveReadSupport) {
|
||||
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
|
||||
// Workaround for this problem is to use temporary files instead of pipes on Windows platform.
|
||||
@@ -53,7 +52,7 @@ class WindowsPipes extends AbstractPipes
|
||||
set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
|
||||
for ($i = 0;; ++$i) {
|
||||
foreach ($pipes as $pipe => $name) {
|
||||
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
|
||||
$file = \sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
|
||||
|
||||
if (!$h = fopen($file.'.lock', 'w')) {
|
||||
if (file_exists($file.'.lock')) {
|
||||
|
||||
Vendored
+17
-10
@@ -85,6 +85,7 @@ class Process implements \IteratorAggregate
|
||||
private ?int $cachedExitCode = null;
|
||||
|
||||
private static ?bool $sigchild = null;
|
||||
private static array $executables = [];
|
||||
|
||||
/**
|
||||
* Exit codes translation table.
|
||||
@@ -338,7 +339,7 @@ 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));
|
||||
}
|
||||
|
||||
$lastError = null;
|
||||
@@ -1220,7 +1221,7 @@ 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;
|
||||
}
|
||||
@@ -1504,10 +1505,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;
|
||||
@@ -1517,12 +1518,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;
|
||||
@@ -1543,13 +1544,19 @@ class Process implements \IteratorAggregate
|
||||
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 = uniqid('', true);
|
||||
$uid = bin2hex(random_bytes(4));
|
||||
$cmd = preg_replace_callback(
|
||||
'/"(?:(
|
||||
[^"%!^]*+
|
||||
@@ -1608,7 +1615,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1620,7 +1627,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1650,7 +1657,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]]);
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ class ProcessUtils
|
||||
return new \IteratorIterator($input);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller));
|
||||
throw new InvalidArgumentException(\sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller));
|
||||
}
|
||||
|
||||
return $input;
|
||||
|
||||
Reference in New Issue
Block a user