Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<filesystem>: exists() fails with "Access denied" for certain type of symlinks #5095

Open
alexlipa91 opened this issue Nov 18, 2024 · 5 comments
Labels
bug Something isn't working filesystem C++17 filesystem

Comments

@alexlipa91
Copy link

Describe the bug

We use std::filesystem::exists to check existence of paths on the system. Since few days we started to see an error where this function would fail with Access Denied. Unfortunately I could not get a fully reproducible example but I am gonna describe what I noticed.

This is the logged error

Exists D:\some_path\WebKit.framework = 0 with ec 5 and msg Access is denied.

This is the list of paths inside some_path

d-----        11/15/2024   4:51 AM                _SceneKit_SwiftUI.framework
d-----        11/15/2024   4:51 AM                _StoreKit_SwiftUI.framework
d-----        11/15/2024   4:51 AM                _SwiftData_SwiftUI.framework
d-----        11/15/2024   4:51 AM                _Translation_SwiftUI.framework
-a---l        11/15/2024   4:51 AM              0 AuthenticationServices.framework
-a---l        11/15/2024   4:51 AM              0 JavaScriptCore.framework
-a---l        11/15/2024   4:51 AM              0 SafariServices.framework
-a---l        11/15/2024   4:51 AM              0 WebKit.framework

The function fails on all the paths having the a and l bit on while it succeeds on the others.
Permissions should not be the problem: I tried with different users and with all of them I can read the files using powershell (with a simple ls).

Other things worth mentioning: these are symlinks that point to other symlinks

$ stat Modules
  File: Modules -> Versions/Current/Modules
  Size: 24              Blocks: 0          IO Block: 65536  symbolic link

$ stat Versions/Current/Modules
  File: Versions/Current/Modules
  Size: 0               Blocks: 0          IO Block: 65536  directory

$ stat Versions/Current
  File: Versions/Current -> A
  Size: 1               Blocks: 0          IO Block: 65536  symbolic link

$ stat Versions/A/
  File: Versions/A/
  Size: 0               Blocks: 0          IO Block: 65536  directory

again, I tried to repro by re-building a folder structure of this type but I couldn't reproduce the error

Any advice?

@CaseyCarter
Copy link
Member

I can reproduce the behavior by creating a non-directory symlink to a directory:

C:\tmp>type exists.cpp
#include <filesystem>
#include <print>

int main(int argc, const char* argv[]) {
    int error_count = 0;
    for (int i = 1; i < argc; ++i) {
        std::error_code ec;
        std::filesystem::path p{argv[i]};
        auto const result = std::filesystem::exists(p, ec);
        if (ec) ++error_count;
        std::println("{:?} {}: {}\n", p.string(), (result ? "exists" : "does not exist"), ec.message());
    }
}

C:\tmp>cl /nologo /std:c++latest /EHsc exists.cpp
exists.cpp

C:\tmp>md target

C:\tmp>mklink /d source target
symbolic link created for source <<===>> target

C:\tmp>exists target source
"target" exists: The operation completed successfully.

"source" exists: The operation completed successfully.

C:\tmp>rd source

C:\tmp>mklink source target
symbolic link created for source <<===>> target

C:\tmp>exists target source
"target" exists: The operation completed successfully.

"source" does not exist: Access is denied.

I'm not sure why EACCESS is the error code for this condition, but I didn't implement the OS so I assume it's intended. Is this your scenario?

@StephanTLavavej StephanTLavavej changed the title exists fails with "Access denied" for certain type of symlinks <filesystem>: exists() fails with "Access denied" for certain type of symlinks Nov 20, 2024
@StephanTLavavej StephanTLavavej added bug Something isn't working filesystem C++17 filesystem labels Nov 20, 2024
@StephanTLavavej
Copy link
Member

We talked about this at the weekly maintainer meeting and @CaseyCarter's repro needs more investigation to determine whether it's a bug or by design according to Windows filesystem rules.

@YexuanXiao
Copy link
Contributor

Three years ago, I encountered the same issue and concluded that it's due to the design of std::filesystem and Windows. In fact, I think std::filesystem does not accurately reflect the design of both Windows and POSIX, whether it's the good parts or the quirks.

@CaseyCarter
Copy link
Member

@YexuanXiao Thanks for the archaeology! I agree completely with @strega-nil-ms' assertion that we want to report an error for this case. Is there a more appropriate error than EACCESS, or should we continue to return the error code we get from the underlying Win32 API?

@YexuanXiao
Copy link
Contributor

YexuanXiao commented Nov 23, 2024

[fs.dir.entry.obs] points out that exist uses status, and status will follow symlinks. In the STL, this falls back to CreateFileW, and when the current issue occurs, it gets INVALID_HANDLE_VALUE, then retrieves the error code from GetLastError.

I also noticed a discrepancy: when a symlink pointing to a file actually points to a directory, GetLastError returns 5 (Access Denied), causing an exception to be thrown; but when a symlink pointing to a directory actually points to a file, GetLastError returns 267 (Dictionary name is invalid) and no exception is thrown. #4844 changed the previous behavior, and it may need to be reconsidered. If we maintain the behavior of throwing an exception, I think we could reinvent the message while retaining the original error code to inform the user about what really happened, rather than the current unintelligible message. @CaseyCarter @StephanTLavavej

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working filesystem C++17 filesystem
Projects
None yet
Development

No branches or pull requests

4 participants