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?
In the Windows API (with some exceptions discussed in the following paragraphs), the maximum length for a path is MAX_PATH, which is defined as 260 characters. A local path is structured in the following order: drive letter, colon, backslash, name components separated by backslashes, and a terminating null character.
Individual components of a filename (i.e. each subdirectory along the path, and the final filename) are limited to 255 characters, and the total path length is limited to approximately 32,000 characters. However, on Windows, you can't exceed MAX_PATH value (259 characters for files, 248 for folders).
The 260-character path limit is due to fixed buffers in the runtime library for DOS/Windows path processing (e.g. ANSI <=> Unicode, working directory, and DOS<=>NT path conversion).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With