PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
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 1 year ago Output.php 2 years ago OutputInterface.php 2 years ago Process.php 1 year ago ProcessSymfony.php 1 year ago RequestCommand.php 1 year ago StaticOutput.php 1 year ago
Process.php
264 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://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 public const PS_COMMAND = 'ps wwx';
27 public 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 } elseif (!$getMyPidDisabled) {
194 $pid = @\getmypid();
195 if (empty($pid) || !in_array($pid, self::getRunningProcesses())) {
196 $reasons[] = 'could not find our pid (from getmypid()) in the output of `' . self::PS_COMMAND . '`';
197 }
198 }
199 if (!self::awkExistsAndRunsCorrectly()) {
200 $reasons[] = 'awk is not available or did not run as we would expect it to';
201 }
202 return $reasons;
203 }
204 private static function psExistsAndRunsCorrectly()
205 {
206 return self::returnsSuccessCode(self::PS_COMMAND . ' 2>/dev/null');
207 }
208 private static function awkExistsAndRunsCorrectly()
209 {
210 $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');
211 return trim($testResult ?? '') == '537';
212 }
213 private static function isSystemNotSupported()
214 {
215 $uname = @shell_exec('uname -a 2> /dev/null');
216 if (empty($uname)) {
217 $uname = php_uname();
218 }
219 if (strpos($uname, 'synology') !== \false) {
220 return \true;
221 }
222 return \false;
223 }
224 public static function isMethodDisabled($command)
225 {
226 if (!function_exists($command)) {
227 return \true;
228 }
229 $disabled = explode(',', ini_get('disable_functions'));
230 $disabled = array_map('trim', $disabled);
231 return in_array($command, $disabled) || !function_exists($command);
232 }
233 private static function returnsSuccessCode($command)
234 {
235 $exec = $command . ' > /dev/null 2>&1; echo $?';
236 $returnCode = @shell_exec($exec);
237 if (\false === $returnCode || null === $returnCode) {
238 return \false;
239 }
240 $returnCode = trim($returnCode);
241 return 0 == (int) $returnCode;
242 }
243 public static function getListOfRunningProcesses()
244 {
245 $processes = @shell_exec(self::PS_COMMAND . ' 2>/dev/null');
246 if (empty($processes)) {
247 return array();
248 }
249 return explode("\n", $processes);
250 }
251 /**
252 * @return int[] The ids of the currently running processes
253 */
254 public static function getRunningProcesses()
255 {
256 $ids = explode("\n", trim(shell_exec(self::PS_COMMAND . ' 2>/dev/null | ' . self::AWK_COMMAND . ' 2>/dev/null') ?? ''));
257 $ids = array_map('intval', $ids);
258 $ids = array_filter($ids, function ($id) {
259 return $id > 0;
260 });
261 return $ids;
262 }
263 }
264