From 478f923ec20bde4d723e64cf7b6b65fa147be829 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Thu, 25 Feb 2016 22:16:38 +0100 Subject: [PATCH 1/2] Improved junction detection on Windows NTFS, fixes #4955 --- src/Composer/Util/Filesystem.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 94044756e..463fac1e7 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -618,10 +618,17 @@ class Filesystem if (!is_dir($junction) || is_link($junction)) { return false; } - // Junctions have no link stat but are otherwise indistinguishable from real directories + /** + * According to MSDN at https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx we can detect a junction now + * using the 'mode' value from stat: "The _S_IFDIR bit is set if path specifies a directory; the _S_IFREG bit + * is set if path specifies an ordinary file or a device." We have just tested for a directory above, so if + * we have a directory that isn't one according to lstat(...) we must have a junction. + * + * #define _S_IFDIR 0x4000 + * #define _S_IFREG 0x8000 + */ $stat = lstat($junction); - - return ($stat['mode'] === 0); + return !($stat['mode'] & 0xC000); } /** From 756f9e10b7a35a34585a4cfff3ff40679cf98651 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Thu, 25 Feb 2016 22:29:55 +0100 Subject: [PATCH 2/2] Further protect junctioned path repos from accidentally deleting source packages. --- src/Composer/Downloader/PathDownloader.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 1156ff62d..afb3e5de3 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -101,4 +101,25 @@ class PathDownloader extends FileDownloader $this->io->writeError(''); } + + /** + * {@inheritDoc} + */ + public function remove(PackageInterface $package, $path) + { + /** + * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process + * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which + * is disastrous within a junction. So in that case we have no other real choice but to fail hard. + */ + if (Platform::isWindows() && $this->filesystem->isJunction($path)) { + $this->io->writeError(" - Removing junction for " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); + if (!$this->filesystem->removeJunction($path)) { + $this->io->writeError("Could not remove junction at " . $path . " - is another process locking it?"); + throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName()); + } + } else { + parent::remove($package, $path); + } + } }