The correct way to determine if a file is a directory.
After writing for Microsoft’s Windows platform for 20 years I thought I knew all I could know about GetFileAttributes(). Until I found a rather odd and subtle bug in some code that interacted with data supplied by the user of the software. A call would succeed that I expected to fail. Naturally this meant the software didn’t make the right choices and instead of being presented with an helpful dialog explaining what had failed, the software sat silently in a corner humming to itself waiting for the user to work out what had happened. The failure was that I was presenting incorrect data to GetFileAttributes() assuming that it would always fail for bad input. How wrong I was!
I thought I’d write up what can go wrong with GetFileAttributes().
It’s tempting to test if a file is a directory by writing code like this:
if ((GetFileAttributes(fileName) & FILE_ATTRIBUTE_DIRECTORY) != 0) { // file is a directory }
The above looks logically correct. But there are problems with it.
First, a refresher on file attribute values…
File Attributes
The list of defined file attributes is in WinNT.h. The values are shown below.
#define FILE_ATTRIBUTE_READONLY 0x00000001 #define FILE_ATTRIBUTE_HIDDEN 0x00000002 #define FILE_ATTRIBUTE_SYSTEM 0x00000004 #define FILE_ATTRIBUTE_DIRECTORY 0x00000010 #define FILE_ATTRIBUTE_ARCHIVE 0x00000020 #define FILE_ATTRIBUTE_DEVICE 0x00000040 #define FILE_ATTRIBUTE_NORMAL 0x00000080 #define FILE_ATTRIBUTE_TEMPORARY 0x00000100 #define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200 #define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400 #define FILE_ATTRIBUTE_COMPRESSED 0x00000800 #define FILE_ATTRIBUTE_OFFLINE 0x00001000 #define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 #define FILE_ATTRIBUTE_ENCRYPTED 0x00004000 #define FILE_ATTRIBUTE_VIRTUAL 0x00010000
Rather strangely, the invalid attributes flag is defined in a different file, WinBase.h.
#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
Problem 1
What if GetFileAttributes() fails? If the file doesn’t exist, the call fails. If the filename specifies a computer name, the call fails. See GetFileAttributes() documentation for more informtion. When GetFileAttributes() fails it returns INVALID_FILE_ATTRIBUTES. This error status passes the above test. OK, so add an additional check and the code becomes
DWORD attribs; attribs = GetFileAttributes(fileName); if ((attribs != INVALID_FILE_ATTRIBUTES) && ((attribs & FILE_ATTRIBUTE_DIRECTORY) != 0)) { // file is a directory }
Problem 2
Even with the above file-does-not-exist problem solved there is another problem. The file could be a directory, but it could be a directory that you don’t want. For example what if you’ve allowed the user to specify the directory name and they typed _T(“/”), or what if your filename creation code has a bug in it that fails when passed an empty name, resulting in a calculated filename of _T(“\”). What then?
In these cases the following calls all return 0x16.
GetFileAttributes(_T("\\")); GetFileAttributes(_T("/"));
0x16 means hidden (0x02), system (0x04), directory (0x10).
It’s a reasonable bet that in your code, any code looking for a directory to use is probably not looking for a hidden directory and almost certainly not intending to use a system directory. OK, time for a new implementation.
DWORD attribs; attribs = GetFileAttributes(fileName); if ((attribs != INVALID_FILE_ATTRIBUTES) && // check if a valid file ((attribs & FILE_ATTRIBUTE_DIRECTORY) != 0) && // file is a directory ((attribs & FILE_ATTRIBUTE_HIDDEN) == 0) && // file is not hidden ((attribs & FILE_ATTRIBUTE_SYSTEM) == 0)) // file is not system { // file is a directory that isn't hidden and isn't system }
What about files, rather than directories?
It’s natural to think about implementing checks for if a filename identifies a file rather than a directory. You test for this in exactly the same way but looking for different attributes. You’ll want to exclude FILE_ATTRIBUTE_DIRECTORY and then depending on the job your code is doing you’ll want to consider excluding files depending upon the following attributes:
FILE_ATTRIBUTE_DEVICE
FILE_ATTRIBUTE_INTEGRITY_STREAM
FILE_ATTRIBUTE_OFFLINE
FILE_ATTRIBUTE_REPARSE_POINT
FILE_ATTRIBUTE_SPARSE_FILE
FILE_ATTRIBUTE_TEMPORARY
FILE_ATTRIBUTE_VIRTUAL
and of course, you might also want to consider FILE_ATTRIBUTE_HIDDEN and FILE_ATTRIBUTE_SYSTEM.
Additional reading
Microsoft documentation on GetFileAttributes().
Why is GetFileAttributes the way old-timers test file existence? Old New Thing.