From d26932cc7ef9c22558a01a95840a7bc71ce5808e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 19 Nov 2012 10:29:32 +0100 Subject: [PATCH 1/4] Fallback to PHP early if proc_open not allowed. --- src/Composer/Util/Filesystem.php | 46 +++++++++++++++++++++ tests/Composer/Test/Util/FilesystemTest.php | 15 +++++++ 2 files changed, 61 insertions(+) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index fc5a590cf..fd2dae21f 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -12,6 +12,9 @@ namespace Composer\Util; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; + /** * @author Jordi Boggiano * @author Johannes M. Schmitt @@ -38,12 +41,25 @@ class Filesystem return false; } + /** + * Recursively remove a directory + * + * Uses the process component if proc_open is enabled on the PHP + * installation. + * + * @param string $directory + * @return bool + */ public function removeDirectory($directory) { if (!is_dir($directory)) { return true; } + if (!function_exists('proc_open')) { + return $this->removeDirectoryPhp($directory); + } + if (defined('PHP_WINDOWS_VERSION_BUILD')) { $cmd = sprintf('rmdir /S /Q %s', escapeshellarg(realpath($directory))); } else { @@ -58,6 +74,36 @@ class Filesystem return $result && !is_dir($directory); } + /** + * Recursively delete directory using PHP iterators. + * + * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files + * before directories, creating a single non-recursive loop + * to delete files/directories in the correct order. + * + * @param string $directory + * @return bool + */ + public function removeDirectoryPhp($directory) + { + $it = new RecursiveDirectoryIterator($directory); + $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($ri as $file) { + if ($file->getFilename() == "." || $file->getFilename() == "..") { + continue; + } + + if ($file->isDir()) { + rmdir($file->getPathname()); + } else { + unlink($file->getPathname()); + } + } + + return rmdir($directory); + } + public function ensureDirectoryExists($directory) { if (!is_dir($directory)) { diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php index d1459169a..586e48568 100644 --- a/tests/Composer/Test/Util/FilesystemTest.php +++ b/tests/Composer/Test/Util/FilesystemTest.php @@ -93,4 +93,19 @@ class FilesystemTest extends TestCase array('C:/Temp', 'c:\Temp\test', "test"), ); } + + /** + * @group GH-1339 + */ + public function testRemoveDirectoryPhp() + { + $tmp = sys_get_temp_dir(); + @mkdir($tmp . "/composer_testdir/level1/level2", 0777, true); + file_put_contents($tmp . "/composer_testdir/level1/level2/hello.txt", "hello world"); + + $fs = new Filesystem; + $this->assertTrue($fs->removeDirectoryPhp($tmp . "/composer_testdir")); + $this->assertFalse(file_exists($tmp . "/composer_testdir/level1/level2/hello.txt")); + } } + From cd7db1861d8ef59c7a7f76d4e53252a60c46860f Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 19 Nov 2012 11:21:41 +0100 Subject: [PATCH 2/4] Remove reliance on proc_open in Filesystem#rename() --- src/Composer/Util/Filesystem.php | 40 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index fd2dae21f..90736995e 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -86,14 +86,10 @@ class Filesystem */ public function removeDirectoryPhp($directory) { - $it = new RecursiveDirectoryIterator($directory); + $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS); $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); foreach ($ri as $file) { - if ($file->getFilename() == "." || $file->getFilename() == "..") { - continue; - } - if ($file->isDir()) { rmdir($file->getPathname()); } else { @@ -120,6 +116,36 @@ class Filesystem } } + /** + * Copy then delete is a non-atomic version of {@link rename}. + * + * Some systems can't rename and also dont have proc_open, + * which requires this solution. + * + * @param string $source + * @param string $target + */ + public function copyThenRemove($source, $target) + { + $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS); + $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST); + + if ( !file_exists($target)) { + mkdir($target, 0777, true); + } + + foreach ($ri as $file) { + $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName(); + if ($file->isDir()) { + mkdir($targetPath); + } else { + copy($file->getPathname(), $targetPath); + } + } + + $this->removeDirectoryPhp($source); + } + public function rename($source, $target) { if (true === @rename($source, $target)) { @@ -127,6 +153,10 @@ class Filesystem } if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (!function_exists('proc_open')) { + return $this->copyThenRemove($source, $target); + } + // Try to copy & delete - this is a workaround for random "Access denied" errors. $command = sprintf('xcopy %s %s /E /I /Q', escapeshellarg($source), escapeshellarg($target)); if (0 === $this->processExecutor->execute($command)) { From 5e12da0203929a40a506193c60cd149df93f7610 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 19 Nov 2012 11:24:11 +0100 Subject: [PATCH 3/4] Skip locking dev package to time when proc_open does not exist on system. --- src/Composer/Package/Locker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index bd636c8f1..cf38eb670 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -288,7 +288,7 @@ class Locker unset($spec['version_normalized']); if ($package->isDev()) { - if ('git' === $package->getSourceType() && $path = $this->installationManager->getInstallPath($package)) { + if ('git' === $package->getSourceType() && $path = $this->installationManager->getInstallPath($package) && function_exists('proc_open')) { $sourceRef = $package->getSourceReference() ?: $package->getDistReference(); $process = new ProcessExecutor(); if (0 === $process->execute('git log -n1 --pretty=%ct '.escapeshellarg($sourceRef), $output, $path)) { From fbf9a27132f028ed2c25f1b76347552c8c598e66 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 19 Nov 2012 13:51:24 +0100 Subject: [PATCH 4/4] Works on Linux when proc_open misses --- src/Composer/Util/Filesystem.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 90736995e..7a8982131 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -152,11 +152,11 @@ class Filesystem return; } - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - if (!function_exists('proc_open')) { - return $this->copyThenRemove($source, $target); - } + if (!function_exists('proc_open')) { + return $this->copyThenRemove($source, $target); + } + if (defined('PHP_WINDOWS_VERSION_BUILD')) { // Try to copy & delete - this is a workaround for random "Access denied" errors. $command = sprintf('xcopy %s %s /E /I /Q', escapeshellarg($source), escapeshellarg($target)); if (0 === $this->processExecutor->execute($command)) {