Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / symfony / process / Process.php
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
12 namespace Symfony\Component\Process;
13
14 use Symfony\Component\Process\Exception\InvalidArgumentException;
15 use Symfony\Component\Process\Exception\LogicException;
16 use Symfony\Component\Process\Exception\ProcessFailedException;
17 use Symfony\Component\Process\Exception\ProcessTimedOutException;
18 use Symfony\Component\Process\Exception\RuntimeException;
19 use Symfony\Component\Process\Pipes\PipesInterface;
20 use Symfony\Component\Process\Pipes\UnixPipes;
21 use Symfony\Component\Process\Pipes\WindowsPipes;
22
23 /**
24  * Process is a thin wrapper around proc_* functions to easily
25  * start independent PHP processes.
26  *
27  * @author Fabien Potencier <fabien@symfony.com>
28  * @author Romain Neutron <imprec@gmail.com>
29  */
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 = array('suppress_errors' => true);
62     private $exitcode;
63     private $fallbackStatus = array();
64     private $processInformation;
65     private $outputDisabled = false;
66     private $stdout;
67     private $stderr;
68     private $enhanceWindowsCompatibility = true;
69     private $enhanceSigchildCompatibility;
70     private $process;
71     private $status = self::STATUS_READY;
72     private $incrementalOutputOffset = 0;
73     private $incrementalErrorOutputOffset = 0;
74     private $tty;
75     private $pty;
76     private $inheritEnv = false;
77
78     private $useFileHandles = false;
79     /** @var PipesInterface */
80     private $processPipes;
81
82     private $latestSignal;
83
84     private static $sigchild;
85
86     /**
87      * Exit codes translation table.
88      *
89      * User-defined errors must use exit codes in the 64-113 range.
90      */
91     public static $exitCodes = array(
92         0 => 'OK',
93         1 => 'General error',
94         2 => 'Misuse of shell builtins',
95
96         126 => 'Invoked command cannot execute',
97         127 => 'Command not found',
98         128 => 'Invalid exit argument',
99
100         // signals
101         129 => 'Hangup',
102         130 => 'Interrupt',
103         131 => 'Quit and dump core',
104         132 => 'Illegal instruction',
105         133 => 'Trace/breakpoint trap',
106         134 => 'Process aborted',
107         135 => 'Bus error: "access to undefined portion of memory object"',
108         136 => 'Floating point exception: "erroneous arithmetic operation"',
109         137 => 'Kill (terminate immediately)',
110         138 => 'User-defined 1',
111         139 => 'Segmentation violation',
112         140 => 'User-defined 2',
113         141 => 'Write to pipe with no one reading',
114         142 => 'Signal raised by alarm',
115         143 => 'Termination (request to terminate)',
116         // 144 - not defined
117         145 => 'Child process terminated, stopped (or continued*)',
118         146 => 'Continue if stopped',
119         147 => 'Stop executing temporarily',
120         148 => 'Terminal stop signal',
121         149 => 'Background process attempting to read from tty ("in")',
122         150 => 'Background process attempting to write to tty ("out")',
123         151 => 'Urgent data available on socket',
124         152 => 'CPU time limit exceeded',
125         153 => 'File size limit exceeded',
126         154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
127         155 => 'Profiling timer expired',
128         // 156 - not defined
129         157 => 'Pollable event',
130         // 158 - not defined
131         159 => 'Bad syscall',
132     );
133
134     /**
135      * @param string|array   $commandline The command line to run
136      * @param string|null    $cwd         The working directory or null to use the working dir of the current PHP process
137      * @param array|null     $env         The environment variables or null to use the same environment as the current PHP process
138      * @param mixed|null     $input       The input as stream resource, scalar or \Traversable, or null for no input
139      * @param int|float|null $timeout     The timeout in seconds or null to disable
140      * @param array          $options     An array of options for proc_open
141      *
142      * @throws RuntimeException When proc_open is not installed
143      */
144     public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null)
145     {
146         if (!function_exists('proc_open')) {
147             throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
148         }
149
150         $this->commandline = $commandline;
151         $this->cwd = $cwd;
152
153         // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
154         // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
155         // @see : https://bugs.php.net/bug.php?id=51800
156         // @see : https://bugs.php.net/bug.php?id=50524
157         if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) {
158             $this->cwd = getcwd();
159         }
160         if (null !== $env) {
161             $this->setEnv($env);
162         }
163
164         $this->setInput($input);
165         $this->setTimeout($timeout);
166         $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR;
167         $this->pty = false;
168         $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
169         if (null !== $options) {
170             @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);
171             $this->options = array_replace($this->options, $options);
172         }
173     }
174
175     public function __destruct()
176     {
177         $this->stop(0);
178     }
179
180     public function __clone()
181     {
182         $this->resetProcessData();
183     }
184
185     /**
186      * Runs the process.
187      *
188      * The callback receives the type of output (out or err) and
189      * some bytes from the output in real-time. It allows to have feedback
190      * from the independent process during execution.
191      *
192      * The STDOUT and STDERR are also available after the process is finished
193      * via the getOutput() and getErrorOutput() methods.
194      *
195      * @param callable|null $callback A PHP callback to run whenever there is some
196      *                                output available on STDOUT or STDERR
197      * @param array         $env      An array of additional env vars to set when running the process
198      *
199      * @return int The exit status code
200      *
201      * @throws RuntimeException When process can't be launched
202      * @throws RuntimeException When process stopped after receiving signal
203      * @throws LogicException   In case a callback is provided and output has been disabled
204      *
205      * @final since version 3.3
206      */
207     public function run($callback = null/*, array $env = array()*/)
208     {
209         $env = 1 < func_num_args() ? func_get_arg(1) : null;
210         $this->start($callback, $env);
211
212         return $this->wait();
213     }
214
215     /**
216      * Runs the process.
217      *
218      * This is identical to run() except that an exception is thrown if the process
219      * exits with a non-zero exit code.
220      *
221      * @param callable|null $callback
222      * @param array         $env      An array of additional env vars to set when running the process
223      *
224      * @return self
225      *
226      * @throws RuntimeException       if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled
227      * @throws ProcessFailedException if the process didn't terminate successfully
228      *
229      * @final since version 3.3
230      */
231     public function mustRun(callable $callback = null/*, array $env = array()*/)
232     {
233         if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
234             throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
235         }
236         $env = 1 < func_num_args() ? func_get_arg(1) : null;
237
238         if (0 !== $this->run($callback, $env)) {
239             throw new ProcessFailedException($this);
240         }
241
242         return $this;
243     }
244
245     /**
246      * Starts the process and returns after writing the input to STDIN.
247      *
248      * This method blocks until all STDIN data is sent to the process then it
249      * returns while the process runs in the background.
250      *
251      * The termination of the process can be awaited with wait().
252      *
253      * The callback receives the type of output (out or err) and some bytes from
254      * the output in real-time while writing the standard input to the process.
255      * It allows to have feedback from the independent process during execution.
256      *
257      * @param callable|null $callback A PHP callback to run whenever there is some
258      *                                output available on STDOUT or STDERR
259      * @param array         $env      An array of additional env vars to set when running the process
260      *
261      * @throws RuntimeException When process can't be launched
262      * @throws RuntimeException When process is already running
263      * @throws LogicException   In case a callback is provided and output has been disabled
264      */
265     public function start(callable $callback = null/*, array $env = array()*/)
266     {
267         if ($this->isRunning()) {
268             throw new RuntimeException('Process is already running');
269         }
270         if (2 <= func_num_args()) {
271             $env = func_get_arg(1);
272         } else {
273             if (__CLASS__ !== static::class) {
274                 $r = new \ReflectionMethod($this, __FUNCTION__);
275                 if (__CLASS__ !== $r->getDeclaringClass()->getName() && (2 > $r->getNumberOfParameters() || 'env' !== $r->getParameters()[0]->name)) {
276                     @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);
277                 }
278             }
279             $env = null;
280         }
281
282         $this->resetProcessData();
283         $this->starttime = $this->lastOutputTime = microtime(true);
284         $this->callback = $this->buildCallback($callback);
285         $this->hasCallback = null !== $callback;
286         $descriptors = $this->getDescriptors();
287         $inheritEnv = $this->inheritEnv;
288
289         if (is_array($commandline = $this->commandline)) {
290             $commandline = implode(' ', array_map(array($this, 'escapeArgument'), $commandline));
291
292             if ('\\' !== DIRECTORY_SEPARATOR) {
293                 // exec is mandatory to deal with sending a signal to the process
294                 $commandline = 'exec '.$commandline;
295             }
296         }
297
298         if (null === $env) {
299             $env = $this->env;
300         } else {
301             if ($this->env) {
302                 $env += $this->env;
303             }
304             $inheritEnv = true;
305         }
306
307         if (null !== $env && $inheritEnv) {
308             $env += $this->getDefaultEnv();
309         } elseif (null !== $env) {
310             @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);
311         } else {
312             $env = $this->getDefaultEnv();
313         }
314         if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
315             $this->options['bypass_shell'] = true;
316             $commandline = $this->prepareWindowsCommandLine($commandline, $env);
317         } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
318             // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
319             $descriptors[3] = array('pipe', 'w');
320
321             // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
322             $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
323             $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
324
325             // Workaround for the bug, when PTS functionality is enabled.
326             // @see : https://bugs.php.net/69442
327             $ptsWorkaround = fopen(__FILE__, 'r');
328         }
329         if (defined('HHVM_VERSION')) {
330             $envPairs = $env;
331         } else {
332             $envPairs = array();
333             foreach ($env as $k => $v) {
334                 if (false !== $v) {
335                     $envPairs[] = $k.'='.$v;
336                 }
337             }
338         }
339
340         if (!is_dir($this->cwd)) {
341             @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);
342         }
343
344         $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
345
346         if (!is_resource($this->process)) {
347             throw new RuntimeException('Unable to launch a new process.');
348         }
349         $this->status = self::STATUS_STARTED;
350
351         if (isset($descriptors[3])) {
352             $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
353         }
354
355         if ($this->tty) {
356             return;
357         }
358
359         $this->updateStatus(false);
360         $this->checkTimeout();
361     }
362
363     /**
364      * Restarts the process.
365      *
366      * Be warned that the process is cloned before being started.
367      *
368      * @param callable|null $callback A PHP callback to run whenever there is some
369      *                                output available on STDOUT or STDERR
370      * @param array         $env      An array of additional env vars to set when running the process
371      *
372      * @return $this
373      *
374      * @throws RuntimeException When process can't be launched
375      * @throws RuntimeException When process is already running
376      *
377      * @see start()
378      *
379      * @final since version 3.3
380      */
381     public function restart(callable $callback = null/*, array $env = array()*/)
382     {
383         if ($this->isRunning()) {
384             throw new RuntimeException('Process is already running');
385         }
386         $env = 1 < func_num_args() ? func_get_arg(1) : null;
387
388         $process = clone $this;
389         $process->start($callback, $env);
390
391         return $process;
392     }
393
394     /**
395      * Waits for the process to terminate.
396      *
397      * The callback receives the type of output (out or err) and some bytes
398      * from the output in real-time while writing the standard input to the process.
399      * It allows to have feedback from the independent process during execution.
400      *
401      * @param callable|null $callback A valid PHP callback
402      *
403      * @return int The exitcode of the process
404      *
405      * @throws RuntimeException When process timed out
406      * @throws RuntimeException When process stopped after receiving signal
407      * @throws LogicException   When process is not yet started
408      */
409     public function wait(callable $callback = null)
410     {
411         $this->requireProcessIsStarted(__FUNCTION__);
412
413         $this->updateStatus(false);
414
415         if (null !== $callback) {
416             if (!$this->processPipes->haveReadSupport()) {
417                 $this->stop(0);
418                 throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait');
419             }
420             $this->callback = $this->buildCallback($callback);
421         }
422
423         do {
424             $this->checkTimeout();
425             $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
426             $this->readPipes($running, '\\' !== DIRECTORY_SEPARATOR || !$running);
427         } while ($running);
428
429         while ($this->isRunning()) {
430             usleep(1000);
431         }
432
433         if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
434             throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
435         }
436
437         return $this->exitcode;
438     }
439
440     /**
441      * Returns the Pid (process identifier), if applicable.
442      *
443      * @return int|null The process id if running, null otherwise
444      */
445     public function getPid()
446     {
447         return $this->isRunning() ? $this->processInformation['pid'] : null;
448     }
449
450     /**
451      * Sends a POSIX signal to the process.
452      *
453      * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
454      *
455      * @return $this
456      *
457      * @throws LogicException   In case the process is not running
458      * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
459      * @throws RuntimeException In case of failure
460      */
461     public function signal($signal)
462     {
463         $this->doSignal($signal, true);
464
465         return $this;
466     }
467
468     /**
469      * Disables fetching output and error output from the underlying process.
470      *
471      * @return $this
472      *
473      * @throws RuntimeException In case the process is already running
474      * @throws LogicException   if an idle timeout is set
475      */
476     public function disableOutput()
477     {
478         if ($this->isRunning()) {
479             throw new RuntimeException('Disabling output while the process is running is not possible.');
480         }
481         if (null !== $this->idleTimeout) {
482             throw new LogicException('Output can not be disabled while an idle timeout is set.');
483         }
484
485         $this->outputDisabled = true;
486
487         return $this;
488     }
489
490     /**
491      * Enables fetching output and error output from the underlying process.
492      *
493      * @return $this
494      *
495      * @throws RuntimeException In case the process is already running
496      */
497     public function enableOutput()
498     {
499         if ($this->isRunning()) {
500             throw new RuntimeException('Enabling output while the process is running is not possible.');
501         }
502
503         $this->outputDisabled = false;
504
505         return $this;
506     }
507
508     /**
509      * Returns true in case the output is disabled, false otherwise.
510      *
511      * @return bool
512      */
513     public function isOutputDisabled()
514     {
515         return $this->outputDisabled;
516     }
517
518     /**
519      * Returns the current output of the process (STDOUT).
520      *
521      * @return string The process output
522      *
523      * @throws LogicException in case the output has been disabled
524      * @throws LogicException In case the process is not started
525      */
526     public function getOutput()
527     {
528         $this->readPipesForOutput(__FUNCTION__);
529
530         if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
531             return '';
532         }
533
534         return $ret;
535     }
536
537     /**
538      * Returns the output incrementally.
539      *
540      * In comparison with the getOutput method which always return the whole
541      * output, this one returns the new output since the last call.
542      *
543      * @return string The process output since the last call
544      *
545      * @throws LogicException in case the output has been disabled
546      * @throws LogicException In case the process is not started
547      */
548     public function getIncrementalOutput()
549     {
550         $this->readPipesForOutput(__FUNCTION__);
551
552         $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
553         $this->incrementalOutputOffset = ftell($this->stdout);
554
555         if (false === $latest) {
556             return '';
557         }
558
559         return $latest;
560     }
561
562     /**
563      * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
564      *
565      * @param int $flags A bit field of Process::ITER_* flags
566      *
567      * @throws LogicException in case the output has been disabled
568      * @throws LogicException In case the process is not started
569      *
570      * @return \Generator
571      */
572     public function getIterator($flags = 0)
573     {
574         $this->readPipesForOutput(__FUNCTION__, false);
575
576         $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
577         $blocking = !(self::ITER_NON_BLOCKING & $flags);
578         $yieldOut = !(self::ITER_SKIP_OUT & $flags);
579         $yieldErr = !(self::ITER_SKIP_ERR & $flags);
580
581         while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
582             if ($yieldOut) {
583                 $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
584
585                 if (isset($out[0])) {
586                     if ($clearOutput) {
587                         $this->clearOutput();
588                     } else {
589                         $this->incrementalOutputOffset = ftell($this->stdout);
590                     }
591
592                     yield self::OUT => $out;
593                 }
594             }
595
596             if ($yieldErr) {
597                 $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
598
599                 if (isset($err[0])) {
600                     if ($clearOutput) {
601                         $this->clearErrorOutput();
602                     } else {
603                         $this->incrementalErrorOutputOffset = ftell($this->stderr);
604                     }
605
606                     yield self::ERR => $err;
607                 }
608             }
609
610             if (!$blocking && !isset($out[0]) && !isset($err[0])) {
611                 yield self::OUT => '';
612             }
613
614             $this->checkTimeout();
615             $this->readPipesForOutput(__FUNCTION__, $blocking);
616         }
617     }
618
619     /**
620      * Clears the process output.
621      *
622      * @return $this
623      */
624     public function clearOutput()
625     {
626         ftruncate($this->stdout, 0);
627         fseek($this->stdout, 0);
628         $this->incrementalOutputOffset = 0;
629
630         return $this;
631     }
632
633     /**
634      * Returns the current error output of the process (STDERR).
635      *
636      * @return string The process error output
637      *
638      * @throws LogicException in case the output has been disabled
639      * @throws LogicException In case the process is not started
640      */
641     public function getErrorOutput()
642     {
643         $this->readPipesForOutput(__FUNCTION__);
644
645         if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
646             return '';
647         }
648
649         return $ret;
650     }
651
652     /**
653      * Returns the errorOutput incrementally.
654      *
655      * In comparison with the getErrorOutput method which always return the
656      * whole error output, this one returns the new error output since the last
657      * call.
658      *
659      * @return string The process error output since the last call
660      *
661      * @throws LogicException in case the output has been disabled
662      * @throws LogicException In case the process is not started
663      */
664     public function getIncrementalErrorOutput()
665     {
666         $this->readPipesForOutput(__FUNCTION__);
667
668         $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
669         $this->incrementalErrorOutputOffset = ftell($this->stderr);
670
671         if (false === $latest) {
672             return '';
673         }
674
675         return $latest;
676     }
677
678     /**
679      * Clears the process output.
680      *
681      * @return $this
682      */
683     public function clearErrorOutput()
684     {
685         ftruncate($this->stderr, 0);
686         fseek($this->stderr, 0);
687         $this->incrementalErrorOutputOffset = 0;
688
689         return $this;
690     }
691
692     /**
693      * Returns the exit code returned by the process.
694      *
695      * @return null|int The exit status code, null if the Process is not terminated
696      *
697      * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
698      */
699     public function getExitCode()
700     {
701         if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
702             throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
703         }
704
705         $this->updateStatus(false);
706
707         return $this->exitcode;
708     }
709
710     /**
711      * Returns a string representation for the exit code returned by the process.
712      *
713      * This method relies on the Unix exit code status standardization
714      * and might not be relevant for other operating systems.
715      *
716      * @return null|string A string representation for the exit status code, null if the Process is not terminated
717      *
718      * @see http://tldp.org/LDP/abs/html/exitcodes.html
719      * @see http://en.wikipedia.org/wiki/Unix_signal
720      */
721     public function getExitCodeText()
722     {
723         if (null === $exitcode = $this->getExitCode()) {
724             return;
725         }
726
727         return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
728     }
729
730     /**
731      * Checks if the process ended successfully.
732      *
733      * @return bool true if the process ended successfully, false otherwise
734      */
735     public function isSuccessful()
736     {
737         return 0 === $this->getExitCode();
738     }
739
740     /**
741      * Returns true if the child process has been terminated by an uncaught signal.
742      *
743      * It always returns false on Windows.
744      *
745      * @return bool
746      *
747      * @throws RuntimeException In case --enable-sigchild is activated
748      * @throws LogicException   In case the process is not terminated
749      */
750     public function hasBeenSignaled()
751     {
752         $this->requireProcessIsTerminated(__FUNCTION__);
753
754         if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
755             throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
756         }
757
758         return $this->processInformation['signaled'];
759     }
760
761     /**
762      * Returns the number of the signal that caused the child process to terminate its execution.
763      *
764      * It is only meaningful if hasBeenSignaled() returns true.
765      *
766      * @return int
767      *
768      * @throws RuntimeException In case --enable-sigchild is activated
769      * @throws LogicException   In case the process is not terminated
770      */
771     public function getTermSignal()
772     {
773         $this->requireProcessIsTerminated(__FUNCTION__);
774
775         if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) {
776             throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
777         }
778
779         return $this->processInformation['termsig'];
780     }
781
782     /**
783      * Returns true if the child process has been stopped by a signal.
784      *
785      * It always returns false on Windows.
786      *
787      * @return bool
788      *
789      * @throws LogicException In case the process is not terminated
790      */
791     public function hasBeenStopped()
792     {
793         $this->requireProcessIsTerminated(__FUNCTION__);
794
795         return $this->processInformation['stopped'];
796     }
797
798     /**
799      * Returns the number of the signal that caused the child process to stop its execution.
800      *
801      * It is only meaningful if hasBeenStopped() returns true.
802      *
803      * @return int
804      *
805      * @throws LogicException In case the process is not terminated
806      */
807     public function getStopSignal()
808     {
809         $this->requireProcessIsTerminated(__FUNCTION__);
810
811         return $this->processInformation['stopsig'];
812     }
813
814     /**
815      * Checks if the process is currently running.
816      *
817      * @return bool true if the process is currently running, false otherwise
818      */
819     public function isRunning()
820     {
821         if (self::STATUS_STARTED !== $this->status) {
822             return false;
823         }
824
825         $this->updateStatus(false);
826
827         return $this->processInformation['running'];
828     }
829
830     /**
831      * Checks if the process has been started with no regard to the current state.
832      *
833      * @return bool true if status is ready, false otherwise
834      */
835     public function isStarted()
836     {
837         return self::STATUS_READY != $this->status;
838     }
839
840     /**
841      * Checks if the process is terminated.
842      *
843      * @return bool true if process is terminated, false otherwise
844      */
845     public function isTerminated()
846     {
847         $this->updateStatus(false);
848
849         return self::STATUS_TERMINATED == $this->status;
850     }
851
852     /**
853      * Gets the process status.
854      *
855      * The status is one of: ready, started, terminated.
856      *
857      * @return string The current process status
858      */
859     public function getStatus()
860     {
861         $this->updateStatus(false);
862
863         return $this->status;
864     }
865
866     /**
867      * Stops the process.
868      *
869      * @param int|float $timeout The timeout in seconds
870      * @param int       $signal  A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
871      *
872      * @return int The exit-code of the process
873      */
874     public function stop($timeout = 10, $signal = null)
875     {
876         $timeoutMicro = microtime(true) + $timeout;
877         if ($this->isRunning()) {
878             // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
879             $this->doSignal(15, false);
880             do {
881                 usleep(1000);
882             } while ($this->isRunning() && microtime(true) < $timeoutMicro);
883
884             if ($this->isRunning()) {
885                 // Avoid exception here: process is supposed to be running, but it might have stopped just
886                 // after this line. In any case, let's silently discard the error, we cannot do anything.
887                 $this->doSignal($signal ?: 9, false);
888             }
889         }
890
891         if ($this->isRunning()) {
892             if (isset($this->fallbackStatus['pid'])) {
893                 unset($this->fallbackStatus['pid']);
894
895                 return $this->stop(0, $signal);
896             }
897             $this->close();
898         }
899
900         return $this->exitcode;
901     }
902
903     /**
904      * Adds a line to the STDOUT stream.
905      *
906      * @internal
907      *
908      * @param string $line The line to append
909      */
910     public function addOutput($line)
911     {
912         $this->lastOutputTime = microtime(true);
913
914         fseek($this->stdout, 0, SEEK_END);
915         fwrite($this->stdout, $line);
916         fseek($this->stdout, $this->incrementalOutputOffset);
917     }
918
919     /**
920      * Adds a line to the STDERR stream.
921      *
922      * @internal
923      *
924      * @param string $line The line to append
925      */
926     public function addErrorOutput($line)
927     {
928         $this->lastOutputTime = microtime(true);
929
930         fseek($this->stderr, 0, SEEK_END);
931         fwrite($this->stderr, $line);
932         fseek($this->stderr, $this->incrementalErrorOutputOffset);
933     }
934
935     /**
936      * Gets the command line to be executed.
937      *
938      * @return string The command to execute
939      */
940     public function getCommandLine()
941     {
942         return is_array($this->commandline) ? implode(' ', array_map(array($this, 'escapeArgument'), $this->commandline)) : $this->commandline;
943     }
944
945     /**
946      * Sets the command line to be executed.
947      *
948      * @param string|array $commandline The command to execute
949      *
950      * @return self The current Process instance
951      */
952     public function setCommandLine($commandline)
953     {
954         $this->commandline = $commandline;
955
956         return $this;
957     }
958
959     /**
960      * Gets the process timeout (max. runtime).
961      *
962      * @return float|null The timeout in seconds or null if it's disabled
963      */
964     public function getTimeout()
965     {
966         return $this->timeout;
967     }
968
969     /**
970      * Gets the process idle timeout (max. time since last output).
971      *
972      * @return float|null The timeout in seconds or null if it's disabled
973      */
974     public function getIdleTimeout()
975     {
976         return $this->idleTimeout;
977     }
978
979     /**
980      * Sets the process timeout (max. runtime).
981      *
982      * To disable the timeout, set this value to null.
983      *
984      * @param int|float|null $timeout The timeout in seconds
985      *
986      * @return self The current Process instance
987      *
988      * @throws InvalidArgumentException if the timeout is negative
989      */
990     public function setTimeout($timeout)
991     {
992         $this->timeout = $this->validateTimeout($timeout);
993
994         return $this;
995     }
996
997     /**
998      * Sets the process idle timeout (max. time since last output).
999      *
1000      * To disable the timeout, set this value to null.
1001      *
1002      * @param int|float|null $timeout The timeout in seconds
1003      *
1004      * @return self The current Process instance
1005      *
1006      * @throws LogicException           if the output is disabled
1007      * @throws InvalidArgumentException if the timeout is negative
1008      */
1009     public function setIdleTimeout($timeout)
1010     {
1011         if (null !== $timeout && $this->outputDisabled) {
1012             throw new LogicException('Idle timeout can not be set while the output is disabled.');
1013         }
1014
1015         $this->idleTimeout = $this->validateTimeout($timeout);
1016
1017         return $this;
1018     }
1019
1020     /**
1021      * Enables or disables the TTY mode.
1022      *
1023      * @param bool $tty True to enabled and false to disable
1024      *
1025      * @return self The current Process instance
1026      *
1027      * @throws RuntimeException In case the TTY mode is not supported
1028      */
1029     public function setTty($tty)
1030     {
1031         if ('\\' === DIRECTORY_SEPARATOR && $tty) {
1032             throw new RuntimeException('TTY mode is not supported on Windows platform.');
1033         }
1034         if ($tty) {
1035             static $isTtySupported;
1036
1037             if (null === $isTtySupported) {
1038                 $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', array(array('file', '/dev/tty', 'r'), array('file', '/dev/tty', 'w'), array('file', '/dev/tty', 'w')), $pipes);
1039             }
1040
1041             if (!$isTtySupported) {
1042                 throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
1043             }
1044         }
1045
1046         $this->tty = (bool) $tty;
1047
1048         return $this;
1049     }
1050
1051     /**
1052      * Checks if the TTY mode is enabled.
1053      *
1054      * @return bool true if the TTY mode is enabled, false otherwise
1055      */
1056     public function isTty()
1057     {
1058         return $this->tty;
1059     }
1060
1061     /**
1062      * Sets PTY mode.
1063      *
1064      * @param bool $bool
1065      *
1066      * @return self
1067      */
1068     public function setPty($bool)
1069     {
1070         $this->pty = (bool) $bool;
1071
1072         return $this;
1073     }
1074
1075     /**
1076      * Returns PTY state.
1077      *
1078      * @return bool
1079      */
1080     public function isPty()
1081     {
1082         return $this->pty;
1083     }
1084
1085     /**
1086      * Gets the working directory.
1087      *
1088      * @return string|null The current working directory or null on failure
1089      */
1090     public function getWorkingDirectory()
1091     {
1092         if (null === $this->cwd) {
1093             // getcwd() will return false if any one of the parent directories does not have
1094             // the readable or search mode set, even if the current directory does
1095             return getcwd() ?: null;
1096         }
1097
1098         return $this->cwd;
1099     }
1100
1101     /**
1102      * Sets the current working directory.
1103      *
1104      * @param string $cwd The new working directory
1105      *
1106      * @return self The current Process instance
1107      */
1108     public function setWorkingDirectory($cwd)
1109     {
1110         $this->cwd = $cwd;
1111
1112         return $this;
1113     }
1114
1115     /**
1116      * Gets the environment variables.
1117      *
1118      * @return array The current environment variables
1119      */
1120     public function getEnv()
1121     {
1122         return $this->env;
1123     }
1124
1125     /**
1126      * Sets the environment variables.
1127      *
1128      * Each environment variable value should be a string.
1129      * If it is an array, the variable is ignored.
1130      * If it is false or null, it will be removed when
1131      * env vars are otherwise inherited.
1132      *
1133      * That happens in PHP when 'argv' is registered into
1134      * the $_ENV array for instance.
1135      *
1136      * @param array $env The new environment variables
1137      *
1138      * @return self The current Process instance
1139      */
1140     public function setEnv(array $env)
1141     {
1142         // Process can not handle env values that are arrays
1143         $env = array_filter($env, function ($value) {
1144             return !is_array($value);
1145         });
1146
1147         $this->env = $env;
1148
1149         return $this;
1150     }
1151
1152     /**
1153      * Gets the Process input.
1154      *
1155      * @return resource|string|\Iterator|null The Process input
1156      */
1157     public function getInput()
1158     {
1159         return $this->input;
1160     }
1161
1162     /**
1163      * Sets the input.
1164      *
1165      * This content will be passed to the underlying process standard input.
1166      *
1167      * @param string|int|float|bool|resource|\Traversable|null $input The content
1168      *
1169      * @return self The current Process instance
1170      *
1171      * @throws LogicException In case the process is running
1172      */
1173     public function setInput($input)
1174     {
1175         if ($this->isRunning()) {
1176             throw new LogicException('Input can not be set while the process is running.');
1177         }
1178
1179         $this->input = ProcessUtils::validateInput(__METHOD__, $input);
1180
1181         return $this;
1182     }
1183
1184     /**
1185      * Gets the options for proc_open.
1186      *
1187      * @return array The current options
1188      *
1189      * @deprecated since version 3.3, to be removed in 4.0.
1190      */
1191     public function getOptions()
1192     {
1193         @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
1194
1195         return $this->options;
1196     }
1197
1198     /**
1199      * Sets the options for proc_open.
1200      *
1201      * @param array $options The new options
1202      *
1203      * @return self The current Process instance
1204      *
1205      * @deprecated since version 3.3, to be removed in 4.0.
1206      */
1207     public function setOptions(array $options)
1208     {
1209         @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
1210
1211         $this->options = $options;
1212
1213         return $this;
1214     }
1215
1216     /**
1217      * Gets whether or not Windows compatibility is enabled.
1218      *
1219      * This is true by default.
1220      *
1221      * @return bool
1222      *
1223      * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
1224      */
1225     public function getEnhanceWindowsCompatibility()
1226     {
1227         @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);
1228
1229         return $this->enhanceWindowsCompatibility;
1230     }
1231
1232     /**
1233      * Sets whether or not Windows compatibility is enabled.
1234      *
1235      * @param bool $enhance
1236      *
1237      * @return self The current Process instance
1238      *
1239      * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
1240      */
1241     public function setEnhanceWindowsCompatibility($enhance)
1242     {
1243         @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);
1244
1245         $this->enhanceWindowsCompatibility = (bool) $enhance;
1246
1247         return $this;
1248     }
1249
1250     /**
1251      * Returns whether sigchild compatibility mode is activated or not.
1252      *
1253      * @return bool
1254      *
1255      * @deprecated since version 3.3, to be removed in 4.0. Sigchild compatibility will always be enabled.
1256      */
1257     public function getEnhanceSigchildCompatibility()
1258     {
1259         @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);
1260
1261         return $this->enhanceSigchildCompatibility;
1262     }
1263
1264     /**
1265      * Activates sigchild compatibility mode.
1266      *
1267      * Sigchild compatibility mode is required to get the exit code and
1268      * determine the success of a process when PHP has been compiled with
1269      * the --enable-sigchild option
1270      *
1271      * @param bool $enhance
1272      *
1273      * @return self The current Process instance
1274      *
1275      * @deprecated since version 3.3, to be removed in 4.0.
1276      */
1277     public function setEnhanceSigchildCompatibility($enhance)
1278     {
1279         @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);
1280
1281         $this->enhanceSigchildCompatibility = (bool) $enhance;
1282
1283         return $this;
1284     }
1285
1286     /**
1287      * Sets whether environment variables will be inherited or not.
1288      *
1289      * @param bool $inheritEnv
1290      *
1291      * @return self The current Process instance
1292      */
1293     public function inheritEnvironmentVariables($inheritEnv = true)
1294     {
1295         if (!$inheritEnv) {
1296             @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);
1297         }
1298
1299         $this->inheritEnv = (bool) $inheritEnv;
1300
1301         return $this;
1302     }
1303
1304     /**
1305      * Returns whether environment variables will be inherited or not.
1306      *
1307      * @return bool
1308      *
1309      * @deprecated since version 3.3, to be removed in 4.0. Environment variables will always be inherited.
1310      */
1311     public function areEnvironmentVariablesInherited()
1312     {
1313         @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);
1314
1315         return $this->inheritEnv;
1316     }
1317
1318     /**
1319      * Performs a check between the timeout definition and the time the process started.
1320      *
1321      * In case you run a background process (with the start method), you should
1322      * trigger this method regularly to ensure the process timeout
1323      *
1324      * @throws ProcessTimedOutException In case the timeout was reached
1325      */
1326     public function checkTimeout()
1327     {
1328         if (self::STATUS_STARTED !== $this->status) {
1329             return;
1330         }
1331
1332         if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
1333             $this->stop(0);
1334
1335             throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
1336         }
1337
1338         if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
1339             $this->stop(0);
1340
1341             throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
1342         }
1343     }
1344
1345     /**
1346      * Returns whether PTY is supported on the current operating system.
1347      *
1348      * @return bool
1349      */
1350     public static function isPtySupported()
1351     {
1352         static $result;
1353
1354         if (null !== $result) {
1355             return $result;
1356         }
1357
1358         if ('\\' === DIRECTORY_SEPARATOR) {
1359             return $result = false;
1360         }
1361
1362         return $result = (bool) @proc_open('echo 1 >/dev/null', array(array('pty'), array('pty'), array('pty')), $pipes);
1363     }
1364
1365     /**
1366      * Creates the descriptors needed by the proc_open.
1367      *
1368      * @return array
1369      */
1370     private function getDescriptors()
1371     {
1372         if ($this->input instanceof \Iterator) {
1373             $this->input->rewind();
1374         }
1375         if ('\\' === DIRECTORY_SEPARATOR) {
1376             $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
1377         } else {
1378             $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
1379         }
1380
1381         return $this->processPipes->getDescriptors();
1382     }
1383
1384     /**
1385      * Builds up the callback used by wait().
1386      *
1387      * The callbacks adds all occurred output to the specific buffer and calls
1388      * the user callback (if present) with the received output.
1389      *
1390      * @param callable|null $callback The user defined PHP callback
1391      *
1392      * @return \Closure A PHP closure
1393      */
1394     protected function buildCallback(callable $callback = null)
1395     {
1396         if ($this->outputDisabled) {
1397             return function ($type, $data) use ($callback) {
1398                 if (null !== $callback) {
1399                     call_user_func($callback, $type, $data);
1400                 }
1401             };
1402         }
1403
1404         $out = self::OUT;
1405
1406         return function ($type, $data) use ($callback, $out) {
1407             if ($out == $type) {
1408                 $this->addOutput($data);
1409             } else {
1410                 $this->addErrorOutput($data);
1411             }
1412
1413             if (null !== $callback) {
1414                 call_user_func($callback, $type, $data);
1415             }
1416         };
1417     }
1418
1419     /**
1420      * Updates the status of the process, reads pipes.
1421      *
1422      * @param bool $blocking Whether to use a blocking read call
1423      */
1424     protected function updateStatus($blocking)
1425     {
1426         if (self::STATUS_STARTED !== $this->status) {
1427             return;
1428         }
1429
1430         $this->processInformation = proc_get_status($this->process);
1431         $running = $this->processInformation['running'];
1432
1433         $this->readPipes($running && $blocking, '\\' !== DIRECTORY_SEPARATOR || !$running);
1434
1435         if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1436             $this->processInformation = $this->fallbackStatus + $this->processInformation;
1437         }
1438
1439         if (!$running) {
1440             $this->close();
1441         }
1442     }
1443
1444     /**
1445      * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
1446      *
1447      * @return bool
1448      */
1449     protected function isSigchildEnabled()
1450     {
1451         if (null !== self::$sigchild) {
1452             return self::$sigchild;
1453         }
1454
1455         if (!function_exists('phpinfo') || defined('HHVM_VERSION')) {
1456             return self::$sigchild = false;
1457         }
1458
1459         ob_start();
1460         phpinfo(INFO_GENERAL);
1461
1462         return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
1463     }
1464
1465     /**
1466      * Reads pipes for the freshest output.
1467      *
1468      * @param string $caller   The name of the method that needs fresh outputs
1469      * @param bool   $blocking Whether to use blocking calls or not
1470      *
1471      * @throws LogicException in case output has been disabled or process is not started
1472      */
1473     private function readPipesForOutput($caller, $blocking = false)
1474     {
1475         if ($this->outputDisabled) {
1476             throw new LogicException('Output has been disabled.');
1477         }
1478
1479         $this->requireProcessIsStarted($caller);
1480
1481         $this->updateStatus($blocking);
1482     }
1483
1484     /**
1485      * Validates and returns the filtered timeout.
1486      *
1487      * @param int|float|null $timeout
1488      *
1489      * @return float|null
1490      *
1491      * @throws InvalidArgumentException if the given timeout is a negative number
1492      */
1493     private function validateTimeout($timeout)
1494     {
1495         $timeout = (float) $timeout;
1496
1497         if (0.0 === $timeout) {
1498             $timeout = null;
1499         } elseif ($timeout < 0) {
1500             throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
1501         }
1502
1503         return $timeout;
1504     }
1505
1506     /**
1507      * Reads pipes, executes callback.
1508      *
1509      * @param bool $blocking Whether to use blocking calls or not
1510      * @param bool $close    Whether to close file handles or not
1511      */
1512     private function readPipes($blocking, $close)
1513     {
1514         $result = $this->processPipes->readAndWrite($blocking, $close);
1515
1516         $callback = $this->callback;
1517         foreach ($result as $type => $data) {
1518             if (3 !== $type) {
1519                 $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
1520             } elseif (!isset($this->fallbackStatus['signaled'])) {
1521                 $this->fallbackStatus['exitcode'] = (int) $data;
1522             }
1523         }
1524     }
1525
1526     /**
1527      * Closes process resource, closes file handles, sets the exitcode.
1528      *
1529      * @return int The exitcode
1530      */
1531     private function close()
1532     {
1533         $this->processPipes->close();
1534         if (is_resource($this->process)) {
1535             proc_close($this->process);
1536         }
1537         $this->exitcode = $this->processInformation['exitcode'];
1538         $this->status = self::STATUS_TERMINATED;
1539
1540         if (-1 === $this->exitcode) {
1541             if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
1542                 // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
1543                 $this->exitcode = 128 + $this->processInformation['termsig'];
1544             } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1545                 $this->processInformation['signaled'] = true;
1546                 $this->processInformation['termsig'] = -1;
1547             }
1548         }
1549
1550         // Free memory from self-reference callback created by buildCallback
1551         // Doing so in other contexts like __destruct or by garbage collector is ineffective
1552         // Now pipes are closed, so the callback is no longer necessary
1553         $this->callback = null;
1554
1555         return $this->exitcode;
1556     }
1557
1558     /**
1559      * Resets data related to the latest run of the process.
1560      */
1561     private function resetProcessData()
1562     {
1563         $this->starttime = null;
1564         $this->callback = null;
1565         $this->exitcode = null;
1566         $this->fallbackStatus = array();
1567         $this->processInformation = null;
1568         $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
1569         $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
1570         $this->process = null;
1571         $this->latestSignal = null;
1572         $this->status = self::STATUS_READY;
1573         $this->incrementalOutputOffset = 0;
1574         $this->incrementalErrorOutputOffset = 0;
1575     }
1576
1577     /**
1578      * Sends a POSIX signal to the process.
1579      *
1580      * @param int  $signal         A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
1581      * @param bool $throwException Whether to throw exception in case signal failed
1582      *
1583      * @return bool True if the signal was sent successfully, false otherwise
1584      *
1585      * @throws LogicException   In case the process is not running
1586      * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
1587      * @throws RuntimeException In case of failure
1588      */
1589     private function doSignal($signal, $throwException)
1590     {
1591         if (null === $pid = $this->getPid()) {
1592             if ($throwException) {
1593                 throw new LogicException('Can not send signal on a non running process.');
1594             }
1595
1596             return false;
1597         }
1598
1599         if ('\\' === DIRECTORY_SEPARATOR) {
1600             exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
1601             if ($exitCode && $this->isRunning()) {
1602                 if ($throwException) {
1603                     throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
1604                 }
1605
1606                 return false;
1607             }
1608         } else {
1609             if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) {
1610                 $ok = @proc_terminate($this->process, $signal);
1611             } elseif (function_exists('posix_kill')) {
1612                 $ok = @posix_kill($pid, $signal);
1613             } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), array(2 => array('pipe', 'w')), $pipes)) {
1614                 $ok = false === fgets($pipes[2]);
1615             }
1616             if (!$ok) {
1617                 if ($throwException) {
1618                     throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
1619                 }
1620
1621                 return false;
1622             }
1623         }
1624
1625         $this->latestSignal = (int) $signal;
1626         $this->fallbackStatus['signaled'] = true;
1627         $this->fallbackStatus['exitcode'] = -1;
1628         $this->fallbackStatus['termsig'] = $this->latestSignal;
1629
1630         return true;
1631     }
1632
1633     private function prepareWindowsCommandLine($cmd, array &$env)
1634     {
1635         $uid = uniqid('', true);
1636         $varCount = 0;
1637         $varCache = array();
1638         $cmd = preg_replace_callback(
1639             '/"(?:(
1640                 [^"%!^]*+
1641                 (?:
1642                     (?: !LF! | "(?:\^[%!^])?+" )
1643                     [^"%!^]*+
1644                 )++
1645             ) | [^"]*+ )"/x',
1646             function ($m) use (&$env, &$varCache, &$varCount, $uid) {
1647                 if (!isset($m[1])) {
1648                     return $m[0];
1649                 }
1650                 if (isset($varCache[$m[0]])) {
1651                     return $varCache[$m[0]];
1652                 }
1653                 if (false !== strpos($value = $m[1], "\0")) {
1654                     $value = str_replace("\0", '?', $value);
1655                 }
1656                 if (false === strpbrk($value, "\"%!\n")) {
1657                     return '"'.$value.'"';
1658                 }
1659
1660                 $value = str_replace(array('!LF!', '"^!"', '"^%"', '"^^"', '""'), array("\n", '!', '%', '^', '"'), $value);
1661                 $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
1662                 $var = $uid.++$varCount;
1663
1664                 $env[$var] = $value;
1665
1666                 return $varCache[$m[0]] = '!'.$var.'!';
1667             },
1668             $cmd
1669         );
1670
1671         $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
1672         foreach ($this->processPipes->getFiles() as $offset => $filename) {
1673             $cmd .= ' '.$offset.'>"'.$filename.'"';
1674         }
1675
1676         return $cmd;
1677     }
1678
1679     /**
1680      * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
1681      *
1682      * @param string $functionName The function name that was called
1683      *
1684      * @throws LogicException if the process has not run
1685      */
1686     private function requireProcessIsStarted($functionName)
1687     {
1688         if (!$this->isStarted()) {
1689             throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
1690         }
1691     }
1692
1693     /**
1694      * Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`.
1695      *
1696      * @param string $functionName The function name that was called
1697      *
1698      * @throws LogicException if the process is not yet terminated
1699      */
1700     private function requireProcessIsTerminated($functionName)
1701     {
1702         if (!$this->isTerminated()) {
1703             throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
1704         }
1705     }
1706
1707     /**
1708      * Escapes a string to be used as a shell argument.
1709      *
1710      * @param string $argument The argument that will be escaped
1711      *
1712      * @return string The escaped argument
1713      */
1714     private function escapeArgument($argument)
1715     {
1716         if ('\\' !== DIRECTORY_SEPARATOR) {
1717             return "'".str_replace("'", "'\\''", $argument)."'";
1718         }
1719         if ('' === $argument = (string) $argument) {
1720             return '""';
1721         }
1722         if (false !== strpos($argument, "\0")) {
1723             $argument = str_replace("\0", '?', $argument);
1724         }
1725         if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
1726             return $argument;
1727         }
1728         $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
1729
1730         return '"'.str_replace(array('"', '^', '%', '!', "\n"), array('""', '"^^"', '"^%"', '"^!"', '!LF!'), $argument).'"';
1731     }
1732
1733     private function getDefaultEnv()
1734     {
1735         $env = array();
1736
1737         foreach ($_SERVER as $k => $v) {
1738             if (is_string($v) && false !== $v = getenv($k)) {
1739                 $env[$k] = $v;
1740             }
1741         }
1742
1743         foreach ($_ENV as $k => $v) {
1744             if (is_string($v)) {
1745                 $env[$k] = $v;
1746             }
1747         }
1748
1749         return $env;
1750     }
1751 }