UnixPipes.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Process\Pipes;
  11. use Symfony\Component\Process\Process;
  12. /**
  13. * UnixPipes implementation uses unix pipes as handles.
  14. *
  15. * @author Romain Neutron <imprec@gmail.com>
  16. *
  17. * @internal
  18. */
  19. class UnixPipes extends AbstractPipes
  20. {
  21. /** @var bool */
  22. private $ttyMode;
  23. /** @var bool */
  24. private $ptyMode;
  25. /** @var bool */
  26. private $disableOutput;
  27. public function __construct($ttyMode, $ptyMode, $input, $disableOutput)
  28. {
  29. $this->ttyMode = (bool) $ttyMode;
  30. $this->ptyMode = (bool) $ptyMode;
  31. $this->disableOutput = (bool) $disableOutput;
  32. if (is_resource($input)) {
  33. $this->input = $input;
  34. } else {
  35. $this->inputBuffer = (string) $input;
  36. }
  37. }
  38. public function __destruct()
  39. {
  40. $this->close();
  41. }
  42. /**
  43. * {@inheritdoc}
  44. */
  45. public function getDescriptors()
  46. {
  47. if ($this->disableOutput) {
  48. $nullstream = fopen('/dev/null', 'c');
  49. return array(
  50. array('pipe', 'r'),
  51. $nullstream,
  52. $nullstream,
  53. );
  54. }
  55. if ($this->ttyMode) {
  56. return array(
  57. array('file', '/dev/tty', 'r'),
  58. array('file', '/dev/tty', 'w'),
  59. array('file', '/dev/tty', 'w'),
  60. );
  61. }
  62. if ($this->ptyMode && Process::isPtySupported()) {
  63. return array(
  64. array('pty'),
  65. array('pty'),
  66. array('pty'),
  67. );
  68. }
  69. return array(
  70. array('pipe', 'r'),
  71. array('pipe', 'w'), // stdout
  72. array('pipe', 'w'), // stderr
  73. );
  74. }
  75. /**
  76. * {@inheritdoc}
  77. */
  78. public function getFiles()
  79. {
  80. return array();
  81. }
  82. /**
  83. * {@inheritdoc}
  84. */
  85. public function readAndWrite($blocking, $close = false)
  86. {
  87. // only stdin is left open, job has been done !
  88. // we can now close it
  89. if (1 === count($this->pipes) && array(0) === array_keys($this->pipes)) {
  90. fclose($this->pipes[0]);
  91. unset($this->pipes[0]);
  92. }
  93. if (empty($this->pipes)) {
  94. return array();
  95. }
  96. $this->unblock();
  97. $read = array();
  98. if (null !== $this->input) {
  99. // if input is a resource, let's add it to stream_select argument to
  100. // fill a buffer
  101. $r = array_merge($this->pipes, array('input' => $this->input));
  102. } else {
  103. $r = $this->pipes;
  104. }
  105. // discard read on stdin
  106. unset($r[0]);
  107. $w = isset($this->pipes[0]) ? array($this->pipes[0]) : null;
  108. $e = null;
  109. // let's have a look if something changed in streams
  110. if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
  111. // if a system call has been interrupted, forget about it, let's try again
  112. // otherwise, an error occurred, let's reset pipes
  113. if (!$this->hasSystemCallBeenInterrupted()) {
  114. $this->pipes = array();
  115. }
  116. return $read;
  117. }
  118. // nothing has changed
  119. if (0 === $n) {
  120. return $read;
  121. }
  122. foreach ($r as $pipe) {
  123. // prior PHP 5.4 the array passed to stream_select is modified and
  124. // lose key association, we have to find back the key
  125. $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input';
  126. $data = '';
  127. while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) {
  128. $data .= $dataread;
  129. }
  130. if ('' !== $data) {
  131. if ($type === 'input') {
  132. $this->inputBuffer .= $data;
  133. } else {
  134. $read[$type] = $data;
  135. }
  136. }
  137. if (false === $data || (true === $close && feof($pipe) && '' === $data)) {
  138. if ($type === 'input') {
  139. // no more data to read on input resource
  140. // use an empty buffer in the next reads
  141. $this->input = null;
  142. } else {
  143. fclose($this->pipes[$type]);
  144. unset($this->pipes[$type]);
  145. }
  146. }
  147. }
  148. if (null !== $w && 0 < count($w)) {
  149. while (strlen($this->inputBuffer)) {
  150. $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k
  151. if ($written > 0) {
  152. $this->inputBuffer = (string) substr($this->inputBuffer, $written);
  153. } else {
  154. break;
  155. }
  156. }
  157. }
  158. // no input to read on resource, buffer is empty and stdin still open
  159. if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) {
  160. fclose($this->pipes[0]);
  161. unset($this->pipes[0]);
  162. }
  163. return $read;
  164. }
  165. /**
  166. * {@inheritdoc}
  167. */
  168. public function areOpen()
  169. {
  170. return (bool) $this->pipes;
  171. }
  172. /**
  173. * Creates a new UnixPipes instance
  174. *
  175. * @param Process $process
  176. * @param string|resource $input
  177. *
  178. * @return UnixPipes
  179. */
  180. public static function create(Process $process, $input)
  181. {
  182. return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled());
  183. }
  184. }