Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Process.php

Zuletzt modifiziert: 02.04.2025, 15:02 - Dateigröße: 54.46 KiB


0001  <?php
0002   
0003  /*
0004   * This file is part of the Symfony package.
0005   *
0006   * (c) Fabien Potencier <fabien@symfony.com>
0007   *
0008   * For the full copyright and license information, please view the LICENSE
0009   * file that was distributed with this source code.
0010   */
0011   
0012  namespace Symfony\Component\Process;
0013   
0014  use Symfony\Component\Process\Exception\InvalidArgumentException;
0015  use Symfony\Component\Process\Exception\LogicException;
0016  use Symfony\Component\Process\Exception\ProcessFailedException;
0017  use Symfony\Component\Process\Exception\ProcessTimedOutException;
0018  use Symfony\Component\Process\Exception\RuntimeException;
0019  use Symfony\Component\Process\Pipes\PipesInterface;
0020  use Symfony\Component\Process\Pipes\UnixPipes;
0021  use Symfony\Component\Process\Pipes\WindowsPipes;
0022   
0023  /**
0024   * Process is a thin wrapper around proc_* functions to easily
0025   * start independent PHP processes.
0026   *
0027   * @author Fabien Potencier <fabien@symfony.com>
0028   * @author Romain Neutron <imprec@gmail.com>
0029   */
0030  class Process implements \IteratorAggregate
0031  {
0032      const ERR = 'err';
0033      const OUT = 'out';
0034   
0035      const STATUS_READY = 'ready';
0036      const STATUS_STARTED = 'started';
0037      const STATUS_TERMINATED = 'terminated';
0038   
0039      const STDIN = 0;
0040      const STDOUT = 1;
0041      const STDERR = 2;
0042   
0043      // Timeout Precision in seconds.
0044      const TIMEOUT_PRECISION = 0.2;
0045   
0046      const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
0047      const ITER_KEEP_OUTPUT = 2;  // By default, outputs are cleared while iterating, use this flag to keep them in memory
0048      const ITER_SKIP_OUT = 4;     // Use this flag to skip STDOUT while iterating
0049      const ITER_SKIP_ERR = 8;     // Use this flag to skip STDERR while iterating
0050   
0051      private $callback;
0052      private $hasCallback = false;
0053      private $commandline;
0054      private $cwd;
0055      private $env;
0056      private $input;
0057      private $starttime;
0058      private $lastOutputTime;
0059      private $timeout;
0060      private $idleTimeout;
0061      private $options = ['suppress_errors' => true];
0062      private $exitcode;
0063      private $fallbackStatus = [];
0064      private $processInformation;
0065      private $outputDisabled = false;
0066      private $stdout;
0067      private $stderr;
0068      private $enhanceWindowsCompatibility = true;
0069      private $enhanceSigchildCompatibility;
0070      private $process;
0071      private $status = self::STATUS_READY;
0072      private $incrementalOutputOffset = 0;
0073      private $incrementalErrorOutputOffset = 0;
0074      private $tty = false;
0075      private $pty;
0076      private $inheritEnv = false;
0077   
0078      private $useFileHandles = false;
0079      /** @var PipesInterface */
0080      private $processPipes;
0081   
0082      private $latestSignal;
0083   
0084      private static $sigchild;
0085   
0086      /**
0087       * Exit codes translation table.
0088       *
0089       * User-defined errors must use exit codes in the 64-113 range.
0090       */
0091      public static $exitCodes = [
0092          0 => 'OK',
0093          1 => 'General error',
0094          2 => 'Misuse of shell builtins',
0095   
0096          126 => 'Invoked command cannot execute',
0097          127 => 'Command not found',
0098          128 => 'Invalid exit argument',
0099   
0100          // signals
0101          129 => 'Hangup',
0102          130 => 'Interrupt',
0103          131 => 'Quit and dump core',
0104          132 => 'Illegal instruction',
0105          133 => 'Trace/breakpoint trap',
0106          134 => 'Process aborted',
0107          135 => 'Bus error: "access to undefined portion of memory object"',
0108          136 => 'Floating point exception: "erroneous arithmetic operation"',
0109          137 => 'Kill (terminate immediately)',
0110          138 => 'User-defined 1',
0111          139 => 'Segmentation violation',
0112          140 => 'User-defined 2',
0113          141 => 'Write to pipe with no one reading',
0114          142 => 'Signal raised by alarm',
0115          143 => 'Termination (request to terminate)',
0116          // 144 - not defined
0117          145 => 'Child process terminated, stopped (or continued*)',
0118          146 => 'Continue if stopped',
0119          147 => 'Stop executing temporarily',
0120          148 => 'Terminal stop signal',
0121          149 => 'Background process attempting to read from tty ("in")',
0122          150 => 'Background process attempting to write to tty ("out")',
0123          151 => 'Urgent data available on socket',
0124          152 => 'CPU time limit exceeded',
0125          153 => 'File size limit exceeded',
0126          154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
0127          155 => 'Profiling timer expired',
0128          // 156 - not defined
0129          157 => 'Pollable event',
0130          // 158 - not defined
0131          159 => 'Bad syscall',
0132      ];
0133   
0134      /**
0135       * @param string|array   $commandline The command line to run
0136       * @param string|null    $cwd         The working directory or null to use the working dir of the current PHP process
0137       * @param array|null     $env         The environment variables or null to use the same environment as the current PHP process
0138       * @param mixed|null     $input       The input as stream resource, scalar or \Traversable, or null for no input
0139       * @param int|float|null $timeout     The timeout in seconds or null to disable
0140       * @param array          $options     An array of options for proc_open
0141       *
0142       * @throws RuntimeException When proc_open is not installed
0143       */
0144      public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null)
0145      {
0146          if (!\function_exists('proc_open')) {
0147              throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
0148          }
0149   
0150          $this->commandline = $commandline;
0151          $this->cwd = $cwd;
0152   
0153          // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
0154          // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
0155          // @see : https://bugs.php.net/51800
0156          // @see : https://bugs.php.net/50524
0157          if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
0158              $this->cwd = getcwd();
0159          }
0160          if (null !== $env) {
0161              $this->setEnv($env);
0162          }
0163   
0164          $this->setInput($input);
0165          $this->setTimeout($timeout);
0166          $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
0167          $this->pty = false;
0168          $this->enhanceSigchildCompatibility = '\\' !== \DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
0169          if (null !== $options) {
0170              @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), \E_USER_DEPRECATED);
0171              $this->options = array_replace($this->options, $options);
0172          }
0173      }
0174   
0175      public function __destruct()
0176      {
0177          $this->stop(0);
0178      }
0179   
0180      public function __clone()
0181      {
0182          $this->resetProcessData();
0183      }
0184   
0185      /**
0186       * Runs the process.
0187       *
0188       * The callback receives the type of output (out or err) and
0189       * some bytes from the output in real-time. It allows to have feedback
0190       * from the independent process during execution.
0191       *
0192       * The STDOUT and STDERR are also available after the process is finished
0193       * via the getOutput() and getErrorOutput() methods.
0194       *
0195       * @param callable|null $callback A PHP callback to run whenever there is some
0196       *                                output available on STDOUT or STDERR
0197       *
0198       * @return int The exit status code
0199       *
0200       * @throws RuntimeException When process can't be launched
0201       * @throws RuntimeException When process stopped after receiving signal
0202       * @throws LogicException   In case a callback is provided and output has been disabled
0203       *
0204       * @final since version 3.3
0205       */
0206      public function run($callback = null/*, array $env = []*/)
0207      {
0208          $env = 1 < \func_num_args() ? func_get_arg(1) : null;
0209          $this->start($callback, $env);
0210   
0211          return $this->wait();
0212      }
0213   
0214      /**
0215       * Runs the process.
0216       *
0217       * This is identical to run() except that an exception is thrown if the process
0218       * exits with a non-zero exit code.
0219       *
0220       * @return $this
0221       *
0222       * @throws RuntimeException       if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled
0223       * @throws ProcessFailedException if the process didn't terminate successfully
0224       *
0225       * @final since version 3.3
0226       */
0227      public function mustRun(callable $callback = null/*, array $env = []*/)
0228      {
0229          if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
0230              throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
0231          }
0232          $env = 1 < \func_num_args() ? func_get_arg(1) : null;
0233   
0234          if (0 !== $this->run($callback, $env)) {
0235              throw new ProcessFailedException($this);
0236          }
0237   
0238          return $this;
0239      }
0240   
0241      /**
0242       * Starts the process and returns after writing the input to STDIN.
0243       *
0244       * This method blocks until all STDIN data is sent to the process then it
0245       * returns while the process runs in the background.
0246       *
0247       * The termination of the process can be awaited with wait().
0248       *
0249       * The callback receives the type of output (out or err) and some bytes from
0250       * the output in real-time while writing the standard input to the process.
0251       * It allows to have feedback from the independent process during execution.
0252       *
0253       * @param callable|null $callback A PHP callback to run whenever there is some
0254       *                                output available on STDOUT or STDERR
0255       *
0256       * @throws RuntimeException When process can't be launched
0257       * @throws RuntimeException When process is already running
0258       * @throws LogicException   In case a callback is provided and output has been disabled
0259       */
0260      public function start(callable $callback = null/*, array $env = [*/)
0261      {
0262          if ($this->isRunning()) {
0263              throw new RuntimeException('Process is already running.');
0264          }
0265          if (2 <= \func_num_args()) {
0266              $env = func_get_arg(1);
0267          } else {
0268              if (__CLASS__ !== static::class) {
0269                  $r = new \ReflectionMethod($this, __FUNCTION__);
0270                  if (__CLASS__ !== $r->getDeclaringClass()->getName() && (2 > $r->getNumberOfParameters() || 'env' !== $r->getParameters()[1]->name)) {
0271                      @trigger_error(sprintf('The %s::start() method expects a second "$env" argument since Symfony 3.3. It will be made mandatory in 4.0.', static::class), \E_USER_DEPRECATED);
0272                  }
0273              }
0274              $env = null;
0275          }
0276   
0277          $this->resetProcessData();
0278          $this->starttime = $this->lastOutputTime = microtime(true);
0279          $this->callback = $this->buildCallback($callback);
0280          $this->hasCallback = null !== $callback;
0281          $descriptors = $this->getDescriptors();
0282          $inheritEnv = $this->inheritEnv;
0283   
0284          if (\is_array($commandline = $this->commandline)) {
0285              $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
0286   
0287              if ('\\' !== \DIRECTORY_SEPARATOR) {
0288                  // exec is mandatory to deal with sending a signal to the process
0289                  $commandline = 'exec '.$commandline;
0290              }
0291          }
0292   
0293          if (null === $env) {
0294              $env = $this->env;
0295          } else {
0296              if ($this->env) {
0297                  $env += $this->env;
0298              }
0299              $inheritEnv = true;
0300          }
0301   
0302          if (null !== $env && $inheritEnv) {
0303              $env += $this->getDefaultEnv();
0304          } elseif (null !== $env) {
0305              @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', \E_USER_DEPRECATED);
0306          } else {
0307              $env = $this->getDefaultEnv();
0308          }
0309          if ('\\' === \DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
0310              $this->options['bypass_shell'] = true;
0311              $commandline = $this->prepareWindowsCommandLine($commandline, $env);
0312          } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
0313              // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
0314              $descriptors[3] = ['pipe', 'w'];
0315   
0316              // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
0317              $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
0318              $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
0319   
0320              // Workaround for the bug, when PTS functionality is enabled.
0321              // @see : https://bugs.php.net/69442
0322              $ptsWorkaround = fopen(__FILE__, 'r');
0323          }
0324          if (\defined('HHVM_VERSION')) {
0325              $envPairs = $env;
0326          } else {
0327              $envPairs = [];
0328              foreach ($env as $k => $v) {
0329                  if (false !== $v) {
0330                      $envPairs[] = $k.'='.$v;
0331                  }
0332              }
0333          }
0334   
0335          if (!is_dir($this->cwd)) {
0336              @trigger_error('The provided cwd does not exist. Command is currently ran against getcwd(). This behavior is deprecated since Symfony 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED);
0337          }
0338   
0339          $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
0340   
0341          if (!\is_resource($this->process)) {
0342              throw new RuntimeException('Unable to launch a new process.');
0343          }
0344          $this->status = self::STATUS_STARTED;
0345   
0346          if (isset($descriptors[3])) {
0347              $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
0348          }
0349   
0350          if ($this->tty) {
0351              return;
0352          }
0353   
0354          $this->updateStatus(false);
0355          $this->checkTimeout();
0356      }
0357   
0358      /**
0359       * Restarts the process.
0360       *
0361       * Be warned that the process is cloned before being started.
0362       *
0363       * @param callable|null $callback A PHP callback to run whenever there is some
0364       *                                output available on STDOUT or STDERR
0365       *
0366       * @return static
0367       *
0368       * @throws RuntimeException When process can't be launched
0369       * @throws RuntimeException When process is already running
0370       *
0371       * @see start()
0372       *
0373       * @final since version 3.3
0374       */
0375      public function restart(callable $callback = null/*, array $env = []*/)
0376      {
0377          if ($this->isRunning()) {
0378              throw new RuntimeException('Process is already running.');
0379          }
0380          $env = 1 < \func_num_args() ? func_get_arg(1) : null;
0381   
0382          $process = clone $this;
0383          $process->start($callback, $env);
0384   
0385          return $process;
0386      }
0387   
0388      /**
0389       * Waits for the process to terminate.
0390       *
0391       * The callback receives the type of output (out or err) and some bytes
0392       * from the output in real-time while writing the standard input to the process.
0393       * It allows to have feedback from the independent process during execution.
0394       *
0395       * @param callable|null $callback A valid PHP callback
0396       *
0397       * @return int The exitcode of the process
0398       *
0399       * @throws RuntimeException When process timed out
0400       * @throws RuntimeException When process stopped after receiving signal
0401       * @throws LogicException   When process is not yet started
0402       */
0403      public function wait(callable $callback = null)
0404      {
0405          $this->requireProcessIsStarted(__FUNCTION__);
0406   
0407          $this->updateStatus(false);
0408   
0409          if (null !== $callback) {
0410              if (!$this->processPipes->haveReadSupport()) {
0411                  $this->stop(0);
0412                  throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait.');
0413              }
0414              $this->callback = $this->buildCallback($callback);
0415          }
0416   
0417          do {
0418              $this->checkTimeout();
0419              $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
0420              $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
0421          } while ($running);
0422   
0423          while ($this->isRunning()) {
0424              $this->checkTimeout();
0425              usleep(1000);
0426          }
0427   
0428          if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
0429              throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
0430          }
0431   
0432          return $this->exitcode;
0433      }
0434   
0435      /**
0436       * Returns the Pid (process identifier), if applicable.
0437       *
0438       * @return int|null The process id if running, null otherwise
0439       */
0440      public function getPid()
0441      {
0442          return $this->isRunning() ? $this->processInformation['pid'] : null;
0443      }
0444   
0445      /**
0446       * Sends a POSIX signal to the process.
0447       *
0448       * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
0449       *
0450       * @return $this
0451       *
0452       * @throws LogicException   In case the process is not running
0453       * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
0454       * @throws RuntimeException In case of failure
0455       */
0456      public function signal($signal)
0457      {
0458          $this->doSignal($signal, true);
0459   
0460          return $this;
0461      }
0462   
0463      /**
0464       * Disables fetching output and error output from the underlying process.
0465       *
0466       * @return $this
0467       *
0468       * @throws RuntimeException In case the process is already running
0469       * @throws LogicException   if an idle timeout is set
0470       */
0471      public function disableOutput()
0472      {
0473          if ($this->isRunning()) {
0474              throw new RuntimeException('Disabling output while the process is running is not possible.');
0475          }
0476          if (null !== $this->idleTimeout) {
0477              throw new LogicException('Output can not be disabled while an idle timeout is set.');
0478          }
0479   
0480          $this->outputDisabled = true;
0481   
0482          return $this;
0483      }
0484   
0485      /**
0486       * Enables fetching output and error output from the underlying process.
0487       *
0488       * @return $this
0489       *
0490       * @throws RuntimeException In case the process is already running
0491       */
0492      public function enableOutput()
0493      {
0494          if ($this->isRunning()) {
0495              throw new RuntimeException('Enabling output while the process is running is not possible.');
0496          }
0497   
0498          $this->outputDisabled = false;
0499   
0500          return $this;
0501      }
0502   
0503      /**
0504       * Returns true in case the output is disabled, false otherwise.
0505       *
0506       * @return bool
0507       */
0508      public function isOutputDisabled()
0509      {
0510          return $this->outputDisabled;
0511      }
0512   
0513      /**
0514       * Returns the current output of the process (STDOUT).
0515       *
0516       * @return string The process output
0517       *
0518       * @throws LogicException in case the output has been disabled
0519       * @throws LogicException In case the process is not started
0520       */
0521      public function getOutput()
0522      {
0523          $this->readPipesForOutput(__FUNCTION__);
0524   
0525          if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
0526              return '';
0527          }
0528   
0529          return $ret;
0530      }
0531   
0532      /**
0533       * Returns the output incrementally.
0534       *
0535       * In comparison with the getOutput method which always return the whole
0536       * output, this one returns the new output since the last call.
0537       *
0538       * @return string The process output since the last call
0539       *
0540       * @throws LogicException in case the output has been disabled
0541       * @throws LogicException In case the process is not started
0542       */
0543      public function getIncrementalOutput()
0544      {
0545          $this->readPipesForOutput(__FUNCTION__);
0546   
0547          $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
0548          $this->incrementalOutputOffset = ftell($this->stdout);
0549   
0550          if (false === $latest) {
0551              return '';
0552          }
0553   
0554          return $latest;
0555      }
0556   
0557      /**
0558       * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
0559       *
0560       * @param int $flags A bit field of Process::ITER_* flags
0561       *
0562       * @throws LogicException in case the output has been disabled
0563       * @throws LogicException In case the process is not started
0564       *
0565       * @return \Generator
0566       */
0567      public function getIterator($flags = 0)
0568      {
0569          $this->readPipesForOutput(__FUNCTION__, false);
0570   
0571          $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
0572          $blocking = !(self::ITER_NON_BLOCKING & $flags);
0573          $yieldOut = !(self::ITER_SKIP_OUT & $flags);
0574          $yieldErr = !(self::ITER_SKIP_ERR & $flags);
0575   
0576          while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
0577              if ($yieldOut) {
0578                  $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
0579   
0580                  if (isset($out[0])) {
0581                      if ($clearOutput) {
0582                          $this->clearOutput();
0583                      } else {
0584                          $this->incrementalOutputOffset = ftell($this->stdout);
0585                      }
0586   
0587                      yield self::OUT => $out;
0588                  }
0589              }
0590   
0591              if ($yieldErr) {
0592                  $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
0593   
0594                  if (isset($err[0])) {
0595                      if ($clearOutput) {
0596                          $this->clearErrorOutput();
0597                      } else {
0598                          $this->incrementalErrorOutputOffset = ftell($this->stderr);
0599                      }
0600   
0601                      yield self::ERR => $err;
0602                  }
0603              }
0604   
0605              if (!$blocking && !isset($out[0]) && !isset($err[0])) {
0606                  yield self::OUT => '';
0607              }
0608   
0609              $this->checkTimeout();
0610              $this->readPipesForOutput(__FUNCTION__, $blocking);
0611          }
0612      }
0613   
0614      /**
0615       * Clears the process output.
0616       *
0617       * @return $this
0618       */
0619      public function clearOutput()
0620      {
0621          ftruncate($this->stdout, 0);
0622          fseek($this->stdout, 0);
0623          $this->incrementalOutputOffset = 0;
0624   
0625          return $this;
0626      }
0627   
0628      /**
0629       * Returns the current error output of the process (STDERR).
0630       *
0631       * @return string The process error output
0632       *
0633       * @throws LogicException in case the output has been disabled
0634       * @throws LogicException In case the process is not started
0635       */
0636      public function getErrorOutput()
0637      {
0638          $this->readPipesForOutput(__FUNCTION__);
0639   
0640          if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
0641              return '';
0642          }
0643   
0644          return $ret;
0645      }
0646   
0647      /**
0648       * Returns the errorOutput incrementally.
0649       *
0650       * In comparison with the getErrorOutput method which always return the
0651       * whole error output, this one returns the new error output since the last
0652       * call.
0653       *
0654       * @return string The process error output since the last call
0655       *
0656       * @throws LogicException in case the output has been disabled
0657       * @throws LogicException In case the process is not started
0658       */
0659      public function getIncrementalErrorOutput()
0660      {
0661          $this->readPipesForOutput(__FUNCTION__);
0662   
0663          $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
0664          $this->incrementalErrorOutputOffset = ftell($this->stderr);
0665   
0666          if (false === $latest) {
0667              return '';
0668          }
0669   
0670          return $latest;
0671      }
0672   
0673      /**
0674       * Clears the process output.
0675       *
0676       * @return $this
0677       */
0678      public function clearErrorOutput()
0679      {
0680          ftruncate($this->stderr, 0);
0681          fseek($this->stderr, 0);
0682          $this->incrementalErrorOutputOffset = 0;
0683   
0684          return $this;
0685      }
0686   
0687      /**
0688       * Returns the exit code returned by the process.
0689       *
0690       * @return int|null The exit status code, null if the Process is not terminated
0691       *
0692       * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
0693       */
0694      public function getExitCode()
0695      {
0696          if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
0697              throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
0698          }
0699   
0700          $this->updateStatus(false);
0701   
0702          return $this->exitcode;
0703      }
0704   
0705      /**
0706       * Returns a string representation for the exit code returned by the process.
0707       *
0708       * This method relies on the Unix exit code status standardization
0709       * and might not be relevant for other operating systems.
0710       *
0711       * @return string|null A string representation for the exit status code, null if the Process is not terminated
0712       *
0713       * @see http://tldp.org/LDP/abs/html/exitcodes.html
0714       * @see http://en.wikipedia.org/wiki/Unix_signal
0715       */
0716      public function getExitCodeText()
0717      {
0718          if (null === $exitcode = $this->getExitCode()) {
0719              return null;
0720          }
0721   
0722          return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
0723      }
0724   
0725      /**
0726       * Checks if the process ended successfully.
0727       *
0728       * @return bool true if the process ended successfully, false otherwise
0729       */
0730      public function isSuccessful()
0731      {
0732          return 0 === $this->getExitCode();
0733      }
0734   
0735      /**
0736       * Returns true if the child process has been terminated by an uncaught signal.
0737       *
0738       * It always returns false on Windows.
0739       *
0740       * @return bool
0741       *
0742       * @throws RuntimeException In case --enable-sigchild is activated
0743       * @throws LogicException   In case the process is not terminated
0744       */
0745      public function hasBeenSignaled()
0746      {
0747          $this->requireProcessIsTerminated(__FUNCTION__);
0748   
0749          if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
0750              throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
0751          }
0752   
0753          return $this->processInformation['signaled'];
0754      }
0755   
0756      /**
0757       * Returns the number of the signal that caused the child process to terminate its execution.
0758       *
0759       * It is only meaningful if hasBeenSignaled() returns true.
0760       *
0761       * @return int
0762       *
0763       * @throws RuntimeException In case --enable-sigchild is activated
0764       * @throws LogicException   In case the process is not terminated
0765       */
0766      public function getTermSignal()
0767      {
0768          $this->requireProcessIsTerminated(__FUNCTION__);
0769   
0770          if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) {
0771              throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
0772          }
0773   
0774          return $this->processInformation['termsig'];
0775      }
0776   
0777      /**
0778       * Returns true if the child process has been stopped by a signal.
0779       *
0780       * It always returns false on Windows.
0781       *
0782       * @return bool
0783       *
0784       * @throws LogicException In case the process is not terminated
0785       */
0786      public function hasBeenStopped()
0787      {
0788          $this->requireProcessIsTerminated(__FUNCTION__);
0789   
0790          return $this->processInformation['stopped'];
0791      }
0792   
0793      /**
0794       * Returns the number of the signal that caused the child process to stop its execution.
0795       *
0796       * It is only meaningful if hasBeenStopped() returns true.
0797       *
0798       * @return int
0799       *
0800       * @throws LogicException In case the process is not terminated
0801       */
0802      public function getStopSignal()
0803      {
0804          $this->requireProcessIsTerminated(__FUNCTION__);
0805   
0806          return $this->processInformation['stopsig'];
0807      }
0808   
0809      /**
0810       * Checks if the process is currently running.
0811       *
0812       * @return bool true if the process is currently running, false otherwise
0813       */
0814      public function isRunning()
0815      {
0816          if (self::STATUS_STARTED !== $this->status) {
0817              return false;
0818          }
0819   
0820          $this->updateStatus(false);
0821   
0822          return $this->processInformation['running'];
0823      }
0824   
0825      /**
0826       * Checks if the process has been started with no regard to the current state.
0827       *
0828       * @return bool true if status is ready, false otherwise
0829       */
0830      public function isStarted()
0831      {
0832          return self::STATUS_READY != $this->status;
0833      }
0834   
0835      /**
0836       * Checks if the process is terminated.
0837       *
0838       * @return bool true if process is terminated, false otherwise
0839       */
0840      public function isTerminated()
0841      {
0842          $this->updateStatus(false);
0843   
0844          return self::STATUS_TERMINATED == $this->status;
0845      }
0846   
0847      /**
0848       * Gets the process status.
0849       *
0850       * The status is one of: ready, started, terminated.
0851       *
0852       * @return string The current process status
0853       */
0854      public function getStatus()
0855      {
0856          $this->updateStatus(false);
0857   
0858          return $this->status;
0859      }
0860   
0861      /**
0862       * Stops the process.
0863       *
0864       * @param int|float $timeout The timeout in seconds
0865       * @param int       $signal  A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
0866       *
0867       * @return int|null The exit-code of the process or null if it's not running
0868       */
0869      public function stop($timeout = 10, $signal = null)
0870      {
0871          $timeoutMicro = microtime(true) + $timeout;
0872          if ($this->isRunning()) {
0873              // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
0874              $this->doSignal(15, false);
0875              do {
0876                  usleep(1000);
0877              } while ($this->isRunning() && microtime(true) < $timeoutMicro);
0878   
0879              if ($this->isRunning()) {
0880                  // Avoid exception here: process is supposed to be running, but it might have stopped just
0881                  // after this line. In any case, let's silently discard the error, we cannot do anything.
0882                  $this->doSignal($signal ?: 9, false);
0883              }
0884          }
0885   
0886          if ($this->isRunning()) {
0887              if (isset($this->fallbackStatus['pid'])) {
0888                  unset($this->fallbackStatus['pid']);
0889   
0890                  return $this->stop(0, $signal);
0891              }
0892              $this->close();
0893          }
0894   
0895          return $this->exitcode;
0896      }
0897   
0898      /**
0899       * Adds a line to the STDOUT stream.
0900       *
0901       * @internal
0902       *
0903       * @param string $line The line to append
0904       */
0905      public function addOutput($line)
0906      {
0907          $this->lastOutputTime = microtime(true);
0908   
0909          fseek($this->stdout, 0, \SEEK_END);
0910          fwrite($this->stdout, $line);
0911          fseek($this->stdout, $this->incrementalOutputOffset);
0912      }
0913   
0914      /**
0915       * Adds a line to the STDERR stream.
0916       *
0917       * @internal
0918       *
0919       * @param string $line The line to append
0920       */
0921      public function addErrorOutput($line)
0922      {
0923          $this->lastOutputTime = microtime(true);
0924   
0925          fseek($this->stderr, 0, \SEEK_END);
0926          fwrite($this->stderr, $line);
0927          fseek($this->stderr, $this->incrementalErrorOutputOffset);
0928      }
0929   
0930      /**
0931       * Gets the command line to be executed.
0932       *
0933       * @return string The command to execute
0934       */
0935      public function getCommandLine()
0936      {
0937          return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
0938      }
0939   
0940      /**
0941       * Sets the command line to be executed.
0942       *
0943       * @param string|array $commandline The command to execute
0944       *
0945       * @return $this
0946       */
0947      public function setCommandLine($commandline)
0948      {
0949          $this->commandline = $commandline;
0950   
0951          return $this;
0952      }
0953   
0954      /**
0955       * Gets the process timeout (max. runtime).
0956       *
0957       * @return float|null The timeout in seconds or null if it's disabled
0958       */
0959      public function getTimeout()
0960      {
0961          return $this->timeout;
0962      }
0963   
0964      /**
0965       * Gets the process idle timeout (max. time since last output).
0966       *
0967       * @return float|null The timeout in seconds or null if it's disabled
0968       */
0969      public function getIdleTimeout()
0970      {
0971          return $this->idleTimeout;
0972      }
0973   
0974      /**
0975       * Sets the process timeout (max. runtime) in seconds.
0976       *
0977       * To disable the timeout, set this value to null.
0978       *
0979       * @param int|float|null $timeout The timeout in seconds
0980       *
0981       * @return $this
0982       *
0983       * @throws InvalidArgumentException if the timeout is negative
0984       */
0985      public function setTimeout($timeout)
0986      {
0987          $this->timeout = $this->validateTimeout($timeout);
0988   
0989          return $this;
0990      }
0991   
0992      /**
0993       * Sets the process idle timeout (max. time since last output).
0994       *
0995       * To disable the timeout, set this value to null.
0996       *
0997       * @param int|float|null $timeout The timeout in seconds
0998       *
0999       * @return $this
1000       *
1001       * @throws LogicException           if the output is disabled
1002       * @throws InvalidArgumentException if the timeout is negative
1003       */
1004      public function setIdleTimeout($timeout)
1005      {
1006          if (null !== $timeout && $this->outputDisabled) {
1007              throw new LogicException('Idle timeout can not be set while the output is disabled.');
1008          }
1009   
1010          $this->idleTimeout = $this->validateTimeout($timeout);
1011   
1012          return $this;
1013      }
1014   
1015      /**
1016       * Enables or disables the TTY mode.
1017       *
1018       * @param bool $tty True to enabled and false to disable
1019       *
1020       * @return $this
1021       *
1022       * @throws RuntimeException In case the TTY mode is not supported
1023       */
1024      public function setTty($tty)
1025      {
1026          if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
1027              throw new RuntimeException('TTY mode is not supported on Windows platform.');
1028          }
1029          if ($tty) {
1030              static $isTtySupported;
1031   
1032              if (null === $isTtySupported) {
1033                  $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
1034              }
1035   
1036              if (!$isTtySupported) {
1037                  throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
1038              }
1039          }
1040   
1041          $this->tty = (bool) $tty;
1042   
1043          return $this;
1044      }
1045   
1046      /**
1047       * Checks if the TTY mode is enabled.
1048       *
1049       * @return bool true if the TTY mode is enabled, false otherwise
1050       */
1051      public function isTty()
1052      {
1053          return $this->tty;
1054      }
1055   
1056      /**
1057       * Sets PTY mode.
1058       *
1059       * @param bool $bool
1060       *
1061       * @return $this
1062       */
1063      public function setPty($bool)
1064      {
1065          $this->pty = (bool) $bool;
1066   
1067          return $this;
1068      }
1069   
1070      /**
1071       * Returns PTY state.
1072       *
1073       * @return bool
1074       */
1075      public function isPty()
1076      {
1077          return $this->pty;
1078      }
1079   
1080      /**
1081       * Gets the working directory.
1082       *
1083       * @return string|null The current working directory or null on failure
1084       */
1085      public function getWorkingDirectory()
1086      {
1087          if (null === $this->cwd) {
1088              // getcwd() will return false if any one of the parent directories does not have
1089              // the readable or search mode set, even if the current directory does
1090              return getcwd() ?: null;
1091          }
1092   
1093          return $this->cwd;
1094      }
1095   
1096      /**
1097       * Sets the current working directory.
1098       *
1099       * @param string $cwd The new working directory
1100       *
1101       * @return $this
1102       */
1103      public function setWorkingDirectory($cwd)
1104      {
1105          $this->cwd = $cwd;
1106   
1107          return $this;
1108      }
1109   
1110      /**
1111       * Gets the environment variables.
1112       *
1113       * @return array The current environment variables
1114       */
1115      public function getEnv()
1116      {
1117          return $this->env;
1118      }
1119   
1120      /**
1121       * Sets the environment variables.
1122       *
1123       * Each environment variable value should be a string.
1124       * If it is an array, the variable is ignored.
1125       * If it is false or null, it will be removed when
1126       * env vars are otherwise inherited.
1127       *
1128       * That happens in PHP when 'argv' is registered into
1129       * the $_ENV array for instance.
1130       *
1131       * @param array $env The new environment variables
1132       *
1133       * @return $this
1134       */
1135      public function setEnv(array $env)
1136      {
1137          // Process can not handle env values that are arrays
1138          $env = array_filter($env, function ($value) {
1139              return !\is_array($value);
1140          });
1141   
1142          $this->env = $env;
1143   
1144          return $this;
1145      }
1146   
1147      /**
1148       * Gets the Process input.
1149       *
1150       * @return resource|string|\Iterator|null The Process input
1151       */
1152      public function getInput()
1153      {
1154          return $this->input;
1155      }
1156   
1157      /**
1158       * Sets the input.
1159       *
1160       * This content will be passed to the underlying process standard input.
1161       *
1162       * @param string|int|float|bool|resource|\Traversable|null $input The content
1163       *
1164       * @return $this
1165       *
1166       * @throws LogicException In case the process is running
1167       */
1168      public function setInput($input)
1169      {
1170          if ($this->isRunning()) {
1171              throw new LogicException('Input can not be set while the process is running.');
1172          }
1173   
1174          $this->input = ProcessUtils::validateInput(__METHOD__, $input);
1175   
1176          return $this;
1177      }
1178   
1179      /**
1180       * Gets the options for proc_open.
1181       *
1182       * @return array The current options
1183       *
1184       * @deprecated since version 3.3, to be removed in 4.0.
1185       */
1186      public function getOptions()
1187      {
1188          @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED);
1189   
1190          return $this->options;
1191      }
1192   
1193      /**
1194       * Sets the options for proc_open.
1195       *
1196       * @param array $options The new options
1197       *
1198       * @return $this
1199       *
1200       * @deprecated since version 3.3, to be removed in 4.0.
1201       */
1202      public function setOptions(array $options)
1203      {
1204          @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED);
1205   
1206          $this->options = $options;
1207   
1208          return $this;
1209      }
1210   
1211      /**
1212       * Gets whether or not Windows compatibility is enabled.
1213       *
1214       * This is true by default.
1215       *
1216       * @return bool
1217       *
1218       * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
1219       */
1220      public function getEnhanceWindowsCompatibility()
1221      {
1222          @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);
1223   
1224          return $this->enhanceWindowsCompatibility;
1225      }
1226   
1227      /**
1228       * Sets whether or not Windows compatibility is enabled.
1229       *
1230       * @param bool $enhance
1231       *
1232       * @return $this
1233       *
1234       * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
1235       */
1236      public function setEnhanceWindowsCompatibility($enhance)
1237      {
1238          @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);
1239   
1240          $this->enhanceWindowsCompatibility = (bool) $enhance;
1241   
1242          return $this;
1243      }
1244   
1245      /**
1246       * Returns whether sigchild compatibility mode is activated or not.
1247       *
1248       * @return bool
1249       *
1250       * @deprecated since version 3.3, to be removed in 4.0. Sigchild compatibility will always be enabled.
1251       */
1252      public function getEnhanceSigchildCompatibility()
1253      {
1254          @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);
1255   
1256          return $this->enhanceSigchildCompatibility;
1257      }
1258   
1259      /**
1260       * Activates sigchild compatibility mode.
1261       *
1262       * Sigchild compatibility mode is required to get the exit code and
1263       * determine the success of a process when PHP has been compiled with
1264       * the --enable-sigchild option
1265       *
1266       * @param bool $enhance
1267       *
1268       * @return $this
1269       *
1270       * @deprecated since version 3.3, to be removed in 4.0.
1271       */
1272      public function setEnhanceSigchildCompatibility($enhance)
1273      {
1274          @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);
1275   
1276          $this->enhanceSigchildCompatibility = (bool) $enhance;
1277   
1278          return $this;
1279      }
1280   
1281      /**
1282       * Sets whether environment variables will be inherited or not.
1283       *
1284       * @param bool $inheritEnv
1285       *
1286       * @return $this
1287       */
1288      public function inheritEnvironmentVariables($inheritEnv = true)
1289      {
1290          if (!$inheritEnv) {
1291              @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', \E_USER_DEPRECATED);
1292          }
1293   
1294          $this->inheritEnv = (bool) $inheritEnv;
1295   
1296          return $this;
1297      }
1298   
1299      /**
1300       * Returns whether environment variables will be inherited or not.
1301       *
1302       * @return bool
1303       *
1304       * @deprecated since version 3.3, to be removed in 4.0. Environment variables will always be inherited.
1305       */
1306      public function areEnvironmentVariablesInherited()
1307      {
1308          @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Environment variables will always be inherited.', __METHOD__), \E_USER_DEPRECATED);
1309   
1310          return $this->inheritEnv;
1311      }
1312   
1313      /**
1314       * Performs a check between the timeout definition and the time the process started.
1315       *
1316       * In case you run a background process (with the start method), you should
1317       * trigger this method regularly to ensure the process timeout
1318       *
1319       * @throws ProcessTimedOutException In case the timeout was reached
1320       */
1321      public function checkTimeout()
1322      {
1323          if (self::STATUS_STARTED !== $this->status) {
1324              return;
1325          }
1326   
1327          if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
1328              $this->stop(0);
1329   
1330              throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
1331          }
1332   
1333          if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
1334              $this->stop(0);
1335   
1336              throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
1337          }
1338      }
1339   
1340      /**
1341       * Returns whether PTY is supported on the current operating system.
1342       *
1343       * @return bool
1344       */
1345      public static function isPtySupported()
1346      {
1347          static $result;
1348   
1349          if (null !== $result) {
1350              return $result;
1351          }
1352   
1353          if ('\\' === \DIRECTORY_SEPARATOR) {
1354              return $result = false;
1355          }
1356   
1357          return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
1358      }
1359   
1360      /**
1361       * Creates the descriptors needed by the proc_open.
1362       *
1363       * @return array
1364       */
1365      private function getDescriptors()
1366      {
1367          if ($this->input instanceof \Iterator) {
1368              $this->input->rewind();
1369          }
1370          if ('\\' === \DIRECTORY_SEPARATOR) {
1371              $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
1372          } else {
1373              $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
1374          }
1375   
1376          return $this->processPipes->getDescriptors();
1377      }
1378   
1379      /**
1380       * Builds up the callback used by wait().
1381       *
1382       * The callbacks adds all occurred output to the specific buffer and calls
1383       * the user callback (if present) with the received output.
1384       *
1385       * @param callable|null $callback The user defined PHP callback
1386       *
1387       * @return \Closure A PHP closure
1388       */
1389      protected function buildCallback(callable $callback = null)
1390      {
1391          if ($this->outputDisabled) {
1392              return function ($type, $data) use ($callback) {
1393                  if (null !== $callback) {
1394                      \call_user_func($callback, $type, $data);
1395                  }
1396              };
1397          }
1398   
1399          $out = self::OUT;
1400   
1401          return function ($type, $data) use ($callback, $out) {
1402              if ($out == $type) {
1403                  $this->addOutput($data);
1404              } else {
1405                  $this->addErrorOutput($data);
1406              }
1407   
1408              if (null !== $callback) {
1409                  \call_user_func($callback, $type, $data);
1410              }
1411          };
1412      }
1413   
1414      /**
1415       * Updates the status of the process, reads pipes.
1416       *
1417       * @param bool $blocking Whether to use a blocking read call
1418       */
1419      protected function updateStatus($blocking)
1420      {
1421          if (self::STATUS_STARTED !== $this->status) {
1422              return;
1423          }
1424   
1425          $this->processInformation = proc_get_status($this->process);
1426          $running = $this->processInformation['running'];
1427   
1428          $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
1429   
1430          if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1431              $this->processInformation = $this->fallbackStatus + $this->processInformation;
1432          }
1433   
1434          if (!$running) {
1435              $this->close();
1436          }
1437      }
1438   
1439      /**
1440       * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
1441       *
1442       * @return bool
1443       */
1444      protected function isSigchildEnabled()
1445      {
1446          if (null !== self::$sigchild) {
1447              return self::$sigchild;
1448          }
1449   
1450          if (!\function_exists('phpinfo') || \defined('HHVM_VERSION')) {
1451              return self::$sigchild = false;
1452          }
1453   
1454          ob_start();
1455          phpinfo(\INFO_GENERAL);
1456   
1457          return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
1458      }
1459   
1460      /**
1461       * Reads pipes for the freshest output.
1462       *
1463       * @param string $caller   The name of the method that needs fresh outputs
1464       * @param bool   $blocking Whether to use blocking calls or not
1465       *
1466       * @throws LogicException in case output has been disabled or process is not started
1467       */
1468      private function readPipesForOutput($caller, $blocking = false)
1469      {
1470          if ($this->outputDisabled) {
1471              throw new LogicException('Output has been disabled.');
1472          }
1473   
1474          $this->requireProcessIsStarted($caller);
1475   
1476          $this->updateStatus($blocking);
1477      }
1478   
1479      /**
1480       * Validates and returns the filtered timeout.
1481       *
1482       * @param int|float|null $timeout
1483       *
1484       * @return float|null
1485       *
1486       * @throws InvalidArgumentException if the given timeout is a negative number
1487       */
1488      private function validateTimeout($timeout)
1489      {
1490          $timeout = (float) $timeout;
1491   
1492          if (0.0 === $timeout) {
1493              $timeout = null;
1494          } elseif ($timeout < 0) {
1495              throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
1496          }
1497   
1498          return $timeout;
1499      }
1500   
1501      /**
1502       * Reads pipes, executes callback.
1503       *
1504       * @param bool $blocking Whether to use blocking calls or not
1505       * @param bool $close    Whether to close file handles or not
1506       */
1507      private function readPipes($blocking, $close)
1508      {
1509          $result = $this->processPipes->readAndWrite($blocking, $close);
1510   
1511          $callback = $this->callback;
1512          foreach ($result as $type => $data) {
1513              if (3 !== $type) {
1514                  $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
1515              } elseif (!isset($this->fallbackStatus['signaled'])) {
1516                  $this->fallbackStatus['exitcode'] = (int) $data;
1517              }
1518          }
1519      }
1520   
1521      /**
1522       * Closes process resource, closes file handles, sets the exitcode.
1523       *
1524       * @return int The exitcode
1525       */
1526      private function close()
1527      {
1528          $this->processPipes->close();
1529          if (\is_resource($this->process)) {
1530              proc_close($this->process);
1531          }
1532          $this->exitcode = $this->processInformation['exitcode'];
1533          $this->status = self::STATUS_TERMINATED;
1534   
1535          if (-1 === $this->exitcode) {
1536              if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
1537                  // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
1538                  $this->exitcode = 128 + $this->processInformation['termsig'];
1539              } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1540                  $this->processInformation['signaled'] = true;
1541                  $this->processInformation['termsig'] = -1;
1542              }
1543          }
1544   
1545          // Free memory from self-reference callback created by buildCallback
1546          // Doing so in other contexts like __destruct or by garbage collector is ineffective
1547          // Now pipes are closed, so the callback is no longer necessary
1548          $this->callback = null;
1549   
1550          return $this->exitcode;
1551      }
1552   
1553      /**
1554       * Resets data related to the latest run of the process.
1555       */
1556      private function resetProcessData()
1557      {
1558          $this->starttime = null;
1559          $this->callback = null;
1560          $this->exitcode = null;
1561          $this->fallbackStatus = [];
1562          $this->processInformation = null;
1563          $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
1564          $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
1565          $this->process = null;
1566          $this->latestSignal = null;
1567          $this->status = self::STATUS_READY;
1568          $this->incrementalOutputOffset = 0;
1569          $this->incrementalErrorOutputOffset = 0;
1570      }
1571   
1572      /**
1573       * Sends a POSIX signal to the process.
1574       *
1575       * @param int  $signal         A valid POSIX signal (see https://php.net/pcntl.constants)
1576       * @param bool $throwException Whether to throw exception in case signal failed
1577       *
1578       * @return bool True if the signal was sent successfully, false otherwise
1579       *
1580       * @throws LogicException   In case the process is not running
1581       * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
1582       * @throws RuntimeException In case of failure
1583       */
1584      private function doSignal($signal, $throwException)
1585      {
1586          if (null === $pid = $this->getPid()) {
1587              if ($throwException) {
1588                  throw new LogicException('Can not send signal on a non running process.');
1589              }
1590   
1591              return false;
1592          }
1593   
1594          if ('\\' === \DIRECTORY_SEPARATOR) {
1595              exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
1596              if ($exitCode && $this->isRunning()) {
1597                  if ($throwException) {
1598                      throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
1599                  }
1600   
1601                  return false;
1602              }
1603          } else {
1604              if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) {
1605                  $ok = @proc_terminate($this->process, $signal);
1606              } elseif (\function_exists('posix_kill')) {
1607                  $ok = @posix_kill($pid, $signal);
1608              } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
1609                  $ok = false === fgets($pipes[2]);
1610              }
1611              if (!$ok) {
1612                  if ($throwException) {
1613                      throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
1614                  }
1615   
1616                  return false;
1617              }
1618          }
1619   
1620          $this->latestSignal = (int) $signal;
1621          $this->fallbackStatus['signaled'] = true;
1622          $this->fallbackStatus['exitcode'] = -1;
1623          $this->fallbackStatus['termsig'] = $this->latestSignal;
1624   
1625          return true;
1626      }
1627   
1628      private function prepareWindowsCommandLine($cmd, array &$env)
1629      {
1630          $uid = uniqid('', true);
1631          $varCount = 0;
1632          $varCache = [];
1633          $cmd = preg_replace_callback(
1634              '/"(?:(
1635                  [^"%!^]*+
1636                  (?:
1637                      (?: !LF! | "(?:\^[%!^])?+" )
1638                      [^"%!^]*+
1639                  )++
1640              ) | [^"]*+ )"/x',
1641              function ($m) use (&$env, &$varCache, &$varCount, $uid) {
1642                  if (!isset($m[1])) {
1643                      return $m[0];
1644                  }
1645                  if (isset($varCache[$m[0]])) {
1646                      return $varCache[$m[0]];
1647                  }
1648                  if (false !== strpos($value = $m[1], "\0")) {
1649                      $value = str_replace("\0", '?', $value);
1650                  }
1651                  if (false === strpbrk($value, "\"%!\n")) {
1652                      return '"'.$value.'"';
1653                  }
1654   
1655                  $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
1656                  $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
1657                  $var = $uid.++$varCount;
1658   
1659                  $env[$var] = $value;
1660   
1661                  return $varCache[$m[0]] = '!'.$var.'!';
1662              },
1663              $cmd
1664          );
1665   
1666          $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
1667          foreach ($this->processPipes->getFiles() as $offset => $filename) {
1668              $cmd .= ' '.$offset.'>"'.$filename.'"';
1669          }
1670   
1671          return $cmd;
1672      }
1673   
1674      /**
1675       * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
1676       *
1677       * @param string $functionName The function name that was called
1678       *
1679       * @throws LogicException if the process has not run
1680       */
1681      private function requireProcessIsStarted($functionName)
1682      {
1683          if (!$this->isStarted()) {
1684              throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName));
1685          }
1686      }
1687   
1688      /**
1689       * Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`.
1690       *
1691       * @param string $functionName The function name that was called
1692       *
1693       * @throws LogicException if the process is not yet terminated
1694       */
1695      private function requireProcessIsTerminated($functionName)
1696      {
1697          if (!$this->isTerminated()) {
1698              throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName));
1699          }
1700      }
1701   
1702      /**
1703       * Escapes a string to be used as a shell argument.
1704       *
1705       * @param string $argument The argument that will be escaped
1706       *
1707       * @return string The escaped argument
1708       */
1709      private function escapeArgument($argument)
1710      {
1711          if ('\\' !== \DIRECTORY_SEPARATOR) {
1712              return "'".str_replace("'", "'\\''", $argument)."'";
1713          }
1714          if ('' === $argument = (string) $argument) {
1715              return '""';
1716          }
1717          if (false !== strpos($argument, "\0")) {
1718              $argument = str_replace("\0", '?', $argument);
1719          }
1720          if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
1721              return $argument;
1722          }
1723          $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
1724   
1725          return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
1726      }
1727   
1728      private function getDefaultEnv()
1729      {
1730          $env = [];
1731   
1732          foreach ($_SERVER as $k => $v) {
1733              if (\is_string($v) && false !== $v = getenv($k)) {
1734                  $env[$k] = $v;
1735              }
1736          }
1737   
1738          foreach ($_ENV as $k => $v) {
1739              if (\is_string($v)) {
1740                  $env[$k] = $v;
1741              }
1742          }
1743   
1744          return $env;
1745      }
1746  }
1747