Skip to content

<filesystem>: Fix filesystem::equivalent() (and filesystem::copy() with overwrite_existing) on FAT32 etc.#6187

Open
StephanTLavavej wants to merge 3 commits intomicrosoft:mainfrom
StephanTLavavej:fix-fs-copy
Open

<filesystem>: Fix filesystem::equivalent() (and filesystem::copy() with overwrite_existing) on FAT32 etc.#6187
StephanTLavavej wants to merge 3 commits intomicrosoft:mainfrom
StephanTLavavej:fix-fs-copy

Conversation

@StephanTLavavej
Copy link
Copy Markdown
Member

Fixes #6180, a regression that was introduced by #5434 in the MSVC Build Tools 14.50.

Commits

(1) Partial revert

Partially revert #5434. This reverts only the alternatives to GetFileInformationByHandleEx() with FileIdInfo. All other changes, including the unconditional use of GetFileInformationByHandleEx() with FileStandardInfo to get NumberOfLinks in legacy filesys.cpp _Hard_links(), are being allowed to stand as correct. Note that that case matches what modern filesystem.cpp __std_fs_get_stats() has always done:

STL/stl/src/filesys.cpp

Lines 294 to 296 in bed5dd5

const auto _Ok = GetFileInformationByHandleEx(_Handle, FileStandardInfo, &_Info, sizeof(_Info));
CloseHandle(_Handle);
return _Ok ? _Info.NumberOfLinks : static_cast<uintmax_t>(-1);

STL/stl/src/filesystem.cpp

Lines 960 to 969 in bed5dd5

if (_STD _Bitmask_includes_any(_Flags, _Standard_info_data)) {
FILE_STANDARD_INFO _Info;
if (!GetFileInformationByHandleEx(_Handle._Get(), FileStandardInfo, &_Info, sizeof(_Info))) {
return __std_win_error{GetLastError()};
}
_Stats->_File_size = _Info.EndOfFile.QuadPart;
_Stats->_Link_count = _Info.NumberOfLinks;
_Flags &= ~_Standard_info_data;
}

(2) Fix modern filesystem.cpp

filesystem.cpp: Always build the fallback, improve comment.

We don't need _CRT_APP guards anymore. @amyw-msft explained:

All the banned API restrictions were lifted back in 2020, and all the devices/OS versions that would have had a runtime issue with using a desktop-only API have been out of support for a long time now. Shouldn't be any issue with this. [...] there are still APIs that are banned in the sense that you can't use the user32 UI stuff in a UWP sandbox. It's the deployment time blocks that historically prevented common Win32 API use in Store apps (and forced us to create the uwp variants) that are gone.

(3) Fix legacy filesys.cpp

filesys.cpp: Add fallback behavior, improve comments.

Previously, the legacy implementation had no fallback behavior.

For _CRT_APP, 128-bit file IDs were the only codepath. That might have been okay, depending on what filesystems were available, but it might have been a mistake.

For non-App, 64-bit file indexes were the only codepath, so we weren't handling modern filesystems in an ideal way.

I'm bringing the logic closer to the modern implementation. It isn't an exact match (we don't look at the error codes, so if neither file exists, we try the fallback only to discover again that neither file exists). However, it should be good enough for the legacy implementation, and a strict improvement over the status quo. This is already a fairly major change, and I don't want it to become even more invasive.

Manual testing

For obvious reasons, adding automated coverage for this would be far more difficult than it's worth. I've manually verified both the legacy and modern implementations. With the new comments, the risk of regression in the future should be low.

Click to expand before-and-after testing on NTFS and FAT32:
C:\Temp>type meow.cpp
#include <exception>
#include <filesystem>
#include <fstream>
#include <print>
#include <string>
#include <system_error>

#include <experimental_filesystem.hpp>
using namespace std;

int main() {
    try {
        println("_MSVC_STL_VERSION: {}", _MSVC_STL_VERSION);
        println("_MSVC_STL_UPDATE:  {}", _MSVC_STL_UPDATE);
        println();

        const filesystem::path src{"DELME_src.txt"};
        const filesystem::path dst{"DELME_dst.txt"};

        for (int api = 0; api < 2; ++api) {
            filesystem::remove(src);
            filesystem::remove(dst);

            {
                ofstream f{src.string()};
                f << "Cats!\n";
            }
            {
                ofstream f{dst.string()};
                f << "Dogs!\n";
            }

            error_code ec{};

            if (api == 0) {
                println("Testing Standard <filesystem>.");
                filesystem::copy(src, dst, filesystem::copy_options::overwrite_existing, ec);
            } else {
                println("Testing legacy <experimental/filesystem>.");
                namespace exfs = experimental::filesystem;
                exfs::copy(src.string(), dst.string(), exfs::copy_options::overwrite_existing, ec);
            }

            if (ec) {
                println(R"(Copy failed! Error code: {}, "{}")", ec.value(), ec.message());
            } else {
                println("Copy succeeded!");
            }

            {
                print("After copy, {} contains: ", dst.string());
                ifstream f{dst.string()};
                for (string s; getline(f, s);) {
                    println("{}", s);
                }
                println();
            }

            filesystem::remove(src);
            filesystem::remove(dst);
        }
    } catch (const exception& e) {
        println(R"(Exception: "{}")", e.what());
    }
}
C:\Temp>cl /EHsc /nologo /W4 /std:c++latest /MTd /Od /I D:\GitHub\STL\tests\std\include /D_SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING meow.cpp
meow.cpp

NTFS is unaffected:

C:\Temp>pwsh -Command "(Get-Volume -DriveLetter C).FileSystemType"
NTFS

C:\Temp>meow_broken.exe
_MSVC_STL_VERSION: 145
_MSVC_STL_UPDATE:  202601

Testing Standard <filesystem>.
Copy succeeded!
After copy, DELME_dst.txt contains: Cats!

Testing legacy <experimental/filesystem>.
Copy succeeded!
After copy, DELME_dst.txt contains: Cats!


C:\Temp>meow_fixed.exe
_MSVC_STL_VERSION: 145
_MSVC_STL_UPDATE:  202603

Testing Standard <filesystem>.
Copy succeeded!
After copy, DELME_dst.txt contains: Cats!

Testing legacy <experimental/filesystem>.
Copy succeeded!
After copy, DELME_dst.txt contains: Cats!

FAT32 is fixed:

E:\>pwsh -Command "(Get-Volume -DriveLetter E).FileSystemType"
FAT32

E:\>C:\Temp\meow_broken.exe
_MSVC_STL_VERSION: 145
_MSVC_STL_UPDATE:  202601

Testing Standard <filesystem>.
Copy failed! Error code: 87, "The parameter is incorrect."
After copy, DELME_dst.txt contains: Dogs!

Testing legacy <experimental/filesystem>.
Copy failed! Error code: 1, "operation not permitted"
After copy, DELME_dst.txt contains: Dogs!


E:\>C:\Temp\meow_fixed.exe
_MSVC_STL_VERSION: 145
_MSVC_STL_UPDATE:  202603

Testing Standard <filesystem>.
Copy succeeded!
After copy, DELME_dst.txt contains: Cats!

Testing legacy <experimental/filesystem>.
Copy succeeded!
After copy, DELME_dst.txt contains: Cats!

…calls".

This reverts only the alternatives to GetFileInformationByHandleEx() with FileIdInfo.

All other changes, including the unconditional use of GetFileInformationByHandleEx() with FileStandardInfo
to get NumberOfLinks in legacy filesys.cpp _Hard_links(), are being allowed to stand as correct.
Note that that case matches what modern filesystem.cpp __std_fs_get_stats() has always done.
Previously, the legacy implementation had NO fallback behavior.

For `_CRT_APP`, 128-bit file IDs were the only codepath. That might have been okay, depending on what filesystems were available, but it might have been a mistake.

For non-App, 64-bit file indexes were the only codepath, so we weren't handling modern filesystems in an ideal way.

I'm bringing the logic closer to the modern implementation. It isn't an exact match (we don't look at the error codes, so if neither file exists,
we try the fallback only to discover again that neither file exists). However, it should be good enough for the legacy implementation, and a strict improvement over the status quo.
@StephanTLavavej StephanTLavavej requested a review from a team as a code owner March 28, 2026 18:52
@StephanTLavavej StephanTLavavej added bug Something isn't working filesystem C++17 filesystem labels Mar 28, 2026
@github-project-automation github-project-automation bot moved this to Initial Review in STL Code Reviews Mar 28, 2026
@StephanTLavavej StephanTLavavej moved this from Initial Review to Final Review in STL Code Reviews Mar 28, 2026
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

Status: Final Review

1 participant