PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.13.5
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.13.5
5.12.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 4 years ago OutputInterface.php 4 years ago Process.php 4 years ago RequestCommand.php 5 years ago RequestParser.php 5 years ago StaticOutput.php 4 years ago
Process.php
324 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\Common;
12 use Piwik\Container\StaticContainer;
13 use Piwik\Filesystem;
14 use Piwik\SettingsServer;
15
16 /**
17 * There are three different states
18 * - PID file exists with empty content: Process is created but not started
19 * - PID file exists with the actual process PID as content: Process is running
20 * - PID file does not exist: Process is marked as finished
21 *
22 * Class Process
23 */
24 class Process
25 {
26 const PS_COMMAND = 'ps x';
27 const AWK_COMMAND = 'awk \'! /defunct/ {print $1}\'';
28
29 private $finished = null;
30 private $pidFile = '';
31 private $timeCreation = null;
32 private static $isSupported = null;
33 private $pid = null;
34 private $started = null;
35
36 public function __construct($pid)
37 {
38 if (!Filesystem::isValidFilename($pid)) {
39 throw new \Exception('The given pid has an invalid format');
40 }
41
42 $pidDir = CliMulti::getTmpPath();
43 Filesystem::mkdir($pidDir);
44
45 $this->pidFile = $pidDir . '/' . $pid . '.pid';
46 $this->timeCreation = time();
47 $this->pid = $pid;
48
49 $this->markAsNotStarted();
50 }
51
52 private static function isForcingAsyncProcessMode()
53 {
54 try {
55 return (bool) StaticContainer::get('test.vars.forceCliMultiViaCurl');
56 } catch (\Exception $ex) {
57 return false;
58 }
59 }
60
61 public function getPid()
62 {
63 return $this->pid;
64 }
65
66 private function markAsNotStarted()
67 {
68 $content = $this->getPidFileContent();
69
70 if ($this->doesPidFileExist($content)) {
71 return;
72 }
73
74 $this->writePidFileContent('');
75 }
76
77 public function hasStarted($content = null)
78 {
79 if (!$this->started) {
80 $this->started = $this->checkPidIfHasStarted($content);
81 }
82 // 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.
83 // therefore we want to "cache" a successful start
84 return $this->started;
85 }
86
87 private function checkPidIfHasStarted($content = null)
88 {
89 if (is_null($content)) {
90 $content = $this->getPidFileContent();
91 }
92
93 if (!$this->doesPidFileExist($content)) {
94 // process is finished, this means there was a start before
95 return true;
96 }
97
98 if ('' === trim($content)) {
99 // pid file is overwritten by startProcess()
100 return false;
101 }
102
103 // process is probably running or pid file was not removed
104 return true;
105 }
106
107 public function hasFinished()
108 {
109 if ($this->finished) {
110 return true;
111 }
112
113 $content = $this->getPidFileContent();
114
115 return !$this->doesPidFileExist($content);
116 }
117
118 public function getSecondsSinceCreation()
119 {
120 return time() - $this->timeCreation;
121 }
122
123 public function startProcess()
124 {
125 $this->writePidFileContent(getmypid());
126 }
127
128 public function isRunning()
129 {
130 $content = $this->getPidFileContent();
131
132 if (!$this->doesPidFileExist($content)) {
133 return false;
134 }
135
136 if (!$this->pidFileSizeIsNormal($content)) {
137 $this->finishProcess();
138 return false;
139 }
140
141 if ($this->isProcessStillRunning($content)) {
142 return true;
143 }
144
145 if ($this->hasStarted($content)) {
146 $this->finishProcess();
147 }
148
149 return false;
150 }
151
152 private function pidFileSizeIsNormal($content)
153 {
154 $size = Common::mb_strlen($content);
155
156 return $size < 500;
157 }
158
159 public function finishProcess()
160 {
161 $this->finished = true;
162 Filesystem::deleteFileIfExists($this->pidFile);
163 }
164
165 private function doesPidFileExist($content)
166 {
167 return false !== $content;
168 }
169
170 private function isProcessStillRunning($content)
171 {
172 if (!self::isSupported()) {
173 return true;
174 }
175
176 $lockedPID = trim($content);
177 $runningPIDs = self::getRunningProcesses();
178
179 return !empty($lockedPID) && in_array($lockedPID, $runningPIDs);
180 }
181
182 private function getPidFileContent()
183 {
184 return @file_get_contents($this->pidFile);
185 }
186
187 /**
188 * Tests only
189 * @internal
190 * @param $content
191 */
192 public function writePidFileContent($content)
193 {
194 file_put_contents($this->pidFile, $content);
195 }
196
197 public static function isSupported()
198 {
199 if (!isset(self::$isSupported)) {
200 $reasons = self::isSupportedWithReason();
201 self::$isSupported = empty($reasons);
202 }
203 return self::$isSupported;
204 }
205
206 public static function isSupportedWithReason()
207 {
208 $reasons = [];
209
210 if (defined('PIWIK_TEST_MODE')
211 && self::isForcingAsyncProcessMode()
212 ) {
213 $reasons[] = 'forcing multicurl use for tests';
214 }
215
216 if (SettingsServer::isWindows()) {
217 $reasons[] = 'not supported on windows';
218 return $reasons;
219 }
220
221 if (self::isMethodDisabled('shell_exec')) {
222 $reasons[] = 'shell_exec is disabled';
223 return $reasons; // shell_exec is used for almost every other check
224 }
225
226 $getMyPidDisabled = self::isMethodDisabled('getmypid');
227 if ($getMyPidDisabled) {
228 $reasons[] = 'getmypid is disabled';
229 }
230
231 if (self::isSystemNotSupported()) {
232 $reasons[] = 'system returned by `uname -a` is not supported';
233 }
234
235 if (!self::psExistsAndRunsCorrectly()) {
236 $reasons[] = 'shell_exec(' . self::PS_COMMAND . '" 2> /dev/null") did not return a success code';
237 } else if (!$getMyPidDisabled) {
238 $pid = @getmypid();
239 if (empty($pid) || !in_array($pid, self::getRunningProcesses())) {
240 $reasons[] = 'could not find our pid (from getmypid()) in the output of `' . self::PS_COMMAND . '`';
241 }
242 }
243
244 if (!self::awkExistsAndRunsCorrectly()) {
245 $reasons[] = 'awk is not available or did not run as we would expect it to';
246 }
247
248 return $reasons;
249 }
250
251 private static function psExistsAndRunsCorrectly()
252 {
253 return self::returnsSuccessCode(self::PS_COMMAND . ' 2>/dev/null');
254 }
255
256 private static function awkExistsAndRunsCorrectly()
257 {
258 $testResult = @shell_exec('echo " 537 s000 Ss 0:00.05 login -pfl theuser /bin/bash -c exec -la bash /bin/bash" | ' . self::AWK_COMMAND . ' 2>/dev/null');
259 return trim($testResult) == '537';
260 }
261
262 private static function isSystemNotSupported()
263 {
264 $uname = @shell_exec('uname -a 2> /dev/null');
265
266 if (empty($uname)) {
267 $uname = php_uname();
268 }
269
270 if (strpos($uname, 'synology') !== false) {
271 return true;
272 }
273 return false;
274 }
275
276 public static function isMethodDisabled($command)
277 {
278 if (!function_exists($command)) {
279 return true;
280 }
281
282 $disabled = explode(',', ini_get('disable_functions'));
283 $disabled = array_map('trim', $disabled);
284 return in_array($command, $disabled) || !function_exists($command);
285 }
286
287 private static function returnsSuccessCode($command)
288 {
289 $exec = $command . ' > /dev/null 2>&1; echo $?';
290 $returnCode = @shell_exec($exec);
291
292 if (false === $returnCode || null === $returnCode) {
293 return false;
294 }
295
296 $returnCode = trim($returnCode);
297 return 0 == (int) $returnCode;
298 }
299
300 public static function getListOfRunningProcesses()
301 {
302 $processes = @shell_exec(self::PS_COMMAND . ' 2>/dev/null');
303 if (empty($processes)) {
304 return array();
305 }
306 return explode("\n", $processes);
307 }
308
309 /**
310 * @return int[] The ids of the currently running processes
311 */
312 public static function getRunningProcesses()
313 {
314 $ids = explode("\n", trim(shell_exec(self::PS_COMMAND . ' 2>/dev/null | ' . self::AWK_COMMAND . ' 2>/dev/null')));
315
316 $ids = array_map('intval', $ids);
317 $ids = array_filter($ids, function ($id) {
318 return $id > 0;
319 });
320
321 return $ids;
322 }
323 }
324