Question

In Windows (assume 2000 onwards), a file path can be at most approximately 32767 characters in length. This limitation exists due to the internal handling with UNICODE_STRING in the native API (also on the kernel side, in drivers etc). So far so good. I know the theory behind that part.

The reason for the limit is that the the Length and MaximumLength members of UNICODE_STRING count the number of bytes in the Buffer, but are 16 bit unsigned integers themselves.

I also know why the limit is an approximation rather than a set limit. This is mostly due to how your file name (e.g. \\.\C:\boot.ini) gets resolved to its native form (e.g. \??\C:\boot.ini) and then to something that is prefixed by the actual volume device name and then followed by the path relative to that volume, e.g. \Device\HarddiskVolume2\boot.ini.

Furthermore from Windows Explorer the known symptom when hitting the ("ANSI") MAX_PATH limit is to pretend the file or folder doesn't exist in some versions of Windows (possible this got fixed at some point).

But what happens at the object manager, I/O manager and file system driver levels respectively when I call CreateFile() with a path that looks like \\.\C:\...\filename.ext and the whole path does not exceed the limit, but reaches it, in my call to kernel32.dll's CreateFile() and then gets expanded? ...

Neither the SDKs nor the WDKs seem to be particularly chatty about the topic. Or did I look in the wrong sections?

Was it helpful?

Solution

Because I'm lazy, I didn't write a test program but tested it using the excellent Far Manager which handles things like long paths (longer than MAX_PATH) or special filenames (con, prn etc) just fine.

I made a string of exactly 255 characters ("12345678901234...012345") and started creating nested directories. Luckily, Far's "Make Directory" function takes a slash-separated string to mean "create nested directories" so I was able to do it in just a few steps by preparing a string in the internal editor with some copy&paste.

The longest path I was able to create was 32739 characters long, counting from "C:\" (i.e. it does not include "\\?\" added by Far). The error that I get when trying to create a directory or file with just one additional character is "The filename or extension is too long.". If I try to enter that directory, I get the same error.

EDIT: spent some time in the debugger and here's what happens on the Win32 API level:

  1. I try to create a file with one character above the limit
  2. Far calls CreateFileW with the string "\\?\C:\123[...]012345" which is 32744 wide characters long (not counting the terminating zero).
  3. CreateFileW does some extra checks, converts the null-terminated string to UNICODE_STRING (Length=65488, MaximumLength=65490) and prepares an OBJECT_ATTRIBUTES struct.
  4. CreateFileW then calls NtCreateFile in ntdll.dll, which is just a wrapper around syscall instruction.
  5. NtCreateFile returns 0xC0000106 (STATUS_NAME_TOO_LONG).
  6. That status value is then converted (using RtlNtStatusToDosError) to the Win32 error 206 (ERROR_FILENAME_EXCED_RANGE).

I did not bother checking what happens in the kernel, but I guess I could have a look at that too.

EDIT2: I ran WinObj and found that on my system C: is a symlink to \Device\HarddiskVolume1. This string is 23 characters long. If we replace the \C: in the string passed to NtCreateFile with it, we get 32744 - 3 + 23 = 32764 characters. Together with the terminating zero, this requires 65530 bytes. Still short of the limit (0xFFFF=65535) so I guess there's something extra being added, like a session or namespace name.

EDIT3: after going through the kernel:

  1. NtCreateFile calls IopCreateFile
  2. IopCreateFile calls ObOpenObjectByName
  3. ObOpenObjectByName calls ObpLookupObjectName
  4. ObpLookupObjectName checks for ObpDosDevicesShortNamePrefix ("\??\") -> success
  5. it skips the prefix and splits the remaining part into "C:" and "\1234..."
  6. it resolves the "C:" with a call to ObpLookupDirectoryEntry
  7. it then calls ObpParseSymbolicLink passing to it the looked-up directory entry (_OBJECT_SYMBOLIC_LINK with LinkTarget == "\Device\HarddiskVolume1" and DosDeviceDriveIndex == 3) and the remaining part of the name.
  8. It then does something like this (faithfully reproduced by ReactOS):

    TargetPath = &SymlinkObject->LinkTarget;
    TempLength = TargetPath->Length;
    TotalLength = TempLength + RemainingName->Length;
    if (LengthUsed > 0xFFF0)
        return STATUS_NAME_TOO_LONG;
    

    In our case, 46 + 65476 = 65522 (0xfff2) which is just above the limit.

    So there, mystery solved (I hope!).

P.S. everything tested under Windows 7 x64 SP1.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top