Skip to content

Commit 67bdb93

Browse files
Merge remote-tracking branch 'origin/minor-next' into smithing-table
2 parents 75d7a8a + b680a16 commit 67bdb93

33 files changed

+995
-357
lines changed

build/server-phar-stub.php

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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';

build/server-phar.php

+3-15
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323

2424
namespace pocketmine\build\server_phar;
2525

26+
use pocketmine\utils\Filesystem;
2627
use pocketmine\utils\Git;
28+
use Symfony\Component\Filesystem\Path;
2729
use function array_map;
2830
use function count;
2931
use function dirname;
@@ -159,21 +161,7 @@ function main() : void{
159161
'git' => $gitHash,
160162
'build' => $build
161163
],
162-
<<<'STUB'
163-
<?php
164-
165-
$tmpDir = sys_get_temp_dir();
166-
if(!is_readable($tmpDir) or !is_writable($tmpDir)){
167-
echo "ERROR: tmpdir $tmpDir is not accessible." . PHP_EOL;
168-
echo "Check that the directory exists, and that the current user has read/write permissions for it." . PHP_EOL;
169-
echo "Alternatively, set 'sys_temp_dir' to a different directory in your php.ini file." . PHP_EOL;
170-
exit(1);
171-
}
172-
173-
require("phar://" . __FILE__ . "/src/PocketMine.php");
174-
__HALT_COMPILER();
175-
STUB
176-
,
164+
Filesystem::fileGetContents(Path::join(__DIR__, 'server-phar-stub.php')) . "\n__HALT_COMPILER();",
177165
\Phar::SHA1,
178166
\Phar::GZ
179167
) as $line){

composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545
"pocketmine/log": "^0.4.0",
4646
"pocketmine/math": "~1.0.0",
4747
"pocketmine/nbt": "~1.0.0",
48-
"pocketmine/raklib": "^0.15.0",
49-
"pocketmine/raklib-ipc": "^0.2.0",
48+
"pocketmine/raklib": "~1.1.0",
49+
"pocketmine/raklib-ipc": "~1.0.0",
5050
"pocketmine/snooze": "^0.5.0",
5151
"ramsey/uuid": "~4.7.0",
5252
"symfony/filesystem": "~6.3.0"

0 commit comments

Comments
 (0)