From 55322a282c9a44e49865b9ecb00db40eda7f0522 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 23 Oct 2025 15:19:45 +0800 Subject: [PATCH 1/2] Fix rename cross-device link bug (using copy-delete) --- src/SPC/store/FileSystem.php | 43 +++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/SPC/store/FileSystem.php b/src/SPC/store/FileSystem.php index e66aee0a..f6ce371e 100644 --- a/src/SPC/store/FileSystem.php +++ b/src/SPC/store/FileSystem.php @@ -645,6 +645,36 @@ class FileSystem }; } + /** + * Move file or directory, handling cross-device scenarios + * Uses rename() if possible, falls back to copy+delete for cross-device moves + * + * @param string $source Source path + * @param string $dest Destination path + */ + private static function moveFileOrDir(string $source, string $dest): void + { + $source = self::convertPath($source); + $dest = self::convertPath($dest); + + // Try rename first (fast, atomic) + if (@rename($source, $dest)) { + return; + } + + if (is_dir($source)) { + self::copyDir($source, $dest); + self::removeDir($source); + } else { + if (!copy($source, $dest)) { + throw new FileSystemException("Failed to copy file from {$source} to {$dest}"); + } + if (!unlink($source)) { + throw new FileSystemException("Failed to remove source file: {$source}"); + } + } + } + /** * Unzip file with stripping top-level directory */ @@ -675,10 +705,10 @@ class FileSystem if (is_dir($extract_path)) { self::removeDir($extract_path); } - // if only one dir, move its contents to extract_path using rename + // if only one dir, move its contents to extract_path $subdir = self::convertPath("{$temp_dir}/{$contents[0]}"); if (count($contents) === 1 && is_dir($subdir)) { - rename($subdir, $extract_path); + self::moveFileOrDir($subdir, $extract_path); } else { // else, if it contains only one dir, strip dir and copy other files $dircount = 0; @@ -701,17 +731,20 @@ class FileSystem throw new FileSystemException("Cannot scan unzip temp sub-dir: {$dir[0]}"); } foreach ($sub_contents as $sub_item) { - rename(self::convertPath("{$temp_dir}/{$dir[0]}/{$sub_item}"), self::convertPath("{$extract_path}/{$sub_item}")); + self::moveFileOrDir(self::convertPath("{$temp_dir}/{$dir[0]}/{$sub_item}"), self::convertPath("{$extract_path}/{$sub_item}")); } } else { foreach ($dir as $item) { - rename(self::convertPath("{$temp_dir}/{$item}"), self::convertPath("{$extract_path}/{$item}")); + self::moveFileOrDir(self::convertPath("{$temp_dir}/{$item}"), self::convertPath("{$extract_path}/{$item}")); } } // move top-level files to extract_path foreach ($top_files as $top_file) { - rename(self::convertPath("{$temp_dir}/{$top_file}"), self::convertPath("{$extract_path}/{$top_file}")); + self::moveFileOrDir(self::convertPath("{$temp_dir}/{$top_file}"), self::convertPath("{$extract_path}/{$top_file}")); } } + + // Clean up temp directory + self::removeDir($temp_dir); } } From c6de6e705676eebb597972694cfb2f1fb8c49894 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 23 Oct 2025 16:29:46 +0800 Subject: [PATCH 2/2] test zip unarchive --- src/globals/test-extensions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index 03e0c324..ad814c0d 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -49,7 +49,7 @@ $prefer_pre_built = false; // If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`). $extensions = match (PHP_OS_FAMILY) { - 'Linux', 'Darwin' => 'pdo_pgsql', + 'Linux', 'Darwin' => 'spx', 'Windows' => 'bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,iconv,xml,mbstring,mbregex,mysqlnd,openssl,pdo,pdo_mysql,pdo_sqlite,phar,session,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip', };