The correct way to determine if a file is a directory.

By Stephen Kellett
30 November, 2016

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: GetFileAttributes() failure

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: Single Slash handling

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. Since it’s starting to get a little involved, it makes sense to put this in it’s own function to prevent code duplication.

Is This A Directory?

Set the default values (in the header file definition) for allowHidden and allowSystem to TRUE to get the same behaviour as if you were only detecting for FILE_ATTRIBUTE_DIRECTORY.

int isThisADirectory(const DWORD attribs,
                     const int   allowHidden,
                     const int   allowSystem)
{
    int ok;

    ok = (attribs != INVALID_FILE_ATTRIBUTES);
    if (ok)
    {
        // check must be a directory

        if ((attribs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
        {
            // a directory
            // check system and hidden status

            if ((attribs & FILE_ATTRIBUTE_SYSTEM) == FILE_ATTRIBUTE_SYSTEM)
            {
                if (!allowSystem)
                    ok = FALSE;
            }

            if ((attribs & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN)
            {
                if (!allowHidden)
                    ok = FALSE;
            }
        }
        else
        {
            // not a directory

            ok = FALSE;
        }
    }

    return ok;
}

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.

Is This A File?

The implementation for determining if something is a file rather than a directory is a bit more complex. More flags have to be checked. In this example we check the flags one at a time, but an optimized version could group some of the flags together.

Set the default values (in the header file definition) for allowHidden and allowSystem to TRUE to get the same behaviour as if you were only detecting for not FILE_ATTRIBUTE_DIRECTORY.

int isThisAFile(const DWORD attribs,
                const int   allowHidden,
                const int   allowSystem)
{
    int ok;

    ok = (attribs != INVALID_FILE_ATTRIBUTES);
    if (ok)
    {
        // check that we're not a directory or any of these other things 
        // (check it's not a directory, not that it's a FILE_ATTRIBUTE_NORMAL as this will fail if it has the "ready for archiving" tag)

        if ((attribs & FILE_ATTRIBUTE_DEVICE) == FILE_ATTRIBUTE_DEVICE)
            ok = FALSE;

        if ((attribs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
            ok = FALSE;

        if ((attribs & FILE_ATTRIBUTE_INTEGRITY_STREAM) == FILE_ATTRIBUTE_INTEGRITY_STREAM)
            ok = FALSE;

        if ((attribs & FILE_ATTRIBUTE_OFFLINE) == FILE_ATTRIBUTE_OFFLINE)
            ok = FALSE;

        if ((attribs & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT)
            ok = FALSE;

        // some applications built with Cygwin have this attribute set, so ignore this, it's not a sign of an error
        //if ((attribs & FILE_ATTRIBUTE_SPARSE_FILE) == FILE_ATTRIBUTE_SPARSE_FILE)
        //    ok = FALSE;

        if ((attribs & FILE_ATTRIBUTE_SYSTEM) == FILE_ATTRIBUTE_SYSTEM)
        {
            if (!allowSystem)
                ok = FALSE;
        }

        if ((attribs & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN)
        {
            if (!allowHidden)
                ok = FALSE;
        }

        if ((attribs & FILE_ATTRIBUTE_TEMPORARY) == FILE_ATTRIBUTE_TEMPORARY)
            ok = FALSE;

        if ((attribs & FILE_ATTRIBUTE_VIRTUAL) == FILE_ATTRIBUTE_VIRTUAL)
            ok = FALSE;
    }

    return ok;
}

Does this file exist?

A trivial test for if a file or directory exists is to test the return value of GetFileAttributes() for any value other than INVALID_FILE_ATTRIBUTES.

int doesThisFileExist(const TCHAR	*fileName)
{
    DWORD attribs;

    attribs = GetFileAttributes(fileName);
    return (attribs != INVALID_FILE_ATTRIBUTES);
}

It makes sense to put this in your code rather than having calls to GetFileAttributes() all through your code base. It will make your code more readable.

if (doesThisFileExist(fileName))
{
    workOnFileThatExists();
}
if (!doesThisFileExist(fileName))
{
    workOnFileThatDoesNotExist();
}

Additional reading

Microsoft documentation on GetFileAttributes().

Why is GetFileAttributes the way old-timers test file existence? Old New Thing.

Fully functional, free for 30 days