commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / civicrm / vendor / symfony / process / Symfony / Component / Process / ProcessPipes.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\RuntimeException;
15
16 /**
17 * ProcessPipes manages descriptors and pipes for the use of proc_open.
18 */
19 class ProcessPipes
20 {
21 /** @var array */
22 public $pipes = array();
23 /** @var array */
24 private $files = array();
25 /** @var array */
26 private $fileHandles = array();
27 /** @var array */
28 private $readBytes = array();
29 /** @var bool */
30 private $useFiles;
31 /** @var bool */
32 private $ttyMode;
33
34 const CHUNK_SIZE = 16384;
35
36 public function __construct($useFiles, $ttyMode)
37 {
38 $this->useFiles = (bool) $useFiles;
39 $this->ttyMode = (bool) $ttyMode;
40
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.
43 //
44 // @see https://bugs.php.net/bug.php?id=51800
45 if ($this->useFiles) {
46 $this->files = array(
47 Process::STDOUT => tempnam(sys_get_temp_dir(), 'out_sf_proc'),
48 Process::STDERR => tempnam(sys_get_temp_dir(), 'err_sf_proc'),
49 );
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');
53 }
54 }
55 $this->readBytes = array(
56 Process::STDOUT => 0,
57 Process::STDERR => 0,
58 );
59 }
60 }
61
62 public function __destruct()
63 {
64 $this->close();
65 $this->removeFiles();
66 }
67
68 /**
69 * Sets non-blocking mode on pipes.
70 */
71 public function unblock()
72 {
73 foreach ($this->pipes as $pipe) {
74 stream_set_blocking($pipe, 0);
75 }
76 }
77
78 /**
79 * Closes file handles and pipes.
80 */
81 public function close()
82 {
83 $this->closeUnixPipes();
84 foreach ($this->fileHandles as $handle) {
85 fclose($handle);
86 }
87 $this->fileHandles = array();
88 }
89
90 /**
91 * Closes Unix pipes.
92 *
93 * Nothing happens in case file handles are used.
94 */
95 public function closeUnixPipes()
96 {
97 foreach ($this->pipes as $pipe) {
98 fclose($pipe);
99 }
100 $this->pipes = array();
101 }
102
103 /**
104 * Returns an array of descriptors for the use of proc_open.
105 *
106 * @return array
107 */
108 public function getDescriptors()
109 {
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
114 return array(
115 array('pipe', 'r'),
116 array('file', 'NUL', 'w'),
117 array('file', 'NUL', 'w'),
118 );
119 }
120
121 if ($this->ttyMode) {
122 return array(
123 array('file', '/dev/tty', 'r'),
124 array('file', '/dev/tty', 'w'),
125 array('file', '/dev/tty', 'w'),
126 );
127 }
128
129 return array(
130 array('pipe', 'r'), // stdin
131 array('pipe', 'w'), // stdout
132 array('pipe', 'w'), // stderr
133 );
134 }
135
136 /**
137 * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
138 *
139 * @return array
140 */
141 public function getFiles()
142 {
143 if ($this->useFiles) {
144 return $this->files;
145 }
146
147 return array();
148 }
149
150 /**
151 * Reads data in file handles and pipes.
152 *
153 * @param bool $blocking Whether to use blocking calls or not.
154 *
155 * @return array An array of read data indexed by their fd.
156 */
157 public function read($blocking)
158 {
159 return array_replace($this->readStreams($blocking), $this->readFileHandles());
160 }
161
162 /**
163 * Reads data in file handles and pipes, closes them if EOF is reached.
164 *
165 * @param bool $blocking Whether to use blocking calls or not.
166 *
167 * @return array An array of read data indexed by their fd.
168 */
169 public function readAndCloseHandles($blocking)
170 {
171 return array_replace($this->readStreams($blocking, true), $this->readFileHandles(true));
172 }
173
174 /**
175 * Returns if the current state has open file handles or pipes.
176 *
177 * @return bool
178 */
179 public function hasOpenHandles()
180 {
181 if (!$this->useFiles) {
182 return (bool) $this->pipes;
183 }
184
185 return (bool) $this->pipes && (bool) $this->fileHandles;
186 }
187
188 /**
189 * Writes stdin data.
190 *
191 * @param bool $blocking Whether to use blocking calls or not.
192 * @param string|null $stdin The data to write.
193 */
194 public function write($blocking, $stdin)
195 {
196 if (null === $stdin) {
197 fclose($this->pipes[0]);
198 unset($this->pipes[0]);
199
200 return;
201 }
202
203 $writePipes = array($this->pipes[0]);
204 unset($this->pipes[0]);
205 $stdinLen = strlen($stdin);
206 $stdinOffset = 0;
207
208 while ($writePipes) {
209 $r = null;
210 $w = $writePipes;
211 $e = null;
212
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()) {
216 continue;
217 }
218 break;
219 }
220
221 // nothing has changed, let's wait until the process is ready
222 if (0 === $n) {
223 continue;
224 }
225
226 if ($w) {
227 $written = fwrite($writePipes[0], (binary) substr($stdin, $stdinOffset), 8192);
228 if (false !== $written) {
229 $stdinOffset += $written;
230 }
231 if ($stdinOffset >= $stdinLen) {
232 fclose($writePipes[0]);
233 $writePipes = null;
234 }
235 }
236 }
237 }
238
239 /**
240 * Reads data in file handles.
241 *
242 * @param bool $close Whether to close file handles or not.
243 *
244 * @return array An array of read data indexed by their fd.
245 */
246 private function readFileHandles($close = false)
247 {
248 $read = array();
249 $fh = $this->fileHandles;
250 foreach ($fh as $type => $fileHandle) {
251 if (0 !== fseek($fileHandle, $this->readBytes[$type])) {
252 continue;
253 }
254 $data = '';
255 $dataread = null;
256 while (!feof($fileHandle)) {
257 if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) {
258 $data .= $dataread;
259 }
260 }
261 if (0 < $length = strlen($data)) {
262 $this->readBytes[$type] += $length;
263 $read[$type] = $data;
264 }
265
266 if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) {
267 fclose($this->fileHandles[$type]);
268 unset($this->fileHandles[$type]);
269 }
270 }
271
272 return $read;
273 }
274
275 /**
276 * Reads data in file pipes streams.
277 *
278 * @param bool $blocking Whether to use blocking calls or not.
279 * @param bool $close Whether to close file handles or not.
280 *
281 * @return array An array of read data indexed by their fd.
282 */
283 private function readStreams($blocking, $close = false)
284 {
285 if (empty($this->pipes)) {
286 usleep(Process::TIMEOUT_PRECISION * 1E4);
287
288 return array();
289 }
290
291 $read = array();
292
293 $r = $this->pipes;
294 $w = null;
295 $e = null;
296
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();
303 }
304
305 return $read;
306 }
307
308 // nothing has changed
309 if (0 === $n) {
310 return $read;
311 }
312
313 foreach ($r as $pipe) {
314 $type = array_search($pipe, $this->pipes);
315
316 $data = '';
317 while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) {
318 $data .= $dataread;
319 }
320
321 if ('' !== $data) {
322 $read[$type] = $data;
323 }
324
325 if (false === $data || (true === $close && feof($pipe) && '' === $data)) {
326 fclose($this->pipes[$type]);
327 unset($this->pipes[$type]);
328 }
329 }
330
331 return $read;
332 }
333
334 /**
335 * Returns true if a system call has been interrupted.
336 *
337 * @return bool
338 */
339 private function hasSystemCallBeenInterrupted()
340 {
341 $lastError = error_get_last();
342
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');
345 }
346
347 /**
348 * Removes temporary files.
349 */
350 private function removeFiles()
351 {
352 foreach ($this->files as $filename) {
353 if (file_exists($filename)) {
354 @unlink($filename);
355 }
356 }
357 $this->files = array();
358 }
359 }