|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2009-06-30 17:19 UTC] ddkees at illinois dot edu
Description:
------------
After updating this morning (June 30) to 5.3.0, our __autoload() function failed to identify any classes located in subfolders of the windows Junction Point which contains our class files.
Our __autoload() function is recursive, descending into the filesystem looking for class files which match the one that we're trying to load. However, since the /includes/classes folder is a Junction Point, only other Junction Points return true when we use both is_dir() and DirectoryIterator::isDir() to try and identify folders from files. DirectoryIterator::isLink() also returns false.
However, if we change our __autoloader() to include files from the source of the Junction Point, it works as expected, but this is only a solution for a sub-set of the sites that are available on our server.
Reproduce code:
---------------
function __autoload($class) {
if(!function_exists("find_file")) {
function find_file($directory, $target) {
$dir = opendir($directory);
while(($file = readdir($dir))!==false) {
if($file == "_notes" || substr($file, 0, 1)==".") continue;
if(is_dir($directory . "/" . $file)) find_file($directory . "/" . $file, $target);
elseif(basename($file, ".php") == $target) { require_once($directory . "/" . $file); return; }
}
}
}
find_file($_SERVER['DOCUMENT_ROOT'] . "includes/classes", strtolower($class));
}
Expected result:
----------------
The expected result is that starting from $_SERVER["DOCUMENT_ROOt"] . "/includes/classes" we identify folders and descend into them to look for a file named $class.php where $class is the parameter sent to __autoload(). When that file is found, it's included.
Actual result:
--------------
No folders are actually traversed, either when using the code above or when altering it to use the DirectoryIteratory SPL object. All non-Junction Points return false from is_dir() and, as a result, they are never traversed and the system will also attempt to include them as files if the elseif-conditional evaluates to true.
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Thu Oct 30 22:00:01 2025 UTC |
The file structure looks like this. Junction points are followed by an asterisk; that asterisk is _not_ a part of the folder's name within the filesystem. / /includes /includes/classes* /includes/classes/.SwiftMailer /includes/classes/course_websites /includes/classes/directory /includes/classes/faculty_awards* /includes/classes/FirePHP There are more folders than what's listed above, but that's enough to give you the idea, I hope. If not, let me know. As you can see from the code example above, we start the process in /includes/classes and look for folders/files to identify class definitions to include. Using this code and 5.3.0 this morning, we found that, using the above list, only the faculty_awards junction point would be identified as a directory when using is_dir() and when using DirectoryIterator::isDir(). Here's a script to show what fails: function find_directories($directory) { $files = new DirectoryIterator($directory); foreach($files as $file) { if($file->isDot() || $file->getFilename()=="_notes") continue; echo "Analyzing: $file<br>"; var_dump($file->isDir()); echo "Analysis Complete.<br><br>"; } } --- If we execute that function as follows: find_directories($_SERVER["DOCUMENT_ROOT"] . "/includes/classes"); We would expect that all of the information listed above would result in true. However, the actual results of that test were: Analyzing: .SwiftMailer bool(false) Analysis Complete Analyzing: course_websites bool(false) Analysis Complete Analyzing: directories bool(false) Analysis Complete Analyzing: faculty_awards bool(true) Analysis Complete Analyzing: FirePHP bool(false) Analysis CompleteThis bug can easily be reproduced with the following scenario: - change to a directory within your webserver's document root - create a junction named "apps" pointing to the desired target within the directory tree - calling a php file (residing in parallel to the "apps" folder) with the following code: if(file_exists('./apps') === true){ echo 'yes!'; } else { echo 'no!'; } The result will be "no!".@dr dot e dot sheppard at web dot de Can you try using CLI/CGI please? As you can see below it works just fine. C:\Users\pierre\Documents\php-sdk\php53\vc9\x86>dir .. 10.08.2009 17:36 <JUNCTION> apps [C:\Users\pierre\Documents\php-sdk\php53\vc9\x86\deps] 28.07.2009 00:59 <DIR> deps ... php.exe -r "var_dump(file_exists('./apps'));" bool(true)cgi executable does not seem to make a difference. This is a console log of a test I just ran: C:\mnt\test>type phptest.php <?php var_dump(file_exists('junction')); var_dump(file_exists('directory')); ?> C:\mnt\test>dir Volume in drive C is coreI7_System Volume Serial Number is 38E2-2B62 Directory of C:\mnt\test 2009.08.10 16.11 <DIR> . 2009.08.10 16.11 <DIR> .. 2009.08.10 16.10 <DIR> directory 2009.08.10 16.10 <JUNCTION> junction [\??\Volume{e13ba66a-14db-11de-8e96-001fd0ae05ac}\] 2009.08.10 16.11 82 phptest.php 1 File(s) 82 bytes 4 Dir(s) 24,899,223,552 bytes free C:\mnt\test>php.exe phptest.php bool(false) bool(true) C:\mnt\test>php-cgi.exe phptest.php X-Powered-By: PHP/5.3.0 Content-type: text/html bool(false) bool(true) C:\mnt\test>'mnt' is a regular directory. 'junction' was indeed pointing to the root of a volume. However, I get exactly the same behavior if I create the junction as a link to anouther directory via the junction.exe from sysinternals. Again, this is a log from Windows 7 x64: C:\mnt\test>type phptest.php <?php var_dump(file_exists('junction')); var_dump(file_exists('directory')); ?> C:\mnt\test>dir Volume in drive C is coreI7_System Volume Serial Number is 38E2-2B62 Directory of C:\mnt\test 2009.08.10 16.11 <DIR> . 2009.08.10 16.11 <DIR> .. 2009.08.11 08.22 <DIR> directory 2009.08.10 16.10 <JUNCTION> junction [\??\C:\mnt\test\directory] 2009.08.10 16.11 82 phptest.php 1 File(s) 82 bytes 4 Dir(s) 24,210,726,912 bytes free C:\mnt\test>php.exe phptest.php bool(false) bool(true)From my testing, this snapshot works for directory junction points created with mklink. However still no go on mounted volumes or directory junction points created with junction.exe. Re-creating any faulting junctions with mklink seems like a good workaround, but mounted volumes are still left in the cold. Looking at the output of the dir command, the only visible difference between mklink and junction.exe is the prepended "\??\", which is also present on mounted volumes by necessity. Full test log follows: C:\mnt\test>dir Volume in drive C is coreI7_System Volume Serial Number is 38E2-2B62 Directory of C:\mnt\test 2009.08.26 23.32 <DIR> . 2009.08.26 23.32 <DIR> .. 2009.08.11 16.35 <DIR> directory 2009.08.11 14.47 <JUNCTION> junction [\??\C:\mnt\test\directory] 2009.08.20 13.26 <JUNCTION> mklink_junction [C:\mnt\test\directory] 2009.08.26 23.26 <JUNCTION> mounted_volume [\??\Volume{e13ba66a-14db-11de-8e96-001fd0ae05ac} \] 2009.08.26 23.32 283 phptest.php 1 File(s) 283 bytes 6 Dir(s) 28,765,544,448 bytes free C:\mnt\test>php -v PHP 5.3.1-dev (cli) (built: Aug 27 2009 03:52:14) Copyright (c) 1997-2009 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2009 Zend Technologies C:\mnt\test>type phptest.php <?php echo "directory:\t\t"; var_dump(file_exists("directory")); echo "junction.exe junction:\t"; var_dump(file_exists("junction")); echo "mklink junction:\t"; var_dump(file_exists("mklink_junction")); echo "mounted volume:\t\t"; var_dump(file_exists("mounted_volume")); ?> C:\mnt\test>php phptest.php directory: bool(true) junction.exe junction: bool(false) mklink junction: bool(true) mounted volume: bool(false)To do not miss a case again, can you please tell me how you create each mounted volume or junction? If mnt is a mounted volume or not, etc. Junction created with junction.exe works fine here on xp, 2k3, win7, vista and 2k8, using normal parent directories (mounted volume or not). For example (XP): c:\mnt mounted volume (HDD) c:\mnt\directory (mkdir directory) c:\mnt\junction_abs (created with junction junction c:\mnt\directory) c:\mnt\junction (created with junction junction directory from c:\mnt) Using this script from c:\mnt\test (c:\mnt being a mounted volume): <?php var_dump(file_exists('directory')); var_dump(file_exists('junction_abs')); var_dump(file_exists('junction')); C:\mnt\test>\php-sdk\snap_5_2\vc6\x86\php52\Debug\php.exe t.php bool(true) bool(true) bool(true)Ok, this snap seems to get everything except mounted volumes for me. test output is now: C:\mnt\test>php phptest.php directory: bool(true) junction.exe junction: bool(true) mklink junction: bool(true) mounted volume: bool(false) I've found 3 ways to create volume junction points on Win 7: Through the mountvol command, thourgh the mklink command, and though the diskmgmt.msc GUI. They all result in the same behavior. I also just found that mklink can create symlinks to volumes, which, alas, also fail. (symlinks to directories & files work fine) 'dir' command for symlinks vs. junctions to volumes looks like this: 2009.08.27 15.18 <SYMLINKD> test2 [\\?\Volume{c7981ed5-d602-11dd-ac24-806e6f6e6963}\] 2009.08.27 15.19 <JUNCTION> test3 [\\?\Volume{c7981ed5-d602-11dd-ac24-806e6f6e6963}\] For the record, the following command sequence can be used to re-create my test setup... c:\mnt & c:\mnt\test are regular directories. C:\mnt\test>mkdir directory C:\mnt\test>junction junction directory Junction v1.05 - Windows junction creator and reparse point viewer Copyright (C) 2000-2007 Mark Russinovich Systems Internals - http://www.sysinternals.com Created: C:\mnt\test\junction Targetted at: C:\mnt\test\directory C:\mnt\test>mklink /j mklink_junction directory Junction created for mklink_junction <<===>> directory C:\mnt\test>mklink /j mounted_volume \\?\Volume{c7981ed5-d602-11dd-ac24-806e6f6e6963}\ Junction created for mounted_volume <<===>> \\?\Volume{c7981ed5-d602-11dd-ac24-806e6f6e6963}\ C:\mnt\test>dir Volume in drive C is coreI7_System Volume Serial Number is 38E2-2B62 Directory of C:\mnt\test 2009.08.27 15.29 <DIR> . 2009.08.27 15.29 <DIR> .. 2009.08.27 15.27 <DIR> directory 2009.08.27 15.27 <JUNCTION> junction [\??\C:\mnt\test\directory] 2009.08.27 15.28 <JUNCTION> mklink_junction [C:\mnt\test\directory] 2009.08.27 15.29 <JUNCTION> mounted_volume [\\?\Volume{c7981ed5-d602-11dd-ac24-806e6f6e6963} \] 2009.08.27 15.23 451 phptest.php 1 File(s) 451 bytes 6 Dir(s) 28,746,731,520 bytes free C:\mnt\test>php phptest.php directory: bool(true) junction.exe junction: bool(true) mklink junction: bool(true) mounted volume: bool(false) Thanks for your work on this, unfortunately, my inability to keep track of drive letters means I use a lot of mounted volumes...Hmmm. may have spoke too soon. Getting some weird results when working with mounted volumes. 'C:\mnt\test\mounted_volume' is a junction mounted volume that is *not* the system drive ie: not c:\ Take the following sequence of commands, working directory is 'C:\mnt\test\': is_dir('mounted_volume'); Returns true, yay! scandir('mounted_volume'); Here's the strange behavior; this command enumerates the root system drive, c:\, rather than the correct pointed-to volume. mkdir('mounted_volume\test34'); Returns true, but test32 is created on the incorrect volume: c:\test32 now exists is_dir('mounted_volume\test34'); Returns FALSE, this is the correct behavior because the directory does not exist in the correct location, but it is obviously inconsistent with the above mkdir unlink('mounted_volume\test34'); Throws a 'No such file or directory' warning. Appears to be looking in the correct place. My previous tests all used c:\ as the 'mounted volume' which is why I didn't see this at first.Found the problems, the windows port of dirent (scandir) as well as php_mkdir (used by mkdir()) lack a call to realpath, making them use the current path with the standard API. It ends to use c:. Testing the patch but it should do it for RC1 next Tuesday. You can test the patch yourself by calling realpath manually: scandir(realpath('mounted_volume')); or mkdir(realpath('mounted_volume\\test34')); Other files operations work as expected.@ pajoye: Sorry, but i don't understand why the correct behaviour should be that of 5.3: dir1 directory subdir1 subdirectory of dir1 cfg.php file in dir1 dir2 directory cfg.php file in dir2 junction1 junction to subdir1 created in dir2 and i have in dir1/subdir1/file.php: require_once("../cfg.php"); I expect that if i run dir1/subdir1/file.php i get require_once("dir1/cfg.php"); BUT if i run dir2/subdir1/file.php i will get require_once("dir2/cfg.php"); Could you please tell me why this would be wrong? File inclusions have always been relative to the path of the main php file that's executing... Thanks!sorry, found another mounted volume issue: is_dir reutrns true when passed a file on a mounted volume. Additionaly, chdir retruns true when passed that file. eg: is_dir('c:\mounted_volume\image.jpg'); returns true. image.jpg is a file and mounted_volume is a junction point mounted volume.Using Windows 7 x64. It seems all files in my mounted volumes are being treated as directories. (is_dir returns true, is_file returns false.) Directory operations pointed at files seem to point back to the root of the volume. The following script: echo "dumping: scandir('mounted_volume')\n"; var_dump(scandir('mounted_volume')); echo "dumping: scandir('mounted_volume\\file1')\n"; var_dump(scandir('mounted_volume\file1')); gives this output: dumping: scandir('mounted_volume') array(4) { [0]=> string(12) "$RECYCLE.BIN" [1]=> string(4) "dir1" [2]=> string(4) "dir2" [3]=> string(5) "file1" } dumping: scandir('mounted_volume\file1') array(4) { [0]=> string(12) "$RECYCLE.BIN" [1]=> string(4) "dir1" [2]=> string(4) "dir2" [3]=> string(5) "file1" } Nesting does not seem to matter, eg: scandir('mounted_volume\dir1\file_in_dir1'); gives the same output Something else that's interesting... When I create the junction from a drive letter, eg: "mklink mounted_volume y:" everything works perfectly, files are files and dirs are dirs. It's only when I use the volume name in the creation ("mklink /J mounted_volume \\?\Volume{feeac7c1-2ad0-11de-89bb-001fd0ae05ac}\") that I get this strange behavior. Bizarre, but I swear I'm not making this up :)@shoresofnowhere at gmail dot com I suppose you mean: c:\Dir |_ one.php |_ Dir3 (junction to d:\dir) |_ two.php Using this constellation, here is my output on xp SP3: C:\mnt>junction dir3 Junction v1.05 - .. ... C:\mnt\dir3: JUNCTION Substitute Name: c:\test C:\mnt>\test\php53vc6ts\php.exe one.php bool(true) Are you sure: - apache has been restarted after the update? - the right version is used? Can you try in CLI as well please?Ignore my last two comments, it works perfectly using what you describe. I was testing it from another VM where this junction did not exist. I added a include 'dir3/two.php' to one.php, two.php being a simple echo "two.php" The output: C:\test>\php53\debug_ts\php.exe -n one.php two.php bool(true) C:\test>junction dir3 .... C:\test\dir3: JUNCTION Substitute Name: e:\test C:\test>dir dir3 ... 09/04/2009 07:33 PM 24 two.php 1 File(s) 24 bytes 2 Dir(s) 202,975,232 bytes freeI was able replicate shoresofnowhere's behavior using windows 7... I created a junction to a folder on another drive; running is_file() on a file inside that junction returned false, as did is_dir(). Curious to see what php thought it was looking at, I tested filetype(), which threw an error. I then tested symlinks in the same manner, and got good behavior. Symlinks seem to be a good workaround for this issue. Test log follows: C:\mnt\test>mklink /J junction_otherDrive f:\downloads Junction created for junction_otherDrive <<===>> f:\downloads C:\mnt\test>mklink /D symlink_otherDrive f:\downloads symbolic link created for symlink_otherDrive <<===>> f:\downloads C:\mnt\test>dir Volume in drive C is coreI7_System Volume Serial Number is 38E2-2B62 Directory of C:\mnt\test 2009.09.04 16.05 <DIR> . 2009.09.04 16.05 <DIR> .. 2009.09.04 16.05 <JUNCTION> junction_otherDrive [f:\downloads] 2009.09.04 16.05 <SYMLINKD> symlink_otherDrive [f:\downloads] 0 File(s) 0 bytes 4 Dir(s) 30,034,223,104 bytes free C:\mnt\test>php -r var_dump(filetype('junction_otherdrive')); PHP Warning: filetype(): Lstat failed for junction_otherdrive in Command line code on line 1 Warning: filetype(): Lstat failed for junction_otherdrive in Command line code on line 1 bool(false) C:\mnt\test>php -r var_dump(filetype('junction_otherdrive\php-5.2.0-win32-installer.msi')); PHP Warning: filetype(): Lstat failed for junction_otherdrive\php-5.2.0-win32-installer.msi in Comm and line code on line 1 Warning: filetype(): Lstat failed for junction_otherdrive\php-5.2.0-win32-installer.msi in Command l ine code on line 1 bool(false) C:\mnt\test>php -r var_dump(filetype('symlink_otherdrive')); string(3) "dir" C:\mnt\test>php -r var_dump(filetype('symlink_otherdrive\php-5.2.0-win32-installer.msi')); string(4) "file"Hi, This is a note for the bug archives. I just discovered that the bug reported in #49039 which was merged with this bug entry, can also be reproduced when the web server spawning PHP-CGI.EXE incorrectly prepares the CGI environment string: PATH_INFO The correct "official" (IETF) standard definition is in RFC 3875: "The Common Gateway Interface (CGI) Version 1.1": http://www.ietf.org/rfc/rfc3875 If PATH_INFO is incorrect, the function sapi_cgi_register_variables() sapi\cgi\cgi_main.c, which effectively does: PHP_SELF = SCRIPT_NAME + PATH_INFO will potentially cause the same "No input file specified" behavior we saw in bug #49039. The original bug #49039 report indicated a script in a sub-folder name with 3 letters, e.g.; /public/pwe/test.php would yield a file not found response. It was found if the server-side document root was a junction point, the no input file error was produced. c:\web\HTTP\public\pwe where C:\WEB\HTTP is a junction, like using SysInternals.com Junction.EXE utility: C: CD \WEB junction HTTP D:\WEB_SRC\HTTP_REV1.2.3.4 We internally use junctions in this way to test various versions of our html templates. Eventually it was determined by pajoye@php.net the PHP 5.3 logic for resolving junctions has some issues to address and that is when I last left this issue until now. Recently, in an independent report related to PHP_SELF, we found out our web server was not creating the environment string PATH_INFO CGI correctly per RFC 3875. This serve issue was just fixed and after seeing how PHP expects PHP_SELF to be created using PATH_INFO, I was curious if the web server PATH_INFO fix also fixes #49039. Well, indeed it appears that it does fix it, even when the document root is a junction point. For the curious, our WEB SERVER first supported real CGI binary processes, no script maps, for example for a URI with: http://example.com/cgi-bin/applet.exe/file.txt?p1=v1 its parts are: SCRIPT_NAME = "/cgi-bin/applet.exe" QUERY_STRING = "p1=v1" PATH_INFO = "/file.txt" if for the sake of example, the document root is: DOCUMENT_ROOT = "c:\web\http" then PATH_TRANSLATED is: PATH_TRANSLATED = "c:\web\http\file.txt" When script mapping support was added, including support for specific PHP's SCRIPT_FILENAME environment string, a bug was apparently introduced for creating PATH_INFO. So in an valid URI with a PHP script map: http://example.com/test.php/file.txt?p1=v2 Our web server correctly created: DOCUMENT_ROOT = "c:\web\http" SCRIPT_NAME = "/test.php" QUERY_STRING = "p1=v1" SCRIPT_FILENAME = "c:\web\http\test.php" PATH_TRANSLATED = "c:\web\http\file.txt" but incorrectly created: PATH_INFO = "/test.php/file.txt" So when PHP-CGI started and created PHP_SELF in cgi_main.c PHP_SELF = SCRIPT_NAME + PATH_INFO its value was now: PHP_SELF = "/test.php/test.php/file.txt" thus the "no input file found" PHP response. As noted, this as fixed in our web sever, and it appears to resolve also bug #49039. I am sure inadvertingly the junction point issues were legit and being (and had been?) addressed, I am just nothing as it another possible reason caused by a web server not correctly implementing PATH_INFO per RFC 3875.I don't know if the following behaviour regarding __FILE__ is intended, or if it's a regression due to the patch for this bug. Config test: Win 7, PHP 5.3.2 and 5.2.6 (VC6) I created the following structure: H:\www\directory H:\www\junction (=> H:\www\directory, created with junction.exe or any other tool) H:\www\symlink (=> directory, created with mklink.exe) The test case is file.php: <?php echo __FILE__; ?> In PHP 5.3.2: C:\php-cli.532>php.exe H:\www\directory\file.php H:\www\directory\file.php C:\php-cli.532>php.exe H:\www\junction\file.php H:\www\directory\file.php C:\php-cli.532>php.exe H:\www\symlink\file.php H:\www\directory\file.php This is not the case in, for instance, 5.2.6, which returns the correct paths. Haven't tested in the SAPIs if the result is the same. Can someone with win XP (pre-Vista) please try if the case with a junction yields the same output? Test case 2: <?php var_dump(lstat('H:\\www\\directory')); var_dump(lstat('H:\\www\\junction')); var_dump(lstat('H:\\www\\symlink')); ?> All three outputs are the same, despite having different timestamps and using lstat. Once again, 5.2.6 gives correct results.