Edit report at https://bugs.php.net/bug.php?id=73884&edit=1
ID: 73884
Comment by: kulakov74 at yandex dot ru
Reported by: kulakov74 at yandex dot ru
Summary: is_dir() returns false for junction / symlinkd
Status: Feedback
Type: Bug
Package: Filesystem function related
Operating System: Windows 8.1
PHP Version: 7.1.0
Block user comment: N
Private report: N
New Comment:
I did many tests and I finally came up with the conclusion the problem is at least in Cyrillic
(Russian) characters in directory names (even though I have the latest PHP where the UTF problem has
been resolved). I'll give the last lines of my tests for you to make it clear. I'm using
russian letters in my tests so make sure you see them.
I have created 2 directories for tests:
C:\Temp\Dir
C:\Temp\ÐÐ°Ñ (this one is rus)
I'm giving 4 combinations for eng/rus:
//1. eng -> eng: OK
c:\Temp>mklink /j linktoeng C:\Temp\Dir
Junction created for linktoeng <<===>> C:\Temp\Dir
c:\Temp>php.exe -n -r "$p = 'c:\Temp\linktoeng'; var_dump(is_dir($p),
readlink($p));"
bool(true)
string(11) "C:\Temp\Dir"
//2. rus -> eng: OK!
c:\Temp>mklink /j ÑÑÑ-ÑÑÑлка-наангл C:\Temp\Dir
Junction created for ÑÑÑ-ÑÑÑлка-наангл
<<===>> C:\Temp\Dir
c:\Temp>php.exe -n -r "$p =
'c:\Temp\ÑÑÑ-ÑÑÑлка-наангл';
var_dump(is_dir($p), readlink($p));"
bool(true)
string(11) "C:\Temp\Dir"
//3. eng -> rus: NO
c:\Temp>mklink /j linktorus C:\Temp\ÐаÑ
Junction created for linktorus <<===>> C:\Temp\ÐаÑ
c:\Temp>php.exe -n -r "$p = 'c:\Temp\linktorus'; var_dump(is_dir($p),
readlink($p));"
bool(false)
string(14) "C:\Temp\ÐаÑ"
In this case note that readlink() does perfectly well, also, if I try
c:\Temp>php.exe -n -r "$p = 'C:\Temp\ÐаÑ'; var_dump(is_dir($p),
readlink($p));"
bool(true)
string(14) "C:\Temp\ÐаÑ"
is_dir() is correct, so it's is_dir()'s only bug and it only shows with links, cause when
I give it the russian dir directly it knows it's a dir!
//4. rus -> rus: NO (this is formal, we know it won't work :)
c:\Temp>mklink /j ÑÑÑ-ÑÑÑлка C:\Temp\ÐаÑ
Junction created for ÑÑÑ-ÑÑÑлка <<===>> C:\Temp\ÐаÑ
c:\Temp>php.exe -n -r "$p = 'c:\Temp\ÑÑÑ-ÑÑÑлка';
var_dump(is_dir($p), readlink($p));"
bool(false)
string(14) "C:\Temp\ÐаÑ"
And because I have a russian user name all of the links in my C:\Users fail with PHP.
//-----------------
As for permissions (ACLs), when you run
icacls "c:\Users\All Users"
as "All Users" is a link icacls displays ACLs for the target, not for the link. If you try
icacls "c:\programdata"
you'll see the same ACLs, while in fact "All Users" has the same Deny rule you
mentioned (I must say all the built-in links have this Deny rule). So you should use
icacls "c:\Users\All Users" /L
>>With c:\users\default user - i cannot even view it in the explorer, "access
>>denied" popup
The same with me :) BUT if I open console (cmd.exe) I can do this:
dir /A:L C:\Users
...
<JUNCTION> Default User [C:\Users\Default]
and then I can do
c:\Users>cd "Default User"
c:\Users\Default User>
So, Explorer behaves different with links that have equal ACLs, so this is probably where junctions
and symlinks differ. It looks like PHP follows the logic used by Explorer and I don't see why
Explorer can't access the objects other tools can.
Other tools: cmd.exe, Total Commander.
Previous Comments:
------------------------------------------------------------------------
[2017-01-08 19:49:50] ab@php.net
Thanks for the effort so far. Maybe I expressed myself wrong. I was talking about the NTFS
permissions. Either in GUI by viewing advanced security, or by icacls or similar tools. Here is it
on my side, with c:\users\all users
php.exe -n -r "$p = 'c:\users\all users'; var_dump(is_dir($p), readlink($p));"
bool(true)
string(14) "C:\ProgramData"
$ icacls "c:\Users\All Users"
c:\Users\All Users NT AUTHORITY\SYSTEM:(OI)(CI)(F)
BUILTIN\Administrators:(OI)(CI)(F)
CREATOR OWNER:(OI)(CI)(IO)(F)
BUILTIN\Users:(OI)(CI)(RX)
BUILTIN\Users:(CI)(WD,AD,WEA,WA)
With c:\users\default user - i cannot even view it in the explorer, "access denied" popup.
So that's what it gives
php.exe -n -r "$p = 'c:\users\default user'; var_dump(is_dir($p),
readlink($p));"
Warning: readlink(): readlink failed to read the symbolic link (c:\users\default user), error 5) in
Command line code on line 1
bool(true)
bool(false)
And that's not a wonder, as
$ icacls "c:\Users\Default User"
c:\Users\Default User Everyone:(DENY)(S,RD)
Everyone:(RX)
NT AUTHORITY\SYSTEM:(F)
BUILTIN\Administrators:(F)
Given you say, that a custom junction works as expected, but some particular path don't - it
still looks like a permission issue. I might be still wrong, though, but really need a reliable
reproducer for the case. Which other tools do you mean? Best way would be to have a synthetic case,
that creates an fs object with exact access privileges and shows the issue so then it's
repeatable on any system.
Thanks.
------------------------------------------------------------------------
[2017-01-07 20:19:56] kulakov74 at yandex dot ru
Note that readlink() returns an error for "c:\Users\Default User\":
Warning: readlink(): readlink failed to read the symbolic link (C:\Users\Default User), error 5)
I must add that the "Default User" junction does have a Deny rule for everyone to list
folder/read data, but in practice it has no effect (if you use any other tool but PHP). After I
removed it, both is_dir() and readlink() produced correct results. So it looks like PHP checks the
permissions, finds the Deny rule and uses some wrong logic to conclude it has no right to access the
link.
BUT I have another junction also created by me before (not for tests)
c:\Users\Sergei\ pointing to C:\Users\СеÑÑжка (my name in Russian)
and it doesn't have the deny rule, while it has full control for myself, AND is_dir() returns
false for it (wrong!), while readlink() returns the right target. So there's some other kind of
bug with is_dir().
------------------------------------------------------------------------
[2017-01-07 19:37:35] kulakov74 at yandex dot ru
For tests, I tried "Default User", it was owned by System and had a right to read/execute
for everyone. After I took over ownership and removed the right, I could not "enter" it
and Dir stopped displaying its target (just showed "[..]"). is_dir() still returned false,
which is actully not correct because if it has no way to know and check the target, it can't
know if it's a dir or not, no error message displayed either. I readded the right to
read/execute for everyone and everything works as it did before.
The junction I created myself (templink) has "read & execute" right for
"Users", not for everyone. Also it has Modify permission for Authenticated users. I added
both to "Default User" but it didn't help. "Default User" also had
"hs" attributes (hidden and system), but after I cleared them nothing changed. The only
difference left is that "Default User" has the permissions added to it directly while
templink inherits them from C:\. I disabled inheritance for templink but is_dir() still worked for
it.
Still, I could make is_dir() return false for templink by removing all rights.
So, this could be related with permissions, but I couldn't prove it, and even if I did, php
does smth wrong cause it shouldn't cause problems.
------------------------------------------------------------------------
[2017-01-06 21:41:54] ab@php.net
Thanks for the report. Might be good a permission issue, yep. Would you mind to investigate more on
it, maybe checking what difference the extended permissions tell, etc.?
Thanks.
------------------------------------------------------------------------
[2017-01-06 20:12:44] kulakov74 at yandex dot ru
Description:
------------
On my Windows PC, I scanned the whole directory structure in C:\ (both from browsers and in CLI
mode) and found out that for all of the junctions / symlinks that had been created by the system
is_dir() returns false.
According to the manual, "If filename is a symbolic or hard link then the link will be resolved
and checked." but this is only the case with the junctions / symlinks I created manually for
testing. It might seem the problem is with permissions but I run the script as admin and also I can
see the fact that the items in question are junctions by using plain console (cmd.exe) even not as
admin, for ex.:
c:\Users>dir /a:l
...
22.08.2013 17:45 <SYMLINKD> All Users [C:\ProgramData]
22.08.2013 17:45 <JUNCTION> Default User [C:\Users\Default]
...
BTW, In Windows, junctions and symlinks are almost the same.
Test script:
---------------
$Dir="C:\Users\All Users";
if (is_dir($Dir)) echo("dir"); else echo("not a dir");
Expected result:
----------------
dir
Actual result:
--------------
not a dir
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=73884&edit=1