PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.0.3
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.0.3
5.12.1 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 2 years ago Output.php 2 years ago OutputInterface.php 2 years ago Process.php 2 years ago RequestCommand.php 2 years ago RequestParser.php 2 years ago StaticOutput.php 2 years ago
Process.php
266 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\CliMulti;
10
11 use Piwik\CliMulti;
12 use Piwik\Common;
13 use Piwik\Container\StaticContainer;
14 use Piwik\Filesystem;
15 use Piwik\SettingsServer;
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 private $finished = null;
29 private $pidFile = '';
30 private $timeCreation = null;
31 private static $isSupported = null;
32 private $pid = null;
33 private $started = null;
34 public function __construct($pid)
35 {
36 if (!Filesystem::isValidFilename($pid)) {
37 throw new \Exception('The given pid has an invalid format');
38 }
39 $pidDir = CliMulti::getTmpPath();
40 Filesystem::mkdir($pidDir);
41 $this->pidFile = $pidDir . '/' . $pid . '.pid';
42 $this->timeCreation = time();
43 $this->pid = $pid;
44 $this->markAsNotStarted();
45 }
46 private static function isForcingAsyncProcessMode()
47 {
48 try {
49 return (bool) StaticContainer::get('test.vars.forceCliMultiViaCurl');
50 } catch (\Exception $ex) {
51 return false;
52 }
53 }
54 public function getPid()
55 {
56 return $this->pid;
57 }
58 private function markAsNotStarted()
59 {
60 $content = $this->getPidFileContent();
61 if ($this->doesPidFileExist($content)) {
62 return;
63 }
64 $this->writePidFileContent('');
65 }
66 public function hasStarted($content = null)
67 {
68 if (!$this->started) {
69 $this->started = $this->checkPidIfHasStarted($content);
70 }
71 // 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.
72 // therefore we want to "cache" a successful start
73 return $this->started;
74 }
75 private function checkPidIfHasStarted($content = null)
76 {
77 if (is_null($content)) {
78 $content = $this->getPidFileContent();
79 }
80 if (!$this->doesPidFileExist($content)) {
81 // process is finished, this means there was a start before
82 return true;
83 }
84 if ('' === trim($content)) {
85 // pid file is overwritten by startProcess()
86 return false;
87 }
88 // process is probably running or pid file was not removed
89 return true;
90 }
91 public function hasFinished()
92 {
93 if ($this->finished) {
94 return true;
95 }
96 $content = $this->getPidFileContent();
97 return !$this->doesPidFileExist($content);
98 }
99 public function getSecondsSinceCreation()
100 {
101 return time() - $this->timeCreation;
102 }
103 public function startProcess()
104 {
105 $this->writePidFileContent(Common::getProcessId());
106 }
107 public function isRunning()
108 {
109 $content = $this->getPidFileContent();
110 if (!$this->doesPidFileExist($content)) {
111 return false;
112 }
113 if (!$this->pidFileSizeIsNormal($content)) {
114 $this->finishProcess();
115 return false;
116 }
117 if ($this->isProcessStillRunning($content)) {
118 return true;
119 }
120 if ($this->hasStarted($content)) {
121 $this->finishProcess();
122 }
123 return false;
124 }
125 private function pidFileSizeIsNormal($content)
126 {
127 $size = Common::mb_strlen($content);
128 return $size < 500;
129 }
130 public function finishProcess()
131 {
132 $this->finished = true;
133 Filesystem::deleteFileIfExists($this->pidFile);
134 }
135 private function doesPidFileExist($content)
136 {
137 return false !== $content;
138 }
139 private function isProcessStillRunning($content)
140 {
141 if (!self::isSupported()) {
142 return true;
143 }
144 $lockedPID = trim($content);
145 $runningPIDs = self::getRunningProcesses();
146 return !empty($lockedPID) && in_array($lockedPID, $runningPIDs);
147 }
148 private function getPidFileContent()
149 {
150 return @file_get_contents($this->pidFile);
151 }
152 /**
153 * Tests only
154 * @internal
155 * @param $content
156 */
157 public function writePidFileContent($content)
158 {
159 file_put_contents($this->pidFile, $content);
160 }
161 public static function isSupported()
162 {
163 if (!isset(self::$isSupported)) {
164 $reasons = self::isSupportedWithReason();
165 self::$isSupported = empty($reasons);
166 }
167 return self::$isSupported;
168 }
169 public static function isSupportedWithReason()
170 {
171 $reasons = [];
172 if (defined('PIWIK_TEST_MODE') && self::isForcingAsyncProcessMode()) {
173 $reasons[] = 'forcing multicurl use for tests';
174 }
175 if (SettingsServer::isWindows()) {
176 $reasons[] = 'not supported on windows';
177 return $reasons;
178 }
179 if (self::isMethodDisabled('shell_exec')) {
180 $reasons[] = 'shell_exec is disabled';
181 return $reasons;
182 // shell_exec is used for almost every other check
183 }
184 $getMyPidDisabled = self::isMethodDisabled('getmypid');
185 if ($getMyPidDisabled) {
186 $reasons[] = 'getmypid is disabled';
187 }
188 if (self::isSystemNotSupported()) {
189 $reasons[] = 'system returned by `uname -a` is not supported';
190 }
191 if (!self::psExistsAndRunsCorrectly()) {
192 $reasons[] = 'shell_exec(' . self::PS_COMMAND . '" 2> /dev/null") did not return a success code';
193 } else {
194 if (!$getMyPidDisabled) {
195 $pid = @\getmypid();
196 if (empty($pid) || !in_array($pid, self::getRunningProcesses())) {
197 $reasons[] = 'could not find our pid (from getmypid()) in the output of `' . self::PS_COMMAND . '`';
198 }
199 }
200 }
201 if (!self::awkExistsAndRunsCorrectly()) {
202 $reasons[] = 'awk is not available or did not run as we would expect it to';
203 }
204 return $reasons;
205 }
206 private static function psExistsAndRunsCorrectly()
207 {
208 return self::returnsSuccessCode(self::PS_COMMAND . ' 2>/dev/null');
209 }
210 private static function awkExistsAndRunsCorrectly()
211 {
212 $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');
213 return trim($testResult ?? '') == '537';
214 }
215 private static function isSystemNotSupported()
216 {
217 $uname = @shell_exec('uname -a 2> /dev/null');
218 if (empty($uname)) {
219 $uname = php_uname();
220 }
221 if (strpos($uname, 'synology') !== false) {
222 return true;
223 }
224 return false;
225 }
226 public static function isMethodDisabled($command)
227 {
228 if (!function_exists($command)) {
229 return true;
230 }
231 $disabled = explode(',', ini_get('disable_functions'));
232 $disabled = array_map('trim', $disabled);
233 return in_array($command, $disabled) || !function_exists($command);
234 }
235 private static function returnsSuccessCode($command)
236 {
237 $exec = $command . ' > /dev/null 2>&1; echo $?';
238 $returnCode = @shell_exec($exec);
239 if (false === $returnCode || null === $returnCode) {
240 return false;
241 }
242 $returnCode = trim($returnCode);
243 return 0 == (int) $returnCode;
244 }
245 public static function getListOfRunningProcesses()
246 {
247 $processes = @shell_exec(self::PS_COMMAND . ' 2>/dev/null');
248 if (empty($processes)) {
249 return array();
250 }
251 return explode("\n", $processes);
252 }
253 /**
254 * @return int[] The ids of the currently running processes
255 */
256 public static function getRunningProcesses()
257 {
258 $ids = explode("\n", trim(shell_exec(self::PS_COMMAND . ' 2>/dev/null | ' . self::AWK_COMMAND . ' 2>/dev/null')));
259 $ids = array_map('intval', $ids);
260 $ids = array_filter($ids, function ($id) {
261 return $id > 0;
262 });
263 return $ids;
264 }
265 }
266