FormatConverter: Copy worlds for backup if rename fails

this can fail if the backups directory points to a different drive than the original worlds location. In this case, we have to copy and delete the files instead, which is much slower, but works.
I REALLY advise against putting backups on a different mount point than worlds if you plan to convert large worlds.
This commit is contained in:
Dylan K. Taylor 2021-06-17 20:45:47 +01:00
parent ec6103d61e
commit 43f71d0d63
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
2 changed files with 62 additions and 2 deletions

View File

@ -23,8 +23,11 @@ declare(strict_types=1);
namespace pocketmine\utils;
use function copy;
use function dirname;
use function fclose;
use function fflush;
use function file_exists;
use function flock;
use function fopen;
use function ftruncate;
@ -33,6 +36,7 @@ use function getmypid;
use function is_dir;
use function is_file;
use function ltrim;
use function mkdir;
use function preg_match;
use function realpath;
use function rmdir;
@ -88,6 +92,53 @@ final class Filesystem{
}
}
/**
* Recursively copies a directory to a new location. The parent directories for the destination must exist.
*/
public static function recursiveCopy(string $origin, string $destination) : void{
if(!is_dir($origin)){
throw new \RuntimeException("$origin does not exist, or is not a directory");
}
if(!is_dir($destination)){
if(file_exists($destination)){
throw new \RuntimeException("$destination already exists, and is not a directory");
}
if(!is_dir(dirname($destination))){
//if the parent dir doesn't exist, the user most likely made a mistake
throw new \RuntimeException("The parent directory of $destination does not exist, or is not a directory");
}
if(!@mkdir($destination) && !is_dir($destination)){
throw new \RuntimeException("Failed to create output directory $destination (permission denied?)");
}
}
self::recursiveCopyInternal($origin, $destination);
}
private static function recursiveCopyInternal(string $origin, string $destination) : void{
if(is_dir($origin)){
if(!is_dir($destination)){
if(file_exists($destination)){
throw new \RuntimeException("Path $destination does not exist, or is not a directory");
}
mkdir($destination); //TODO: access permissions?
}
$objects = scandir($origin, 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 === "." || $object === ".."){
continue;
}
self::recursiveCopyInternal($origin . "/" . $object, $destination . "/" . $object);
}
}else{
$dirName = dirname($destination);
if(!is_dir($dirName)){ //the destination folder should already exist
throw new AssumptionFailedError("The destination folder should have been created in the parent call");
}
copy($origin, $destination);
}
}
public static function addCleanedPath(string $path, string $replacement) : void{
self::$cleanedPaths[$path] = $replacement;
uksort(self::$cleanedPaths, function(string $str1, string $str2) : int{

View File

@ -91,8 +91,17 @@ class FormatConverter{
$new->close();
$this->logger->info("Backing up pre-conversion world to " . $this->backupPath);
rename($path, $this->backupPath);
rename($new->getPath(), $path);
if(!@rename($path, $this->backupPath)){
$this->logger->warning("Moving old world files for backup failed, attempting copy instead. This might take a long time.");
Filesystem::recursiveCopy($path, $this->backupPath);
Filesystem::recursiveUnlink($path);
}
if(!@rename($new->getPath(), $path)){
//we don't expect this to happen because worlds/ should most likely be all on the same FS, but just in case...
$this->logger->debug("Relocation of new world files to location failed, attempting copy and delete instead");
Filesystem::recursiveCopy($new->getPath(), $path);
Filesystem::recursiveUnlink($new->getPath());
}
$this->logger->info("Conversion completed");
/**