Bug #73884 [Com]: is_dir() returns false for junction / symlinkd
| From: | kulakov74 at yandex dot ru | Date: | Tue, 10 Jan 2017 20:57:16 +0000 |
| Subject: | Bug #73884 [Com]: is_dir() returns false for junction / symlinkd | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-206477@lists.php.net to get a copy of this message | ||
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:
Hm. I'm surprised links to cyrillic dirs worked for you. I thought I found the reason. Well, I
can't help now.
>>The points 3. and 4. are intended to work, otherwise the whole UTF-8 thing were useful.
I know, and readlink() and is_dir() (when applied directly to a cyrillic dir) work fine. But when
is_dir() meets a link it has to do an extra step and it may have a bug with buffer length, like the
one readlink() had.
Yeah, I agree Dir seems to go there but it can't show the files there. But it doesn't
really matter - is_dir() only has to find out the target dir and "dir /a:l" shows it. So,
PHP could get it and check whether it is a dir. But I can't say what Windows API function would
return the target like dir does. Anyway, that's a different issue.
As for cd'ing to the dir, this is what Total Commander does: if I go to "All Users"
(it's a symlink), it goes there and the current path is "c:\Users\All Users\", as if
it was a folder, although we really get to "C:\ProgramData". And if I go to "Default
User" (it's a junction) I get to "c:\Users\Default" - I guess because TC first
sniffs the target and then goes to it directly. This is what PHP could do with is_dir(), but without
going (cd) there.
I didn't mean my non-ASCII username was the problem, only non-ASCII dir names, and they could
have my name in the path.
Privileges/ACLs are not the reason here, if only for "Default User" that has a Deny rule,
but that's a different issue.
I use Win 8.1 (x64), the snapshot from 01/07/17.
I'll also try the latest PHP at office, where I have Win 7 x64.
Codepage (437) should not matter, as I run the tests from a php script that uses UTF-8. Codepage
only matters for cmd.exe, I had 866 and now I have 1251, but that shouldn't matter.
Previous Comments:
------------------------------------------------------------------------
[2017-01-09 22:50:36] ab@php.net
Typo, 3. and 4. are intended to work, otherwise UTF-8 were USELESS :)
Thanks.
------------------------------------------------------------------------
[2017-01-09 22:48:17] ab@php.net
Thanks for the further investigation. I still not reproduce the cases 3. and 4. from your post. Here
$ mkdir bugs\bug73884\ÐаÑ
$ icacls bugs\bug73884\ÐаÑ
bugs\bug73884\ÐÐ°Ñ BUILTIN\Administrators:(I)(OI)(CI)(F)
NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
BUILTIN\Users:(I)(OI)(CI)(RX)
NT AUTHORITY\Authenticated Users:(I)(M)
NT AUTHORITY\Authenticated Users:(I)(OI)(CI)(IO)(M)
$ mklink /j bugs\bug73884\linktorus bugs\bug73884\ÐаÑ
Junction created for bugs\bug73884\linktorus <<===>> bugs\bug73884\ÐаÑ
$ icacls bugs\bug73884\linktorus /L
bugs\bug73884\linktorus BUILTIN\Administrators:(I)(OI)(CI)(F)
NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
BUILTIN\Users:(I)(OI)(CI)(RX)
NT AUTHORITY\Authenticated Users:(I)(M)
NT AUTHORITY\Authenticated Users:(I)(OI)(CI)(IO)(M)
$ x64\Release\php.exe -n -r "$p = 'bugs\bug73884\linktorus'; var_dump(is_dir($p),
readlink($p));"
bool(true)
string(54) "C:\php-sdk\php71\vc14\x64\php-src\bugs\bug73884\ÐаÑ"
$ mklink /j bugs\bug73884\ÑÑÑ-ÑÑÑлка bugs\bug73884\ÐаÑ
Junction created for bugs\bug73884\ÑÑÑ-ÑÑÑлка <<===>>
bugs\bug73884\ÐаÑ
$ icacls bugs\bug73884\ÑÑÑ-ÑÑÑлка /L
bugs\bug73884\ÑÑÑ-ÑÑÑлка BUILTIN\Administrators:(I)(OI)(CI)(F)
NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
BUILTIN\Users:(I)(OI)(CI)(RX)
NT AUTHORITY\Authenticated Users:(I)(M)
NT AUTHORITY\Authenticated Users:(I)(OI)(CI)(IO)(M)
$ x64\Release\php.exe -n -r "$p =
'bugs\bug73884\ÑÑÑ-ÑÑÑлка'; var_dump(is_dir($p),
readlink($p));"
bool(true)
string(54) "C:\php-sdk\php71\vc14\x64\php-src\bugs\bug73884\ÐаÑ"
Used the current dev tree, of course. Hopefully you used some snapshot after the readlink() fix,
too. I'm on win10 however, but not sure it is of importance in this case. Also I don't
think the username being not ASCII is relevant. Now see, cmd indeed looks like going into
c:\users\default user, doing same as you
$ cd "Default User"
c:\Users\Default User
$ dir
Volume in drive C is SYSTEM
Volume Serial Number is AE0A-76BD
Directory of c:\Users\Default User
File Not Found
But the dir command shows - it cannot read it. Even the dir were empty, it should have shown
periods. IMO, what the cd command does, is weird. It in no case looks, like the access no
"c:\users\default user" is granted.
The points 3. and 4. are intended to work, otherwise the whole UTF-8 thing were useful. You
don't have to explain the russian words to me, anyway ;) The fact is - everything path related
is converted to UTF-16 internally, and the corresponding W API is used. Also, PHP doesn't
automatically elevate any privileges, it always runs with the default security descriptor, so the
one corresponding to the current user. An exception from this the impersonation, in that case the
impersonated user identity is used. Thereby, if such an issue is real - it doesn't concern only
one particular codepage or language, it is most likely present in any other. For some reason, on
machines i've tried this particular case doesn't produce issues you have. Used win 10 and
server 2012 both with the cp 437.
I'd wonder, whether you maybe have some group policies, that force to create files with
particular ACLs, etc. Otherwise, it's an awkward situation, as i can't reproduce the issue
from your side :( If you're sure, the non ASCII username could be an issue, please try some
user with ASCII symbols only. Otherwise, there has to be another factor that we didn't
determine yet, causing different behavior.
Thanks.
------------------------------------------------------------------------
[2017-01-08 22:13:12] kulakov74 at yandex dot ru
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.
------------------------------------------------------------------------
[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().
------------------------------------------------------------------------
The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at
https://bugs.php.net/bug.php?id=73884
--
Edit this bug report at https://bugs.php.net/bug.php?id=73884&edit=1