PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.3.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.3.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / CliMulti / Process.php
matomo / app / core / CliMulti Last commit date
CliPhp.php 5 years ago Output.php 5 years ago Process.php 5 years ago RequestCommand.php 5 years ago RequestParser.php 5 years ago
Process.php
308 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 */
8 namespace Piwik\CliMulti;
9
10 use Piwik\CliMulti;
11 use Piwik\Container\StaticContainer;
12 use Piwik\Filesystem;
13 use Piwik\SettingsServer;
14
15 /**
16 * There are three different states
17 * - PID file exists with empty content: Process is created but not started
18 * - PID file exists with the actual process PID as content: Process is running
19 * - PID file does not exist: Process is marked as finished
20 *
21 * Class Process
22 */
23 class Process
24 {
25 private $finished = null;
26 private $pidFile = '';
27 private $timeCreation = null;
28 private $isSupported = null;
29 private $pid = null;
30 private $started = null;
31
32 public function __construct($pid)
33 {
34 if (!Filesystem::isValidFilename($pid)) {
35 throw new \Exception('The given pid has an invalid format');
36 }
37
38 $pidDir = CliMulti::getTmpPath();
39 Filesystem::mkdir($pidDir);
40
41 $this->isSupported = self::isSupported();
42 $this->pidFile = $pidDir . '/' . $pid . '.pid';
43 $this->timeCreation = time();
44 $this->pid = $pid;
45
46 $this->markAsNotStarted();
47 }
48
49 private static function isForcingAsyncProcessMode()
50 {
51 try {
52 return (bool) StaticContainer::get('test.vars.forceCliMultiViaCurl');
53 } catch (\Exception $ex) {
54 return false;
55 }
56 }
57
58 public function getPid()
59 {
60 return $this->pid;
61 }
62
63 private function markAsNotStarted()
64 {
65 $content = $this->getPidFileContent();
66
67 if ($this->doesPidFileExist($content)) {
68 return;
69 }
70
71 $this->writePidFileContent('');
72 }
73
74 public function hasStarted($content = null)
75 {
76 if (!$this->started) {
77 $this->started = $this->checkPidIfHasStarted($content);
78 }
79 // PID will be deleted when process has finished so we want to remember this process started at some point. Otherwise we might return false here once the process finished.
80 // therefore we want to "cache" a successful start
81 return $this->started;
82 }
83
84 private function checkPidIfHasStarted($content = null)
85 {
86 if (is_null($content)) {
87 $content = $this->getPidFileContent();
88 }
89
90 if (!$this->doesPidFileExist($content)) {
91 // process is finished, this means there was a start before
92 return true;
93 }
94
95 if ('' === trim($content)) {
96 // pid file is overwritten by startProcess()
97 return false;
98 }
99
100 // process is probably running or pid file was not removed
101 return true;
102 }
103
104 public function hasFinished()
105 {
106 if ($this->finished) {
107 return true;
108 }
109
110 $content = $this->getPidFileContent();
111
112 return !$this->doesPidFileExist($content);
113 }
114
115 public function getSecondsSinceCreation()
116 {
117 return time() - $this->timeCreation;
118 }
119
120 public function startProcess()
121 {
122 $this->writePidFileContent(getmypid());
123 }
124
125 public function isRunning()
126 {
127 $content = $this->getPidFileContent();
128
129 if (!$this->doesPidFileExist($content)) {
130 return false;
131 }
132
133 if (!$this->pidFileSizeIsNormal()) {
134 $this->finishProcess();
135 return false;
136 }
137
138 if ($this->isProcessStillRunning($content)) {
139 return true;
140 }
141
142 if ($this->hasStarted($content)) {
143 $this->finishProcess();
144 }
145
146 return false;
147 }
148
149 private function pidFileSizeIsNormal()
150 {
151 $size = Filesystem::getFileSize($this->pidFile);
152
153 return $size !== null && $size < 500;
154 }
155
156 public function finishProcess()
157 {
158 $this->finished = true;
159 Filesystem::deleteFileIfExists($this->pidFile);
160 }
161
162 private function doesPidFileExist($content)
163 {
164 return false !== $content;
165 }
166
167 private function isProcessStillRunning($content)
168 {
169 if (!$this->isSupported) {
170 return true;
171 }
172
173 $lockedPID = trim($content);
174 $runningPIDs = self::getRunningProcesses();
175
176 return !empty($lockedPID) && in_array($lockedPID, $runningPIDs);
177 }
178
179 private function getPidFileContent()
180 {
181 return @file_get_contents($this->pidFile);
182 }
183
184 private function writePidFileContent($content)
185 {
186 file_put_contents($this->pidFile, $content);
187 }
188
189 public static function isSupported()
190 {
191 if (defined('PIWIK_TEST_MODE')
192 && self::isForcingAsyncProcessMode()
193 ) {
194 return false;
195 }
196
197 if (SettingsServer::isWindows()) {
198 return false;
199 }
200
201 if (self::isMethodDisabled('shell_exec')) {
202 return false;
203 }
204
205 if (self::isMethodDisabled('getmypid')) {
206 return false;
207 }
208
209 if (self::isSystemNotSupported()) {
210 return false;
211 }
212
213 if (!self::commandExists('ps') || !self::returnsSuccessCode('ps') || !self::commandExists('awk')) {
214 return false;
215 }
216
217 $pid = @getmypid();
218 if (empty($pid) || !in_array($pid, self::getRunningProcesses())) {
219 return false;
220 }
221
222 if (!self::isProcFSMounted() && !SettingsServer::isMac()) {
223 return false;
224 }
225
226 return true;
227 }
228
229 private static function isSystemNotSupported()
230 {
231 $uname = @shell_exec('uname -a 2> /dev/null');
232
233 if (empty($uname)) {
234 $uname = php_uname();
235 }
236
237 if (strpos($uname, 'synology') !== false) {
238 return true;
239 }
240 return false;
241 }
242
243 public static function isMethodDisabled($command)
244 {
245 if (!function_exists($command)) {
246 return true;
247 }
248
249 $disabled = explode(',', ini_get('disable_functions'));
250 $disabled = array_map('trim', $disabled);
251 return in_array($command, $disabled) || !function_exists($command);
252 }
253
254 private static function returnsSuccessCode($command)
255 {
256 $exec = $command . ' > /dev/null 2>&1; echo $?';
257 $returnCode = shell_exec($exec);
258 $returnCode = trim($returnCode);
259 return 0 == (int) $returnCode;
260 }
261
262 private static function commandExists($command)
263 {
264 $result = @shell_exec('which ' . escapeshellarg($command) . ' 2> /dev/null');
265
266 return !empty($result);
267 }
268
269 /**
270 * ps -e requires /proc
271 * @return bool
272 */
273 private static function isProcFSMounted()
274 {
275 if (is_resource(@fopen('/proc', 'r'))) {
276 return true;
277 }
278 // Testing if /proc is a resource with @fopen fails on systems with open_basedir set.
279 // by using stat we not only test the existence of /proc but also confirm it's a 'proc' filesystem
280 $type = @shell_exec('stat -f -c "%T" /proc 2>/dev/null');
281 return strpos($type, 'proc') === 0;
282 }
283
284 public static function getListOfRunningProcesses()
285 {
286 $processes = `ps x 2>/dev/null`;
287 if (empty($processes)) {
288 return array();
289 }
290 return explode("\n", $processes);
291 }
292
293 /**
294 * @return int[] The ids of the currently running processes
295 */
296 public static function getRunningProcesses()
297 {
298 $ids = explode("\n", trim(`ps x 2>/dev/null | awk '! /defunct/ {print $1}' 2>/dev/null`));
299
300 $ids = array_map('intval', $ids);
301 $ids = array_filter($ids, function ($id) {
302 return $id > 0;
303 });
304
305 return $ids;
306 }
307 }
308