PHP realpath on Windows Case Issue

2019-05-24 03:56发布

问题:

I have a symlink on my Windows server which was made like this:

F:\>mkdir link-target
F:\>mklink /D link f:\link-target 

(Note the lower case f: in the symlink target)

In PHP I run this:

$dir = realpath('f:\link');
var_dump($dir);

$dir = realpath($dir);
var_dump($dir);

Which outputs:

string 'f:\link-target' (length=14)
string 'F:\link-target' (length=14)

Notice the change in case on the second realpath.

Is this a bug, or intended? And whats the best way to work around it?

It is breaking a case like this:

function check_link($to, $from) {
    if (realpath($to) !== realpath($from)) {
        ...
    }
}

Which is used to check $to exists, and is linked to $from.

  • PHP 5.4
  • Windows 7

Edit:

I need consistent behavior on both Windows and Linux, and have the following work around be its pretty nasty:

if (realpath($from) === false) {
} elseif (realpath($to) === false) {
} else {
    do {
        $to = realpath($to);
    } while (realpath($to) !== false && $to !== realpath($to));
    do {
        $from = realpath($from);
    } while (realpath($from) !== false && $from !== realpath($from));
    if ($to !== $from) {
        ...
    }
}

Edit 2:

On furter investigation I have noticed that on Windows symlinks are only followed 1 level deep:

// F:\>mkdir link-target
// F:\>mklink /D link f:\link-target 
// F:\>mklink /D link2 f:\link

$dir = realpath('f:\link2');
var_dump($dir);

$dir = realpath($dir);
var_dump($dir);

$dir = realpath($dir);
var_dump($dir);

// string 'f:\link' (length=7)
// string 'f:\link-target' (length=14)
// string 'F:\link-target' (length=14)

回答1:

Turns out

do {
    $to = realpath($to);
} while (realpath($to) !== false && $to !== realpath($to));

is the only way.

https://bugs.php.net/bug.php?id=61933



回答2:

This makes sense, if you think it through. For the first path resolution, it is finding what was defined as the link target. For the second, you end up with the same path, but one with the proper upper-case for the drive letter.

Under Windows file systems, paths/file names are case insensitive. To compare them, just convert both to upper or lower case before testing.

if (strtoupper(realpath($to)) !== strtotupper(realpath($from))) {