Merge branch 'minor-next' into stable

This commit is contained in:
Dylan K. Taylor
2024-04-05 17:30:59 +01:00
55 changed files with 2090 additions and 383 deletions

168
build/server-phar-stub.php Normal file
View File

@ -0,0 +1,168 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\server_phar_stub;
use function clearstatcache;
use function copy;
use function fclose;
use function fflush;
use function flock;
use function fopen;
use function fwrite;
use function getmypid;
use function hrtime;
use function is_dir;
use function is_file;
use function mkdir;
use function number_format;
use function str_replace;
use function stream_get_contents;
use function sys_get_temp_dir;
use function tempnam;
use function unlink;
use const DIRECTORY_SEPARATOR;
use const LOCK_EX;
use const LOCK_NB;
use const LOCK_UN;
/**
* Finds the appropriate tmp directory to store the decompressed phar cache, accounting for potential file name
* collisions.
*/
function preparePharCacheDirectory() : string{
clearstatcache();
$i = 0;
do{
$tmpPath = sys_get_temp_dir() . '/PocketMine-MP-phar-cache.' . $i;
$i++;
}while(is_file($tmpPath));
if(!@mkdir($tmpPath) && !is_dir($tmpPath)){
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.");
}
return $tmpPath;
}
/**
* Deletes caches left behind by previous server instances.
* This ensures that the tmp directory doesn't get flooded by servers crashing in restart loops.
*/
function cleanupPharCache(string $tmpPath) : void{
clearstatcache();
/** @var string[] $matches */
foreach(new \RegexIterator(
new \FilesystemIterator(
$tmpPath,
\FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS
),
'/(.+)\.lock$/',
\RegexIterator::GET_MATCH
) as $matches){
$lockFilePath = $matches[0];
$baseTmpPath = $matches[1];
$file = @fopen($lockFilePath, "rb");
if($file === false){
//another process probably deleted the lock file already
continue;
}
if(flock($file, LOCK_EX | LOCK_NB)){
//this tmpfile is no longer in use
flock($file, LOCK_UN);
fclose($file);
unlink($lockFilePath);
unlink($baseTmpPath . ".tar");
unlink($baseTmpPath);
echo "Deleted stale phar cache at $baseTmpPath\n";
}else{
$pid = stream_get_contents($file);
fclose($file);
echo "Phar cache at $baseTmpPath is still in use by PID $pid\n";
}
}
}
function convertPharToTar(string $tmpName, string $pharPath) : string{
$tmpPharPath = $tmpName . ".phar";
copy($pharPath, $tmpPharPath);
$phar = new \Phar($tmpPharPath);
//phar requires phar.readonly=0, and zip doesn't support disabling compression - tar is the only viable option
//we don't need phar anyway since we don't need to directly execute the file, only require files from inside it
$phar->convertToData(\Phar::TAR, \Phar::NONE);
unset($phar);
\Phar::unlinkArchive($tmpPharPath);
return $tmpName . ".tar";
}
/**
* Locks a phar tmp cache to prevent it from being deleted by other server instances.
* This code looks similar to Filesystem::createLockFile(), but we can't use that because it's inside the compressed
* phar.
*/
function lockPharCache(string $lockFilePath) : void{
//this static variable will keep the file(s) locked until the process ends
static $lockFiles = [];
$lockFile = fopen($lockFilePath, "wb");
if($lockFile === false){
throw new \RuntimeException("Failed to open temporary file");
}
flock($lockFile, LOCK_EX); //this tells other server instances not to delete this cache file
fwrite($lockFile, (string) getmypid()); //maybe useful for debugging
fflush($lockFile);
$lockFiles[$lockFilePath] = $lockFile;
}
/**
* Prepares a decompressed .tar of PocketMine-MP.phar in the system temp directory for loading code from.
*
* @return string path to the temporary decompressed phar (actually a .tar)
*/
function preparePharCache(string $tmpPath, string $pharPath) : string{
clearstatcache();
$tmpName = tempnam($tmpPath, "PMMP");
if($tmpName === false){
throw new \RuntimeException("Failed to create temporary file");
}
lockPharCache($tmpName . ".lock");
return convertPharToTar($tmpName, $pharPath);
}
$tmpDir = preparePharCacheDirectory();
cleanupPharCache($tmpDir);
echo "Preparing PocketMine-MP.phar decompressed cache...\n";
$start = hrtime(true);
$cacheName = preparePharCache($tmpDir, __FILE__);
echo "Cache ready at $cacheName in " . number_format((hrtime(true) - $start) / 1e9, 2) . "s\n";
require 'phar://' . str_replace(DIRECTORY_SEPARATOR, '/', $cacheName) . '/src/PocketMine.php';

View File

@ -23,7 +23,9 @@ declare(strict_types=1);
namespace pocketmine\build\server_phar;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Git;
use Symfony\Component\Filesystem\Path;
use function array_map;
use function count;
use function dirname;
@ -169,21 +171,7 @@ function main() : void{
'git' => $gitHash,
'build' => $build
],
<<<'STUB'
<?php
$tmpDir = sys_get_temp_dir();
if(!is_readable($tmpDir) or !is_writable($tmpDir)){
echo "ERROR: tmpdir $tmpDir is not accessible." . PHP_EOL;
echo "Check that the directory exists, and that the current user has read/write permissions for it." . PHP_EOL;
echo "Alternatively, set 'sys_temp_dir' to a different directory in your php.ini file." . PHP_EOL;
exit(1);
}
require("phar://" . __FILE__ . "/src/PocketMine.php");
__HALT_COMPILER();
STUB
,
Filesystem::fileGetContents(Path::join(__DIR__, 'server-phar-stub.php')) . "\n__HALT_COMPILER();",
\Phar::SHA1,
\Phar::GZ
) as $line){