Skip to content

<filesystem>: copy() with overwrite_existing fails with ERROR_INVALID_PARAMETER on filesystems without FILE_ID_128 support (FAT32, exFAT, SMB/non-NTFS) — regression from PR #5434 #6180

@mpexo

Description

@mpexo

Describe the bug

std::filesystem::copy() with copy_options::overwrite_existing fails with error 87 (ERROR_INVALID_PARAMETER) when the source file resides on a filesystem that does not support 128-bit file IDs, such as FAT32, exFAT, or SMB shares backed by a non-NTFS filesystem.

The failure only occurs when the destination file already exists. In that case, fs::copy() internally calls equivalent() to confirm that source and destination are not the same file. equivalent() queries file identity via GetFileInformationByHandleEx(FileIdInfo), which returns ERROR_INVALID_PARAMETER on any filesystem that does not support FILE_ID_128. The error is propagated back through copy() to the caller.

This is a regression introduced by PR #5434 ("Win8 baseline: Simplify <filesystem> API calls"), merged April 2025, first shipped in v14.50.

Command-line test case

#include <filesystem>
#include <fstream>
#include <iostream>
#include <system_error>

namespace fs = std::filesystem;

int main(int argc, char *argv[]) {
  if (argc < 3) {
    std::cerr << "Usage: repro <source-on-mapped-drive> <local-dest>\n"
              << "Example: repro R:\\testfile %TEMP%\\testfile\n";
    return 1;
  }

  fs::path src = argv[1];
  fs::path dst = argv[2];

  std::cout << "_MSVC_STL_VERSION = " << _MSVC_STL_VERSION
            << ", _MSVC_STL_UPDATE = " << _MSVC_STL_UPDATE << "\n";

  { std::ofstream(dst).flush(); } // pre-create destination
  std::cout << "Source: " << src << "\n";
  std::cout << "Destination: " << dst << "\n";

  std::error_code ec;
  fs::copy(src, dst, fs::copy_options::overwrite_existing, ec);
  if (ec)
    std::cout << "[FAIL] fs::copy() error " << ec.value() << " (" << ec.message() << ")\n";
  else
    std::cout << "[OK]   fs::copy() succeeded\n";
}

Build with both toolsets from a VS 2026 installation:

cmd /c "call "C:\Program Files\Microsoft Visual Studio\18\Professional\VC\Auxiliary\Build\vcvars64.bat" -vcvars_ver=14.50 && cl /std:c++17 /EHsc repro.cpp /Fe:repro_v145.exe"
cmd /c "call "C:\Program Files\Microsoft Visual Studio\18\Professional\VC\Auxiliary\Build\vcvars64.bat" -vcvars_ver=14.44 && cl /std:c++17 /EHsc repro.cpp /Fe:repro_v143.exe"

Run both against a file on a FAT32 volume (see setup instructions below):

repro_v145.exe R:\testfile %TEMP%\testfile
repro_v143.exe R:\testfile %TEMP%\testfile

Output:

# v145 (regression):
_MSVC_STL_VERSION = 145, _MSVC_STL_UPDATE = 202508
Source: "R:\testfile"
Destination: "C:\Users\...\AppData\Local\Temp\testfile"
[FAIL] fs::copy() error 87 (The parameter is incorrect.)

# v143 (baseline, works correctly):
_MSVC_STL_VERSION = 143, _MSVC_STL_UPDATE = 202503
Source: "R:\testfile"
Destination: "C:\Users\...\AppData\Local\Temp\testfile"
[OK]   fs::copy() succeeded

Expected behavior

fs::copy(src, dst, fs::copy_options::overwrite_existing, ec) should succeed (and ec should be clear) regardless of whether the source filesystem supports FILE_ID_128.

STL version

Regression introduced in 14.50. Version 14.44 is not affected.

Additional context

PR #5130 ("Fix false positives by filesystem::equivalent on file systems with transient file IDs") introduced GetFileInformationByHandleEx(FileIdInfo) to query FILE_ID_128 in _Get_file_id_by_handle, but crucially kept a fallback: when FileIdInfo failed with ERROR_INVALID_PARAMETER or ERROR_NOT_SUPPORTED, the code would fall back to GetFileInformationByHandle (BY_HANDLE_FILE_INFORMATION), synthesizing a 128-bit ID from the 32-bit file index. This fallback made v143 work correctly on FAT32.

PR #5434 then removed this fallback as part of collapsing the #ifdef _CRT_APP / non-_CRT_APP bifurcation to a single Win8-API code path. The assumption was that Windows 8+ always supports FileIdInfo. However, FILE_ID_128 support is a filesystem capability, not a Windows version guarantee — FAT32 and exFAT do not support it on any Windows version.

The removed code was in stl/src/filesystem.cpp, _Get_file_id_by_handle:

// Before PR #5434 (v143, works):
switch (_Last_error) {
case __std_win_error::_Not_supported:
case __std_win_error::_Invalid_parameter:
    break; // try more things
default:
    return _Last_error;
}
// try GetFileInformationByHandle as a fallback
BY_HANDLE_FILE_INFORMATION _Info;
if (GetFileInformationByHandle(_Handle, &_Info)) {
    _Id->VolumeSerialNumber = _Info.dwVolumeSerialNumber;
    memcpy(&_Id->FileId.Identifier[0], &_Info.nFileIndexHigh, 4);
    memcpy(&_Id->FileId.Identifier[4], &_Info.nFileIndexLow, 4);
    memset(&_Id->FileId.Identifier[8], 0, 8);
    return __std_win_error::_Success;
}

// After PR #5434 (v145, broken):
return __std_win_error{GetLastError()};

Test setup: create a FAT32 VHD mounted as R: (requires admin)

# Run as Administrator
$vhdPath = "$PSScriptRoot\repro.vhd"
$disk = New-VHD -Path $vhdPath -SizeBytes 64MB -Fixed |
        Mount-VHD -Path $vhdPath -PassThru |
        Initialize-Disk -PartitionStyle MBR -PassThru
New-Partition -DiskNumber $disk.DiskNumber -UseMaximumSize -DriveLetter R
Format-Volume -DriveLetter R -FileSystem FAT32 -Confirm:$false
echo test > R:\testfile

Metadata

Metadata

Labels

bugSomething isn't workingfilesystemC++17 filesystem

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions