php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #79486 file_exists() returns true for a non-existing file
Submitted: 2020-04-17 12:14 UTC Modified: 2020-04-20 09:09 UTC
Votes:8
Avg. Score:3.8 ± 0.8
Reproduced:5 of 6 (83.3%)
Same Version:5 (100.0%)
Same OS:2 (40.0%)
From: m dot staab at complex-it dot de Assigned:
Status: Open Package: *General Issues
PHP Version: 7.3 OS: *
Private report: No CVE-ID: None
View Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
If you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: m dot staab at complex-it dot de
New email:
PHP Version: OS:

 

 [2020-04-17 12:14 UTC] m dot staab at complex-it dot de
Description:
------------
I am running into a mysterious problem, where php's file_exists() returns TRUE for a path, where no file exists.

I tried opening the filepath via notepad++ or the windows explorer, and every tool is telling me that the path points to a non-existant file... except php..

putting the offending path into a plain 3 lines php file it reports false though.

since its really hard to reproduce (even when copying the paths 1:1) it might be a deeper php issue, e.g. opcache statcache or similar.

I tried invoking clearstatcache() before the file_exists() but this does not change the output

Test script:
---------------
error not reproducible with

```
<?php

var_dump(file_exists('C:\Users\mstaab\Documents\GitHub\kunzmann\app\shop\models\clxBaureihe.php'));
```

but at runtime when my sources look like things look different


        foreach (array(
            self::$model_paths,
            self::$lib_paths,
            self::$helper_paths,
            self::$controller_paths,
            self::$plugin_paths
        ) as $paths) {
            foreach ($paths as $path => $dummyVal) {
                if (file_exists($path . DIRECTORY_SEPARATOR . $file)) {
                    if ($class_name == 'clxBaureihe') {
                        var_dump($class_name); // "clxBaureihe"
                        var_dump($path); // "C:\Users\mstaab\Documents\GitHub\kunzmann/app/shop/models"

                        var_dump($file); // "clxBaureihe.php"
                        var_dump($path . DIRECTORY_SEPARATOR . $file); // "C:\Users\mstaab\Documents\GitHub\kunzmann/app/shop/models\clxBaureihe.php"
                        var_dump(file_exists($path . DIRECTORY_SEPARATOR . $file)); // bool(true)
                    }


Expected result:
----------------
file_exists() should return false on non-existing files

Actual result:
--------------
it returns true

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2020-04-17 12:18 UTC] m dot staab at complex-it dot de
when adding
```
file_get_contents($path . DIRECTORY_SEPARATOR . $file)
```

after the line which returns 
```
file_exists($path . DIRECTORY_SEPARATOR . $file) == true
```

`file_get_contents()` will return false


also tested
```
var_dump(is_dir($path . DIRECTORY_SEPARATOR . $file)); // false
var_dump(is_file($path . DIRECTORY_SEPARATOR . $file)); // true
var_dump(is_link($path . DIRECTORY_SEPARATOR . $file)); // false
```
 [2020-04-17 12:21 UTC] m dot staab at complex-it dot de
a few more tests

var_dump(is_readable($path . DIRECTORY_SEPARATOR . $file)); // true


var_dump(stat($path . DIRECTORY_SEPARATOR . $file));

array(26) {
  [0]=>
  int(2056)
  [1]=>
  int(19679399)
  [2]=>
  int(33204)
  [3]=>
  int(1)
  [4]=>
  int(1000)
  [5]=>
  int(1000)
  [6]=>
  int(0)
  [7]=>
  int(1)
  [8]=>
  int(1587125995)
  [9]=>
  int(1587125995)
  [10]=>
  int(1587125995)
  [11]=>
  int(-1)
  [12]=>
  int(-1)
  ["dev"]=>
  int(2056)
  ["ino"]=>
  int(19679399)
  ["mode"]=>
  int(33204)
  ["nlink"]=>
  int(1)
  ["uid"]=>
  int(1000)
  ["gid"]=>
  int(1000)
  ["rdev"]=>
  int(0)
  ["size"]=>
  int(1)
  ["atime"]=>
  int(1587125995)
  ["mtime"]=>
  int(1587125995)
  ["ctime"]=>
  int(1587125995)
  ["blksize"]=>
  int(-1)
  ["blocks"]=>
  int(-1)
}
 [2020-04-17 12:21 UTC] sjon@php.net
-Summary: file_exists() returns true for a non-existant file +Summary: file_exists() returns true for a non-existing file
 [2020-04-17 12:21 UTC] sjon@php.net
if this is reproducible through the cli, you'd need opcache.enable_cli=1; did you enable that? Did you try without enabling the opcache module?
 [2020-04-17 12:28 UTC] m dot staab at complex-it dot de
you are right... opcache is disabled..

$ php -m
[PHP Modules]
bcmath
calendar
Core
ctype
date
dom
filter
hash
iconv
json
libxml
mysqlnd
pcre
PDO
Phar
readline
Reflection
session
SimpleXML
SPL
standard
tokenizer
xml
xmlreader
xmlwriter
zip
zlib

[Zend Modules]




$ php -v
PHP 7.4.5 (cli) (built: Apr 14 2020 16:17:19) ( NTS Visual C++ 2017 x64 )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
 [2020-04-17 17:30 UTC] cmb@php.net
-Assigned To: +Assigned To: cmb
 [2020-04-17 17:30 UTC] cmb@php.net
>   ["uid"]=>
>  int(1000)
>  ["gid"]=>
>  int(1000)

Huh, that's interesting.  These values are supposed to be zero on
Windows.  Is there any reparse point (symlink, mounted volume, VFS
for Git etc.) involved in the path?
 [2020-04-18 06:14 UTC] m dot staab at complex-it dot de
Not that I am aware of. 

C:\Users\mstaab\Documents\GitHub\kunzmann is a regular git checkout of a private github repo

I am wondering why its not reproducible with a 3 line script from within the same path
 [2020-04-18 06:47 UTC] m dot staab at complex-it dot de
Btw: I tried adding clearstatcache(true); right before the file_exists(), but this did not change the result
 [2020-04-18 10:41 UTC] cmb@php.net
Thanks for the further info!

Since this issue is obviously not easily reproducible, and I have
doubts that tracing with Process Monitor[1] will reveal the
underlying issue, I can only speculate.  As of PHP 7.4.0, the
stat() call (either explicitly or implictly) ends up in
php_win32_ioutil_fstat_int()[2], and given that that the uid and
gid members are not zero, it looks like your hitting the
_fstat64() fallback[3].  This code path uses _open_osfhandle(),
but apparently doesn't regard that "The _open_osfhandle call
transfers ownership of the Win32 file handle to the file
descriptor."[4]  If the documentation is correct, this might
indeed cause issues.

Would you have a PHP build environment[5] available, to do some
debugging, or trying out some potential fixes?  Otherwise I could
try to apply fixes, and provide pre-built binaries for testing,
but that would be somewhat tedious.

[1] <https://docs.microsoft.com/en-us/sysinternals/downloads/procmon>
[2] <https://github.com/php/php-src/blob/php-7.4.5/win32/ioutil.c#L860>
[3] <https://github.com/php/php-src/blob/php-7.4.5/win32/ioutil.c#L872-L888>
[4] <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/open-osfhandle?view=vs-2019>
[5] <https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2>
 [2020-04-18 12:49 UTC] m dot staab at complex-it dot de
I dont have a buildenv on my workstation (and actually dont want to setup one).
Anything else I could do to to ahead?

I thought about downloading a 7.2 and/or 7.3 binary to test whether the issue was present before.

I could offer a teamviewer pairing session, so you can have a look at it right on the machine..
 [2020-04-20 08:57 UTC] m dot staab at complex-it dot de
I did some more tests and can reproduce the issue also on php 7.2.30 and also on ubuntu18

not sure where the problem cause could be
 [2020-04-20 09:09 UTC] cmb@php.net
-Status: Assigned +Status: Open -Operating System: windows10 x64 +Operating System: * -PHP Version: 7.4.5 +PHP Version: 7.3 -Assigned To: cmb +Assigned To:
 [2020-04-20 09:09 UTC] cmb@php.net
> I did some more tests and can reproduce the issue also on php
> 7.2.30 and also on ubuntu18

Then strace might be helpful.
 [2020-04-20 09:19 UTC] m dot staab at complex-it dot de
I reproduce the error on ubuntu18 with php 7.2 with the following commands:

$ GITHUB_ACTION=1 php vendor/bin/php-cs-fixer fix -vvv
string(75) "clxKunzmann=>/cluster/www/www/www/kunzmann/app/shared/lib/clxKunzmann.php 1"
string(105) "CmsContext=>/cluster/www/www/www/kunzmann/vendor/plugins/rocket/builtin/models/modulecms/CmsContext.php 1"
string(93) "cms\ViewTypeProvider=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/ViewTypeProvider.php 1"
string(77) "cms\ViewType=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/ViewType.php 1"
string(83) "cms\UrlProvider=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/UrlProvider.php 1"
string(113) "CmsUrlProvider=>/cluster/www/www/www/kunzmann/vendor/plugins/rocket/builtin/models/modulecms/CmsUrlProvider.php 1"
string(119) "CmsModuleMetaData=>/cluster/www/www/www/kunzmann/vendor/plugins/rocket/builtin/models/modulecms/CmsModuleMetaData.php 1"
string(95) "cms\metadata\shop\Usp=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/shop/Usp.php 1"
string(109) "cms\metadata\ModuleMetaTrait=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/ModuleMetaTrait.php 1"
string(128) "CmsInputTypeText=>/cluster/www/www/www/kunzmann/vendor/plugins/rocket/builtin/models/modulecms/inputtypes/CmsInputTypeText.php 1"
string(109) "CmsInputType=>/cluster/www/www/www/kunzmann/vendor/plugins/rocket/builtin/models/modulecms/CmsInputType.php 1"
string(99) "cms\metadata\Navigation=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Navigation.php 1"
string(85) "cms\metadata\Gap=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Gap.php 1"
string(97) "cms\metadata\Accordion=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Accordion.php 1"
string(105) "cms\metadata\Contactperson=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Contactperson.php 1"
string(95) "cms\metadata\Carousel=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Carousel.php 1"
string(111) "cms\metadata\ImageMultiColumn=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/ImageMultiColumn.php 1"
string(101) "cms\metadata\Vehiclelist=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Vehiclelist.php 1"
string(115) "cms\metadata\VehicleQuickSearch=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/VehicleQuickSearch.php 1"
string(101) "cms\metadata\Contactform=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Contactform.php 1"
string(89) "cms\metadata\Brand=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Brand.php 1"
string(93) "cms\metadata\Masonry=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Masonry.php 1"
string(105) "cms\metadata\Testdriveform=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Testdriveform.php 1"
string(101) "cms\metadata\Productlist=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Productlist.php 1"
string(105) "cms\metadata\Productrating=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Productrating.php 1"
string(105) "cms\metadata\Productslider=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Productslider.php 1"
string(91) "cms\metadata\Slider=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Slider.php 1"
string(95) "cms\metadata\Location=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Location.php 1"
string(87) "cms\metadata\Text=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Text.php 1"
string(103) "cms\metadata\TextAndImage=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/TextAndImage.php 1"
string(109) "cms\metadata\TextMultiColumn=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/TextMultiColumn.php 1"
string(93) "cms\metadata\Divider=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Divider.php 1"
string(93) "cms\metadata\Heading=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Heading.php 1"
string(89) "cms\metadata\Video=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Video.php 1"
string(95) "cms\metadata\Benefits=>/cluster/www/www/www/kunzmann/app/shared/lib/cms/metadata/Benefits.php 1"
string(79) "ClxShopPortal=>/cluster/www/www/www/kunzmann/app/shared/lib/ClxShopPortal.php 1"
Loaded config default from "/cluster/www/www/www/kunzmann/.php_cs.dist".
Using cache file ".php_cs.cache".
string(92) "KMAccountController=>/cluster/www/www/www/kunzmann/app/shop/models/KMAccountController.php 1"

Fatal error: require_once(): Failed opening required '/cluster/www/www/www/kunzmann/app/shop/models/KMAccountController.php' (include_path='.:/usr/share/php') in /cluster/www/www/www/kunzma                                                                nn/vendor/plugins/rocket/lib/Rocket.php on line 313
string(82) "CacheMemcached=>/cluster/www/www/www/kunzmann/app/shop/models/CacheMemcached.php 1"

Fatal error: require_once(): Failed opening required '/cluster/www/www/www/kunzmann/app/shop/models/CacheMemcached.php' (include_path='.:/usr/share/php') in /cluster/www/www/www/kunzmann/ve                                                                ndor/plugins/rocket/lib/Rocket.php on line 313


the php code(parts) which trigger the output and the error is
// snip
        /*
         * The include mechanism over all paths
         */
        foreach ([
            self::$model_paths,
            self::$lib_paths,
            self::$helper_paths,
            self::$controller_paths,
            self::$plugin_paths,
        ] as $paths) {
            foreach ($paths as $path => $dummyVal) {
                if (file_exists($path.DIRECTORY_SEPARATOR.$file)) {
                    self::$classMap[$class_name] = $path.DIRECTORY_SEPARATOR.$file;

                    break 2;
                }
            }
        }

        // trigger cache re-newal, but only once per request
        if (!self::$shutdownRegistered) {
            self::$shutdownRegistered = true;
            register_shutdown_function([
                __CLASS__,
                'storeCache',
            ]);
        }

        // require the already validated path
        if (isset(self::$classMap[$class_name])) {
            clearstatcache(true);
            var_dump($class_name . '=>'. self::$classMap[$class_name] . ' '. file_exists(self::$classMap[$class_name]));
            require_once self::$classMap[$class_name];

            return true;
        }
        // Remember that this class does not exist.
        self::$missingClassMap[$class_name] = true;
// snip


the strace of the command `GITHUB_ACTION=1 sudo strace php vendor/bin/php-cs-fixer fix -vvv 2> strace.out` can be found here: https://gist.github.com/clxmstaab/3773f5e76cd1b266b2b7b952ddd177d6
 [2020-04-20 14:10 UTC] m dot staab at complex-it dot de
for whatever reason, I have the feeling php is mixing up

    /cluster/www/www/www/kunzmann/app/shared/controllers/KMAccountController.php

with

    /cluster/www/www/www/kunzmann/app/shop/controllers/KMAccountController.php



/cluster/www/www/www/kunzmann/app/shared/controllers/KMAccountController.php
is a totally valid path and the file exists.
 [2020-04-24 09:50 UTC] m dot staab at complex-it dot de
if relevant: here is the result of the "stat" command when executed via php on ubuntu18lts 

php -v
PHP 7.2.30-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Apr 19 2020 07:50:50) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.30-1+ubuntu18.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies


$ tail /www/www/logs/php_errors.log
[24-Apr-2020 11:37:07 Europe/Berlin] PHP Fatal error:  require(): Failed opening required '/cluster/www/www/www/kunzmann/app/admin/models/Filter.php' (include_path='.:/usr/share/php') in /c                                                                luster/www/www/www/kunzmann/vendor/symfony/class-loader/MapClassLoader.php on line 53
[24-Apr-2020 11:37:11 Europe/Berlin] PHP Fatal error:  require(): Failed opening required '/cluster/www/www/www/kunzmann/app/admin/models/Filter.php' (include_path='.:/usr/share/php') in /c                                                                luster/www/www/www/kunzmann/vendor/symfony/class-loader/MapClassLoader.php on line 53
[24-Apr-2020 11:38:09 Europe/Berlin] PHP Fatal error:  require(): Failed opening required '/cluster/www/www/www/kunzmann/app/shared/controllers/KMAccountController.php' (include_path='.:/us                                                                r/share/php') in /cluster/www/www/www/kunzmann/vendor/symfony/class-loader/MapClassLoader.php on line 53
[24-Apr-2020 11:38:24 Europe/Berlin] PHP Fatal error:  require(): Failed opening required '/cluster/www/www/www/kunzmann/app/shared/controllers/KMAccountController.php' (include_path='.:/us                                                                r/share/php') in /cluster/www/www/www/kunzmann/vendor/symfony/class-loader/MapClassLoader.php on line 53
[24-Apr-2020 11:39:44 Europe/Berlin] PHP Fatal error:  require(): Failed opening required '/cluster/www/www/www/kunzmann/app/shared/controllers/KMAccountController.php' (include_path='.:/us                                                                r/share/php') in /cluster/www/www/www/kunzmann/vendor/symfony/class-loader/MapClassLoader.php on line 53
[24-Apr-2020 11:40:09 Europe/Berlin] PHP Fatal error:  require(): Failed opening required '/cluster/www/www/www/kunzmann/app/shared/controllers/KMAccountController.php' (include_path='.:/us                                                                r/share/php') in /cluster/www/www/www/kunzmann/vendor/symfony/class-loader/MapClassLoader.php on line 53
[24-Apr-2020 11:40:21 Europe/Berlin] PHP Fatal error:  require(): Failed opening required '/cluster/www/www/www/kunzmann/app/shared/controllers/KMAccountController.php' (include_path='.:/us                                                                r/share/php') in /cluster/www/www/www/kunzmann/vendor/symfony/class-loader/MapClassLoader.php on line 53
mstaab@mst18:/cluster/www/www/www/kunzmann$ ls /cluster/www/www/www/kunzmann/app/shared/controllers/KMAccountController.php
/cluster/www/www/www/kunzmann/app/shared/controllers/KMAccountController.php

php -r 'var_dump(stat("/cluster/www/www/www/kunzmann/app/shared/controllers/KMAccountController.php"));'
array(26) {
  [0]=>
  int(51)
  [1]=>
  int(4474739)
  [2]=>
  int(33279)
  [3]=>
  int(1)
  [4]=>
  int(11226)
  [5]=>
  int(12128)
  [6]=>
  int(0)
  [7]=>
  int(23570)
  [8]=>
  int(1587720923)
  [9]=>
  int(1585235276)
  [10]=>
  int(1587720894)
  [11]=>
  int(262144)
  [12]=>
  int(48)
  ["dev"]=>
  int(51)
  ["ino"]=>
  int(4474739)
  ["mode"]=>
  int(33279)
  ["nlink"]=>
  int(1)
  ["uid"]=>
  int(11226)
  ["gid"]=>
  int(12128)
  ["rdev"]=>
  int(0)
  ["size"]=>
  int(23570)
  ["atime"]=>
  int(1587720923)
  ["mtime"]=>
  int(1585235276)
  ["ctime"]=>
  int(1587720894)
  ["blksize"]=>
  int(262144)
  ["blocks"]=>
  int(48)
}
 [2020-06-17 18:13 UTC] alexdowad@php.net
Thanks for reporting this issue.

I think the best thing you can do is:

1. Make a copy of the entire application source code in another directory.
2. Delete some code which is not related to the problem.
3. Test to see if the modified application still exhibits the problem.
4A. If so, go to step 2.
4B. If not, revert the change. Go to step 2 but try deleting something different this time.

After you repeat that loop maybe 100 times, you will have a small test file which exhibits the problem. Almost all the proprietary code from your application will be gone, so it won't hurt anything to share that test file, and then the PHP developers can find and fix the problem.

I have done this myself multiple times. It doesn't take as long as it sounds.
 [2021-11-11 13:10 UTC] dr dot joe dot b at gmail dot com
I have this same problem with php 7.4.13 and apache2

The target file was created and deleted from the file system, but file_exists() and stat() still refer to the old file.

The problem appears to be with clearstatcache() - it does not clear the cache, neither does restarting apache & php
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun Oct 06 19:01:27 2024 UTC