Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

is_link does not work correct under php with windows #34

Closed
Zeromax opened this issue Nov 25, 2014 · 25 comments
Closed

is_link does not work correct under php with windows #34

Zeromax opened this issue Nov 25, 2014 · 25 comments
Assignees
Milestone

Comments

@Zeromax
Copy link
Contributor

Zeromax commented Nov 25, 2014

The Problem is, that the is_link returns always false.

That is realy weird. So I haven't found a usefull solution for now. Any Ideas?

@leofeyer
Copy link

There are some comments regarding Windows in the manual: http://php.net/is_link

On windows, take care that is_link returns false for Junctions.
Ways of telling apart a directory from a junction include doing both a stat() and and lstat() call and checking if there is any difference in the results

@Zeromax
Copy link
Contributor Author

Zeromax commented Nov 26, 2014

@Zeromax
Copy link
Contributor Author

Zeromax commented Nov 26, 2014

@leofeyer I have tried this, but there was no difference which I can handle.

I tried the following and this works pretty well:

$linkReal = 'D:\\www\\contao-3.3.5\\system\\modules\\!composer';  // Symbolic link
$targetReal= 'D:\\www\\contao-3.3.5\\composer\\vendor\\contao-community-alliance\\composer-client'; // real folder

if (defined('PHP_WINDOWS_VERSION_BUILD') && readlink($linkReal) == $targetReal) {
    // is_link
}

readlink works for normal folders as well But returns the folder path.

Only Problem: readlink breaks with file usage. so if there is put in a file it shows a warning. The can be resolved with @ or checking if the folder is a folder and not a file ;).

Any other suggestions are welcome. :D

@Zeromax
Copy link
Contributor Author

Zeromax commented Nov 27, 2014

I have added a PR. Please check if the current behaviour is still working ;)

@leofeyer
Copy link

The bug report says:

is_link seems to return false for junction points which show up as <JUNCTION> when using the DIR command. It seems to work for junctions which show up as <SYMLINKD> when using the DIR command.

Junctions are not symlinks, the result is correct. Think of junction like hard links. You can't see hard link with is_link, no matter the platform.

Did you check whether your symlinks show up as <JUNCTION> or <SYMLINKD>?

Right now the only way to tell if a folder is actually a "real one" or not is to use the array_diff trick I put in the test script. It looks hackish, cumbersome, and why should lstat() treat a junction differently than is_link() anyway?)

According to this comment, there is an "array_diff trick" to work around the issue. However, you are using readlink(). Did you come up with the solution yourself or did you find it somewhere online? In the latter case, can you please share the URL?

@tristanlins
Copy link
Contributor

According to this comment, there is an "array_diff trick" to work around the issue. However, you are using readlink(). Did you come up with the solution yourself or did you find it somewhere online? In the latter case, can you please share the URL?

I think its all from the bug report: https://bugs.php.net/bug.php?id=65697

The "array_diff trick" is shown in the initial comment:

foreach ( $paths as $path )
{
  echo "----\n $path\n";
  var_dump( is_link( $path ) );
  var_dump( is_dir( $path ) );
  var_dump( array_diff( stat($path), lstat($path) ) );
}

I expect readlink() is faster than two stat/lstat and an array_diff, but I'm not sure if readlink is really stable?!

@leofeyer
Copy link

but I'm not sure if readlink is really stable?!

That's exactly my concern, too.

@tristanlins
Copy link
Contributor

@Zeromax
Copy link
Contributor Author

Zeromax commented Nov 28, 2014

Did you check whether your symlinks show up as or ?

No I haven't. Will check this later.

@Zeromax
Copy link
Contributor Author

Zeromax commented Nov 28, 2014

So I have checked it again to be save ;)

readlink does not throw an error or warning on windows if the path pointed to a file.

Did you check whether your symlinks show up as or ?

Now I have ;) Folders are point out as symlinkd and files as symlink. Created via composer-plugin.

So if you won't prefer the readlink solution I can check the stat / lstat solution.

@tristanlins
Copy link
Contributor

And what happen, if the link is outdated or manipulated?
When $target is a link, but do not point to $linkTarget the complete logic fail.
Thats why I prefer another solution like the stat/lstat trick.

@Zeromax
Copy link
Contributor Author

Zeromax commented Nov 28, 2014

@tristanlins you are right. I only check if the symbolic link is valid. I have missed this.

The Checks are running under Windows 8.1 Xampp, PHP 5.5.15

So I have made same tests for stat and lstat. I see no benefit in there...

$link = "D:\\www\\contao-3.3.5\\system\\modules\\!composer"; // Symbolic link
var_dump(array_diff(stat($link), lstat($link)));
array (size=0)
  empty
$link2 = "D:\\www\\contao-3.3.5\\system\\modules"; // real folder
var_dump(array_diff(stat($link2), lstat($link2)));
array (size=0)
  empty

So, there should be a differenz when I get it right, but there isn't.
And if the symlink is broken (renamed the target) I get Warnings for stat and lstat. That doesn't help as well.

So with the hint from @tristanlins I would propose the following:

public static function isSymbolicLink($target)
{
    if (defined('PHP_WINDOWS_VERSION_BUILD')) {
        if(file_exists($target) && readlink($target) != $target) {
            return true;
        }
    } elseif (is_link($target)) {
        return true;
    }
    return false;
}

So I think it is enough to check against the target readlink($target) != $target because if they are not the same if it is a link. Or not?
The file_exists is needed, otherwise I get a warning, when the link is broken.

@Zeromax
Copy link
Contributor Author

Zeromax commented Nov 28, 2014

Small note https://github.com/symfony/Filesystem/blob/master/Filesystem.php#L287

Idea: Use the Symfony Filesystem. @discordier ?

@tristanlins
Copy link
Contributor

Sry to say that, but plugins should not depend on any other library.
At least I don't see how this help?! Our problem is not to create the symlink, the problem is to detect symlinks or I'm wrong?

@discordier
Copy link
Member

I am with @tristanlins on this one. A plugin must be independent from other packages.
The sole problem is to detect and validate symlinks under Windows.

@Zeromax
Copy link
Contributor Author

Zeromax commented Dec 1, 2014

@tristanlins @discordier ok. That's no problem.

Btw. I have looked deeper in the Filesystem class and methods. And there is no is_link workaround as well. So to get some code doesn't help as well.

@dmolineus
Copy link
Member

The typo3 flow guys using realpath as a workaround. Didn't validate it on a windows machine, but maybe that's the way to go.

https://forge.typo3.org/projects/package-typo3-flow/repository/revisions/b306b18ac9efcba83ae9525bf470cc020d6d4b34/entry/Classes/Utility/Files.php#L260

@tristanlins
Copy link
Contributor

realpath will follow links, I could imagine something like:

$realpath = realpath($target);

if ($realpath !== $target) {
    // It is a symlink
    return true;
}

// fallback for broken symlinks
$link = readlink($target);

if ($link && $link !== $target) {
    // It is a broken symlink
    return true;
}

return false;

@Zeromax
Copy link
Contributor Author

Zeromax commented Dec 2, 2014

@tristanlins Sounds interresting I will check this.

@tristanlins
Copy link
Contributor

If I rethink my code, the fallback may be wrong / unnecessary.
realpath should return false when the link is broken.
But realpath will also return another link, when one of the parent directories is a symlink ... not easy :-\

@Zeromax
Copy link
Contributor Author

Zeromax commented Dec 2, 2014

Hm. So to do the same as is_link we should check what's happen with a broken symlink.

PHP Doc from is_link:

Returns TRUE if the filename exists and is a symbolic link, FALSE otherwise.

If I understand right: a broken symlink is a symlink without a target. But does it return true or false then?

@Zeromax
Copy link
Contributor Author

Zeromax commented Dec 3, 2014

@tristanlins I have checked your code and it doesn't work correctly.

$realpath = realpath($target);

if ($realpath !== $target) {
    // It is a symlink
    return true;
}

If target is a Folder realpath returns false. So false !== 'SomePath returns true.
realpath returns also false if the symlink points to a not existing directory.

So changing the if clause to

if ($realpath && $realpath !== $target)

everything works like expected.
Tested with Empty Folder(symlink recreation), or Folder with content (Exception like expected)


Next Problem $link = readlink($target); throws a warning if the $target does not exist. Better to check if it exists or adding a '@'.

@tristanlins
Copy link
Contributor

realpath returns also false if the symlink points to a not existing directory.

This is absolutely correct, because a broken link is still a link.

@fritzmg
Copy link
Contributor

fritzmg commented Sep 6, 2015

If target is a Folder realpath returns false.

Are you sure about that? In my testing realpath on a real folder returns just the path to that folder, not false.

@Zeromax
Copy link
Contributor Author

Zeromax commented Mar 21, 2016

I think that this is also resolved by using php-fcgi on xampp:
contao/core-bundle#208 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants