*/ private static $cleanedPaths = [ \pocketmine\PATH => self::CLEAN_PATH_SRC_PREFIX ]; public const CLEAN_PATH_SRC_PREFIX = "pmsrc"; public const CLEAN_PATH_PLUGINS_PREFIX = "plugins"; private function __construct(){ //NOOP } public static function recursiveUnlink(string $dir) : void{ if(is_dir($dir)){ $objects = scandir($dir, SCANDIR_SORT_NONE); if($objects === false) throw new AssumptionFailedError("scandir() shouldn't return false when is_dir() returns true"); foreach($objects as $object){ if($object !== "." and $object !== ".."){ if(is_dir($dir . "/" . $object)){ self::recursiveUnlink($dir . "/" . $object); }else{ unlink($dir . "/" . $object); } } } rmdir($dir); }elseif(is_file($dir)){ unlink($dir); } } public static function addCleanedPath(string $path, string $replacement) : void{ self::$cleanedPaths[$path] = $replacement; uksort(self::$cleanedPaths, function(string $str1, string $str2) : int{ return strlen($str2) <=> strlen($str1); //longest first }); } /** * @return string[] * @phpstan-return array */ public static function getCleanedPaths() : array{ return self::$cleanedPaths; } /** * @param string $path * * @return string */ public static function cleanPath($path){ $result = str_replace([DIRECTORY_SEPARATOR, ".php", "phar://"], ["/", "", ""], $path); //remove relative paths foreach(self::$cleanedPaths as $cleanPath => $replacement){ $cleanPath = rtrim(str_replace([DIRECTORY_SEPARATOR, "phar://"], ["/", ""], $cleanPath), "/"); if(strpos($result, $cleanPath) === 0){ $result = ltrim(str_replace($cleanPath, $replacement, $result), "/"); } } return $result; } /** * Attempts to get a lock on the specified file, creating it if it does not exist. This is typically used for IPC to * inform other processes that some file or folder is already in use, to avoid data corruption. * If this function succeeds in gaining a lock on the file, it writes the current PID to the file. * * @return int|null process ID of the process currently holding the lock failure, null on success. * @throws \InvalidArgumentException if the lock file path is invalid (e.g. parent directory doesn't exist, permission denied) */ public static function createLockFile(string $lockFilePath) : ?int{ $resource = fopen($lockFilePath, "a+b"); if($resource === false){ throw new \InvalidArgumentException("Invalid lock file path"); } if(!flock($resource, LOCK_EX | LOCK_NB)){ //wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the //other server wrote its PID and released exclusive lock before we get our lock flock($resource, LOCK_SH); $pid = stream_get_contents($resource); if(preg_match('/^\d+$/', $pid) === 1){ return (int) $pid; } return -1; } ftruncate($resource, 0); fwrite($resource, (string) getmypid()); fflush($resource); flock($resource, LOCK_SH); //prevent acquiring an exclusive lock from another process, but allow reading self::$lockFileHandles[realpath($lockFilePath)] = $resource; //keep the resource alive to preserve the lock return null; } /** * Releases a file lock previously acquired by createLockFile() and deletes the lock file. * * @throws \InvalidArgumentException if the lock file path is invalid (e.g. parent directory doesn't exist, permission denied) */ public static function releaseLockFile(string $lockFilePath) : void{ $lockFilePath = realpath($lockFilePath); if($lockFilePath === false){ throw new \InvalidArgumentException("Invalid lock file path"); } if(isset(self::$lockFileHandles[$lockFilePath])){ flock(self::$lockFileHandles[$lockFilePath], LOCK_UN); fclose(self::$lockFileHandles[$lockFilePath]); unset(self::$lockFileHandles[$lockFilePath]); @unlink($lockFilePath); } } }