فهرست منبع

Made NTFS junction detection more reliable and added unit tests for the junction functions.

Niels Keurentjes 10 سال پیش
والد
کامیت
b71c67239d
2فایلهای تغییر یافته به همراه53 افزوده شده و 14 حذف شده
  1. 24 14
      src/Composer/Util/Filesystem.php
  2. 29 0
      tests/Composer/Test/Util/FilesystemTest.php

+ 24 - 14
src/Composer/Util/Filesystem.php

@@ -585,19 +585,23 @@ class Filesystem
     /**
      * Creates an NTFS junction.
      *
-     * @param string $originDir
-     * @param string $targetDir
+     * @param string $target
+     * @param string $junction
      */
-    public function junction($originDir, $targetDir)
+    public function junction($target, $junction)
     {
-        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
-            $cmd = sprintf('mklink /J %s %s',
-                           ProcessExecutor::escape(str_replace('/', DIRECTORY_SEPARATOR, $targetDir)),
-                           ProcessExecutor::escape(realpath($originDir)));
-            if ($this->getProcess()->execute($cmd) === 0)
-                return;
+        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
+            throw new \LogicException(sprintf('Function %s is not available on non-Windows platform', __CLASS__));
+        }
+        if (!is_dir($target)) {
+            throw new IOException(sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target);
+        }
+        $cmd = sprintf('mklink /J %s %s',
+                       ProcessExecutor::escape(str_replace('/', DIRECTORY_SEPARATOR, $junction)),
+                       ProcessExecutor::escape(realpath($target)));
+        if ($this->getProcess()->execute($cmd, $output) !== 0) {
+            throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target);
         }
-        throw new IOException(sprintf('Failed to create junction from "%s" to "%s".', $originDir, $targetDir), 0, null, $targetDir);
     }
 
     /**
@@ -611,9 +615,12 @@ class Filesystem
         if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
             return false;
         }
-        $normalized = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $junction), DIRECTORY_SEPARATOR);
-        $real = rtrim(realpath($normalized), DIRECTORY_SEPARATOR);
-        return is_dir($normalized) && ($normalized !== $real);
+        if (!is_dir($junction) || is_link($junction)) {
+            return false;
+        }
+        // Junctions have no link stat but are otherwise indistinguishable from real directories
+        $stat = lstat($junction);
+        return ($stat['mode'] === 0);
     }
 
     /**
@@ -628,7 +635,10 @@ class Filesystem
             return false;
         }
         $junction = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $junction), DIRECTORY_SEPARATOR);
+        if (!$this->isJunction($junction)) {
+            throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction));
+        }
         $cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape($junction));
-        return $this->getProcess()->execute($cmd) === 0;
+        return ($this->getProcess()->execute($cmd) === 0);
     }
 }

+ 29 - 0
tests/Composer/Test/Util/FilesystemTest.php

@@ -266,4 +266,33 @@ class FilesystemTest extends TestCase
         $this->assertFalse(file_exists($symlinkedTrailingSlash));
         $this->assertFalse(file_exists($symlinked));
     }
+
+    public function testJunctions()
+    {
+        @mkdir($this->workingDir . '/real/nesting/testing', 0777, true);
+        $fs = new Filesystem();
+
+        // Non-Windows systems do not support this and will return false on all tests, and an exception on creation
+        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
+            $this->assertFalse($fs->isJunction($this->workingDir));
+            $this->assertFalse($fs->removeJunction($this->workingDir));
+            $this->setExpectedException('LogicException', 'not available on non-Windows platform');
+        }
+
+        $target = $this->workingDir . '/real/../real/nesting';
+        $junction = $this->workingDir . '/junction';
+
+        // Create and detect junction
+        $fs->junction($target, $junction);
+        $this->assertTrue($fs->isJunction($junction));
+        $this->assertFalse($fs->isJunction($target));
+        $this->assertTrue($fs->isJunction($target . '/../../junction'));
+        $this->assertFalse($fs->isJunction($junction . '/../real'));
+        $this->assertTrue($fs->isJunction($junction . '/../junction'));
+
+        // Remove junction
+        $this->assertTrue(is_dir($junction));
+        $this->assertTrue($fs->removeJunction($junction));
+        $this->assertFalse(is_dir($junction));
+    }
 }