Skip to content

Commit 0053a14

Browse files
Fix false positives by filesystem::equivalent on file systems with transient file IDs (#5130)
Co-authored-by: Stephan T. Lavavej <[email protected]>
1 parent d40e8c0 commit 0053a14

File tree

4 files changed

+75
-48
lines changed

4 files changed

+75
-48
lines changed

stl/inc/filesystem

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3431,38 +3431,21 @@ namespace filesystem {
34313431
return _STD filesystem::copy_file(_From, _To, copy_options::none);
34323432
}
34333433

3434-
_NODISCARD inline pair<__std_win_error, bool> _Equivalent(
3435-
const wchar_t* const _Lhs, const wchar_t* const _Rhs) noexcept {
3436-
__std_fs_file_id _Left_id;
3437-
__std_fs_file_id _Right_id;
3438-
auto _Last_error = __std_fs_get_file_id(&_Left_id, _Lhs);
3439-
if (_Last_error != __std_win_error::_Success) {
3440-
return {_Last_error, false};
3441-
}
3442-
3443-
_Last_error = __std_fs_get_file_id(&_Right_id, _Rhs);
3444-
if (_Last_error != __std_win_error::_Success) {
3445-
return {_Last_error, false};
3446-
}
3447-
3448-
return {__std_win_error::_Success, _CSTD memcmp(&_Left_id, &_Right_id, sizeof(__std_fs_file_id)) == 0};
3449-
}
3450-
34513434
_EXPORT_STD _NODISCARD inline bool equivalent(const path& _Lhs, const path& _Rhs) {
34523435
// test if the paths _Lhs and _Rhs refer to the same file
3453-
const auto _Result = _Equivalent(_Lhs.c_str(), _Rhs.c_str());
3454-
if (_Result.first != __std_win_error::_Success) {
3455-
_Throw_fs_error("equivalent", _Result.first, _Lhs, _Rhs);
3436+
const auto _Result = __std_fs_equivalent(_Lhs.c_str(), _Rhs.c_str());
3437+
if (_Result._Error != __std_win_error::_Success) {
3438+
_Throw_fs_error("equivalent", _Result._Error, _Lhs, _Rhs);
34563439
}
34573440

3458-
return _Result.second;
3441+
return _Result._Equivalent;
34593442
}
34603443

34613444
_EXPORT_STD _NODISCARD inline bool equivalent(const path& _Lhs, const path& _Rhs, error_code& _Ec) noexcept {
34623445
// test if the paths _Lhs and _Rhs refer to the same file
3463-
const auto _Result = _Equivalent(_Lhs.c_str(), _Rhs.c_str());
3464-
_Ec = _Make_ec(_Result.first);
3465-
return _Result.second;
3446+
const auto _Result = __std_fs_equivalent(_Lhs.c_str(), _Rhs.c_str());
3447+
_Ec = _Make_ec(_Result._Error);
3448+
return _Result._Equivalent;
34663449
}
34673450

34683451
_EXPORT_STD _NODISCARD inline file_status status(const path& _Path);

stl/inc/xfilesystem_abi.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,6 @@ struct __std_fs_convert_result {
232232
__std_win_error _Err;
233233
};
234234

235-
struct __std_fs_file_id { // typedef struct _FILE_ID_INFO {
236-
unsigned long long _Volume_serial_number; // ULONGLONG VolumeSerialNumber;
237-
unsigned char _Id[16]; // FILE_ID_128 FileId;
238-
}; // } FILE_ID_INFO, ...;
239-
240235
enum class __std_fs_copy_options {
241236
_None = 0x0,
242237

@@ -303,8 +298,13 @@ _NODISCARD __std_fs_convert_result __stdcall __std_fs_convert_wide_to_narrow_rep
303298
_In_ __std_code_page _Code_page, _In_reads_(_Input_len) const wchar_t* _Input_str, _In_ int _Input_len,
304299
_Out_writes_opt_(_Output_len) char* _Output_str, _In_ int _Output_len) noexcept;
305300

306-
_NODISCARD _Success_(return == __std_win_error::_Success) __std_win_error
307-
__stdcall __std_fs_get_file_id(_Out_ __std_fs_file_id* _Id, _In_z_ const wchar_t* _Path) noexcept;
301+
struct __std_fs_equivalent_result {
302+
bool _Equivalent;
303+
__std_win_error _Error;
304+
};
305+
306+
_NODISCARD __std_fs_equivalent_result __stdcall __std_fs_equivalent(
307+
_In_z_ const wchar_t* _Path1, _In_z_ const wchar_t* _Path2) noexcept;
308308

309309
_NODISCARD __std_win_error __stdcall __std_fs_set_last_write_time(
310310
_In_ long long _Last_write_filetime, _In_z_ const wchar_t* _Path) noexcept;

stl/src/filesys.cpp

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -320,22 +320,26 @@ _FS_DLL space_info __CLRCALL_PURE_OR_CDECL _Statvfs(const wchar_t* _Fname) noexc
320320

321321
_FS_DLL int __CLRCALL_PURE_OR_CDECL _Equivalent(
322322
const wchar_t* _Fname1, const wchar_t* _Fname2) noexcept { // test for equivalent file names
323+
// See GH-3571: File IDs are only guaranteed to be unique and stable while handles remain open
323324
#ifdef _CRT_APP
324325
_FILE_ID_INFO _Info1 = {0};
325326
_FILE_ID_INFO _Info2 = {0};
326327
bool _Ok1 = false;
327328
bool _Ok2 = false;
328329

329-
HANDLE _Handle = _FilesysOpenFile(_Fname1, FILE_READ_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS);
330-
if (_Handle != INVALID_HANDLE_VALUE) { // get file1 info
331-
_Ok1 = GetFileInformationByHandleEx(_Handle, FileIdInfo, &_Info1, sizeof(_Info1)) != 0;
332-
CloseHandle(_Handle);
330+
HANDLE _Handle1 = _FilesysOpenFile(_Fname1, FILE_READ_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS);
331+
if (_Handle1 != INVALID_HANDLE_VALUE) { // get file1 info
332+
_Ok1 = GetFileInformationByHandleEx(_Handle1, FileIdInfo, &_Info1, sizeof(_Info1)) != 0;
333333
}
334334

335-
_Handle = _FilesysOpenFile(_Fname2, FILE_READ_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS);
336-
if (_Handle != INVALID_HANDLE_VALUE) { // get file2 info
337-
_Ok2 = GetFileInformationByHandleEx(_Handle, FileIdInfo, &_Info2, sizeof(_Info2)) != 0;
338-
CloseHandle(_Handle);
335+
HANDLE _Handle2 = _FilesysOpenFile(_Fname2, FILE_READ_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS);
336+
if (_Handle2 != INVALID_HANDLE_VALUE) { // get file2 info
337+
_Ok2 = GetFileInformationByHandleEx(_Handle2, FileIdInfo, &_Info2, sizeof(_Info2)) != 0;
338+
CloseHandle(_Handle2);
339+
}
340+
341+
if (_Handle1 != INVALID_HANDLE_VALUE) {
342+
CloseHandle(_Handle1);
339343
}
340344

341345
if (!_Ok1 && !_Ok2) {
@@ -351,16 +355,19 @@ _FS_DLL int __CLRCALL_PURE_OR_CDECL _Equivalent(
351355
bool _Ok1 = false;
352356
bool _Ok2 = false;
353357

354-
HANDLE _Handle = _FilesysOpenFile(_Fname1, FILE_READ_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS);
355-
if (_Handle != INVALID_HANDLE_VALUE) { // get file1 info
356-
_Ok1 = GetFileInformationByHandle(_Handle, &_Info1) != 0;
357-
CloseHandle(_Handle);
358+
HANDLE _Handle1 = _FilesysOpenFile(_Fname1, FILE_READ_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS);
359+
if (_Handle1 != INVALID_HANDLE_VALUE) { // get file1 info
360+
_Ok1 = GetFileInformationByHandle(_Handle1, &_Info1) != 0;
361+
}
362+
363+
HANDLE _Handle2 = _FilesysOpenFile(_Fname2, FILE_READ_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS);
364+
if (_Handle2 != INVALID_HANDLE_VALUE) { // get file2 info
365+
_Ok2 = GetFileInformationByHandle(_Handle2, &_Info2) != 0;
366+
CloseHandle(_Handle2);
358367
}
359368

360-
_Handle = _FilesysOpenFile(_Fname2, FILE_READ_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS);
361-
if (_Handle != INVALID_HANDLE_VALUE) { // get file2 info
362-
_Ok2 = GetFileInformationByHandle(_Handle, &_Info2) != 0;
363-
CloseHandle(_Handle);
369+
if (_Handle1 != INVALID_HANDLE_VALUE) {
370+
CloseHandle(_Handle1);
364371
}
365372

366373
if (!_Ok1 && !_Ok2) {

stl/src/filesystem.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,13 @@ void __stdcall __std_fs_directory_iterator_close(_In_ const __std_fs_dir_handle
434434
return __vcp_Copyfile(_Source, _Target, /* _Fail_if_exists = */ false);
435435
}
436436

437-
_Success_(return == __std_win_error::_Success) __std_win_error
437+
struct __std_fs_file_id { // typedef struct _FILE_ID_INFO {
438+
unsigned long long _Volume_serial_number; // ULONGLONG VolumeSerialNumber;
439+
unsigned char _Id[16]; // FILE_ID_128 FileId;
440+
}; // } FILE_ID_INFO, ...;
441+
442+
// TRANSITION, ABI: preserved for binary compatibility
443+
[[nodiscard]] _Success_(return == __std_win_error::_Success) __std_win_error
438444
__stdcall __std_fs_get_file_id(_Out_ __std_fs_file_id* const _Id, _In_z_ const wchar_t* const _Path) noexcept {
439445
__std_win_error _Last_error;
440446
const _STD _Fs_file _Handle(
@@ -448,6 +454,37 @@ _Success_(return == __std_win_error::_Success) __std_win_error
448454
return _Get_file_id_by_handle(_Handle._Get(), reinterpret_cast<FILE_ID_INFO*>(_Id));
449455
}
450456

457+
[[nodiscard]] __std_fs_equivalent_result __stdcall __std_fs_equivalent(
458+
_In_z_ const wchar_t* const _Left_path, _In_z_ const wchar_t* const _Right_path) noexcept {
459+
// See GH-3571: File IDs are only guaranteed to be unique and stable while handles remain open
460+
__std_win_error _Last_error;
461+
const _STD _Fs_file _Left_handle(
462+
_Left_path, __std_access_rights::_File_read_attributes, __std_fs_file_flags::_Backup_semantics, &_Last_error);
463+
if (_Last_error != __std_win_error::_Success) {
464+
return {false, _Last_error};
465+
}
466+
467+
FILE_ID_INFO _Left_info;
468+
_Last_error = _Get_file_id_by_handle(_Left_handle._Get(), &_Left_info);
469+
if (_Last_error != __std_win_error::_Success) {
470+
return {false, _Last_error};
471+
}
472+
473+
const _STD _Fs_file _Right_handle(
474+
_Right_path, __std_access_rights::_File_read_attributes, __std_fs_file_flags::_Backup_semantics, &_Last_error);
475+
if (_Last_error != __std_win_error::_Success) {
476+
return {false, _Last_error};
477+
}
478+
479+
FILE_ID_INFO _Right_info;
480+
_Last_error = _Get_file_id_by_handle(_Right_handle._Get(), &_Right_info);
481+
if (_Last_error != __std_win_error::_Success) {
482+
return {false, _Last_error};
483+
}
484+
485+
return {_CSTD memcmp(&_Left_info, &_Right_info, sizeof(FILE_ID_INFO)) == 0, __std_win_error::_Success};
486+
}
487+
451488
[[nodiscard]] __std_win_error __stdcall __std_fs_create_directory_symbolic_link(
452489
_In_z_ const wchar_t* const _Symlink_file_name, _In_z_ const wchar_t* const _Target_file_name) noexcept {
453490
return _Create_symlink(_Symlink_file_name, _Target_file_name, SYMBOLIC_LINK_FLAG_DIRECTORY);

0 commit comments

Comments
 (0)