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
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:
CreateFileW
with the string "\\?\C:\123[...]012345"
which is 32744 wide characters long (not counting the terminating zero).CreateFileW
does some extra checks, converts the null-terminated string to UNICODE_STRING
(Length=65488, MaximumLength=65490) and prepares an OBJECT_ATTRIBUTES
struct.CreateFileW
then calls NtCreateFile in ntdll.dll
, which is just a wrapper around syscall
instruction.NtCreateFile
returns 0xC0000106 (STATUS_NAME_TOO_LONG
).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:
NtCreateFile
calls IopCreateFile
IopCreateFile
calls ObOpenObjectByName
ObOpenObjectByName
calls ObpLookupObjectName
ObpLookupObjectName
checks for ObpDosDevicesShortNamePrefix
("\??\"
) -> success"C:"
and "\1234..."
"C:"
with a call to ObpLookupDirectoryEntry
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.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.