summaryrefslogtreecommitdiffstats
path: root/vendor/react/child-process
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/react/child-process')
-rw-r--r--vendor/react/child-process/LICENSE21
-rw-r--r--vendor/react/child-process/composer.json45
-rw-r--r--vendor/react/child-process/src/Process.php567
3 files changed, 633 insertions, 0 deletions
diff --git a/vendor/react/child-process/LICENSE b/vendor/react/child-process/LICENSE
new file mode 100644
index 0000000..d6f8901
--- /dev/null
+++ b/vendor/react/child-process/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/child-process/composer.json b/vendor/react/child-process/composer.json
new file mode 100644
index 0000000..32aa713
--- /dev/null
+++ b/vendor/react/child-process/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "react/child-process",
+ "description": "Event-driven library for executing child processes with ReactPHP.",
+ "keywords": ["process", "event-driven", "ReactPHP"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "homepage": "https://clue.engineering/",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "homepage": "https://wyrihaximus.net/",
+ "email": "reactphp@ceesjankiewiet.nl"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "homepage": "https://sorgalla.com/",
+ "email": "jsorgalla@gmail.com"
+ },
+ {
+ "name": "Chris Boden",
+ "homepage": "https://cboden.dev/",
+ "email": "cboden@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "react/event-loop": "^1.2",
+ "react/stream": "^1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
+ "react/socket": "^1.8",
+ "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0"
+ },
+ "autoload": {
+ "psr-4": { "React\\ChildProcess\\": "src" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\ChildProcess\\": "tests" }
+ }
+}
diff --git a/vendor/react/child-process/src/Process.php b/vendor/react/child-process/src/Process.php
new file mode 100644
index 0000000..2a275e9
--- /dev/null
+++ b/vendor/react/child-process/src/Process.php
@@ -0,0 +1,567 @@
+<?php
+
+namespace React\ChildProcess;
+
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Stream\ReadableResourceStream;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\WritableResourceStream;
+use React\Stream\WritableStreamInterface;
+use React\Stream\DuplexResourceStream;
+use React\Stream\DuplexStreamInterface;
+
+/**
+ * Process component.
+ *
+ * This class borrows logic from Symfony's Process component for ensuring
+ * compatibility when PHP is compiled with the --enable-sigchild option.
+ *
+ * This class also implements the `EventEmitterInterface`
+ * which allows you to react to certain events:
+ *
+ * exit event:
+ * The `exit` event will be emitted whenever the process is no longer running.
+ * Event listeners will receive the exit code and termination signal as two
+ * arguments:
+ *
+ * ```php
+ * $process = new Process('sleep 10');
+ * $process->start();
+ *
+ * $process->on('exit', function ($code, $term) {
+ * if ($term === null) {
+ * echo 'exit with code ' . $code . PHP_EOL;
+ * } else {
+ * echo 'terminated with signal ' . $term . PHP_EOL;
+ * }
+ * });
+ * ```
+ *
+ * Note that `$code` is `null` if the process has terminated, but the exit
+ * code could not be determined (for example
+ * [sigchild compatibility](#sigchild-compatibility) was disabled).
+ * Similarly, `$term` is `null` unless the process has terminated in response to
+ * an uncaught signal sent to it.
+ * This is not a limitation of this project, but actual how exit codes and signals
+ * are exposed on POSIX systems, for more details see also
+ * [here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated).
+ *
+ * It's also worth noting that process termination depends on all file descriptors
+ * being closed beforehand.
+ * This means that all [process pipes](#stream-properties) will emit a `close`
+ * event before the `exit` event and that no more `data` events will arrive after
+ * the `exit` event.
+ * Accordingly, if either of these pipes is in a paused state (`pause()` method
+ * or internally due to a `pipe()` call), this detection may not trigger.
+ */
+class Process extends EventEmitter
+{
+ /**
+ * @var WritableStreamInterface|null|DuplexStreamInterface|ReadableStreamInterface
+ */
+ public $stdin;
+
+ /**
+ * @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface
+ */
+ public $stdout;
+
+ /**
+ * @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface
+ */
+ public $stderr;
+
+ /**
+ * Array with all process pipes (once started)
+ *
+ * Unless explicitly configured otherwise during construction, the following
+ * standard I/O pipes will be assigned by default:
+ * - 0: STDIN (`WritableStreamInterface`)
+ * - 1: STDOUT (`ReadableStreamInterface`)
+ * - 2: STDERR (`ReadableStreamInterface`)
+ *
+ * @var array<ReadableStreamInterface|WritableStreamInterface|DuplexStreamInterface>
+ */
+ public $pipes = array();
+
+ private $cmd;
+ private $cwd;
+ private $env;
+ private $fds;
+
+ private $enhanceSigchildCompatibility;
+ private $sigchildPipe;
+
+ private $process;
+ private $status;
+ private $exitCode;
+ private $fallbackExitCode;
+ private $stopSignal;
+ private $termSignal;
+
+ private static $sigchild;
+
+ /**
+ * Constructor.
+ *
+ * @param string $cmd Command line to run
+ * @param null|string $cwd Current working directory or null to inherit
+ * @param null|array $env Environment variables or null to inherit
+ * @param null|array $fds File descriptors to allocate for this process (or null = default STDIO streams)
+ * @throws \LogicException On windows or when proc_open() is not installed
+ */
+ public function __construct($cmd, $cwd = null, array $env = null, array $fds = null)
+ {
+ if (!\function_exists('proc_open')) {
+ throw new \LogicException('The Process class relies on proc_open(), which is not available on your PHP installation.');
+ }
+
+ $this->cmd = $cmd;
+ $this->cwd = $cwd;
+
+ if (null !== $env) {
+ $this->env = array();
+ foreach ($env as $key => $value) {
+ $this->env[(binary) $key] = (binary) $value;
+ }
+ }
+
+ if ($fds === null) {
+ $fds = array(
+ array('pipe', 'r'), // stdin
+ array('pipe', 'w'), // stdout
+ array('pipe', 'w'), // stderr
+ );
+ }
+
+ if (\DIRECTORY_SEPARATOR === '\\') {
+ foreach ($fds as $fd) {
+ if (isset($fd[0]) && $fd[0] === 'pipe') {
+ throw new \LogicException('Process pipes are not supported on Windows due to their blocking nature on Windows');
+ }
+ }
+ }
+
+ $this->fds = $fds;
+ $this->enhanceSigchildCompatibility = self::isSigchildEnabled();
+ }
+
+ /**
+ * Start the process.
+ *
+ * After the process is started, the standard I/O streams will be constructed
+ * and available via public properties.
+ *
+ * This method takes an optional `LoopInterface|null $loop` parameter that can be used to
+ * pass the event loop instance to use for this process. You can use a `null` value
+ * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
+ * This value SHOULD NOT be given unless you're sure you want to explicitly use a
+ * given event loop instance.
+ *
+ * @param ?LoopInterface $loop Loop interface for stream construction
+ * @param float $interval Interval to periodically monitor process state (seconds)
+ * @throws \RuntimeException If the process is already running or fails to start
+ */
+ public function start(LoopInterface $loop = null, $interval = 0.1)
+ {
+ if ($this->isRunning()) {
+ throw new \RuntimeException('Process is already running');
+ }
+
+ $loop = $loop ?: Loop::get();
+ $cmd = $this->cmd;
+ $fdSpec = $this->fds;
+ $sigchild = null;
+
+ // Read exit code through fourth pipe to work around --enable-sigchild
+ if ($this->enhanceSigchildCompatibility) {
+ $fdSpec[] = array('pipe', 'w');
+ \end($fdSpec);
+ $sigchild = \key($fdSpec);
+
+ // make sure this is fourth or higher (do not mess with STDIO)
+ if ($sigchild < 3) {
+ $fdSpec[3] = $fdSpec[$sigchild];
+ unset($fdSpec[$sigchild]);
+ $sigchild = 3;
+ }
+
+ $cmd = \sprintf('(%s) ' . $sigchild . '>/dev/null; code=$?; echo $code >&' . $sigchild . '; exit $code', $cmd);
+ }
+
+ // on Windows, we do not launch the given command line in a shell (cmd.exe) by default and omit any error dialogs
+ // the cmd.exe shell can explicitly be given as part of the command as detailed in both documentation and tests
+ $options = array();
+ if (\DIRECTORY_SEPARATOR === '\\') {
+ $options['bypass_shell'] = true;
+ $options['suppress_errors'] = true;
+ }
+
+ $this->process = @\proc_open($cmd, $fdSpec, $pipes, $this->cwd, $this->env, $options);
+
+ if (!\is_resource($this->process)) {
+ $error = \error_get_last();
+ throw new \RuntimeException('Unable to launch a new process: ' . $error['message']);
+ }
+
+ // count open process pipes and await close event for each to drain buffers before detecting exit
+ $that = $this;
+ $closeCount = 0;
+ $streamCloseHandler = function () use (&$closeCount, $loop, $interval, $that) {
+ $closeCount--;
+
+ if ($closeCount > 0) {
+ return;
+ }
+
+ // process already closed => report immediately
+ if (!$that->isRunning()) {
+ $that->close();
+ $that->emit('exit', array($that->getExitCode(), $that->getTermSignal()));
+ return;
+ }
+
+ // close not detected immediately => check regularly
+ $loop->addPeriodicTimer($interval, function ($timer) use ($that, $loop) {
+ if (!$that->isRunning()) {
+ $loop->cancelTimer($timer);
+ $that->close();
+ $that->emit('exit', array($that->getExitCode(), $that->getTermSignal()));
+ }
+ });
+ };
+
+ if ($sigchild !== null) {
+ $this->sigchildPipe = $pipes[$sigchild];
+ unset($pipes[$sigchild]);
+ }
+
+ foreach ($pipes as $n => $fd) {
+ // use open mode from stream meta data or fall back to pipe open mode for legacy HHVM
+ $meta = \stream_get_meta_data($fd);
+ $mode = $meta['mode'] === '' ? ($this->fds[$n][1] === 'r' ? 'w' : 'r') : $meta['mode'];
+
+ if ($mode === 'r+') {
+ $stream = new DuplexResourceStream($fd, $loop);
+ $stream->on('close', $streamCloseHandler);
+ $closeCount++;
+ } elseif ($mode === 'w') {
+ $stream = new WritableResourceStream($fd, $loop);
+ } else {
+ $stream = new ReadableResourceStream($fd, $loop);
+ $stream->on('close', $streamCloseHandler);
+ $closeCount++;
+ }
+ $this->pipes[$n] = $stream;
+ }
+
+ $this->stdin = isset($this->pipes[0]) ? $this->pipes[0] : null;
+ $this->stdout = isset($this->pipes[1]) ? $this->pipes[1] : null;
+ $this->stderr = isset($this->pipes[2]) ? $this->pipes[2] : null;
+
+ // immediately start checking for process exit when started without any I/O pipes
+ if (!$closeCount) {
+ $streamCloseHandler();
+ }
+ }
+
+ /**
+ * Close the process.
+ *
+ * This method should only be invoked via the periodic timer that monitors
+ * the process state.
+ */
+ public function close()
+ {
+ if ($this->process === null) {
+ return;
+ }
+
+ foreach ($this->pipes as $pipe) {
+ $pipe->close();
+ }
+
+ if ($this->enhanceSigchildCompatibility) {
+ $this->pollExitCodePipe();
+ $this->closeExitCodePipe();
+ }
+
+ $exitCode = \proc_close($this->process);
+ $this->process = null;
+
+ if ($this->exitCode === null && $exitCode !== -1) {
+ $this->exitCode = $exitCode;
+ }
+
+ if ($this->exitCode === null && $this->status['exitcode'] !== -1) {
+ $this->exitCode = $this->status['exitcode'];
+ }
+
+ if ($this->exitCode === null && $this->fallbackExitCode !== null) {
+ $this->exitCode = $this->fallbackExitCode;
+ $this->fallbackExitCode = null;
+ }
+ }
+
+ /**
+ * Terminate the process with an optional signal.
+ *
+ * @param int $signal Optional signal (default: SIGTERM)
+ * @return bool Whether the signal was sent successfully
+ */
+ public function terminate($signal = null)
+ {
+ if ($this->process === null) {
+ return false;
+ }
+
+ if ($signal !== null) {
+ return \proc_terminate($this->process, $signal);
+ }
+
+ return \proc_terminate($this->process);
+ }
+
+ /**
+ * Get the command string used to launch the process.
+ *
+ * @return string
+ */
+ public function getCommand()
+ {
+ return $this->cmd;
+ }
+
+ /**
+ * Get the exit code returned by the process.
+ *
+ * This value is only meaningful if isRunning() has returned false. Null
+ * will be returned if the process is still running.
+ *
+ * Null may also be returned if the process has terminated, but the exit
+ * code could not be determined (e.g. sigchild compatibility was disabled).
+ *
+ * @return int|null
+ */
+ public function getExitCode()
+ {
+ return $this->exitCode;
+ }
+
+ /**
+ * Get the process ID.
+ *
+ * @return int|null
+ */
+ public function getPid()
+ {
+ $status = $this->getCachedStatus();
+
+ return $status !== null ? $status['pid'] : null;
+ }
+
+ /**
+ * Get the signal that caused the process to stop its execution.
+ *
+ * This value is only meaningful if isStopped() has returned true. Null will
+ * be returned if the process was never stopped.
+ *
+ * @return int|null
+ */
+ public function getStopSignal()
+ {
+ return $this->stopSignal;
+ }
+
+ /**
+ * Get the signal that caused the process to terminate its execution.
+ *
+ * This value is only meaningful if isTerminated() has returned true. Null
+ * will be returned if the process was never terminated.
+ *
+ * @return int|null
+ */
+ public function getTermSignal()
+ {
+ return $this->termSignal;
+ }
+
+ /**
+ * Return whether the process is still running.
+ *
+ * @return bool
+ */
+ public function isRunning()
+ {
+ if ($this->process === null) {
+ return false;
+ }
+
+ $status = $this->getFreshStatus();
+
+ return $status !== null ? $status['running'] : false;
+ }
+
+ /**
+ * Return whether the process has been stopped by a signal.
+ *
+ * @return bool
+ */
+ public function isStopped()
+ {
+ $status = $this->getFreshStatus();
+
+ return $status !== null ? $status['stopped'] : false;
+ }
+
+ /**
+ * Return whether the process has been terminated by an uncaught signal.
+ *
+ * @return bool
+ */
+ public function isTerminated()
+ {
+ $status = $this->getFreshStatus();
+
+ return $status !== null ? $status['signaled'] : false;
+ }
+
+ /**
+ * Return whether PHP has been compiled with the '--enable-sigchild' option.
+ *
+ * @see \Symfony\Component\Process\Process::isSigchildEnabled()
+ * @return bool
+ */
+ public final static function isSigchildEnabled()
+ {
+ if (null !== self::$sigchild) {
+ return self::$sigchild;
+ }
+
+ if (!\function_exists('phpinfo')) {
+ return self::$sigchild = false; // @codeCoverageIgnore
+ }
+
+ \ob_start();
+ \phpinfo(INFO_GENERAL);
+
+ return self::$sigchild = false !== \strpos(\ob_get_clean(), '--enable-sigchild');
+ }
+
+ /**
+ * Enable or disable sigchild compatibility mode.
+ *
+ * Sigchild compatibility mode is required to get the exit code and
+ * determine the success of a process when PHP has been compiled with
+ * the --enable-sigchild option.
+ *
+ * @param bool $sigchild
+ * @return void
+ */
+ public final static function setSigchildEnabled($sigchild)
+ {
+ self::$sigchild = (bool) $sigchild;
+ }
+
+ /**
+ * Check the fourth pipe for an exit code.
+ *
+ * This should only be used if --enable-sigchild compatibility was enabled.
+ */
+ private function pollExitCodePipe()
+ {
+ if ($this->sigchildPipe === null) {
+ return;
+ }
+
+ $r = array($this->sigchildPipe);
+ $w = $e = null;
+
+ $n = @\stream_select($r, $w, $e, 0);
+
+ if (1 !== $n) {
+ return;
+ }
+
+ $data = \fread($r[0], 8192);
+
+ if (\strlen($data) > 0) {
+ $this->fallbackExitCode = (int) $data;
+ }
+ }
+
+ /**
+ * Close the fourth pipe used to relay an exit code.
+ *
+ * This should only be used if --enable-sigchild compatibility was enabled.
+ */
+ private function closeExitCodePipe()
+ {
+ if ($this->sigchildPipe === null) {
+ return;
+ }
+
+ \fclose($this->sigchildPipe);
+ $this->sigchildPipe = null;
+ }
+
+ /**
+ * Return the cached process status.
+ *
+ * @return array
+ */
+ private function getCachedStatus()
+ {
+ if ($this->status === null) {
+ $this->updateStatus();
+ }
+
+ return $this->status;
+ }
+
+ /**
+ * Return the updated process status.
+ *
+ * @return array
+ */
+ private function getFreshStatus()
+ {
+ $this->updateStatus();
+
+ return $this->status;
+ }
+
+ /**
+ * Update the process status, stop/term signals, and exit code.
+ *
+ * Stop/term signals are only updated if the process is currently stopped or
+ * signaled, respectively. Otherwise, signal values will remain as-is so the
+ * corresponding getter methods may be used at a later point in time.
+ */
+ private function updateStatus()
+ {
+ if ($this->process === null) {
+ return;
+ }
+
+ $this->status = \proc_get_status($this->process);
+
+ if ($this->status === false) {
+ throw new \UnexpectedValueException('proc_get_status() failed');
+ }
+
+ if ($this->status['stopped']) {
+ $this->stopSignal = $this->status['stopsig'];
+ }
+
+ if ($this->status['signaled']) {
+ $this->termSignal = $this->status['termsig'];
+ }
+
+ if (!$this->status['running'] && -1 !== $this->status['exitcode']) {
+ $this->exitCode = $this->status['exitcode'];
+ }
+ }
+}