|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2005-12-23 22:16 UTC] jgmtfia at gmail dot com
Description:
------------
I have narrowed down a PHP problem. I can demonstrate it with file_exists().
I have 2 files, A and B, and one symlink C that points to A.
-rw-r--r-- Dec 23 14:13 A
-rw-r--r-- Dec 23 14:13 B
lrwxr-xr-x Dec 23 14:13 C -> A
When I delete link C from PHP, and then clearstatcache(), file_exists() returns true for link C. **It continues to do so until A is removed from disk**. Using the Apache2 module, I do multiple page reloads and only after some time has passed does PHP notice that link C has been removed.
Not a big problem in general, however, if I do the following:
unlink("C");
symlink("B", "C");
file_get_contents("C");
**PHP still reads the contents of A**. Using the CLI version I have to run the script again before the contents of B are read. In Apache I need to reload multiple times.
I can reproduce this on linux kernels 2.6.10 and 2.6.14. On ext2 and ext3 filesystems. The code below executes correctly (passes) with PHP 4.3.10 and fails on 5.1.0b3, 5.1.0 and 5.1.1.
Reproduce code:
---------------
<?php
`touch A; ln -s A C`;
$FILE = 'C';
$LS = "ls -log $FILE 2>&1";
clearstatcache();
if(!file_exists($FILE))
echo "$FILE link does not exist, cannot run test.\n";
echo "File '$FILE' exists:\n\t\t".`$LS`."\n";
if(!unlink($FILE)){
echo "Unable to delete '$FILE'\n\t\t".`$LS`."\n";
exit;
}
clearstatcache();
echo "Deleted '$FILE'\n\t\t".`$LS`."\n";
if(!file_exists($FILE)){
echo "(PASS) PHP sees the file as deleted.\n";
exit;
}
echo "(FAIL) File '$FILE' deleted as seen by OS. ";
echo "file_exists() still thinks it exists:\n\t\t".`$LS`."\n";
echo "Deleting 'A'\n";
unlink('A');
clearstatcache();
if(file_exists($FILE)){
echo "(FAIL) PHP still sees '$FILE' as existing:\n\t\t".`$LS`."\n";
exit;
}
echo "(FAIL) PHP sees file as deleted only after link target deleted.\n";
?>
Expected result:
----------------
~/x$ ./php-4.3.10-15 ./test.php
File 'C' exists:
lrwxr-xr-x 1 1 Dec 23 14:00 C -> A
Deleted 'C'
ls: C: No such file or directory
(PASS) PHP sees the file as deleted.
Actual result:
--------------
~/x$ ./php-5.1.1 ./test.php
File 'C' exists:
lrwxr-xr-x 1 1 Dec 23 14:02 C -> A
Deleted 'C'
ls: C: No such file or directory
(FAIL) File 'C' deleted as seen by OS. file_exists() still thinks it
exists:
ls: C: No such file or directory
Deleting 'A'
(FAIL) PHP sees file as deleted only after link target deleted.
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sat Oct 25 08:00:01 2025 UTC |
File 'C' exists: lrwxrwxrwx 1 1 2005-12-23 21:10 C -> A Deleted 'C' ls: C: No such file or directory (PASS) PHP sees the file as deleted. php -v PHP 5.1.2RC2-dev (cli) (built: Dec 23 2005 17:37:05) SUSE Linux 10 x86_64I want to ensure that I have made my point clear. I have made a simplier example, and have included an edited strace of PHP built from yesterdays source. The test: <?php `touch A; ln -s A C`; echo "Checking A.\n"; if(!file_exists('A')){ echo "A does not exist.\n"; exit; } echo "Checking C.\n"; if(!file_exists('C')){ echo "C does not exist.\n"; exit; } echo "Unlinking C.\n"; unlink('C'); clearstatcache(); echo "Checking C: "; if(file_exists('C')) echo "(FAIL) exists\n"; ?> The ouput: Checking A. Checking C. Unlinking C. Checking C: (FAIL) exists The edited strace: write(1, "Checking A.\n", 12) lstat64("/home", ...) = 0 lstat64("/home/user", ...) = 0 lstat64("/home/user/x", ...) = 0 lstat64("/home/user/x/A", ...) = 0 access("/home/user/x/A", F_OK) = 0 write(1, "Checking C.\n", 12) lstat64("/home", ...) = 0 lstat64("/home/user", = 0 lstat64("/home/user/x", = 0 lstat64("/home/user/x/C", = 0 readlink("/home/user/x/C", "A", 4096) = 1 lstat64("/home/user/x/A", ...) = 0 access("/home/user/x/A", F_OK) = 0 write(1, "Unlinking C.\n", 13) = 13 unlink("/home/user/x/C") = 0 write(1, "Checking C: ", 12) = 12 ***ERROR SHOULD BE "/home/user/x/C" BELOW *** access("/home/user/x/A", F_OK) = 0 ***END*** write(1, "(FAIL) exists\n", 14) = 14 So the question is why is PHP calling access("/home/user/x/A") when the code calls file_exists('C')? Also note that if the file_exists('C') call is removed from the start code, the code then executes correctly. <?php `touch A; ln -s A C`; echo "Unlinking C.\n"; unlink('C'); clearstatcache(); echo "Checking C: "; if(file_exists('C')) echo "(FAIL) exists\n"; ?> I don't know if it would be the operating system that would cause PHP to access("/home/user/x/A") when I call file_exists('C'). debain php4.3.10-15 does pass the test and there are no lstat64 or readlink calls. write(1, "Checking A.\n", 12) = 12 access("A", F_OK) = 0 write(1, "Checking C.\n", 12) = 12 access("C", F_OK) = 0 write(1, "Unlinking C.\n", 13) = 13 unlink("C") = 0 write(1, "Checking C: ", 12) = 12 access("C", F_OK) = -1 ENOENT (No such file or directory)When I use the configure line: ./configure --disable-all --disable-cgi --enable-debug The test passes and the strace output looks very much like that of php 4.3.10-15. write(1, "Checking A.\n", 12) = 12 access("A", F_OK) = 0 write(1, "Checking C.\n", 12) = 12 access("C", F_OK) = 0 write(1, "Unlinking C.\n", 13) = 13 unlink("C") = 0 write(1, "Checking C: ", 12) = 12 access("C", F_OK) = -1 ENOENT (No such file or directory) Which is the expected output. I also tried ./configure with no arguments, which worked. I then went back to my original configure was: ./configure --with-apxs2=/usr/bin/apxs2 --enable-so \ --with-xsl --with-xmlreader I have narrowed it down to the --with-apxs2=/usr/bin/apxs2 flag. When this ./configure flag is given the problem occurs in the cli version of PHP. When this flag is not given the test passes.php_config.h for both a working and non-working configuration were sent to sniper at php dot net. The difference between the two was: diff -puBb ~/src/php_config.h-* --- /home/user/src/php_config.h-broken 2006-01-04 09:30:07.000000000 -0700 +++ /home/user/src/php_config.h-working 2006-01-04 09:56:57.000000000 -0700 @@ -907,22 +907,22 @@ /* #undef ROXEN_USE_ZTS */ /* whether write(2) works */ -/* #undef PHP_WRITE_STDOUT */ +#define PHP_WRITE_STDOUT 1 /* */ -/* #undef FORCE_CGI_REDIRECT */ +#define FORCE_CGI_REDIRECT 0 /* */ -/* #undef DISCARD_PATH */ +#define DISCARD_PATH 0 /* */ -/* #undef ENABLE_PATHINFO_CHECK */ +#define ENABLE_PATHINFO_CHECK 1 /* */ -/* #undef PHP_FASTCGI */ +#define PHP_FASTCGI 0 /* */ -/* #undef PHP_FCGI_STATIC */ +#define PHP_FCGI_STATIC 0 /* Define to the necessary symbol if this constant uses a non-standard name on your system. */ @@ -2543,7 +2543,7 @@ #define PHP_CAN_SUPPORT_PROC_OPEN 1 /* Whether to enable chroot() function */ -/* #undef ENABLE_CHROOT_FUNC */ +#define ENABLE_CHROOT_FUNC 1 /* */ #define HAVE_RES_NMKQUERY 1 @@ -2798,7 +2798,7 @@ #define USE_ZEND_ALLOC 1 /* */ -#define ZTS 1 +/* #undef ZTS */ /* Memory limit */ #define MEMORY_LIMIT 0 @@ -2819,7 +2819,7 @@ #define ZEND_MM_ALIGNMENT_LOG2 2 /* */ -#define ZTS 1 +/* #undef ZTS */ /* Whether you use GNU Pth */ /* #undef GNUPTH */ @@ -2831,7 +2831,7 @@ /* #undef BETHREADS */ /* Whether to use Pthreads */ -#define PTHREADS 1 +/* #undef PTHREADS */ /* PHP build date */ #define PHP_BUILD_DATE "2006-01-04" @@ -2938,4 +2938,4 @@ int zend_sprintf(char *buffer, const cha * indent-tabs-mode: t * End: */ -#define PTHREADS 1 +/* #undef PTHREADS */I removed the apache2 threaded MPM that I had and replaced it with the prefork MPM and the test now passes when ./configure --with-apxs2=/usr/bin/apxs2 is used. Thank you for your help with this. I had no reason to suspect this was an apache related problem as the CLI API had the same problem as the apache2 module. Is it possible to modify the configure script to detect when a threaded apache2 MPM is being used to avoid problems in the future? On another note, the strace of the cli PHP running the test case now looks more like what would be expected: write(1, "Checking A.\n", 12C) = 12 access("A", F_OK) = 0 write(1, "Checking C.\n", 12) = 12 access("C", F_OK) = 0 write(1, "Unlinking C.\n", 13) = 13 unlink("C") = 0 write(1, "Checking C: ", 12) = 12 access("C", F_OK) = -1 ENOENT (No such file or directory) Again thank you.The test given above passes when the non-threaded apache2 MPM is used for both the cli and apache2 API's. However this test, which shows what I actually need to accomplish, fails on both the cli and apache2 API's. Same sort of problem: <?php `echo file A > A; echo file B > B; ln -fs A C`; echo 'Checking: '; $FILES = array('A', 'B', 'C'); foreach(array('A', 'B', 'C') as $FILE){ echo "$FILE "; if(!file_exists($FILE)){ echo "$FILE does not exist.<br>"; exit; } } echo "- ok<br><pre>".`ls -l A B C 2>&1`."</pre><br>"; clearstatcache(); echo "Contents of C => ".file_get_contents('C')."<br>"; echo "Remove C -> A, replace with C -> B<br>"; unlink('C'); clearstatcache(); symlink('B', 'C'); echo "<br><pre>".`ls -l A B C 2>&1`."</pre><br>"; $B = trim(file_get_contents('C')); $RES = '<font color="'.(($B == 'file B') ? 'green">Pass' : 'red">Fail').'</font>'; echo "Contents of C => $B $RES<br>"; ?> Gives the output: Checking: A B C - ok -rw-r--r-- 1 nobody -1 7 Jan 5 19:05 A -rw-r--r-- 1 nobody -1 7 Jan 5 19:05 B lrwxrwxrwx 1 nobody -1 1 Jan 5 19:05 C -> A Contents of C => file A Remove C -> A, replace with C -> B -rw-r--r-- 1 nobody -1 7 Jan 5 19:05 A -rw-r--r-- 1 nobody -1 7 Jan 5 19:05 B lrwxrwxrwx 1 nobody -1 1 Jan 5 19:05 C -> B Contents of C => file A Fail An strace of the cli version running the given test shows again that php is doing an open("/home/user/x/A", O_RDONLY) when it should be doing open("/home/user/x/C", O_RDONLY) So it is the same kind of problem as the first test shows, but it did not go away when the apache MPM was changed to non-threaded.