What happens internally when a file path exceeds approx. 32767 characters in Windows?

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.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)