8

std::filesystem::path doesn't seem to be aware of the windows long path magic prefix. Is this per design or is there a mode/flag/compiler switch/3rd party library which can be used?

f.e.

  • for a path like C:\temp\test.txt, the root_name is C: and the root_path is C:\ which are both valid paths but
  • for \\?\C:\tmp\test.txt they are \\? and \\?\

I hoped that they would be C: and C:\ as well because \\?\ is not a path but just a tag used to work around the legacy windows 260 char path limit.

See a complete example on compilerexplorer.

ridilculous
  • 624
  • 3
  • 16
  • 2
    Just an FYI that in Windows 10 v1607 and later, you don't need to use the ```\\?``` prefix anymore if you opt in to the [new LongPaths feature](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later) that removes the `MAX_PATH` restriction from most Win32 filesystem APIs. – Remy Lebeau Mar 23 '22 at 16:39
  • @ridilculous: You can remove the prefixes. – Nicol Bolas Mar 23 '22 at 16:49
  • I'm working with a legacy codebase which contains tons of fopen and other older apis, and paths are coming in from extern which i cannot control and which may have these prefixes :( – ridilculous Mar 23 '22 at 16:52
  • If you are using `` just to parse paths, you don't need to deal with `\\?` at all, so strip it off from any path strings you are assigning to `std::filesystem::path`. If you are passing `std::filesystem::path`s to APIs that need path strings, then prepend `\\?` when passing the strings, or convert the strings to short 8.3 paths. – Remy Lebeau Mar 23 '22 at 16:56
  • @RemyLebeau i have cases where there are so many 8.3 paths that i overshoot the 260 limit again. I guess i have to strip off \\?\UNC\ and push \\ afterwards to the front and if that doesn't replace anything try to strip off \\?\ but i was hoping that there's a more implicit solution because i have to re-add them again for some cases. – ridilculous Mar 23 '22 at 17:01
  • 3
    There is some info in the [STL source code](https://github.com/microsoft/STL/blob/7671576afbe2370377dbb154a8a803d6a7be6e89/stl/inc/filesystem#L435), where it says that the current behavior is apparently deliberate. There was also [an issue filed for this on github](https://github.com/microsoft/STL/issues/1921), and I interpret the answer as "do not use UNC path with `std::filesystem`". There is also [this open issue](https://github.com/microsoft/STL/issues/2256) which asks basically the same thing as the OP. So, all in all, I fear the answer is: there is no magic switch to enable UNC support. – Sedenion Mar 23 '22 at 20:20
  • 1
    I just tried `boost::filesystem`, and it is closer to what you expect. The authors have thought of the case, as can be seen in the [source code](https://github.com/boostorg/filesystem/blob/97722a3107d136eebe9425804fe0d13eab3ce2bc/src/path.cpp#L928). For `\\?\C:\temp\test.txt` I get `root_name = \\?\C:` and `root_path = \\?\C:\ `. Would that be acceptable for you? – Sedenion Mar 23 '22 at 20:45
  • @Sedenion great links! There's still hope when Casey marked it as a bug tho :) Anyway, i haven't tried boost because i didn't expect it not to work with std::fs but i'm fine with boost for the moment i guess. – ridilculous Mar 23 '22 at 21:29
  • As my comments seem to be acceptable as answer, I posted them as proper answer now. – Sedenion Mar 24 '22 at 20:39

1 Answers1

5

As mentioned in the comments above, the source code of the Microsoft STL shows that the current behavior is intentional for UNC (Uniform Naming Convention) paths and that there is no "magic compiler switch" to change this. Moreover, it seems that UNC paths should not be used with std::filesystem, as implied by this github post. There is also an open issue that requests a change of behavior.

Since the OP is ok with 3rd party libraries: boost::filesystem has special code on Windows builds for UNC paths and gives results that are closer to what is expected.

Trying it out with the following code (adapted from the original post):

#include <filesystem>
#include <iostream>
#include <string>
#include <vector>

#include <boost/filesystem.hpp>

int main()
{
  std::vector<std::string> tests = {
      R"(C:\temp\test.txt)",
      R"(\\?\C:\temp\test.txt)",
  };

  for (auto const & test : tests) {
    std::filesystem::path const p(test); // or boost::filesystem::path
    std::cout << std::endl << test << std::endl;
    std::cout << "root_name\t" << p.root_name().string() << std::endl;
    std::cout << "root_directory\t" << p.root_directory().string() << std::endl;
    std::cout << "root_path\t" << p.root_path().string() << std::endl;
    std::cout << "relative_path\t" << p.relative_path().string() << std::endl;
    std::cout << "parent_path\t" << p.parent_path().string() << std::endl;
    std::cout << "filename\t" << p.filename().string() << std::endl;
    std::cout << "stem\t" << p.stem().string() << std::endl;
    std::cout << "extension\t" << p.extension().string() << std::endl;
  }
}

For std::filesystem on MSVC I get

C:\temp\test.txt
root_name       C:
root_directory  \
root_path       C:\
relative_path   temp\test.txt
parent_path     C:\temp
filename        test.txt
stem    test
extension       .txt

\\?\C:\temp\test.txt
root_name       \\?
root_directory  \
root_path       \\?\
relative_path   C:\temp\test.txt
parent_path     \\?\C:\temp
filename        test.txt
stem    test
extension       .txt

For boost::filesystem I get:

C:\temp\test.txt
root_name       C:
root_directory  \
root_path       C:\
relative_path   temp\test.txt
parent_path     C:\temp
filename        test.txt
stem    test
extension       .txt

\\?\C:\temp\test.txt
root_name       \\?\C:
root_directory  \
root_path       \\?\C:\
relative_path   temp\test.txt
parent_path     \\?\C:\temp
filename        test.txt
stem    test
extension       .txt
Sedenion
  • 5,421
  • 2
  • 14
  • 42