search loading ...

_access

GetFileAttributes lies!

  • GetFileAttribuites, PathFileExists, CFile::GetStatus, and SHGetFileInfo may sometimes return inaccurate values.
  • These functions may intermittently indicate that the file or directory does not exist, when it actually does exist, but never the other way around.
  • I have not been able to replicate this issue with FindFirstFile, but this does not guarantee its reliability since the issue is intermittent.
  • I have not tested with _access or _stat.
  • The cause is unknown.
  • When these functions return an inaccurate value, they do not trigger an exception, nor return a value indicating an error condition. Calling GetLastError will return 0. There is nothing to indicate that the return value is wrong.

On desktop Windows:

  • Calling the function a second time after Sleep( 1 ) seems to return an accurate value.
  • Maybe an initialization bug?
  • If I had to guess, I would say its probably caused by lazy NTFS journaling, or the Windows search indexer has a temporarily lock on the file.
  • Some users on MSDN forums are claiming its caused by a conflict with Kaspersky anti-virus, however I can intermittently replicate the issue without Kaspersky anti-virus.
  • Some commenters on The Old New Thing: Superstition: Why is GetFileAttributes the way old-timers test file existence? are claiming that you have to open the file before calling access/GetFileAttributes, and also that GetFileAttributes lies. Definitely worth reading in its entirety, including all the user replies.
  • If this is true, that you must open the file first, then this might explain why FindFirstFile seems to work properly, because FindFirstFile internally uses NtOpenFile and NtQueryDirectoryFile.

On Windows CE:

  • There seems to be a performance issue in Microsoft's (or the OEM's) storage card driver, specifically for FAT32 formatted SD cards.
  • If you are attempting to check if a file or directory exists on a removable storage card you may be calling your API function before Windows has finished mounting the card.
  • An application launched automatically from the startup folder, or manually, may be launched before Windows has finished mounting all the removable storage cards.
  • I have personally experienced delays up to 1312ms between the time my application launches automatically from the startup folder after a cold boot, and when CeGetVolumeInfo reports the FAT32 SD card exists.
  • This is not just a startup issue either, if you are attempting to check if a file or directory exists on a removable storage card and a user just inserted the card, you may have to wait even longer before Windows CE finishes mounting the card.
  • It is possible to enable device add/remove event notifications, however receiving a new device event notification does not mean the partition on a removable SD card has been mounted yet, especially if its FAT32.
  • There are numerous device event notifications when a file system has been mounted, but these API functions are not included in Windows CE.
  • My suggestion is to override OnDeviceChange in your top level window. If OnDeviceChange is called with nEventType = 0x8000, this indicates device arrive condition, and your function to check if file/directory exists should loop until found or until at least 2000ms have elapsed.

CFile::GetStatus Example:

  • Detailed example useful for debugging.
  • If you are using Windows CE, you should ensure that you are waiting at least 2000ms after receiving a "Device Arrive" notification (WM_DEVICECHANGE with nEventType = 0x8000) .
bool Found = false;  
int Trys = 2;  
while( Trys-- > 0 )  
{
  BOOL Result = CFile::GetStatus( (LPCTSTR)Path, Status );
  if( Result )
  {
    Found = true;
    break; // BREAK: file exists
  }
  Sleep( 1 );
};

GetFileAttributes Example:

  • Detailed example useful for debugging.
  • If you are using Windows CE, you should ensure that you are waiting at least 2000ms after receiving a "Device Arrive" notification (WM_DEVICECHANGE with nEventType = 0x8000).
bool Found = false;  
int Trys = 2;  
while( Trys-- > 0 )  
{
  DWORD Result = GetFileAttributes( (LPCTSTR)Path );
  if( Result != INVALID_FILE_ATTRIBUTES )
  {
    Found = true;
    break; // BREAK: file exists
  }
  Sleep( 1 );
};

REF:

The Old New Thing: Superstition: Why is GetFileAttributes the way old-timers test file existence?

Best way to check if file or directory exists

  • A very common question is how do I check if a file or directory exists.
  • However, there is already a problem. What exactly do you mean by "does file X exist?"
  • Do you want to check if the file exists? do you want to check if the file exists *AND* the current user has permissions to "access" the file? do you care if the file is already open in exclusive mode by another process? what if the current user is denied the "list contents" permission for the parent folder? what if the current user does not have the necessary permissions to read/write/create/update the file?
  • There is no way to check "does file X really exist" because checking if a file exists will always require some sort of user permission, either to the parent folder, or the file object itself.
  • If the current user doesn't have the necessary permissions required, then whatever function you use will return the same value/result as if the file does not exist.
  • However, if the function you use indicates FALSE, you can call GetLastError to determine if the current user has the necessary permissions for that function.
  • If the current user does not have the necessary permissions, there is no way to guarantee if the file exists or not.
  • The question should be asked this way: "does file X exist, and if not, is the reason due to permissions?"
  • There are a few functions which can be used to detect if a file or directory exists. Some are more useful than others.
  • The most noteworthy functions are: PathFileExists, GetFileAttributes, CFile::GetStatus,  FindFirstFile, _access, _stat, and SHGetFileInfo.
  • GetFileAttributes and FindFirstFile are the real functions, all the other functions mentioned above are simply wrapper functions around GetFileAttributes or FindFirstFile.
  • This does not mean that using GetFileAttributes or FindFirstFile is better than the others. Each function serves a specific intended purpose. Some functions retrieve more information about the file than others.
  • Some of these functions do not work with drives.
  • Some do not work with root directories in Windows CE, and some are not even included in Windows CE. By "root directories" I am referring to root directories of mounted directories in Windows CE, such as "FlashDisk" or "SD Card". Windows CE does not have "drives".
  • If you're looking for "the MFC way" to check for file exists, there really isn't one. I do not recommend using CFile or CFileFind just because they are classes. Remember, each of these functions has specific intended purpose. If you just want to check if a file or directory exists, and you do not need other information, then in my opinion you should be using PathFileExists or GetFileAttributes.
  • I recommend PathFileExists. If you're working with files/filenames/paths, etc, there is a good chance that you would also benefit from using the other path handling functions in Shlwapi.lib.

Required user permissions:

  • In order to detect if a file exists, the current user must have the user permissions (specifically: List Contents) to access the parent folder, and depending on the method used, the current user may also need to have access to the file object itself. Some functions do not require access to the file object itself.
  • Functions based on GetFileAttributes do *NOT* require access to the file object itself.
  • Functions based on FindFirstFile do require access to the file object itself.
  • If the current user does not have user permissions (specifically: List Contents) to access the parent folder, then the any method used will indicate FALSE even if the file really does exist, because the current user account doesn't have the necessary user permissions to detect if the file exists.

Avoid opening the file for read:

  • If you are currently opening the file for read to determine if the file exists, then you are going about things the wrong way.
  • If all you need to do is determine if the file can be opened by the current user for read access, then you do not need to check if the file exists, just open the file for read access. However, a lot of programmers improperly use this technique to determine if the file exists, then branch depending on the result.
  • Opening a file for read is not the correct way to determine if a file exists.
  • Opening a file for read indicates that the file exists *AND* that the current user has the necessary permissions to open the file for read access *AND* that the file is not already open in exclusive mode. If this method fails, the file could still exist. This method can fail for more reasons than any of the other functions listed in this article, it is therefore the least reliable method to detect if a file exists.
  • This is a very unreliable method compared to any of the functions mentioned in this article.
  • For example:
  • What if the file does exist, but the current user's permissions is denied read access to the file?
  • What if the file does exist, but the file is already opened in exclusive mode by another process?
  • In either of the above examples, your attempt to open the file for read access will fail.
  • If you do not check GetLastError, it will appear that the file does not exist, but it actually does exist.
  • Even if you do check GetLastError, it will report 5 (ERROR_ACCESS_DENIED), but you will be clueless to which user permission issue is causing the problem.
  • If opening a file for read fails, it is impossible to determine if the problem is caused by user permissions or if the problem is caused by the file already being opened in exclusive mode, or if the file really does not exist.

Ask yourself: what exactly am I trying to do?

  • Why are you even bothering to check if a file exists in the first place?
  • It is my experience, by reviewing other programmer code, that a lot of programmers are under the wrong assumption that you must check if a file exists before opening it.
  • If you need to open a file, then open the file.
  • If you need to check if the file exists, then check if it exists.
  • You do *NOT* need to check if a file exists before opening it.
  • You do *NOT* need to check if a file exists before attempting to create it.
  • You do *NOT* need to check if a file exists before attempting to create it only if it doesn't' already exist.
  • While we're on the subject, you also do *NOT* need to check if a file exists before attempting to delete it, just call DeleteFile and check the return value and call GetLastError if you need more details.
  • If you want to create a file only if it doesn't exist:
  • use CreateFile with dwCreateDisposition = CREATE_NEW. This is the correct way to create a file if not does not already exist. Do not check if the file exists first, just create the file. You can call GetLastError afterwards to determine if the file already existed.
  • -or-
  • If you want to open a file it if already exists, or create it if it doesn't already exist:
  • use CreateFile with dwCreateDisposition = OPEN_ALWAYS. Same as above. This is the correct way to open an existing file, or, create it if it does not already exist. Do not check if the file exists first, just use CreateFile. You can call GetLastError afterwards to determine if the file already existed.
  • If you need to know afterwards if the file existed or not at the time you attempted to open and/or create it:
  • call GetLastError to check for ERROR_ALREADY_EXISTS. Using GetLastError in this scenario is the correct way to determine if the file already existed before you created it. It may seem backwards, but this is the correct way. Checking to see if the file exists first, and if not then creating the file is the wrong way.
  • Using CreateFile with the correct create disposition is more reliable than checking if the file exists first then creating or opening the file.

PathFileExists (recommended):

BOOL PathFileExists( _In_ LPCTSTR pszPath );
  • I think its the easiest to use because it returns a BOOL and only has 1 function arg: the path.
  • If the function returns FALSE, you should then call GetLastError to determine if the failure was indeed caused by ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND, otherwise it means PathFileExists failed for some other reason (such as the current user does not have permission to access the parent folder), and the file could still exist.
  • It is available for both desktop Windows and Windows CE.
  • It works with files, directories, and drives, and root directories in Windows CE.
  • It does not require access to the file object itself, only the parent folder.
  • Include Shlwapi.h and link Shlwapi.lib.
  • Other functions in Shlwapi are very useful (except PathMakePretty).
  • If you do use other functions in Shlwapi, I highly recommend studying the documentation. Some of those functions aren't designed as I would have expected (example PathUnquoteSpaces).
  • FYI: PathFileExists internally uses GetFileAttributes.
  • Please read about known issues.
  • Sample code: something like this can be used to determine if the file exists, and if not is the reason due to permissions. It is then up to you to decide what to do if the flag CanAccess is false (ignore it and keep going, prompt the user, etc). Note the flag CanAccess will be true if the file really exists, true if the file really does not exist, false if the file appears to not exists due to permissions, or false for any other reason such as an invalid filename.
bool DoesFileExist( LPCTSTR FileName, bool &CanAccess )  
{
  if( PathFileExists( FileName ) )
  {
    CanAccess = true; // we have necessary permissions
    return true; // file exists
  }
  DWORD Error = GetLastError();
  if( Error == ERROR_FILE_NOT_FOUND || Error == ERROR_PATH_NOT_FOUND )
  {
    CanAccess = true; // we have necessary permissions
    return false; // file does not exist
  }
  CanAccess = false; // we do not have necessary permissions
  return false; // file may or may not exist
}

GetFileAttributes:

DWORD WINAPI GetFileAttributes( _In_ LPCTSTR lpFileName );
  • Return type must be compared against INVALID_FILE_ATTRIBUTES instead of TRUE/FALSE.
  • If the function returns INVALID_FILE_ATTRIBUTES, you should then call GetLastError to determine if the failure was indeed caused by ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND, otherwise it means GetFileAttributes failed for some other reason (such as the current user does not have permission to access the parent folder), and the file could still exist.
  • It is available for both desktop Windows and Windows CE.
  • It works with files, directories, drives, and root directories in Windows CE.
  • It does not require access to the file object itself, only the parent folder.
  • FYI: GetFileAttributes internally uses NtQueryAttributesFile.
  • Please read about known issues.

CFile::GetStatus:

[sourcecode language="cpp"] BOOL GetStatus( CFileStatus& rStatus ) const;
static BOOL PASCAL GetStatus( LPCTSTR lpszFileName, CFileStatus& rStatus );
[/sourcecode]

  • Requires a CFileStatus variable.
  • If the function returns FALSE, you should then call GetLastError to determine if the failure was indeed caused by ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND, otherwise it means CFile::GetStatus failed for some other reason (such as the current user does not have permission to access the parent folder), and the file could still exist.
  • It is available for both desktop Windows and Windows CE.
  • It works with files, directories, drives, and root directories in Windows CE.
  • It does not require access to the file object itself, only the parent folder.
  • Some users mention it doesn't work with drives, yes it does!
  • FYI: CFile::GetStatus internally seems to use combination of GetVolumeInformation and GetFileAttributes, and it also makes some API calls to get the file access dates for the current timezone.
  • Please read about known issues.

_access:

int _access( const char *path, int mode );
int _waccess( const wchar_t *path, int mode );
  • It is not included in Windows CE.
  • If the function returns -1, you should then check _errno to determine if the failure was indeed caused by ENOENT, otherwise it means _access failed for some other reason (such as the current user does not have permission to access the parent folder), and the file could still exist.
  • Does *NOT* work with drives.
  • It does not require access to the file object itself, only the parent folder.
  • FYI: _access internally uses GetFileAttributes.
  • Please read about known issues.

_stat:

int _stat( const char *path, struct _stat *buffer );
int _stat32( const char *path, struct __stat32 *buffer );
int _stat64( const char *path, struct __stat64 *buffer );
int _stati64( const char *path, struct _stati64 *buffer );
int _stat32i64( str const char *path, struct _stat32i64 *buffer );
int _stat64i32( str const char *path, struct _stat64i32 *buffer );
int _wstat( const wchar_t *path, struct _stat *buffer );
int _wstat32( const wchar_t *path, struct __stat32 *buffer );
int _wstat64( const wchar_t *path, struct __stat64 *buffer );
int _wstati64( const wchar_t *path, struct _stati64 *buffer );
int _wstat32i64( const wchar_t *path, struct _stat32i64 *buffer );
int _wstat64i32( const wchar_t *path, struct _stat64i32 *buffer );
  • It is not included in Windows CE.
  • If the function returns -1, you should then check _errno to determine if the failure was indeed caused by ENOENT, otherwise it means _stat failed for some other reason (such as the current user does not have permission to access the parent folder), and the file could still exist.
  • Does *NOT* work with drives.
  • It requires access to the file object itself, and the parent folder.
  • FYI: _stat internally uses FindFirstFile, and it also makes some API calls to get the file access dates for the current timezone.
  • Please read about known issues.

FindFirstFile:

HANDLE WINAPI FindFirstFile( _In_ LPCTSTR lpFileName, _Out_ LPWIN32_FIND_DATA lpFindFileData );
  • You can use FindFirstFile to check if a file exists, however, this is not its intended purpose.
  • Its intended purpose is to iterate through a list of files and/or directories based on a supplied filespec which may include wildcards, by using FindFirstFile and FindNextFile.
  • Return type must be compared against INVALID_HANDLE_VALUE.
  • If the function returns INVALID_HANDLE_VALUE, you should then call GetLastError to determine if the failure was indeed caused by ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND, otherwise it means FindFirstFile failed for some other reason (such as the current user does not have permission to access the parent folder), and the file could still exist.
  • It is available for both desktop Windows and Windows CE.
  • It works with files, directories, and root directories in Windows CE.
  • It does *NOT* work with drives.
  • It requires access to the file object itself, and the parent folder.
  • FYI: FindFirstFile internally uses NtOpenFile, NtQueryDirectoryFile.
  • Please read about known issues.

CFileFind::FindFile, FindNextFile:

  • Same as FindFirstFile for the purposes of this article.
  • Please read about known issues.

SHGetFileInfo (not recommended):

DWORD_PTR SHGetFileInfo( _In_ LPCTSTR pszPath, DWORD dwFileAttributes, _Inout_ SHFILEINFO *psfi, UINT cbFileInfo, UINT uFlags );
  • 5 function args makes this too complicated for simple "does file exist" check.
  • If the function indicates FALSE, you should then call GetLastError to determine if the failure was indeed caused by ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND, otherwise it means SHGetFileInfo failed for some other reason (such as the current user does not have permission to access the parent folder), and the file could still exist.
  • It is available for both desktop Windows and Windows CE.
  • It works with files, directories, and drives, and root directories in Windows CE.
  • Include Shellapi.h.
  • FYI: SHGetFileInfo is so complicated that I can't even begin to describe its internals. The call stack for SHGetFileInfo (the entire call stack hierarchy) is over 1 thousand calls (1038 to be exact). Its disgusting. Avoid at all costs.
  • Please read about known issues.

REF:

PathFileExists function

GetFileAttributes function

CFile::GetStatus

_access, _waccess

_stat, _wstat Functions

FindFirstFile function

SHGetFileInfo function