xref: /plugin/combo/vendor/symfony/process/Pipes/WindowsPipes.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeau/*
4*04fd306cSNickeau * This file is part of the Symfony package.
5*04fd306cSNickeau *
6*04fd306cSNickeau * (c) Fabien Potencier <fabien@symfony.com>
7*04fd306cSNickeau *
8*04fd306cSNickeau * For the full copyright and license information, please view the LICENSE
9*04fd306cSNickeau * file that was distributed with this source code.
10*04fd306cSNickeau */
11*04fd306cSNickeau
12*04fd306cSNickeaunamespace Symfony\Component\Process\Pipes;
13*04fd306cSNickeau
14*04fd306cSNickeauuse Symfony\Component\Process\Exception\RuntimeException;
15*04fd306cSNickeauuse Symfony\Component\Process\Process;
16*04fd306cSNickeau
17*04fd306cSNickeau/**
18*04fd306cSNickeau * WindowsPipes implementation uses temporary files as handles.
19*04fd306cSNickeau *
20*04fd306cSNickeau * @see https://bugs.php.net/51800
21*04fd306cSNickeau * @see https://bugs.php.net/65650
22*04fd306cSNickeau *
23*04fd306cSNickeau * @author Romain Neutron <imprec@gmail.com>
24*04fd306cSNickeau *
25*04fd306cSNickeau * @internal
26*04fd306cSNickeau */
27*04fd306cSNickeauclass WindowsPipes extends AbstractPipes
28*04fd306cSNickeau{
29*04fd306cSNickeau    private $files = [];
30*04fd306cSNickeau    private $fileHandles = [];
31*04fd306cSNickeau    private $lockHandles = [];
32*04fd306cSNickeau    private $readBytes = [
33*04fd306cSNickeau        Process::STDOUT => 0,
34*04fd306cSNickeau        Process::STDERR => 0,
35*04fd306cSNickeau    ];
36*04fd306cSNickeau    private $haveReadSupport;
37*04fd306cSNickeau
38*04fd306cSNickeau    public function __construct($input, bool $haveReadSupport)
39*04fd306cSNickeau    {
40*04fd306cSNickeau        $this->haveReadSupport = $haveReadSupport;
41*04fd306cSNickeau
42*04fd306cSNickeau        if ($this->haveReadSupport) {
43*04fd306cSNickeau            // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
44*04fd306cSNickeau            // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
45*04fd306cSNickeau            //
46*04fd306cSNickeau            // @see https://bugs.php.net/51800
47*04fd306cSNickeau            $pipes = [
48*04fd306cSNickeau                Process::STDOUT => Process::OUT,
49*04fd306cSNickeau                Process::STDERR => Process::ERR,
50*04fd306cSNickeau            ];
51*04fd306cSNickeau            $tmpDir = sys_get_temp_dir();
52*04fd306cSNickeau            $lastError = 'unknown reason';
53*04fd306cSNickeau            set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
54*04fd306cSNickeau            for ($i = 0;; ++$i) {
55*04fd306cSNickeau                foreach ($pipes as $pipe => $name) {
56*04fd306cSNickeau                    $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
57*04fd306cSNickeau
58*04fd306cSNickeau                    if (!$h = fopen($file.'.lock', 'w')) {
59*04fd306cSNickeau                        if (file_exists($file.'.lock')) {
60*04fd306cSNickeau                            continue 2;
61*04fd306cSNickeau                        }
62*04fd306cSNickeau                        restore_error_handler();
63*04fd306cSNickeau                        throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
64*04fd306cSNickeau                    }
65*04fd306cSNickeau                    if (!flock($h, \LOCK_EX | \LOCK_NB)) {
66*04fd306cSNickeau                        continue 2;
67*04fd306cSNickeau                    }
68*04fd306cSNickeau                    if (isset($this->lockHandles[$pipe])) {
69*04fd306cSNickeau                        flock($this->lockHandles[$pipe], \LOCK_UN);
70*04fd306cSNickeau                        fclose($this->lockHandles[$pipe]);
71*04fd306cSNickeau                    }
72*04fd306cSNickeau                    $this->lockHandles[$pipe] = $h;
73*04fd306cSNickeau
74*04fd306cSNickeau                    if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) {
75*04fd306cSNickeau                        flock($this->lockHandles[$pipe], \LOCK_UN);
76*04fd306cSNickeau                        fclose($this->lockHandles[$pipe]);
77*04fd306cSNickeau                        unset($this->lockHandles[$pipe]);
78*04fd306cSNickeau                        continue 2;
79*04fd306cSNickeau                    }
80*04fd306cSNickeau                    $this->fileHandles[$pipe] = $h;
81*04fd306cSNickeau                    $this->files[$pipe] = $file;
82*04fd306cSNickeau                }
83*04fd306cSNickeau                break;
84*04fd306cSNickeau            }
85*04fd306cSNickeau            restore_error_handler();
86*04fd306cSNickeau        }
87*04fd306cSNickeau
88*04fd306cSNickeau        parent::__construct($input);
89*04fd306cSNickeau    }
90*04fd306cSNickeau
91*04fd306cSNickeau    public function __sleep(): array
92*04fd306cSNickeau    {
93*04fd306cSNickeau        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
94*04fd306cSNickeau    }
95*04fd306cSNickeau
96*04fd306cSNickeau    public function __wakeup()
97*04fd306cSNickeau    {
98*04fd306cSNickeau        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
99*04fd306cSNickeau    }
100*04fd306cSNickeau
101*04fd306cSNickeau    public function __destruct()
102*04fd306cSNickeau    {
103*04fd306cSNickeau        $this->close();
104*04fd306cSNickeau    }
105*04fd306cSNickeau
106*04fd306cSNickeau    /**
107*04fd306cSNickeau     * {@inheritdoc}
108*04fd306cSNickeau     */
109*04fd306cSNickeau    public function getDescriptors(): array
110*04fd306cSNickeau    {
111*04fd306cSNickeau        if (!$this->haveReadSupport) {
112*04fd306cSNickeau            $nullstream = fopen('NUL', 'c');
113*04fd306cSNickeau
114*04fd306cSNickeau            return [
115*04fd306cSNickeau                ['pipe', 'r'],
116*04fd306cSNickeau                $nullstream,
117*04fd306cSNickeau                $nullstream,
118*04fd306cSNickeau            ];
119*04fd306cSNickeau        }
120*04fd306cSNickeau
121*04fd306cSNickeau        // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
122*04fd306cSNickeau        // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
123*04fd306cSNickeau        // So we redirect output within the commandline and pass the nul device to the process
124*04fd306cSNickeau        return [
125*04fd306cSNickeau            ['pipe', 'r'],
126*04fd306cSNickeau            ['file', 'NUL', 'w'],
127*04fd306cSNickeau            ['file', 'NUL', 'w'],
128*04fd306cSNickeau        ];
129*04fd306cSNickeau    }
130*04fd306cSNickeau
131*04fd306cSNickeau    /**
132*04fd306cSNickeau     * {@inheritdoc}
133*04fd306cSNickeau     */
134*04fd306cSNickeau    public function getFiles(): array
135*04fd306cSNickeau    {
136*04fd306cSNickeau        return $this->files;
137*04fd306cSNickeau    }
138*04fd306cSNickeau
139*04fd306cSNickeau    /**
140*04fd306cSNickeau     * {@inheritdoc}
141*04fd306cSNickeau     */
142*04fd306cSNickeau    public function readAndWrite(bool $blocking, bool $close = false): array
143*04fd306cSNickeau    {
144*04fd306cSNickeau        $this->unblock();
145*04fd306cSNickeau        $w = $this->write();
146*04fd306cSNickeau        $read = $r = $e = [];
147*04fd306cSNickeau
148*04fd306cSNickeau        if ($blocking) {
149*04fd306cSNickeau            if ($w) {
150*04fd306cSNickeau                @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
151*04fd306cSNickeau            } elseif ($this->fileHandles) {
152*04fd306cSNickeau                usleep(Process::TIMEOUT_PRECISION * 1E6);
153*04fd306cSNickeau            }
154*04fd306cSNickeau        }
155*04fd306cSNickeau        foreach ($this->fileHandles as $type => $fileHandle) {
156*04fd306cSNickeau            $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
157*04fd306cSNickeau
158*04fd306cSNickeau            if (isset($data[0])) {
159*04fd306cSNickeau                $this->readBytes[$type] += \strlen($data);
160*04fd306cSNickeau                $read[$type] = $data;
161*04fd306cSNickeau            }
162*04fd306cSNickeau            if ($close) {
163*04fd306cSNickeau                ftruncate($fileHandle, 0);
164*04fd306cSNickeau                fclose($fileHandle);
165*04fd306cSNickeau                flock($this->lockHandles[$type], \LOCK_UN);
166*04fd306cSNickeau                fclose($this->lockHandles[$type]);
167*04fd306cSNickeau                unset($this->fileHandles[$type], $this->lockHandles[$type]);
168*04fd306cSNickeau            }
169*04fd306cSNickeau        }
170*04fd306cSNickeau
171*04fd306cSNickeau        return $read;
172*04fd306cSNickeau    }
173*04fd306cSNickeau
174*04fd306cSNickeau    /**
175*04fd306cSNickeau     * {@inheritdoc}
176*04fd306cSNickeau     */
177*04fd306cSNickeau    public function haveReadSupport(): bool
178*04fd306cSNickeau    {
179*04fd306cSNickeau        return $this->haveReadSupport;
180*04fd306cSNickeau    }
181*04fd306cSNickeau
182*04fd306cSNickeau    /**
183*04fd306cSNickeau     * {@inheritdoc}
184*04fd306cSNickeau     */
185*04fd306cSNickeau    public function areOpen(): bool
186*04fd306cSNickeau    {
187*04fd306cSNickeau        return $this->pipes && $this->fileHandles;
188*04fd306cSNickeau    }
189*04fd306cSNickeau
190*04fd306cSNickeau    /**
191*04fd306cSNickeau     * {@inheritdoc}
192*04fd306cSNickeau     */
193*04fd306cSNickeau    public function close()
194*04fd306cSNickeau    {
195*04fd306cSNickeau        parent::close();
196*04fd306cSNickeau        foreach ($this->fileHandles as $type => $handle) {
197*04fd306cSNickeau            ftruncate($handle, 0);
198*04fd306cSNickeau            fclose($handle);
199*04fd306cSNickeau            flock($this->lockHandles[$type], \LOCK_UN);
200*04fd306cSNickeau            fclose($this->lockHandles[$type]);
201*04fd306cSNickeau        }
202*04fd306cSNickeau        $this->fileHandles = $this->lockHandles = [];
203*04fd306cSNickeau    }
204*04fd306cSNickeau}
205