ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
Process.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11 
13 
22 
30 class Process implements \IteratorAggregate
31 {
32  const ERR = 'err';
33  const OUT = 'out';
34 
35  const STATUS_READY = 'ready';
36  const STATUS_STARTED = 'started';
37  const STATUS_TERMINATED = 'terminated';
38 
39  const STDIN = 0;
40  const STDOUT = 1;
41  const STDERR = 2;
42 
43  // Timeout Precision in seconds.
44  const TIMEOUT_PRECISION = 0.2;
45 
46  const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
47  const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
48  const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
49  const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
50 
51  private $callback;
52  private $hasCallback = false;
53  private $commandline;
54  private $cwd;
55  private $env;
56  private $input;
57  private $starttime;
58  private $lastOutputTime;
59  private $timeout;
60  private $idleTimeout;
61  private $options;
62  private $exitcode;
63  private $fallbackStatus = array();
65  private $outputDisabled = false;
66  private $stdout;
67  private $stderr;
70  private $process;
71  private $status = self::STATUS_READY;
74  private $tty;
75  private $pty;
76 
77  private $useFileHandles = false;
79  private $processPipes;
80 
81  private $latestSignal;
82 
83  private static $sigchild;
84 
92  public static $exitCodes = array(
93  0 => 'OK',
94  1 => 'General error',
95  2 => 'Misuse of shell builtins',
96 
97  126 => 'Invoked command cannot execute',
98  127 => 'Command not found',
99  128 => 'Invalid exit argument',
100 
101  // signals
102  129 => 'Hangup',
103  130 => 'Interrupt',
104  131 => 'Quit and dump core',
105  132 => 'Illegal instruction',
106  133 => 'Trace/breakpoint trap',
107  134 => 'Process aborted',
108  135 => 'Bus error: "access to undefined portion of memory object"',
109  136 => 'Floating point exception: "erroneous arithmetic operation"',
110  137 => 'Kill (terminate immediately)',
111  138 => 'User-defined 1',
112  139 => 'Segmentation violation',
113  140 => 'User-defined 2',
114  141 => 'Write to pipe with no one reading',
115  142 => 'Signal raised by alarm',
116  143 => 'Termination (request to terminate)',
117  // 144 - not defined
118  145 => 'Child process terminated, stopped (or continued*)',
119  146 => 'Continue if stopped',
120  147 => 'Stop executing temporarily',
121  148 => 'Terminal stop signal',
122  149 => 'Background process attempting to read from tty ("in")',
123  150 => 'Background process attempting to write to tty ("out")',
124  151 => 'Urgent data available on socket',
125  152 => 'CPU time limit exceeded',
126  153 => 'File size limit exceeded',
127  154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
128  155 => 'Profiling timer expired',
129  // 156 - not defined
130  157 => 'Pollable event',
131  // 158 - not defined
132  159 => 'Bad syscall',
133  );
134 
147  public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array())
148  {
149  if (!function_exists('proc_open')) {
150  throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
151  }
152 
153  $this->commandline = $commandline;
154  $this->cwd = $cwd;
155 
156  // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
157  // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
158  // @see : https://bugs.php.net/bug.php?id=51800
159  // @see : https://bugs.php.net/bug.php?id=50524
160  if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) {
161  $this->cwd = getcwd();
162  }
163  if (null !== $env) {
164  $this->setEnv($env);
165  }
166 
167  $this->setInput($input);
168  $this->setTimeout($timeout);
169  $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR;
170  $this->pty = false;
171  $this->enhanceWindowsCompatibility = true;
172  $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
173  $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options);
174  }
175 
176  public function __destruct()
177  {
178  $this->stop(0);
179  }
180 
181  public function __clone()
182  {
183  $this->resetProcessData();
184  }
185 
205  public function run($callback = null)
206  {
207  $this->start($callback);
208 
209  return $this->wait();
210  }
211 
225  public function mustRun(callable $callback = null)
226  {
227  if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
228  throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
229  }
230 
231  if (0 !== $this->run($callback)) {
232  throw new ProcessFailedException($this);
233  }
234 
235  return $this;
236  }
237 
257  public function start(callable $callback = null)
258  {
259  if ($this->isRunning()) {
260  throw new RuntimeException('Process is already running');
261  }
262 
263  $this->resetProcessData();
264  $this->starttime = $this->lastOutputTime = microtime(true);
265  $this->callback = $this->buildCallback($callback);
266  $this->hasCallback = null !== $callback;
267  $descriptors = $this->getDescriptors();
268 
270 
271  if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
272  $commandline = 'cmd /V:ON /E:ON /D /C "('.$commandline.')';
273  foreach ($this->processPipes->getFiles() as $offset => $filename) {
275  }
276  $commandline .= '"';
277 
278  if (!isset($this->options['bypass_shell'])) {
279  $this->options['bypass_shell'] = true;
280  }
281  } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
282  // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
283  $descriptors[3] = array('pipe', 'w');
284 
285  // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
286  $commandline = '{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
287  $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
288 
289  // Workaround for the bug, when PTS functionality is enabled.
290  // @see : https://bugs.php.net/69442
291  $ptsWorkaround = fopen(__FILE__, 'r');
292  }
293 
294  $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
295 
296  if (!is_resource($this->process)) {
297  throw new RuntimeException('Unable to launch a new process.');
298  }
299  $this->status = self::STATUS_STARTED;
300 
301  if (isset($descriptors[3])) {
302  $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
303  }
304 
305  if ($this->tty) {
306  return;
307  }
308 
309  $this->updateStatus(false);
310  $this->checkTimeout();
311  }
312 
328  public function restart(callable $callback = null)
329  {
330  if ($this->isRunning()) {
331  throw new RuntimeException('Process is already running');
332  }
333 
334  $process = clone $this;
335  $process->start($callback);
336 
337  return $process;
338  }
339 
355  public function wait(callable $callback = null)
356  {
357  $this->requireProcessIsStarted(__FUNCTION__);
358 
359  $this->updateStatus(false);
360 
361  if (null !== $callback) {
362  if (!$this->processPipes->haveReadSupport()) {
363  $this->stop(0);
364  throw new \LogicException('Pass the callback to the Process:start method or enableOutput to use a callback with Process::wait');
365  }
366  $this->callback = $this->buildCallback($callback);
367  }
368 
369  do {
370  $this->checkTimeout();
371  $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
372  $this->readPipes($running, '\\' !== DIRECTORY_SEPARATOR || !$running);
373  } while ($running);
374 
375  while ($this->isRunning()) {
376  usleep(1000);
377  }
378 
379  if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
380  throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
381  }
382 
383  return $this->exitcode;
384  }
385 
391  public function getPid()
392  {
393  return $this->isRunning() ? $this->processInformation['pid'] : null;
394  }
395 
407  public function signal($signal)
408  {
409  $this->doSignal($signal, true);
410 
411  return $this;
412  }
413 
422  public function disableOutput()
423  {
424  if ($this->isRunning()) {
425  throw new RuntimeException('Disabling output while the process is running is not possible.');
426  }
427  if (null !== $this->idleTimeout) {
428  throw new LogicException('Output can not be disabled while an idle timeout is set.');
429  }
430 
431  $this->outputDisabled = true;
432 
433  return $this;
434  }
435 
443  public function enableOutput()
444  {
445  if ($this->isRunning()) {
446  throw new RuntimeException('Enabling output while the process is running is not possible.');
447  }
448 
449  $this->outputDisabled = false;
450 
451  return $this;
452  }
453 
459  public function isOutputDisabled()
460  {
461  return $this->outputDisabled;
462  }
463 
472  public function getOutput()
473  {
474  $this->readPipesForOutput(__FUNCTION__);
475 
476  if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
477  return '';
478  }
479 
480  return $ret;
481  }
482 
494  public function getIncrementalOutput()
495  {
496  $this->readPipesForOutput(__FUNCTION__);
497 
498  $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
499  $this->incrementalOutputOffset = ftell($this->stdout);
500 
501  if (false === $latest) {
502  return '';
503  }
504 
505  return $latest;
506  }
507 
518  public function getIterator($flags = 0)
519  {
520  $this->readPipesForOutput(__FUNCTION__, false);
521 
522  $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
523  $blocking = !(self::ITER_NON_BLOCKING & $flags);
524  $yieldOut = !(self::ITER_SKIP_OUT & $flags);
525  $yieldErr = !(self::ITER_SKIP_ERR & $flags);
526 
527  while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
528  if ($yieldOut) {
529  $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
530 
531  if (isset($out[0])) {
532  if ($clearOutput) {
533  $this->clearOutput();
534  } else {
535  $this->incrementalOutputOffset = ftell($this->stdout);
536  }
537 
538  yield self::OUT => $out;
539  }
540  }
541 
542  if ($yieldErr) {
543  $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
544 
545  if (isset($err[0])) {
546  if ($clearOutput) {
547  $this->clearErrorOutput();
548  } else {
549  $this->incrementalErrorOutputOffset = ftell($this->stderr);
550  }
551 
552  yield self::ERR => $err;
553  }
554  }
555 
556  if (!$blocking && !isset($out[0]) && !isset($err[0])) {
557  yield self::OUT => '';
558  }
559 
560  $this->readPipesForOutput(__FUNCTION__, $blocking);
561  }
562  }
563 
569  public function clearOutput()
570  {
571  ftruncate($this->stdout, 0);
572  fseek($this->stdout, 0);
573  $this->incrementalOutputOffset = 0;
574 
575  return $this;
576  }
577 
586  public function getErrorOutput()
587  {
588  $this->readPipesForOutput(__FUNCTION__);
589 
590  if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
591  return '';
592  }
593 
594  return $ret;
595  }
596 
609  public function getIncrementalErrorOutput()
610  {
611  $this->readPipesForOutput(__FUNCTION__);
612 
613  $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
614  $this->incrementalErrorOutputOffset = ftell($this->stderr);
615 
616  if (false === $latest) {
617  return '';
618  }
619 
620  return $latest;
621  }
622 
628  public function clearErrorOutput()
629  {
630  ftruncate($this->stderr, 0);
631  fseek($this->stderr, 0);
632  $this->incrementalErrorOutputOffset = 0;
633 
634  return $this;
635  }
636 
644  public function getExitCode()
645  {
646  if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
647  throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
648  }
649 
650  $this->updateStatus(false);
651 
652  return $this->exitcode;
653  }
654 
666  public function getExitCodeText()
667  {
668  if (null === $exitcode = $this->getExitCode()) {
669  return;
670  }
671 
672  return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
673  }
674 
680  public function isSuccessful()
681  {
682  return 0 === $this->getExitCode();
683  }
684 
695  public function hasBeenSignaled()
696  {
697  $this->requireProcessIsTerminated(__FUNCTION__);
698 
699  if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
700  throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
701  }
702 
703  return $this->processInformation['signaled'];
704  }
705 
716  public function getTermSignal()
717  {
718  $this->requireProcessIsTerminated(__FUNCTION__);
719 
720  if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) {
721  throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
722  }
723 
724  return $this->processInformation['termsig'];
725  }
726 
736  public function hasBeenStopped()
737  {
738  $this->requireProcessIsTerminated(__FUNCTION__);
739 
740  return $this->processInformation['stopped'];
741  }
742 
752  public function getStopSignal()
753  {
754  $this->requireProcessIsTerminated(__FUNCTION__);
755 
756  return $this->processInformation['stopsig'];
757  }
758 
764  public function isRunning()
765  {
766  if (self::STATUS_STARTED !== $this->status) {
767  return false;
768  }
769 
770  $this->updateStatus(false);
771 
772  return $this->processInformation['running'];
773  }
774 
780  public function isStarted()
781  {
782  return $this->status != self::STATUS_READY;
783  }
784 
790  public function isTerminated()
791  {
792  $this->updateStatus(false);
793 
794  return $this->status == self::STATUS_TERMINATED;
795  }
796 
804  public function getStatus()
805  {
806  $this->updateStatus(false);
807 
808  return $this->status;
809  }
810 
819  public function stop($timeout = 10, $signal = null)
820  {
821  $timeoutMicro = microtime(true) + $timeout;
822  if ($this->isRunning()) {
823  // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
824  $this->doSignal(15, false);
825  do {
826  usleep(1000);
827  } while ($this->isRunning() && microtime(true) < $timeoutMicro);
828 
829  if ($this->isRunning()) {
830  // Avoid exception here: process is supposed to be running, but it might have stopped just
831  // after this line. In any case, let's silently discard the error, we cannot do anything.
832  $this->doSignal($signal ?: 9, false);
833  }
834  }
835 
836  if ($this->isRunning()) {
837  if (isset($this->fallbackStatus['pid'])) {
838  unset($this->fallbackStatus['pid']);
839 
840  return $this->stop(0, $signal);
841  }
842  $this->close();
843  }
844 
845  return $this->exitcode;
846  }
847 
855  public function addOutput($line)
856  {
857  $this->lastOutputTime = microtime(true);
858 
859  fseek($this->stdout, 0, SEEK_END);
860  fwrite($this->stdout, $line);
861  fseek($this->stdout, $this->incrementalOutputOffset);
862  }
863 
871  public function addErrorOutput($line)
872  {
873  $this->lastOutputTime = microtime(true);
874 
875  fseek($this->stderr, 0, SEEK_END);
876  fwrite($this->stderr, $line);
877  fseek($this->stderr, $this->incrementalErrorOutputOffset);
878  }
879 
885  public function getCommandLine()
886  {
887  return $this->commandline;
888  }
889 
897  public function setCommandLine($commandline)
898  {
899  $this->commandline = $commandline;
900 
901  return $this;
902  }
903 
909  public function getTimeout()
910  {
911  return $this->timeout;
912  }
913 
919  public function getIdleTimeout()
920  {
921  return $this->idleTimeout;
922  }
923 
935  public function setTimeout($timeout)
936  {
937  $this->timeout = $this->validateTimeout($timeout);
938 
939  return $this;
940  }
941 
954  public function setIdleTimeout($timeout)
955  {
956  if (null !== $timeout && $this->outputDisabled) {
957  throw new LogicException('Idle timeout can not be set while the output is disabled.');
958  }
959 
960  $this->idleTimeout = $this->validateTimeout($timeout);
961 
962  return $this;
963  }
964 
974  public function setTty($tty)
975  {
976  if ('\\' === DIRECTORY_SEPARATOR && $tty) {
977  throw new RuntimeException('TTY mode is not supported on Windows platform.');
978  }
979  if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) {
980  throw new RuntimeException('TTY mode requires /dev/tty to be readable.');
981  }
982 
983  $this->tty = (bool) $tty;
984 
985  return $this;
986  }
987 
993  public function isTty()
994  {
995  return $this->tty;
996  }
997 
1005  public function setPty($bool)
1006  {
1007  $this->pty = (bool) $bool;
1008 
1009  return $this;
1010  }
1011 
1017  public function isPty()
1018  {
1019  return $this->pty;
1020  }
1021 
1027  public function getWorkingDirectory()
1028  {
1029  if (null === $this->cwd) {
1030  // getcwd() will return false if any one of the parent directories does not have
1031  // the readable or search mode set, even if the current directory does
1032  return getcwd() ?: null;
1033  }
1034 
1035  return $this->cwd;
1036  }
1037 
1045  public function setWorkingDirectory($cwd)
1046  {
1047  $this->cwd = $cwd;
1048 
1049  return $this;
1050  }
1051 
1057  public function getEnv()
1058  {
1059  return $this->env;
1060  }
1061 
1075  public function setEnv(array $env)
1076  {
1077  // Process can not handle env values that are arrays
1078  $env = array_filter($env, function ($value) {
1079  return !is_array($value);
1080  });
1081 
1082  $this->env = array();
1083  foreach ($env as $key => $value) {
1084  $this->env[$key] = (string) $value;
1085  }
1086 
1087  return $this;
1088  }
1089 
1095  public function getInput()
1096  {
1097  return $this->input;
1098  }
1099 
1111  public function setInput($input)
1112  {
1113  if ($this->isRunning()) {
1114  throw new LogicException('Input can not be set while the process is running.');
1115  }
1116 
1117  $this->input = ProcessUtils::validateInput(__METHOD__, $input);
1118 
1119  return $this;
1120  }
1121 
1127  public function getOptions()
1128  {
1129  return $this->options;
1130  }
1131 
1139  public function setOptions(array $options)
1140  {
1141  $this->options = $options;
1142 
1143  return $this;
1144  }
1145 
1154  {
1156  }
1157 
1165  public function setEnhanceWindowsCompatibility($enhance)
1166  {
1167  $this->enhanceWindowsCompatibility = (bool) $enhance;
1168 
1169  return $this;
1170  }
1171 
1178  {
1180  }
1181 
1193  public function setEnhanceSigchildCompatibility($enhance)
1194  {
1195  $this->enhanceSigchildCompatibility = (bool) $enhance;
1196 
1197  return $this;
1198  }
1199 
1208  public function checkTimeout()
1209  {
1210  if ($this->status !== self::STATUS_STARTED) {
1211  return;
1212  }
1213 
1214  if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
1215  $this->stop(0);
1216 
1218  }
1219 
1220  if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
1221  $this->stop(0);
1222 
1224  }
1225  }
1226 
1232  public static function isPtySupported()
1233  {
1234  static $result;
1235 
1236  if (null !== $result) {
1237  return $result;
1238  }
1239 
1240  if ('\\' === DIRECTORY_SEPARATOR) {
1241  return $result = false;
1242  }
1243 
1244  return $result = (bool) @proc_open('echo 1', array(array('pty'), array('pty'), array('pty')), $pipes);
1245  }
1246 
1252  private function getDescriptors()
1253  {
1254  if ($this->input instanceof \Iterator) {
1255  $this->input->rewind();
1256  }
1257  if ('\\' === DIRECTORY_SEPARATOR) {
1258  $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
1259  } else {
1260  $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
1261  }
1262 
1263  return $this->processPipes->getDescriptors();
1264  }
1265 
1276  protected function buildCallback(callable $callback = null)
1277  {
1278  if ($this->outputDisabled) {
1279  return function ($type, $data) use ($callback) {
1280  if (null !== $callback) {
1281  call_user_func($callback, $type, $data);
1282  }
1283  };
1284  }
1285 
1286  $out = self::OUT;
1287 
1288  return function ($type, $data) use ($callback, $out) {
1289  if ($out == $type) {
1290  $this->addOutput($data);
1291  } else {
1292  $this->addErrorOutput($data);
1293  }
1294 
1295  if (null !== $callback) {
1296  call_user_func($callback, $type, $data);
1297  }
1298  };
1299  }
1300 
1306  protected function updateStatus($blocking)
1307  {
1308  if (self::STATUS_STARTED !== $this->status) {
1309  return;
1310  }
1311 
1312  $this->processInformation = proc_get_status($this->process);
1313  $running = $this->processInformation['running'];
1314 
1315  $this->readPipes($running && $blocking, '\\' !== DIRECTORY_SEPARATOR || !$running);
1316 
1317  if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1318  $this->processInformation = $this->fallbackStatus + $this->processInformation;
1319  }
1320 
1321  if (!$running) {
1322  $this->close();
1323  }
1324  }
1325 
1331  protected function isSigchildEnabled()
1332  {
1333  if (null !== self::$sigchild) {
1334  return self::$sigchild;
1335  }
1336 
1337  if (!function_exists('phpinfo') || defined('HHVM_VERSION')) {
1338  return self::$sigchild = false;
1339  }
1340 
1341  ob_start();
1342  phpinfo(INFO_GENERAL);
1343 
1344  return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
1345  }
1346 
1355  private function readPipesForOutput($caller, $blocking = false)
1356  {
1357  if ($this->outputDisabled) {
1358  throw new LogicException('Output has been disabled.');
1359  }
1360 
1361  $this->requireProcessIsStarted($caller);
1362 
1363  $this->updateStatus($blocking);
1364  }
1365 
1375  private function validateTimeout($timeout)
1376  {
1377  $timeout = (float) $timeout;
1378 
1379  if (0.0 === $timeout) {
1380  $timeout = null;
1381  } elseif ($timeout < 0) {
1382  throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
1383  }
1384 
1385  return $timeout;
1386  }
1387 
1394  private function readPipes($blocking, $close)
1395  {
1396  $result = $this->processPipes->readAndWrite($blocking, $close);
1397 
1399  foreach ($result as $type => $data) {
1400  if (3 !== $type) {
1401  $callback($type === self::STDOUT ? self::OUT : self::ERR, $data);
1402  } elseif (!isset($this->fallbackStatus['signaled'])) {
1403  $this->fallbackStatus['exitcode'] = (int) $data;
1404  }
1405  }
1406  }
1407 
1413  private function close()
1414  {
1415  $this->processPipes->close();
1416  if (is_resource($this->process)) {
1417  proc_close($this->process);
1418  }
1419  $this->exitcode = $this->processInformation['exitcode'];
1420  $this->status = self::STATUS_TERMINATED;
1421 
1422  if (-1 === $this->exitcode) {
1423  if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
1424  // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
1425  $this->exitcode = 128 + $this->processInformation['termsig'];
1426  } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1427  $this->processInformation['signaled'] = true;
1428  $this->processInformation['termsig'] = -1;
1429  }
1430  }
1431 
1432  // Free memory from self-reference callback created by buildCallback
1433  // Doing so in other contexts like __destruct or by garbage collector is ineffective
1434  // Now pipes are closed, so the callback is no longer necessary
1435  $this->callback = null;
1436 
1437  return $this->exitcode;
1438  }
1439 
1443  private function resetProcessData()
1444  {
1445  $this->starttime = null;
1446  $this->callback = null;
1447  $this->exitcode = null;
1448  $this->fallbackStatus = array();
1449  $this->processInformation = null;
1450  $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
1451  $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
1452  $this->process = null;
1453  $this->latestSignal = null;
1454  $this->status = self::STATUS_READY;
1455  $this->incrementalOutputOffset = 0;
1456  $this->incrementalErrorOutputOffset = 0;
1457  }
1458 
1471  private function doSignal($signal, $throwException)
1472  {
1473  if (null === $pid = $this->getPid()) {
1474  if ($throwException) {
1475  throw new LogicException('Can not send signal on a non running process.');
1476  }
1477 
1478  return false;
1479  }
1480 
1481  if ('\\' === DIRECTORY_SEPARATOR) {
1482  exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
1483  if ($exitCode && $this->isRunning()) {
1484  if ($throwException) {
1485  throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
1486  }
1487 
1488  return false;
1489  }
1490  } else {
1491  if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) {
1492  $ok = @proc_terminate($this->process, $signal);
1493  } elseif (function_exists('posix_kill')) {
1494  $ok = @posix_kill($pid, $signal);
1495  } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), array(2 => array('pipe', 'w')), $pipes)) {
1496  $ok = false === fgets($pipes[2]);
1497  }
1498  if (!$ok) {
1499  if ($throwException) {
1500  throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
1501  }
1502 
1503  return false;
1504  }
1505  }
1506 
1507  $this->latestSignal = (int) $signal;
1508  $this->fallbackStatus['signaled'] = true;
1509  $this->fallbackStatus['exitcode'] = -1;
1510  $this->fallbackStatus['termsig'] = $this->latestSignal;
1511 
1512  return true;
1513  }
1514 
1522  private function requireProcessIsStarted($functionName)
1523  {
1524  if (!$this->isStarted()) {
1525  throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
1526  }
1527  }
1528 
1536  private function requireProcessIsTerminated($functionName)
1537  {
1538  if (!$this->isTerminated()) {
1539  throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
1540  }
1541  }
1542 }
run($callback=null)
Runs the process.
Definition: Process.php:205
setEnv(array $env)
Sets the environment variables.
Definition: Process.php:1075
getOutput()
Returns the current output of the process (STDOUT).
Definition: Process.php:472
getEnhanceWindowsCompatibility()
Gets whether or not Windows compatibility is enabled.
Definition: Process.php:1153
getEnhanceSigchildCompatibility()
Returns whether sigchild compatibility mode is activated or not.
Definition: Process.php:1177
getInput()
Gets the Process input.
Definition: Process.php:1095
clearErrorOutput()
Clears the process output.
Definition: Process.php:628
$result
getErrorOutput()
Returns the current error output of the process (STDERR).
Definition: Process.php:586
addOutput($line)
Adds a line to the STDOUT stream.
Definition: Process.php:855
requireProcessIsTerminated($functionName)
Ensures the process is terminated, throws a LogicException if the process has a status different than...
Definition: Process.php:1536
hasBeenSignaled()
Returns true if the child process has been terminated by an uncaught signal.
Definition: Process.php:695
getWorkingDirectory()
Gets the working directory.
Definition: Process.php:1027
requireProcessIsStarted($functionName)
Ensures the process is running or terminated, throws a LogicException if the process has a not starte...
Definition: Process.php:1522
getTimeout()
Gets the process timeout (max.
Definition: Process.php:909
getTermSignal()
Returns the number of the signal that caused the child process to terminate its execution.
Definition: Process.php:716
doSignal($signal, $throwException)
Sends a POSIX signal to the process.
Definition: Process.php:1471
isOutputDisabled()
Returns true in case the output is disabled, false otherwise.
Definition: Process.php:459
resetProcessData()
Resets data related to the latest run of the process.
Definition: Process.php:1443
Exception that is thrown when a process times out.
setTimeout($timeout)
Sets the process timeout (max.
Definition: Process.php:935
getIdleTimeout()
Gets the process idle timeout (max.
Definition: Process.php:919
Add rich text string
The name of the decorator.
wait(callable $callback=null)
Waits for the process to terminate.
Definition: Process.php:355
enableOutput()
Enables fetching output and error output from the underlying process.
Definition: Process.php:443
getStopSignal()
Returns the number of the signal that caused the child process to stop its execution.
Definition: Process.php:752
signal($signal)
Sends a POSIX signal to the process.
Definition: Process.php:407
getOptions()
Gets the options for proc_open.
Definition: Process.php:1127
isSuccessful()
Checks if the process ended successfully.
Definition: Process.php:680
setEnhanceWindowsCompatibility($enhance)
Sets whether or not Windows compatibility is enabled.
Definition: Process.php:1165
validateTimeout($timeout)
Validates and returns the filtered timeout.
Definition: Process.php:1375
UnixPipes implementation uses unix pipes as handles.
Definition: UnixPipes.php:23
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\+" &#(? foreach( $entity_files as $file) $output
hasBeenStopped()
Returns true if the child process has been stopped by a signal.
Definition: Process.php:736
__construct($commandline, $cwd=null, array $env=null, $input=null, $timeout=60, array $options=array())
Constructor.
Definition: Process.php:147
getStatus()
Gets the process status.
Definition: Process.php:804
readPipes($blocking, $close)
Reads pipes, executes callback.
Definition: Process.php:1394
disableOutput()
Disables fetching output and error output from the underlying process.
Definition: Process.php:422
updateStatus($blocking)
Updates the status of the process, reads pipes.
Definition: Process.php:1306
input
Definition: langcheck.php:166
isRunning()
Checks if the process is currently running.
Definition: Process.php:764
isTerminated()
Checks if the process is terminated.
Definition: Process.php:790
setEnhanceSigchildCompatibility($enhance)
Activates sigchild compatibility mode.
Definition: Process.php:1193
isStarted()
Checks if the process has been started with no regard to the current state.
Definition: Process.php:780
getExitCode()
Returns the exit code returned by the process.
Definition: Process.php:644
checkTimeout()
Performs a check between the timeout definition and the time the process started. ...
Definition: Process.php:1208
setIdleTimeout($timeout)
Sets the process idle timeout (max.
Definition: Process.php:954
static validateInput($caller, $input)
Validates and normalizes a Process input.
setCommandLine($commandline)
Sets the command line to be executed.
Definition: Process.php:897
Create styles array
The data for the language used.
LogicException for the Process Component.
restart(callable $callback=null)
Restarts the process.
Definition: Process.php:328
getCommandLine()
Gets the command line to be executed.
Definition: Process.php:885
getIterator($flags=0)
Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
Definition: Process.php:518
close()
Closes process resource, closes file handles, sets the exitcode.
Definition: Process.php:1413
getPid()
Returns the Pid (process identifier), if applicable.
Definition: Process.php:391
readPipesForOutput($caller, $blocking=false)
Reads pipes for the freshest output.
Definition: Process.php:1355
getIncrementalErrorOutput()
Returns the errorOutput incrementally.
Definition: Process.php:609
static isPtySupported()
Returns whether PTY is supported on the current operating system.
Definition: Process.php:1232
static escapeArgument($argument)
Escapes a string to be used as a shell argument.
isSigchildEnabled()
Returns whether PHP has been compiled with the &#39;–enable-sigchild&#39; option or not. ...
Definition: Process.php:1331
Process is a thin wrapper around proc_* functions to easily start independent PHP processes...
Definition: Process.php:30
start(callable $callback=null)
Starts the process and returns after writing the input to STDIN.
Definition: Process.php:257
getEnv()
Gets the environment variables.
Definition: Process.php:1057
getExitCodeText()
Returns a string representation for the exit code returned by the process.
Definition: Process.php:666
setInput($input)
Sets the input.
Definition: Process.php:1111
setPty($bool)
Sets PTY mode.
Definition: Process.php:1005
buildCallback(callable $callback=null)
Builds up the callback used by wait().
Definition: Process.php:1276
getIncrementalOutput()
Returns the output incrementally.
Definition: Process.php:494
$ret
Definition: parser.php:6
isTty()
Checks if the TTY mode is enabled.
Definition: Process.php:993
stop($timeout=10, $signal=null)
Stops the process.
Definition: Process.php:819
isPty()
Returns PTY state.
Definition: Process.php:1017
defined( 'APPLICATION_ENV')||define( 'APPLICATION_ENV'
Definition: bootstrap.php:27
setWorkingDirectory($cwd)
Sets the current working directory.
Definition: Process.php:1045
clearOutput()
Clears the process output.
Definition: Process.php:569
getDescriptors()
Creates the descriptors needed by the proc_open.
Definition: Process.php:1252
mustRun(callable $callback=null)
Runs the process.
Definition: Process.php:225
setOptions(array $options)
Sets the options for proc_open.
Definition: Process.php:1139
addErrorOutput($line)
Adds a line to the STDERR stream.
Definition: Process.php:871
WindowsPipes implementation uses temporary files as handles.
setTty($tty)
Enables or disables the TTY mode.
Definition: Process.php:974