PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.12.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.12.1
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 weeks ago Output.php 2 years ago OutputInterface.php 2 years ago Process.php 2 weeks ago ProcessSymfony.php 1 year ago RequestCommand.php 1 year ago StaticOutput.php 1 year ago
Process.php
265 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 * Writes the given content to the process' PID file.
154 *
155 * @internal
156 * @param string|int $content
157 */
158 public function writePidFileContent($content)
159 {
160 file_put_contents($this->pidFile, $content);
161 }
162 public static function isSupported()
163 {
164 if (!isset(self::$isSupported)) {
165 $reasons = self::isSupportedWithReason();
166 self::$isSupported = empty($reasons);
167 }
168 return self::$isSupported;
169 }
170 public static function isSupportedWithReason()
171 {
172 $reasons = [];
173 if (defined('PIWIK_TEST_MODE') && self::isForcingAsyncProcessMode()) {
174 $reasons[] = 'forcing multicurl use for tests';
175 }
176 if (SettingsServer::isWindows()) {
177 $reasons[] = 'not supported on windows';
178 return $reasons;
179 }
180 if (self::isMethodDisabled('shell_exec')) {
181 $reasons[] = 'shell_exec is disabled';
182 return $reasons;
183 // shell_exec is used for almost every other check
184 }
185 $getMyPidDisabled = self::isMethodDisabled('getmypid');
186 if ($getMyPidDisabled) {
187 $reasons[] = 'getmypid is disabled';
188 }
189 if (self::isSystemNotSupported()) {
190 $reasons[] = 'system returned by `uname -a` is not supported';
191 }
192 if (!self::psExistsAndRunsCorrectly()) {
193 $reasons[] = 'shell_exec(' . self::PS_COMMAND . '" 2> /dev/null") did not return a success code';
194 } elseif (!$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 if (!self::awkExistsAndRunsCorrectly()) {
201 $reasons[] = 'awk is not available or did not run as we would expect it to';
202 }
203 return $reasons;
204 }
205 private static function psExistsAndRunsCorrectly()
206 {
207 return self::returnsSuccessCode(self::PS_COMMAND . ' 2>/dev/null');
208 }
209 private static function awkExistsAndRunsCorrectly()
210 {
211 $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');
212 return trim($testResult ?? '') == '537';
213 }
214 private static function isSystemNotSupported()
215 {
216 $uname = @shell_exec('uname -a 2> /dev/null');
217 if (empty($uname)) {
218 $uname = php_uname();
219 }
220 if (strpos($uname, 'synology') !== \false) {
221 return \true;
222 }
223 return \false;
224 }
225 public static function isMethodDisabled($command)
226 {
227 if (!function_exists($command)) {
228 return \true;
229 }
230 $disabled = explode(',', ini_get('disable_functions'));
231 $disabled = array_map('trim', $disabled);
232 return in_array($command, $disabled) || !function_exists($command);
233 }
234 private static function returnsSuccessCode($command)
235 {
236 $exec = $command . ' > /dev/null 2>&1; echo $?';
237 $returnCode = @shell_exec($exec);
238 if (\false === $returnCode || null === $returnCode) {
239 return \false;
240 }
241 $returnCode = trim($returnCode);
242 return 0 == (int) $returnCode;
243 }
244 public static function getListOfRunningProcesses()
245 {
246 $processes = @shell_exec(self::PS_COMMAND . ' 2>/dev/null');
247 if (empty($processes)) {
248 return array();
249 }
250 return explode("\n", $processes);
251 }
252 /**
253 * @return int[] The ids of the currently running processes
254 */
255 public static function getRunningProcesses()
256 {
257 $ids = explode("\n", trim(shell_exec(self::PS_COMMAND . ' 2>/dev/null | ' . self::AWK_COMMAND . ' 2>/dev/null') ?? ''));
258 $ids = array_map('intval', $ids);
259 $ids = array_filter($ids, function ($id) {
260 return $id > 0;
261 });
262 return $ids;
263 }
264 }
265