Skip to content

<filesystem>: lexically_relative() can not handle long path with prefix \\?\ #2256

Description

@Trafo

Describe the bug
I would expect that this line of code is true:
std::filesystem::path("\\\\?\\C:\\a\\d").lexically_relative("\\\\?\\C:\\a") == "d"
But it is not, because an empty string will be returned. The reason for that is, that inside of lexically_relative() a check _Relative_path_contains_root_name() will be executed, but this is always true. The reason is how the filesystem is handling the root name as explained in _Find_root_name_end(). There is pointed out, that in my case \\?\ is the root name. But at the same time, the path contains C:\ which is a valid root name as well. Because of that, we have the weird behaving, that:
std::filesystem::path("\\\\?\\C:\\a\\d").relative_path() == "C:\\a\\d") which is not a relative path and causes the misbehaving of lexically_relative, because it can find another root_name inside of the "relative" path.

Command-line test case

#include <iostream>
#include <filesystem>
#include <cassert>
namespace fs = std::filesystem;
 
int main()
{
    assert(fs::path("C:\\a\\d").lexically_relative("C:\\a") == "d");
}

Expected behavior
That \\?\C:\ will be treated as root path and not \\?\

STL version

  • 16.11.3

Additional context
In another issue #1921 StephanTLavavej pointed out, that \\?\ only should be used by expert users and I agree. But in case I want to use the new Desktop-Bridge I had to use the new manifest appxmanifest, which not has a property anymore longPathAware. So I don't know how my app gives the consence of supporting that. (Is there maybe a better way to opt-in to the long path app wise) And I don't find any documentation for this use case, besides https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd. And so my only solution to solve this issue is, of converting the paths with the prefix \\?\. But this from my point of view is not well handled by the std::filesystem lib.

Activity

  1. strega-nil-ms commented on May 12, 2022

    @strega-nil-ms
    Contributor

    Unfortunately, I don't believe this is fixable in our implementation, at least as the standard is currently specified (see [fs.path.gen]/3.4). I may personally agree that \\?\C: should be treated as the root-name here, but that is not the implementation that we chose and it would be a pretty major silent breaking change to change that now.

    Due to this, I believe that this should be opened as a WG21 issue?

  2. BillyONeal commented on May 12, 2022

    @BillyONeal
    Member

    Related: LWG-3070

    I think it is too late to change root_name() but agree we should have parsed \\?\c: as root-name if I had a time machine. It does make analysis more complicated, for example, \\?\c:bar is not a relative path, it is just broken. I think I was thinking "\\?\ is an explicit opt-in to everything-like-posix-paths-semantics" (because that is how the NT namespace behaves) when designing this.

    For your immediate bug/issue, I think a special case inside lexically_relative for \\?\ is probably reasonable to do... crystalizing into standardese that makes sense everywhere won't be so easy but I don't think we need to wait for an accepted resolution to fix it as long as the LWG issue is actually filed.

    Thanks for the bug report!

  3. strega-nil-ms commented on May 13, 2022

    @strega-nil-ms
    Contributor

    I have submitted a WG21 issue, tho it has not been officially opened. I'll update when that's done.

  4. strega-nil commented on May 23, 2022

    @strega-nil
    Contributor
  5. ZubairRafiq commented on Jun 17, 2023

    @ZubairRafiq
    `#include` <filesystem> #include <vector)
    #include <string> #include <iostream>
    
    struct Result {
          std::string s1;
          std::string s2;
          }
    Result test () {
          std::string test = {R" (\\?\C:\temp\test-file.txt)");
          std::filesystem::path const p(test);
          
          Result res;
          res.s1.append(p.root_name ().string()); 
          res.s2.append(p.root_path().string());
    
          std:: cout <‹ std::endl <‹ test <‹ std::endl;
          std::cout <‹ "root_name\t" <‹ p.root_name ().string() <‹ std:: endl;
          std::wcout <‹ L" absolute: " <‹ std::filesystem::absolute (p).wstring() <‹ std::endl; 
          std::cout <‹ "root_directory\t" <‹ p.root_directory().string() <‹ std: :end1; 
          std::cout <‹ "root_path\t" <‹ p.root_path().string() <‹ std::endl;
          std::cout <‹ "relative_path\t" <‹ p.relative_path ().string() <‹ std:: end1; 
          std::cout <‹ "parent_path\t" <‹ p.parent_path().string() ‹‹ std::endl;
          return res;
    }
    

    The output:
    root name
    absolute: w:?\C: \temp\test-file.txt
    root directory \
    root_path \
    relative_path ?\C:\temp\test-file.txt
    parent_path \?\C:\temp

    The “root name” component extracted from the path is reported as an empty string (””). However, the expected value was “C:”, which represents the drive letter of the path.

    The root directory output is "\", however it should be "C:\".
    What is the reason for this behavior of std::filesystem::path?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfilesystemC++17 filesystemfixedSomething works now, yay!

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions