4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Process
;
14 use Symfony\Component\Process\Exception\RuntimeException
;
17 * ProcessPipes manages descriptors and pipes for the use of proc_open.
22 public $pipes = array();
24 private $files = array();
26 private $fileHandles = array();
28 private $readBytes = array();
34 const CHUNK_SIZE
= 16384;
36 public function __construct($useFiles, $ttyMode)
38 $this->useFiles
= (bool) $useFiles;
39 $this->ttyMode
= (bool) $ttyMode;
41 // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
42 // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
44 // @see https://bugs.php.net/bug.php?id=51800
45 if ($this->useFiles
) {
47 Process
::STDOUT
=> tempnam(sys_get_temp_dir(), 'out_sf_proc'),
48 Process
::STDERR
=> tempnam(sys_get_temp_dir(), 'err_sf_proc'),
50 foreach ($this->files
as $offset => $file) {
51 if (false === $file ||
false === $this->fileHandles
[$offset] = fopen($file, 'rb')) {
52 throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
55 $this->readBytes
= array(
62 public function __destruct()
69 * Sets non-blocking mode on pipes.
71 public function unblock()
73 foreach ($this->pipes
as $pipe) {
74 stream_set_blocking($pipe, 0);
79 * Closes file handles and pipes.
81 public function close()
83 $this->closeUnixPipes();
84 foreach ($this->fileHandles
as $handle) {
87 $this->fileHandles
= array();
93 * Nothing happens in case file handles are used.
95 public function closeUnixPipes()
97 foreach ($this->pipes
as $pipe) {
100 $this->pipes
= array();
104 * Returns an array of descriptors for the use of proc_open.
108 public function getDescriptors()
110 if ($this->useFiles
) {
111 // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
112 // We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
113 // So we redirect output within the commandline and pass the nul device to the process
116 array('file', 'NUL', 'w'),
117 array('file', 'NUL', 'w'),
121 if ($this->ttyMode
) {
123 array('file', '/dev/tty', 'r'),
124 array('file', '/dev/tty', 'w'),
125 array('file', '/dev/tty', 'w'),
130 array('pipe', 'r'), // stdin
131 array('pipe', 'w'), // stdout
132 array('pipe', 'w'), // stderr
137 * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
141 public function getFiles()
143 if ($this->useFiles
) {
151 * Reads data in file handles and pipes.
153 * @param bool $blocking Whether to use blocking calls or not.
155 * @return array An array of read data indexed by their fd.
157 public function read($blocking)
159 return array_replace($this->readStreams($blocking), $this->readFileHandles());
163 * Reads data in file handles and pipes, closes them if EOF is reached.
165 * @param bool $blocking Whether to use blocking calls or not.
167 * @return array An array of read data indexed by their fd.
169 public function readAndCloseHandles($blocking)
171 return array_replace($this->readStreams($blocking, true), $this->readFileHandles(true));
175 * Returns if the current state has open file handles or pipes.
179 public function hasOpenHandles()
181 if (!$this->useFiles
) {
182 return (bool) $this->pipes
;
185 return (bool) $this->pipes
&& (bool) $this->fileHandles
;
191 * @param bool $blocking Whether to use blocking calls or not.
192 * @param string|null $stdin The data to write.
194 public function write($blocking, $stdin)
196 if (null === $stdin) {
197 fclose($this->pipes
[0]);
198 unset($this->pipes
[0]);
203 $writePipes = array($this->pipes
[0]);
204 unset($this->pipes
[0]);
205 $stdinLen = strlen($stdin);
208 while ($writePipes) {
213 if (false === $n = @stream_select
($r, $w, $e, 0, $blocking ?
ceil(Process
::TIMEOUT_PRECISION
* 1E6
) : 0)) {
214 // if a system call has been interrupted, forget about it, let's try again
215 if ($this->hasSystemCallBeenInterrupted()) {
221 // nothing has changed, let's wait until the process is ready
227 $written = fwrite($writePipes[0], (binary
) substr($stdin, $stdinOffset), 8192);
228 if (false !== $written) {
229 $stdinOffset +
= $written;
231 if ($stdinOffset >= $stdinLen) {
232 fclose($writePipes[0]);
240 * Reads data in file handles.
242 * @param bool $close Whether to close file handles or not.
244 * @return array An array of read data indexed by their fd.
246 private function readFileHandles($close = false)
249 $fh = $this->fileHandles
;
250 foreach ($fh as $type => $fileHandle) {
251 if (0 !== fseek($fileHandle, $this->readBytes
[$type])) {
256 while (!feof($fileHandle)) {
257 if (false !== $dataread = fread($fileHandle, self
::CHUNK_SIZE
)) {
261 if (0 < $length = strlen($data)) {
262 $this->readBytes
[$type] +
= $length;
263 $read[$type] = $data;
266 if (false === $dataread ||
(true === $close && feof($fileHandle) && '' === $data)) {
267 fclose($this->fileHandles
[$type]);
268 unset($this->fileHandles
[$type]);
276 * Reads data in file pipes streams.
278 * @param bool $blocking Whether to use blocking calls or not.
279 * @param bool $close Whether to close file handles or not.
281 * @return array An array of read data indexed by their fd.
283 private function readStreams($blocking, $close = false)
285 if (empty($this->pipes
)) {
286 usleep(Process
::TIMEOUT_PRECISION
* 1E4
);
297 // let's have a look if something changed in streams
298 if (false === $n = @stream_select
($r, $w, $e, 0, $blocking ?
ceil(Process
::TIMEOUT_PRECISION
* 1E6
) : 0)) {
299 // if a system call has been interrupted, forget about it, let's try again
300 // otherwise, an error occurred, let's reset pipes
301 if (!$this->hasSystemCallBeenInterrupted()) {
302 $this->pipes
= array();
308 // nothing has changed
313 foreach ($r as $pipe) {
314 $type = array_search($pipe, $this->pipes
);
317 while ('' !== $dataread = (string) fread($pipe, self
::CHUNK_SIZE
)) {
322 $read[$type] = $data;
325 if (false === $data ||
(true === $close && feof($pipe) && '' === $data)) {
326 fclose($this->pipes
[$type]);
327 unset($this->pipes
[$type]);
335 * Returns true if a system call has been interrupted.
339 private function hasSystemCallBeenInterrupted()
341 $lastError = error_get_last();
343 // stream_select returns false when the `select` system call is interrupted by an incoming signal
344 return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
348 * Removes temporary files.
350 private function removeFiles()
352 foreach ($this->files
as $filename) {
353 if (file_exists($filename)) {
357 $this->files
= array();