diff --git a/lib/filestorage/file_system_filedir.php b/lib/filestorage/file_system_filedir.php index 19418514937..75338755316 100644 --- a/lib/filestorage/file_system_filedir.php +++ b/lib/filestorage/file_system_filedir.php @@ -352,8 +352,9 @@ class file_system_filedir extends file_system { $newfile = true; - if (file_exists($hashfile)) { - if (filesize($hashfile) === $filesize) { + $hashsize = self::check_file_exists_and_get_size($hashfile); + if ($hashsize !== null) { + if ($hashsize === $filesize) { return array($contenthash, $filesize, false); } if (file_storage::hash_from_path($hashfile) === $contenthash) { @@ -411,6 +412,42 @@ class file_system_filedir extends file_system { return array($contenthash, $filesize, $newfile); } + /** + * Checks if the file exists and gets its size. This function avoids a specific issue with + * networked file systems if they incorrectly report the file exists, but then decide it doesn't + * as soon as you try to get the file size. + * + * @param string $hashfile File to check + * @return int|null Null if the file does not exist, or the result of filesize(), or -1 if error + */ + protected static function check_file_exists_and_get_size(string $hashfile): ?int { + if (!file_exists($hashfile)) { + // The file does not exist, return null. + return null; + } + + // In some networked file systems, it's possible that file_exists will return true when + // the file doesn't exist (due to caching), but filesize will then return false because + // it doesn't exist. + $hashsize = @filesize($hashfile); + if ($hashsize !== false) { + // We successfully got a file size. Return it. + return $hashsize; + } + + // If we can't get the filesize, let's check existence again to see if we really + // for sure think it exists. + clearstatcache(); + if (!file_exists($hashfile)) { + // The file doesn't exist any more, so return null. + return null; + } + + // It still thinks the file exists, but filesize failed, so we had better return an invalid + // value for filesize. + return -1; + } + /** * Add a file with the supplied content to the file system. * @@ -433,8 +470,9 @@ class file_system_filedir extends file_system { $newfile = true; - if (file_exists($hashfile)) { - if (filesize($hashfile) === $filesize) { + $hashsize = self::check_file_exists_and_get_size($hashfile); + if ($hashsize !== null) { + if ($hashsize === $filesize) { return array($contenthash, $filesize, false); } if (file_storage::hash_from_path($hashfile) === $contenthash) {