2013-09-02 09:23:35 +01:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Process ;
use Symfony\Component\Process\Exception\RuntimeException ;
/**
* ProcessPipes manages descriptors and pipes for the use of proc_open .
*/
class ProcessPipes
{
/** @var array */
public $pipes = array ();
/** @var array */
2014-03-11 16:05:18 +00:00
private $files = array ();
/** @var array */
2013-09-02 09:23:35 +01:00
private $fileHandles = array ();
/** @var array */
private $readBytes = array ();
2014-04-16 11:30:19 +01:00
/** @var bool */
2013-09-02 09:23:35 +01:00
private $useFiles ;
2014-04-16 11:30:19 +01:00
/** @var bool */
2014-01-07 09:02:59 +00:00
private $ttyMode ;
2013-09-02 09:23:35 +01:00
2014-03-14 16:03:41 +00:00
const CHUNK_SIZE = 16384 ;
2014-01-07 09:02:59 +00:00
public function __construct ( $useFiles , $ttyMode )
2013-09-02 09:23:35 +01:00
{
2014-04-12 18:44:00 +01:00
$this -> useFiles = ( bool ) $useFiles ;
$this -> ttyMode = ( bool ) $ttyMode ;
2013-09-02 09:23:35 +01:00
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
// Workaround for this problem is to use temporary files instead of pipes on Windows platform.
//
// @see https://bugs.php.net/bug.php?id=51800
if ( $this -> useFiles ) {
2014-03-11 16:05:18 +00:00
$this -> files = array (
Process :: STDOUT => tempnam ( sys_get_temp_dir (), 'sf_proc_stdout' ),
Process :: STDERR => tempnam ( sys_get_temp_dir (), 'sf_proc_stderr' ),
2013-09-02 09:23:35 +01:00
);
2014-03-11 16:05:18 +00:00
foreach ( $this -> files as $offset => $file ) {
$this -> fileHandles [ $offset ] = fopen ( $this -> files [ $offset ], 'rb' );
if ( false === $this -> fileHandles [ $offset ]) {
throw new RuntimeException ( 'A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable' );
}
2013-09-02 09:23:35 +01:00
}
$this -> readBytes = array (
Process :: STDOUT => 0 ,
2014-03-11 16:05:18 +00:00
Process :: STDERR => 0 ,
2013-09-02 09:23:35 +01:00
);
}
}
public function __destruct ()
{
$this -> close ();
2014-03-11 16:05:18 +00:00
$this -> removeFiles ();
2013-09-02 09:23:35 +01:00
}
/**
* Sets non - blocking mode on pipes .
*/
public function unblock ()
{
foreach ( $this -> pipes as $pipe ) {
stream_set_blocking ( $pipe , 0 );
}
}
/**
* Closes file handles and pipes .
*/
public function close ()
{
2013-09-11 20:09:08 +01:00
$this -> closeUnixPipes ();
2014-02-21 07:41:38 +00:00
foreach ( $this -> fileHandles as $handle ) {
2013-09-02 09:23:35 +01:00
fclose ( $handle );
}
2013-09-11 20:09:08 +01:00
$this -> fileHandles = array ();
}
/**
2013-12-27 15:08:19 +00:00
* Closes Unix pipes .
2013-09-11 20:09:08 +01:00
*
* Nothing happens in case file handles are used .
*/
public function closeUnixPipes ()
{
foreach ( $this -> pipes as $pipe ) {
fclose ( $pipe );
}
$this -> pipes = array ();
2013-09-02 09:23:35 +01:00
}
/**
* Returns an array of descriptors for the use of proc_open .
*
* @ return array
*/
public function getDescriptors ()
{
if ( $this -> useFiles ) {
2014-03-11 16:05:18 +00:00
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
// We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
// So we redirect output within the commandline and pass the nul device to the process
2013-09-02 09:23:35 +01:00
return array (
array ( 'pipe' , 'r' ),
2014-03-11 16:05:18 +00:00
array ( 'file' , 'NUL' , 'w' ),
array ( 'file' , 'NUL' , 'w' ),
2013-09-02 09:23:35 +01:00
);
}
2014-01-07 09:02:59 +00:00
if ( $this -> ttyMode ) {
return array (
array ( 'file' , '/dev/tty' , 'r' ),
array ( 'file' , '/dev/tty' , 'w' ),
array ( 'file' , '/dev/tty' , 'w' ),
);
}
2013-09-02 09:23:35 +01:00
return array (
array ( 'pipe' , 'r' ), // stdin
array ( 'pipe' , 'w' ), // stdout
array ( 'pipe' , 'w' ), // stderr
);
}
2014-03-11 16:05:18 +00:00
/**
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files .
*
* @ return array
*/
public function getFiles ()
{
if ( $this -> useFiles ) {
return $this -> files ;
}
return array ();
}
2013-09-02 09:23:35 +01:00
/**
* Reads data in file handles and pipes .
*
2014-04-12 18:54:57 +01:00
* @ param bool $blocking Whether to use blocking calls or not .
2013-09-02 09:23:35 +01:00
*
* @ return array An array of read data indexed by their fd .
*/
public function read ( $blocking )
{
return array_replace ( $this -> readStreams ( $blocking ), $this -> readFileHandles ());
}
2013-09-10 17:39:36 +01:00
/**
* Reads data in file handles and pipes , closes them if EOF is reached .
*
2014-04-12 18:54:57 +01:00
* @ param bool $blocking Whether to use blocking calls or not .
2013-09-10 17:39:36 +01:00
*
* @ return array An array of read data indexed by their fd .
*/
public function readAndCloseHandles ( $blocking )
{
return array_replace ( $this -> readStreams ( $blocking , true ), $this -> readFileHandles ( true ));
}
/**
* Returns if the current state has open file handles or pipes .
*
2014-04-16 11:30:19 +01:00
* @ return bool
2013-09-10 17:39:36 +01:00
*/
public function hasOpenHandles ()
{
2013-10-21 12:04:42 +01:00
if ( ! $this -> useFiles ) {
2014-04-12 18:44:00 +01:00
return ( bool ) $this -> pipes ;
2013-09-10 17:39:36 +01:00
}
2014-04-12 18:44:00 +01:00
return ( bool ) $this -> pipes && ( bool ) $this -> fileHandles ;
2013-09-10 17:39:36 +01:00
}
2013-09-02 09:23:35 +01:00
/**
* Writes stdin data .
*
2014-04-12 18:54:57 +01:00
* @ param bool $blocking Whether to use blocking calls or not .
2013-11-08 00:33:52 +00:00
* @ param string | null $stdin The data to write .
2013-09-02 09:23:35 +01:00
*/
public function write ( $blocking , $stdin )
{
if ( null === $stdin ) {
fclose ( $this -> pipes [ 0 ]);
unset ( $this -> pipes [ 0 ]);
return ;
}
$writePipes = array ( $this -> pipes [ 0 ]);
unset ( $this -> pipes [ 0 ]);
$stdinLen = strlen ( $stdin );
$stdinOffset = 0 ;
while ( $writePipes ) {
$r = null ;
$w = $writePipes ;
$e = null ;
if ( false === $n = @ stream_select ( $r , $w , $e , 0 , $blocking ? ceil ( Process :: TIMEOUT_PRECISION * 1E6 ) : 0 )) {
// if a system call has been interrupted, forget about it, let's try again
if ( $this -> hasSystemCallBeenInterrupted ()) {
continue ;
}
break ;
}
// nothing has changed, let's wait until the process is ready
if ( 0 === $n ) {
continue ;
}
if ( $w ) {
$written = fwrite ( $writePipes [ 0 ], ( binary ) substr ( $stdin , $stdinOffset ), 8192 );
if ( false !== $written ) {
$stdinOffset += $written ;
}
if ( $stdinOffset >= $stdinLen ) {
fclose ( $writePipes [ 0 ]);
$writePipes = null ;
}
}
}
}
/**
* Reads data in file handles .
*
2014-04-12 18:54:57 +01:00
* @ param bool $close Whether to close file handles or not .
2014-02-21 07:41:38 +00:00
*
2013-09-02 09:23:35 +01:00
* @ return array An array of read data indexed by their fd .
*/
2013-09-10 17:39:36 +01:00
private function readFileHandles ( $close = false )
2013-09-02 09:23:35 +01:00
{
$read = array ();
2013-09-10 17:39:36 +01:00
$fh = $this -> fileHandles ;
foreach ( $fh as $type => $fileHandle ) {
if ( 0 !== fseek ( $fileHandle , $this -> readBytes [ $type ])) {
continue ;
}
2013-09-02 09:23:35 +01:00
$data = '' ;
2013-10-09 14:14:24 +01:00
$dataread = null ;
2013-09-02 09:23:35 +01:00
while ( ! feof ( $fileHandle )) {
2014-03-14 16:03:41 +00:00
if ( false !== $dataread = fread ( $fileHandle , self :: CHUNK_SIZE )) {
2013-09-10 17:39:36 +01:00
$data .= $dataread ;
}
2013-09-02 09:23:35 +01:00
}
if ( 0 < $length = strlen ( $data )) {
$this -> readBytes [ $type ] += $length ;
$read [ $type ] = $data ;
}
2013-09-10 17:39:36 +01:00
2013-10-09 14:14:24 +01:00
if ( false === $dataread || ( true === $close && feof ( $fileHandle ) && '' === $data )) {
2013-09-10 17:39:36 +01:00
fclose ( $this -> fileHandles [ $type ]);
unset ( $this -> fileHandles [ $type ]);
}
2013-09-02 09:23:35 +01:00
}
return $read ;
}
/**
* Reads data in file pipes streams .
*
2014-04-12 18:54:57 +01:00
* @ param bool $blocking Whether to use blocking calls or not .
* @ param bool $close Whether to close file handles or not .
2013-09-02 09:23:35 +01:00
*
* @ return array An array of read data indexed by their fd .
*/
2013-09-10 17:39:36 +01:00
private function readStreams ( $blocking , $close = false )
2013-09-02 09:23:35 +01:00
{
2013-10-24 03:56:37 +01:00
if ( empty ( $this -> pipes )) {
return array ();
}
2013-09-02 09:23:35 +01:00
$read = array ();
$r = $this -> pipes ;
$w = null ;
$e = null ;
// let's have a look if something changed in streams
if ( false === $n = @ stream_select ( $r , $w , $e , 0 , $blocking ? ceil ( Process :: TIMEOUT_PRECISION * 1E6 ) : 0 )) {
// if a system call has been interrupted, forget about it, let's try again
2013-11-25 09:14:57 +00:00
// otherwise, an error occurred, let's reset pipes
2013-09-02 09:23:35 +01:00
if ( ! $this -> hasSystemCallBeenInterrupted ()) {
$this -> pipes = array ();
}
return $read ;
}
// nothing has changed
if ( 0 === $n ) {
return $read ;
}
foreach ( $r as $pipe ) {
$type = array_search ( $pipe , $this -> pipes );
2014-01-28 16:56:56 +00:00
$data = '' ;
2014-03-14 16:03:41 +00:00
while ( $dataread = fread ( $pipe , self :: CHUNK_SIZE )) {
2014-01-28 16:56:56 +00:00
$data .= $dataread ;
}
if ( $data ) {
2013-09-02 09:23:35 +01:00
$read [ $type ] = $data ;
}
2013-09-10 17:39:36 +01:00
2013-10-09 14:14:24 +01:00
if ( false === $data || ( true === $close && feof ( $pipe ) && '' === $data )) {
2013-09-10 17:39:36 +01:00
fclose ( $this -> pipes [ $type ]);
unset ( $this -> pipes [ $type ]);
}
2013-09-02 09:23:35 +01:00
}
return $read ;
}
/**
* Returns true if a system call has been interrupted .
*
2014-04-16 11:30:19 +01:00
* @ return bool
2013-09-02 09:23:35 +01:00
*/
private function hasSystemCallBeenInterrupted ()
{
$lastError = error_get_last ();
// stream_select returns false when the `select` system call is interrupted by an incoming signal
return isset ( $lastError [ 'message' ]) && false !== stripos ( $lastError [ 'message' ], 'interrupted system call' );
}
2014-03-11 16:05:18 +00:00
/**
* Removes temporary files
*/
private function removeFiles ()
{
foreach ( $this -> files as $filename ) {
if ( file_exists ( $filename )) {
@ unlink ( $filename );
}
}
$this -> files = array ();
}
2013-09-02 09:23:35 +01:00
}