12

The MSDN document Naming Files, Paths, and Namespaces talks about the \\?\ prefix. To quote:

For file I/O, the "\?\" prefix to a path string tells the Windows APIs to disable all string parsing and to send the string that follows it straight to the file system.

Experimentation showed me that the \??\ prefix has the same effect, both disabling path parsing (.. handling) and enabling paths longer than MAX_PATH.

The MSDN refers to \\? as the "Win32 file namespace", so does it known purely by the Win32 usermode API and translated to \?? in the NT namespace? And anyway, through Winobj I see GLOBAL?? in the NT namespace, not ??.

Ilya
  • 5,533
  • 2
  • 29
  • 57
  • 3
    You have a confusing mix of actual paths and C-style string literals with escaped backslashes. There is no ``\\\\.\\`` prefix. `"\\\\.\\"` is how you write the ``\\.\`` prefix in a C string literal. The ``\\.\`` prefix is described in that same MSDN page, in the section "Win32 Device Namespaces" – Ben Voigt Aug 01 '14 at 23:56
  • possible duplicate of [GUID prefixes \??\ and \\?∖](http://stackoverflow.com/questions/23041983/guid-prefixes-and) – Harry Johnston Aug 02 '14 at 00:13
  • I removed the \\.\ question altogether. Focusing my question. – Ilya Aug 02 '14 at 01:34
  • @HarryJohnston The answer there quite possibly answers my question, thanks – Ilya Aug 02 '14 at 01:54
  • @HarryJohnston: The question has a bit different scope: This is about file names and the other question is about GUIDs. However the answer in the other question should apply to file names, too. – Martin Rosenau Aug 02 '14 at 06:03
  • @MartinRosenau: the GUIDs in the other post are just part of the path name, so they aren't really fundamental to the question. – Harry Johnston Aug 02 '14 at 06:49

1 Answers1

16

The answer to your question is, yes there is a difference between passing \\?\ and \??\ to user mode functions.

Internally, NT always represents paths with the \??\ prefix. Normally, when you call a user mode function (e.g. CreateDirectoryW) with a normal path like C:\foo, the user mode functions call an internal function named RtlDosPathNameToNtPathName_U which converts this to an NT-style path prefixed with \??\. This conversion is done with a fixed size static buffer, which is where the famous MAX_PATH limitation comes from.

When you call a user mode function specifying the \\?\ prefix (note, only one ?), RtlDosPathNameToNtPathName_U is not called. Instead, the second back-slash is turned into a ? character and the path is used verbatim. This is what the docs mean when they talk about \\?\ turning off the "...automatic expansion of the path string."

However, when you call a user mode function with the \??\ prefix, which remember is an internal NT prefix, this expansion is still done.

The user mode functions specifically look for \\?\ to disable the automatic expansion process, and since you're not providing it, your path is treated as a non-prefixed path and fed to RtlDosPathNameToNtPathName_U. This function is smart enough not to add an extra \??\ prefix to the start of the path, however the fixed size static buffer is still used.

This is the key difference. When you pass \??\ as a prefix your paths are still subject to the MAX_PATH length limit.

The following example program demonstrates this. The TestNestedDir function simply attempts to create (and then delete) a path greater than MAX_PATH characters in length, one level at a time. The results you'll see if you run this code are:

CreateDir, no prefix = 0
CreateDir, prefix \\?\ = 1
CreateDir, prefix \??\ = 0

Only the creation done with the \\?\ prefix is successful.

#include <stdio.h>
#include <tchar.h>
#include <string>
#include <assert.h>
#include <Windows.h>

const wchar_t* pszLongPath = 
    L"C:\\"
    L"12345678901234567890123456789012345678901234567890\\"
    L"12345678901234567890123456789012345678901234567890\\"
    L"12345678901234567890123456789012345678901234567890\\"
    L"12345678901234567890123456789012345678901234567890\\"
    L"12345678901234567890123456789012345678901234567890\\"
    L"12345678901234567890123456789012345678901234567890";

bool TestCreateNestedDir(LPCWSTR pszPath)
{
    std::wstring strPath = pszPath;
    std::wstring::size_type pos = 0, first = std::wstring::npos;
    bool fDirs = false, fResult = false;

    // step through each level in the path, but only try to start creating directories
    // after seeing a : character
    while ((pos = strPath.find_first_of(L'\\', pos)) != std::wstring::npos)
    {
        if (fDirs)
        {
            // get a substring for this level of the path
            std::wstring strSub = strPath.substr(0, pos);

            // check if the level already exists for some reason
            DWORD dwAttr = ::GetFileAttributesW(strSub.c_str());
            if (dwAttr != -1 && (dwAttr & FILE_ATTRIBUTE_DIRECTORY))
            {
                ++pos;
                continue;
            }

            // try to make the dir. if it exists, remember the first one we successfully made for later cleanup
            if (!::CreateDirectoryW(strSub.c_str(), nullptr))
                break;
            if (first == std::wstring::npos) first = pos;
        }
        else
        if (pos > 0 && strPath[pos - 1] == L':')
            fDirs = true;

        ++pos;
    }

    if (pos == std::wstring::npos)
    {
        // try to create the last level of the path (we assume this one doesn't exist)
        if (::CreateDirectoryW(pszPath, nullptr))
        {
            fResult = true;
            ::RemoveDirectoryW(pszPath);
        }
    }
    else
        --pos;

    // now delete any dirs we successfully made
    while ((pos = strPath.find_last_of(L'\\', pos)) != std::wstring::npos)
    {
        ::RemoveDirectoryW(strPath.substr(0, pos).c_str());
        if (pos == first) break;
        --pos;
    }
    return fResult;
}


int _tmain(int argc, _TCHAR* argv[])
{
    assert(wcslen(pszLongPath) > MAX_PATH);

    printf("CreateDir, no prefix = %ld\n", TestCreateNestedDir(pszLongPath));

    std::wstring strPrefix = L"\\\\?\\" + std::wstring(pszLongPath);
    printf("CreateDir, prefix \\\\?\\ = %ld\n", TestCreateNestedDir(strPrefix.c_str()));

    strPrefix[1] = L'?';
    printf("CreateDir, prefix \\??\\ = %ld\n", TestCreateNestedDir(strPrefix.c_str()));
    return 0;
}
Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79
  • It seems unlikely that Win32 replaces \\?\ with \??\ because they appear to have different meanings in the kernel; the first is (or is supposed to be) the local DOS namespace while the second is the global DOS namespace. Do you have any references? (My reference is the commentary in the Windows Research Kernel source code; however, I haven't verified this experimentally, and since WRK is based on Windows 2003 it might have been changed in later versions.) – Harry Johnston Aug 02 '14 at 23:46
  • On the other hand I see the replacement you describe [in this question](http://stackoverflow.com/questions/5285585/rtldospathnametontpathname-u-on-c-returns-invalid-path). So perhaps Win32 does just ignore the distinction between the local and global namespaces, or perhaps deliberately changes references to the local namespace to use the global namespace for some reason. – Harry Johnston Aug 02 '14 at 23:51
  • On Windows 7, CreateFileW will open a path longer than MAX_PATH with a \??\ prefix as well. CreateDirectoryW does fail, though. – Ilya Aug 03 '14 at 00:58
  • Also, what do you mean by \??\ not turning off "automatic expansion"? When I try to open "\??\C:\Windows\..\Windows\notepad.exe" with CreateFileW, it does not work. – Ilya Aug 03 '14 at 01:02