Recursively Deleting a directory–with long filename support.

I recently was updating some test code to handle long filename (longer than MAX_PATH) support.

My initial cut at the function was something like the following (don’t worry about the VERIFY_ macros, they’re functionally equivalent to asserts):

 const PCWSTR LongPathPrefix=L"\\\\?\\";

void RecursivelyDeleteDirectory(const std::wstring &strDirectory)
{
    //  Canonicalize the input path to guarantee it's a full path.
    std::wstring longDirectory(GetFullPath(strDirectory));

    //  If the path doesn't have the long path prefix, add it now before we instantiate the
    //  directory_list class.
    std::wstring strPath;
    if (longDirectory.find(LongPathPrefix) == std::wstring::npos)
    {
        strPath = LongPathPrefix;
    }
    strPath += longDirectory;
    strPath += L"\\*";

    directory_list dl(strPath);
    for (const auto && it : dl)
    {
        std::wstring str(longDirectory+L"\\"+it);

        //  It’s possible that the addition of the local filename might push the full path over MAX_PATH so ensure that the filename has the LongPathPrefix.
        if (str.find(LongPathPrefix) == std::wstring::npos)
        {
            str = LongPathPrefix+str;
        }

        DWORD dwAttributes = GetFileAttributes(str.c_str());
        VERIFY_ARE_NOT_EQUAL(dwAttributes, INVALID_FILE_ATTRIBUTES); // Check for error.
        if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            if (it != L"." && it != L"..")
            {
                RecursivelyDeleteDirectory(str);
            }
            else
            {
                VERIFY_WIN32_BOOL_SUCCEEDED(DeleteFile(str.c_str()));
            }
        }
    }
    VERIFY_WIN32_BOOL_SUCCEEDED(RemoveDirectory(longDirectory.c_str()));
}

The weird thing was that this code worked perfectly on files shorter than MAX_PATH. But the call to GetFileAttributes failed 100% of the time as soon as the directory name got longer than MAX_PATH. It wasn’t that the GetFileAttributes API didn’t understand long filenames – it’s documented as working correctly with long filenames.

So what was going on?

I wrote a tiny little program that just had the call to GetFileAttributes and tried it on a bunch of input filenames.

Running the little program showed me that \\?\C:\Directory\FIlename worked perfectly. But [\\?\C:\Directory\](file://\\?\C:\Directory\). (note the trailing “.”) failed every time.

It took a few minutes but I finally remembered something I learned MANY decades ago: On an NTFS filesystem, the “.” and “..” directories don’t actually exist. Instead they’re pseudo directories inserted into the results of the FindFirstFile/FindNextFile API.

Normally the fact that these pseudo directories don’t exist isn’t a problem, since the OS canonicalizes the filename and strips off the “.” and “..” paths before it passes it onto the underlying API.

But if you use the long filename prefix (\\?\ the OS assumes that all filenames are canonical. And on an NTFS filesystem, there is no directory named “.”, so the API call fails!

What was the fix? Simply reverse the check for “.” and “..” and put it outside the call to GetFileAttributes. That way we never ask the filesystem for these invalid directory names.