From 0d5f7e29050cf161dd620b4fa6d004d2cc7a81dc Mon Sep 17 00:00:00 2001 From: flip111 Date: Sat, 12 Dec 2015 17:45:35 +0100 Subject: [PATCH] Update FileSystem * Fixes edge case on windows where PHP does not generate a PHP Warning but instead returns a wrong result https://bugs.php.net/bug.php?id=71103 * Improved error reporting on `unlink` used in `remove()` --- .../Component/Filesystem/Filesystem.php | 27 +++++++++++++++-- .../Filesystem/Tests/FilesystemTest.php | 29 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 1b6eaa68cd22..8b41db93b8e7 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -100,6 +100,10 @@ public function mkdir($dirs, $mode = 0777) public function exists($files) { foreach ($this->toIterator($files) as $file) { + if ('\\' === DIRECTORY_SEPARATOR AND strlen($file) > 258) { + throw new IOException(sprintf('Could not check if file exist because path length exceeds 258 characters for file "%s"', $file)); + } + if (!file_exists($file)) { return false; } @@ -139,7 +143,7 @@ public function remove($files) $files = iterator_to_array($this->toIterator($files)); $files = array_reverse($files); foreach ($files as $file) { - if (!file_exists($file) && !is_link($file)) { + if (!$this->exists($file) && !is_link($file)) { continue; } @@ -157,7 +161,8 @@ public function remove($files) } } else { if (true !== @unlink($file)) { - throw new IOException(sprintf('Failed to remove file %s', $file)); + $error = error_get_last(); + throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, $error['message'])); } } } @@ -253,7 +258,7 @@ public function chgrp($files, $group, $recursive = false) public function rename($origin, $target, $overwrite = false) { // we check that target does not exist - if (!$overwrite && is_readable($target)) { + if (!$overwrite && $this->isReadable($target)) { throw new IOException(sprintf('Cannot rename because the target "%s" already exist.', $target)); } @@ -262,6 +267,22 @@ public function rename($origin, $target, $overwrite = false) } } + /** + * Tells whether a file exists and is readable. + * + * @param string $filename Path to the file. + * + * @throws IOException When windows path is longer than 258 characters + */ + private function isReadable($filename) + { + if ('\\' === DIRECTORY_SEPARATOR AND strlen($filename) > 258) { + throw new IOException(sprintf('Could not check if file is readable because path length exceeds 258 characters for file "%s"', $filename)); + } + + return is_readable($filename); + } + /** * Creates a symbolic link or copy a directory. * diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index b57610cb8120..d6c27d1f77f7 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -19,6 +19,7 @@ class FilesystemTest extends \PHPUnit_Framework_TestCase { private $umask; + private $longPathNamesWindows = array(); /** * @var string @@ -56,6 +57,12 @@ protected function setUp() protected function tearDown() { + if (!empty($this->longPathNamesWindows)) { + foreach ($this->longPathNamesWindows as $path) { + exec('DEL '.$path); + } + } + $this->filesystem->remove($this->workspace); umask($this->umask); } @@ -354,6 +361,28 @@ public function testFilesExists() $this->assertTrue($this->filesystem->exists($basePath.'folder')); } + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testFilesExistsFails() + { + if ('\\' !== DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Test covers edge case on Windows only.'); + } + + $basePath = $this->workspace.'\\directory\\'; + + $oldPath = getcwd(); + mkdir($basePath); + chdir($basePath); + $file = str_repeat('T', 259 - strlen($basePath)); + $path = $basePath.$file; + exec('TYPE NUL >>'.$file); // equivalent of touch, we can not use the php touch() here because it suffers from the same limitation + $this->longPathNamesWindows[] = $path; // save this so we can clean up later + chdir($oldPath); + $this->filesystem->exists($path); + } + public function testFilesExistsTraversableObjectOfFilesAndDirectories() { $basePath = $this->workspace.DIRECTORY_SEPARATOR;