| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\CliMulti; |
|
|
| use Piwik\CliMulti; |
| use Piwik\Common; |
| use Piwik\Container\StaticContainer; |
| use Piwik\Filesystem; |
| use Piwik\SettingsServer; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| class Process |
| { |
| public const PS_COMMAND = 'ps wwx'; |
| public const AWK_COMMAND = 'awk \'! /defunct/ {print $1}\''; |
|
|
| private $finished = null; |
| private $pidFile = ''; |
| private $timeCreation = null; |
| private static $isSupported = null; |
| private $pid = null; |
| private $started = null; |
|
|
| public function __construct($pid) |
| { |
| if (!Filesystem::isValidFilename($pid)) { |
| throw new \Exception('The given pid has an invalid format'); |
| } |
|
|
| $pidDir = CliMulti::getTmpPath(); |
| Filesystem::mkdir($pidDir); |
|
|
| $this->pidFile = $pidDir . '/' . $pid . '.pid'; |
| $this->timeCreation = time(); |
| $this->pid = $pid; |
|
|
| $this->markAsNotStarted(); |
| } |
|
|
| private static function isForcingAsyncProcessMode() |
| { |
| try { |
| return (bool) StaticContainer::get('test.vars.forceCliMultiViaCurl'); |
| } catch (\Exception $ex) { |
| return false; |
| } |
| } |
|
|
| public function getPid() |
| { |
| return $this->pid; |
| } |
|
|
| private function markAsNotStarted() |
| { |
| $content = $this->getPidFileContent(); |
|
|
| if ($this->doesPidFileExist($content)) { |
| return; |
| } |
|
|
| $this->writePidFileContent(''); |
| } |
|
|
| public function hasStarted($content = null) |
| { |
| if (!$this->started) { |
| $this->started = $this->checkPidIfHasStarted($content); |
| } |
| |
| |
| return $this->started; |
| } |
|
|
| private function checkPidIfHasStarted($content = null) |
| { |
| if (is_null($content)) { |
| $content = $this->getPidFileContent(); |
| } |
|
|
| if (!$this->doesPidFileExist($content)) { |
| |
| return true; |
| } |
|
|
| if ('' === trim($content)) { |
| |
| return false; |
| } |
|
|
| |
| return true; |
| } |
|
|
| public function hasFinished() |
| { |
| if ($this->finished) { |
| return true; |
| } |
|
|
| $content = $this->getPidFileContent(); |
|
|
| return !$this->doesPidFileExist($content); |
| } |
|
|
| public function getSecondsSinceCreation() |
| { |
| return time() - $this->timeCreation; |
| } |
|
|
| public function startProcess() |
| { |
| $this->writePidFileContent(Common::getProcessId()); |
| } |
|
|
| public function isRunning() |
| { |
| $content = $this->getPidFileContent(); |
|
|
| if (!$this->doesPidFileExist($content)) { |
| return false; |
| } |
|
|
| if (!$this->pidFileSizeIsNormal($content)) { |
| $this->finishProcess(); |
| return false; |
| } |
|
|
| if ($this->isProcessStillRunning($content)) { |
| return true; |
| } |
|
|
| if ($this->hasStarted($content)) { |
| $this->finishProcess(); |
| } |
|
|
| return false; |
| } |
|
|
| private function pidFileSizeIsNormal($content) |
| { |
| $size = Common::mb_strlen($content); |
|
|
| return $size < 500; |
| } |
|
|
| public function finishProcess() |
| { |
| $this->finished = true; |
| Filesystem::deleteFileIfExists($this->pidFile); |
| } |
|
|
| private function doesPidFileExist($content) |
| { |
| return false !== $content; |
| } |
|
|
| private function isProcessStillRunning($content) |
| { |
| if (!self::isSupported()) { |
| return true; |
| } |
|
|
| $lockedPID = trim($content); |
| $runningPIDs = self::getRunningProcesses(); |
|
|
| return !empty($lockedPID) && in_array($lockedPID, $runningPIDs); |
| } |
|
|
| private function getPidFileContent() |
| { |
| return @file_get_contents($this->pidFile); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function writePidFileContent($content) |
| { |
| file_put_contents($this->pidFile, $content); |
| } |
|
|
| public static function isSupported() |
| { |
| if (!isset(self::$isSupported)) { |
| $reasons = self::isSupportedWithReason(); |
| self::$isSupported = empty($reasons); |
| } |
| return self::$isSupported; |
| } |
|
|
| public static function isSupportedWithReason() |
| { |
| $reasons = []; |
|
|
| if ( |
| defined('PIWIK_TEST_MODE') |
| && self::isForcingAsyncProcessMode() |
| ) { |
| $reasons[] = 'forcing multicurl use for tests'; |
| } |
|
|
| if (SettingsServer::isWindows()) { |
| $reasons[] = 'not supported on windows'; |
| return $reasons; |
| } |
|
|
| if (self::isMethodDisabled('shell_exec')) { |
| $reasons[] = 'shell_exec is disabled'; |
| return $reasons; |
| } |
|
|
| $getMyPidDisabled = self::isMethodDisabled('getmypid'); |
| if ($getMyPidDisabled) { |
| $reasons[] = 'getmypid is disabled'; |
| } |
|
|
| if (self::isSystemNotSupported()) { |
| $reasons[] = 'system returned by `uname -a` is not supported'; |
| } |
|
|
| if (!self::psExistsAndRunsCorrectly()) { |
| $reasons[] = 'shell_exec(' . self::PS_COMMAND . '" 2> /dev/null") did not return a success code'; |
| } elseif (!$getMyPidDisabled) { |
| $pid = @\getmypid(); |
| if (empty($pid) || !in_array($pid, self::getRunningProcesses())) { |
| $reasons[] = 'could not find our pid (from getmypid()) in the output of `' . self::PS_COMMAND . '`'; |
| } |
| } |
|
|
| if (!self::awkExistsAndRunsCorrectly()) { |
| $reasons[] = 'awk is not available or did not run as we would expect it to'; |
| } |
|
|
| return $reasons; |
| } |
|
|
| private static function psExistsAndRunsCorrectly() |
| { |
| return self::returnsSuccessCode(self::PS_COMMAND . ' 2>/dev/null'); |
| } |
|
|
| private static function awkExistsAndRunsCorrectly() |
| { |
| $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'); |
| return trim($testResult ?? '') == '537'; |
| } |
|
|
| private static function isSystemNotSupported() |
| { |
| $uname = @shell_exec('uname -a 2> /dev/null'); |
|
|
| if (empty($uname)) { |
| $uname = php_uname(); |
| } |
|
|
| if (strpos($uname, 'synology') !== false) { |
| return true; |
| } |
| return false; |
| } |
|
|
| public static function isMethodDisabled($command) |
| { |
| if (!function_exists($command)) { |
| return true; |
| } |
|
|
| $disabled = explode(',', ini_get('disable_functions')); |
| $disabled = array_map('trim', $disabled); |
| return in_array($command, $disabled) || !function_exists($command); |
| } |
|
|
| private static function returnsSuccessCode($command) |
| { |
| $exec = $command . ' > /dev/null 2>&1; echo $?'; |
| $returnCode = @shell_exec($exec); |
|
|
| if (false === $returnCode || null === $returnCode) { |
| return false; |
| } |
|
|
| $returnCode = trim($returnCode); |
| return 0 == (int) $returnCode; |
| } |
|
|
| public static function getListOfRunningProcesses() |
| { |
| $processes = @shell_exec(self::PS_COMMAND . ' 2>/dev/null'); |
| if (empty($processes)) { |
| return array(); |
| } |
| return explode("\n", $processes); |
| } |
|
|
| |
| |
| |
| public static function getRunningProcesses() |
| { |
| $ids = explode("\n", trim(shell_exec(self::PS_COMMAND . ' 2>/dev/null | ' . self::AWK_COMMAND . ' 2>/dev/null') ?? '')); |
|
|
| $ids = array_map('intval', $ids); |
| $ids = array_filter($ids, function ($id) { |
| return $id > 0; |
| }); |
|
|
| return $ids; |
| } |
| } |
|
|