|
| 1 | +<?php |
| 2 | + |
| 3 | +/* |
| 4 | + * |
| 5 | + * ____ _ _ __ __ _ __ __ ____ |
| 6 | + * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \ |
| 7 | + * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) | |
| 8 | + * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/ |
| 9 | + * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_| |
| 10 | + * |
| 11 | + * This program is free software: you can redistribute it and/or modify |
| 12 | + * it under the terms of the GNU Lesser General Public License as published by |
| 13 | + * the Free Software Foundation, either version 3 of the License, or |
| 14 | + * (at your option) any later version. |
| 15 | + * |
| 16 | + * @author PocketMine Team |
| 17 | + * @link http://www.pocketmine.net/ |
| 18 | + * |
| 19 | + * |
| 20 | + */ |
| 21 | + |
| 22 | +declare(strict_types=1); |
| 23 | + |
| 24 | +namespace pocketmine\server_phar_stub; |
| 25 | + |
| 26 | +use function clearstatcache; |
| 27 | +use function copy; |
| 28 | +use function fclose; |
| 29 | +use function fflush; |
| 30 | +use function flock; |
| 31 | +use function fopen; |
| 32 | +use function fwrite; |
| 33 | +use function getmypid; |
| 34 | +use function hrtime; |
| 35 | +use function is_dir; |
| 36 | +use function is_file; |
| 37 | +use function mkdir; |
| 38 | +use function number_format; |
| 39 | +use function str_replace; |
| 40 | +use function stream_get_contents; |
| 41 | +use function sys_get_temp_dir; |
| 42 | +use function tempnam; |
| 43 | +use function unlink; |
| 44 | +use const DIRECTORY_SEPARATOR; |
| 45 | +use const LOCK_EX; |
| 46 | +use const LOCK_NB; |
| 47 | +use const LOCK_UN; |
| 48 | + |
| 49 | +/** |
| 50 | + * Finds the appropriate tmp directory to store the decompressed phar cache, accounting for potential file name |
| 51 | + * collisions. |
| 52 | + */ |
| 53 | +function preparePharCacheDirectory() : string{ |
| 54 | + clearstatcache(); |
| 55 | + |
| 56 | + $i = 0; |
| 57 | + do{ |
| 58 | + $tmpPath = sys_get_temp_dir() . '/PocketMine-MP-phar-cache.' . $i; |
| 59 | + $i++; |
| 60 | + }while(is_file($tmpPath)); |
| 61 | + if(!@mkdir($tmpPath) && !is_dir($tmpPath)){ |
| 62 | + throw new \RuntimeException("Failed to create temporary directory $tmpPath. Please ensure the disk has enough space and that the current user has permission to write to this location."); |
| 63 | + } |
| 64 | + |
| 65 | + return $tmpPath; |
| 66 | +} |
| 67 | + |
| 68 | +/** |
| 69 | + * Deletes caches left behind by previous server instances. |
| 70 | + * This ensures that the tmp directory doesn't get flooded by servers crashing in restart loops. |
| 71 | + */ |
| 72 | +function cleanupPharCache(string $tmpPath) : void{ |
| 73 | + clearstatcache(); |
| 74 | + |
| 75 | + /** @var string[] $matches */ |
| 76 | + foreach(new \RegexIterator( |
| 77 | + new \FilesystemIterator( |
| 78 | + $tmpPath, |
| 79 | + \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS |
| 80 | + ), |
| 81 | + '/(.+)\.lock$/', |
| 82 | + \RegexIterator::GET_MATCH |
| 83 | + ) as $matches){ |
| 84 | + $lockFilePath = $matches[0]; |
| 85 | + $baseTmpPath = $matches[1]; |
| 86 | + |
| 87 | + $file = @fopen($lockFilePath, "rb"); |
| 88 | + if($file === false){ |
| 89 | + //another process probably deleted the lock file already |
| 90 | + continue; |
| 91 | + } |
| 92 | + |
| 93 | + if(flock($file, LOCK_EX | LOCK_NB)){ |
| 94 | + //this tmpfile is no longer in use |
| 95 | + flock($file, LOCK_UN); |
| 96 | + fclose($file); |
| 97 | + |
| 98 | + unlink($lockFilePath); |
| 99 | + unlink($baseTmpPath . ".tar"); |
| 100 | + unlink($baseTmpPath); |
| 101 | + echo "Deleted stale phar cache at $baseTmpPath\n"; |
| 102 | + }else{ |
| 103 | + $pid = stream_get_contents($file); |
| 104 | + fclose($file); |
| 105 | + |
| 106 | + echo "Phar cache at $baseTmpPath is still in use by PID $pid\n"; |
| 107 | + } |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +function convertPharToTar(string $tmpName, string $pharPath) : string{ |
| 112 | + $tmpPharPath = $tmpName . ".phar"; |
| 113 | + copy($pharPath, $tmpPharPath); |
| 114 | + |
| 115 | + $phar = new \Phar($tmpPharPath); |
| 116 | + //phar requires phar.readonly=0, and zip doesn't support disabling compression - tar is the only viable option |
| 117 | + //we don't need phar anyway since we don't need to directly execute the file, only require files from inside it |
| 118 | + $phar->convertToData(\Phar::TAR, \Phar::NONE); |
| 119 | + unset($phar); |
| 120 | + \Phar::unlinkArchive($tmpPharPath); |
| 121 | + |
| 122 | + return $tmpName . ".tar"; |
| 123 | +} |
| 124 | + |
| 125 | +/** |
| 126 | + * Locks a phar tmp cache to prevent it from being deleted by other server instances. |
| 127 | + * This code looks similar to Filesystem::createLockFile(), but we can't use that because it's inside the compressed |
| 128 | + * phar. |
| 129 | + */ |
| 130 | +function lockPharCache(string $lockFilePath) : void{ |
| 131 | + //this static variable will keep the file(s) locked until the process ends |
| 132 | + static $lockFiles = []; |
| 133 | + |
| 134 | + $lockFile = fopen($lockFilePath, "wb"); |
| 135 | + if($lockFile === false){ |
| 136 | + throw new \RuntimeException("Failed to open temporary file"); |
| 137 | + } |
| 138 | + flock($lockFile, LOCK_EX); //this tells other server instances not to delete this cache file |
| 139 | + fwrite($lockFile, (string) getmypid()); //maybe useful for debugging |
| 140 | + fflush($lockFile); |
| 141 | + $lockFiles[$lockFilePath] = $lockFile; |
| 142 | +} |
| 143 | + |
| 144 | +/** |
| 145 | + * Prepares a decompressed .tar of PocketMine-MP.phar in the system temp directory for loading code from. |
| 146 | + * |
| 147 | + * @return string path to the temporary decompressed phar (actually a .tar) |
| 148 | + */ |
| 149 | +function preparePharCache(string $tmpPath, string $pharPath) : string{ |
| 150 | + clearstatcache(); |
| 151 | + |
| 152 | + $tmpName = tempnam($tmpPath, "PMMP"); |
| 153 | + if($tmpName === false){ |
| 154 | + throw new \RuntimeException("Failed to create temporary file"); |
| 155 | + } |
| 156 | + |
| 157 | + lockPharCache($tmpName . ".lock"); |
| 158 | + return convertPharToTar($tmpName, $pharPath); |
| 159 | +} |
| 160 | + |
| 161 | +$tmpDir = preparePharCacheDirectory(); |
| 162 | +cleanupPharCache($tmpDir); |
| 163 | +echo "Preparing PocketMine-MP.phar decompressed cache...\n"; |
| 164 | +$start = hrtime(true); |
| 165 | +$cacheName = preparePharCache($tmpDir, __FILE__); |
| 166 | +echo "Cache ready at $cacheName in " . number_format((hrtime(true) - $start) / 1e9, 2) . "s\n"; |
| 167 | + |
| 168 | +require 'phar://' . str_replace(DIRECTORY_SEPARATOR, '/', $cacheName) . '/src/PocketMine.php'; |
0 commit comments