Mercurial > hg > isophonics-drupal-site
comparison vendor/psy/psysh/src/ExecutionLoop/ProcessForker.php @ 13:5fb285c0d0e3
Update Drupal core to 8.4.7 via Composer. Security update; I *think* we've
been lucky to get away with this so far, as we don't support self-registration
which seems to be used by the so-called "drupalgeddon 2" attack that 8.4.5
was vulnerable to.
author | Chris Cannam |
---|---|
date | Mon, 23 Apr 2018 09:33:26 +0100 |
parents | |
children | c2387f117808 |
comparison
equal
deleted
inserted
replaced
12:7a779792577d | 13:5fb285c0d0e3 |
---|---|
1 <?php | |
2 | |
3 /* | |
4 * This file is part of Psy Shell. | |
5 * | |
6 * (c) 2012-2018 Justin Hileman | |
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 Psy\ExecutionLoop; | |
13 | |
14 use Psy\Context; | |
15 use Psy\Exception\BreakException; | |
16 use Psy\Shell; | |
17 | |
18 /** | |
19 * An execution loop listener that forks the process before executing code. | |
20 * | |
21 * This is awesome, as the session won't die prematurely if user input includes | |
22 * a fatal error, such as redeclaring a class or function. | |
23 */ | |
24 class ProcessForker extends AbstractListener | |
25 { | |
26 private $savegame; | |
27 private $up; | |
28 | |
29 /** | |
30 * Process forker is supported if pcntl and posix extensions are available. | |
31 * | |
32 * @return bool | |
33 */ | |
34 public static function isSupported() | |
35 { | |
36 return function_exists('pcntl_signal') && function_exists('posix_getpid'); | |
37 } | |
38 | |
39 /** | |
40 * Forks into a master and a loop process. | |
41 * | |
42 * The loop process will handle the evaluation of all instructions, then | |
43 * return its state via a socket upon completion. | |
44 * | |
45 * @param Shell $shell | |
46 */ | |
47 public function beforeRun(Shell $shell) | |
48 { | |
49 list($up, $down) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); | |
50 | |
51 if (!$up) { | |
52 throw new \RuntimeException('Unable to create socket pair'); | |
53 } | |
54 | |
55 $pid = pcntl_fork(); | |
56 if ($pid < 0) { | |
57 throw new \RuntimeException('Unable to start execution loop'); | |
58 } elseif ($pid > 0) { | |
59 // This is the main thread. We'll just wait for a while. | |
60 | |
61 // We won't be needing this one. | |
62 fclose($up); | |
63 | |
64 // Wait for a return value from the loop process. | |
65 $read = [$down]; | |
66 $write = null; | |
67 $except = null; | |
68 if (stream_select($read, $write, $except, null) === false) { | |
69 throw new \RuntimeException('Error waiting for execution loop'); | |
70 } | |
71 | |
72 $content = stream_get_contents($down); | |
73 fclose($down); | |
74 | |
75 if ($content) { | |
76 $shell->setScopeVariables(@unserialize($content)); | |
77 } | |
78 | |
79 throw new BreakException('Exiting main thread'); | |
80 } | |
81 | |
82 // This is the child process. It's going to do all the work. | |
83 if (function_exists('setproctitle')) { | |
84 setproctitle('psysh (loop)'); | |
85 } | |
86 | |
87 // We won't be needing this one. | |
88 fclose($down); | |
89 | |
90 // Save this; we'll need to close it in `afterRun` | |
91 $this->up = $up; | |
92 } | |
93 | |
94 /** | |
95 * Create a savegame at the start of each loop iteration. | |
96 * | |
97 * @param Shell $shell | |
98 */ | |
99 public function beforeLoop(Shell $shell) | |
100 { | |
101 $this->createSavegame(); | |
102 } | |
103 | |
104 /** | |
105 * Clean up old savegames at the end of each loop iteration. | |
106 * | |
107 * @param Shell $shell | |
108 */ | |
109 public function afterLoop(Shell $shell) | |
110 { | |
111 // if there's an old savegame hanging around, let's kill it. | |
112 if (isset($this->savegame)) { | |
113 posix_kill($this->savegame, SIGKILL); | |
114 pcntl_signal_dispatch(); | |
115 } | |
116 } | |
117 | |
118 /** | |
119 * After the REPL session ends, send the scope variables back up to the main | |
120 * thread (if this is a child thread). | |
121 * | |
122 * @param Shell $shell | |
123 */ | |
124 public function afterRun(Shell $shell) | |
125 { | |
126 // We're a child thread. Send the scope variables back up to the main thread. | |
127 if (isset($this->up)) { | |
128 fwrite($this->up, $this->serializeReturn($shell->getScopeVariables(false))); | |
129 fclose($this->up); | |
130 | |
131 posix_kill(posix_getpid(), SIGKILL); | |
132 } | |
133 } | |
134 | |
135 /** | |
136 * Create a savegame fork. | |
137 * | |
138 * The savegame contains the current execution state, and can be resumed in | |
139 * the event that the worker dies unexpectedly (for example, by encountering | |
140 * a PHP fatal error). | |
141 */ | |
142 private function createSavegame() | |
143 { | |
144 // the current process will become the savegame | |
145 $this->savegame = posix_getpid(); | |
146 | |
147 $pid = pcntl_fork(); | |
148 if ($pid < 0) { | |
149 throw new \RuntimeException('Unable to create savegame fork'); | |
150 } elseif ($pid > 0) { | |
151 // we're the savegame now... let's wait and see what happens | |
152 pcntl_waitpid($pid, $status); | |
153 | |
154 // worker exited cleanly, let's bail | |
155 if (!pcntl_wexitstatus($status)) { | |
156 posix_kill(posix_getpid(), SIGKILL); | |
157 } | |
158 | |
159 // worker didn't exit cleanly, we'll need to have another go | |
160 $this->createSavegame(); | |
161 } | |
162 } | |
163 | |
164 /** | |
165 * Serialize all serializable return values. | |
166 * | |
167 * A naïve serialization will run into issues if there is a Closure or | |
168 * SimpleXMLElement (among other things) in scope when exiting the execution | |
169 * loop. We'll just ignore these unserializable classes, and serialize what | |
170 * we can. | |
171 * | |
172 * @param array $return | |
173 * | |
174 * @return string | |
175 */ | |
176 private function serializeReturn(array $return) | |
177 { | |
178 $serializable = []; | |
179 | |
180 foreach ($return as $key => $value) { | |
181 // No need to return magic variables | |
182 if (Context::isSpecialVariableName($key)) { | |
183 continue; | |
184 } | |
185 | |
186 // Resources and Closures don't error, but they don't serialize well either. | |
187 if (is_resource($value) || $value instanceof \Closure) { | |
188 continue; | |
189 } | |
190 | |
191 try { | |
192 @serialize($value); | |
193 $serializable[$key] = $value; | |
194 } catch (\Throwable $e) { | |
195 // we'll just ignore this one... | |
196 } catch (\Exception $e) { | |
197 // and this one too... | |
198 // @todo remove this once we don't support PHP 5.x anymore :) | |
199 } | |
200 } | |
201 | |
202 return @serialize($serializable); | |
203 } | |
204 } |