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 a 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 information. 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("/"));
If we break down 0x16 to its constituent file attributes, we get:
FILE_ATTRIBUTE_HIDDEN | 0x00000002 |
FILE_ATTRIBUTE_SYSTEM | 0x00000004 |
FILE_ATTRIBUTE_DIRECTORY | 0x00000010 |
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 on 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.