I’m working on a utility called eudo, and as part of this process I wanted to ensure that the utility made the best of modern Windows Long Path Name (LPN) support. This is not to be confused with Long File Name (LFN) support, which was hacked onto the FAT file system and gave MAX_PATH (260) character limit to file and directory names. This is about the LPNs which have been a basic functional capability of NTFS file systems since it was first introduced sometime around 1994 — where individual file and directory names are limited to MAX_PATH but the total length of any given path is PATHCCH_MAX_CCH (32767) characters in length.
Supporting these LPNs has become increasingly important as developers who grew up on Linux and MacOS platforms continue to produce software that relies on it. The npm / node.js community in particular runs into the 260 char limit, especially on distributed build systems with deeply-nested file structures meant to sandbox categories of jobs. This means that eudo really needs to support these, insofar is possible on windows.
For reasons mostly centering around security paranoia, Microsoft never really supported LPNs in any clear or concise fashion. The prevailing theory was (and still is?) that retroactively updating LPN support into system libraries would cause buffer overruns and all the world’s software would become compromised. Never mind the fact that happens a couple times a year already in all manner of other ways for software across all platforms — and the world keeps ticking along. Never mind the fact that such changes are as likely to help engineers find and fix existing-but-unknown exploits as it is to create new unknown exploits that could be abused before engineers discover them. Never mind that having APIs randomly and unexpectedly truncate LPNs as returned by NTFS to MAX_PATH is itself a potential security hazard.
I’ve known since the Windows 2000 days that the wide-char (unicode) versions of most Windows File APIs support LPNs fine enough. But I was hoping there was a better way today, seeing that we’re 15 years later, and staring down the barrel of the bold new Windows 10 and it’s built-in hybrid Ubuntu Linux layer feature.
Step 1. Research Latest Microsoft Path Manipulation APIs
My first stop was to read the MSDN section about MAX_PATH. This sent me down a rabbit hole. There’s some mess about some new LPN notation \\?\ that bypasses some internal processing that might “break” LPNs.
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. For example, if the file system supports large paths and file names, you can exceed the MAX_PATH limits that are otherwise enforced by the Windows APIs.
That sounds like really nuanced behavior that deserves a better explanation. Which Windows APIs? Because I can tell you right now — by direct empirical evidence — that LPNs are working just fine when I feed them into Win32 APIs like CreateFileW. Is this something that’s UWP only? Does it only affect CreateFileA?
Note The maximum path of 32,767 characters is approximate, because the \\?\ prefix may be expanded to a longer string by the system at run time, and this expansion applies to the total length.
So let’s break this down by what it really means: The string that “gets no expansion or processing” still gets expanded or processed somehow, and because of that, the actual max length is unknown — which makes this, in a way, more dangerous than the old stupid truncate-at-MAX_PATH behavior. At least with that one you could assert or error on a path that was too long, and be sure the user got a concrete explanation rather than random "file not found!" error or possibly something less vague. Now we have LPNs but there’s some magic unknown padding somewhere between 32000 and 32767 chars (undisclosed) where things break down and you get potentially mysterious errors resulting from path name truncation and possibly the ability to create a file on NTFS (max length 32767) that can simply never be accessed by windows itself. Waaat?
And then there’s this:
A registry key allows you to enable or disable the new long path behavior. To enable long path behavior set the registry key at HKLM\SYSTEM\CurrentControlSet\Control\FileSystemLongPathsEnabled (Type: REG_DWORD). The key’s value will be cached by the system (per process) after the first call to an affected Win32 file or directory function (list follows). The registry key will not be reloaded during the lifetime of the process. In order for all apps on the system to recognize the value of the key, a reboot might be required because some processes may have started before the key was set.
A registry key? Reboot required? Affects all apps? Ok, this isn’t sounding very ideal. It’s clearly intended for use only by systems engineers in controlled production-level environments. There is a manifest-based alternative.
Finally, we get down to a point where it mentions what functions are actually affected by this new LPN support and registry key mess:
These are the directory management functions that no longer have MAX_PATH restrictions if you opt-in to long path behavior: CreateDirectoryW,CreateDirectoryExW, GetCurrentDirectoryW, RemoveDirectoryW, SetCurrentDirectoryW.
These are the file management functions that no longer have MAX_PATH restrictions if you opt-in to long path behavior: CopyFileW, CopyFile2, CopyFileExW, CreateFileW, CreateFile2, CreateHardLinkW, CreateSymbolicLinkW, DeleteFileW, FindFirstFileW, FindFirstFileExW, FindNextFileW, GetFileAttributesW, GetFileAttributesExW, SetFileAttributesW, GetFullPathNameW, GetLongPathNameW, MoveFileW, MoveFileExW, MoveFileWithProgressW, ReplaceFileW, SearchPathW, FindFirstFileNameW, FindNextFileNameW, FindFirstStreamW, FindNextStreamW, GetCompressedFileSizeW, GetFinalPathNameByHandleW.
But wait a minute? CreateFileW already supports Long Path Names since Windows 2000! I also know for a fact that SetCurrentDirectoryW does not allow LPNs under any circumstances due to a limitation of the Microsoft Common Runtime (CRT). So what is actually changing when we enable this new long path name support? I’m not at all sure. None of this is adding up, which means my next task is to put together some real tests, run them, record the results, and formulate my own documentation.
Further down the rabbit hole…
The next thing I decide to do is read up on some specific LPN details in the PathCchCombine and PathCchCombineEx functions, hoping for more clues. These APIs were introduced in Windows 8, and are wide-char-only so — one would hope — they’re built from the ground up with LPN support. My hopes are dashed instantly: PathCchCombine specifically says it has the MAX_PATH limitation, and that to get past the limitation I must use PathCchCombineEx. I repeat: a brand spanking new API introduced for Windows 8 is apparently still stuck on MAX_PATH, at least according to the MSDN docs.
I add that last bit because I didn’t actually verify behavior of either function. Why bother? I already knew from the old days of Windows 2000 programming that that PathCombineW handles LPNs perfectly fine. I re-confirmed it yesterday.
And then we get to the flags section of PathCchCombineEx, which its own special brand of complexity:

So we have a flag to opt into allowing LPNs — but it’s overridden by the registry setting described earlier — and then flags that override the registry setting (added in 1703). Got all that? Finally, my favorite:
PATHCCH_DO_NOT_NORMALIZE_SEGMENTS
|
Disables the normalization of path segments that includes removing trailing dots and spaces. This enables access to paths that win32 path normalization will block. |
This one I love, because it’s actually a flag meant to disable legacy/buggy behavior of ancient Windows APIs that were designed with the original FAT filesystem’s 8.3 filename limitation in mind. The correct behavior of any system-provided path library should have been to augment filenames in a manner consistent with the user’s selected file system. This is how every other operating system operates — where if you use a new advanced file system, the system libraries actually allow you to, you know, use its features. But not on Windows! Not even on an API added for Windows 8 OS. And what is this about removal of trailing spaces? That’s technically a bug by all accounts. There’s no reason trailing spaces should have ever been automatically stripped by a path concatenation library, especially not without explicitly documenting it in the description or remarks. Keeping that kind of behavior around just because someone made the mistake of adding it 25 yrs ago is bad engineering.
Filtering it all down
One of the challenges of working well with Microsoft APIs and the MSDN is being able to filter the good APIs from the bad ones. The bad APIs, in many cases, are hacks and workarounds that Microsoft engineers probably only intended for use internally, but have to be disclosed publicly to avoid anti-trust lawsuits or other legal annoyances. My only guess is these flags were added as quick hacks to allow working around some specific LPN problems they discovered in the the new Ubuntu Linux feature launched with Windows 10 build 1703. They aren’t really meant for use by anyone else.
Finally, PathCombineW also has the problem where it strips trailing dots and whitespace, so don’t use it either. The best conclusion here is don’t use Microsoft-provided Path tools. Writing fully-functional LPN-friendly path concatenators and path normalizers in modern C++ is much easier than trying to grok these docs and verify windows versions and break compat with Windows Vista/7 and, possibly, end up not supporting LPNs correctly anyway in the process.