microsoft / STL

MSVC's implementation of the C++ Standard Library.
Other
10.23k stars 1.51k forks source link

<filesystem>: std::filesystem::exists fails when testing path naming unix socket #4077

Open vasama opened 1 year ago

vasama commented 1 year ago

Description

std::filesystem::exists(p), where p is a path pointing to a unix socket (e.g. p = "./test.sock"), throws an exception with the message exists: unknown error: "./test.sock".

Command-line test case

C:\temp> cat test.cpp
#include <exception>
#include <filesystem>
#include <iostream>
#include <cassert>

#ifndef _WINSOCKAPI_
#       define _WINSOCKAPI_
#endif

#include <WinSock2.h>
#include <ws2ipdef.h>
#include <afunix.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
        WSADATA wsa_data;
        assert(WSAStartup(MAKEWORD(2, 2), &wsa_data) == 0);

        auto socket = WSASocketW(AF_UNIX, SOCK_STREAM, 0, 0, 0, 0);
        assert(socket != SOCKET_ERROR);
        sockaddr_un addr;
        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path, "./test.sock", sizeof(addr.sun_path));
        assert(bind(socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != SOCKET_ERROR);
        assert(listen(socket, 1) != SOCKET_ERROR);

        try
        {
                (void)std::filesystem::exists("./test.sock");
        }
        catch (std::exception const& e)
        {
                std::cout << e.what() << std::endl;
        }
}
​C:\temp> cl /std:c++17 /EHsc /Gr test.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.37.32822 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

test.cpp
Microsoft (R) Incremental Linker Version 14.37.32822.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj
​C:\temp> ./test.exe
exists: unknown error: "./test.sock"

Expected behavior

I expect the call to std::filesystem::exists to return true.

STL version

Microsoft Visual Studio Community 2022
Version 17.8.0 Preview 1.0
vasama commented 1 year ago

@strega-nil-ms

frederick-vs-ja commented 1 year ago

The error code is 1920 ("The file cannot be accessed by the system.") on my machine, reported by CreateFileW.

Can we just return true when error code 1920 (and it friends) is encountered?

AlexGuteniev commented 1 year ago

Does std::filesystem::exists even call CreateFileW? If so, it should not -- should use GetFileAttributesW or FindFirstFileW.

strega-nil-ms commented 1 year ago

Does std::filesystem::exists even call CreateFileW? If so, it should not -- should use GetFileAttributesW or FindFirstFileW.

It must, because for some reason, unix sockets are considered reparse points. I'm not sure how to solve this, to be quite honest. This seems to be a failing of the standard's interaction with Windows' APIs.

StephanTLavavej commented 1 year ago

We talked about this at the weekly maintainer meeting. Nicole debugged into this and says that the problem is happening here:

https://github.com/microsoft/STL/blob/f362f7d7bea87bd199c927a89718e929ed89b187/stl/src/filesystem.cpp#L910-L918

This is where __std_fs_get_stats() calls CreateFileW() (via _Fs_file). It appears that we need to do something like add extra logic here to detect reparse points, and then to further detect Unix sockets. From quickly searching MSDN Microsoft Learn, https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags mentions something that looks like it might be relevant: IO_REPARSE_TAG_AF_UNIX resembles the AF_UNIX used in the repro (which curiously isn't mentioned in WSASocketW documentation).

This would add extra calls to the filesystem::exists() codepath, but we currently don't see a better way to avoid this, if we want to handle this aspect of the filesystem.

We might want to ask internal mailing lists (e.g. win32prg) if there's some better way to achieve this with the public APIs that we can use.

ChrisDenton commented 1 year ago

You may just need to use IsReparseTagNameSurrogate on the reparse tag to test if it's some kind of link or not.