@@ -485,16 +485,12 @@ _Success_(return == __std_win_error::_Success) __std_win_error
485485 return __std_win_error{GetLastError ()};
486486}
487487
488- [[nodiscard]] __std_fs_remove_result __stdcall __std_fs_remove (_In_z_ const wchar_t * const _Target) noexcept {
489- // remove _Target without caring whether _Target is a file or directory
490- __std_win_error _Last_error;
491-
492- constexpr auto _Flags = __std_fs_file_flags::_Backup_semantics | __std_fs_file_flags::_Open_reparse_point;
493- const _STD _Fs_file _Handle (_Target, __std_access_rights::_Delete, _Flags, &_Last_error);
494- if (_Last_error != __std_win_error::_Success) {
495- return {false , _Translate_not_found_to_success (_Last_error)};
496- }
488+ struct _File_disposition_info_ex {
489+ DWORD _Flags;
490+ };
497491
492+ [[nodiscard]] _Success_(return == __std_win_error::_Success) __std_win_error
493+ _Set_delete_flag(_In_ __std_fs_file_handle _Handle) {
498494 // From newer Windows SDK than currently used to build vctools:
499495 // #define FILE_DISPOSITION_FLAG_DELETE 0x00000001
500496 // #define FILE_DISPOSITION_FLAG_POSIX_SEMANTICS 0x00000002
@@ -503,33 +499,124 @@ _Success_(return == __std_win_error::_Success) __std_win_error
503499 // DWORD Flags;
504500 // } FILE_DISPOSITION_INFO_EX, *PFILE_DISPOSITION_INFO_EX;
505501
506- struct _File_disposition_info_ex {
507- DWORD _Flags;
508- };
509502 _File_disposition_info_ex _Info_ex{0x3 };
510503
511504 // FileDispositionInfoEx isn't documented in MSDN at the time of this writing, but is present
512505 // in minwinbase.h as of at least 10.0.16299.0
513506 constexpr auto _FileDispositionInfoExClass = static_cast <FILE_INFO_BY_HANDLE_CLASS >(21 );
514- if (SetFileInformationByHandle (_Handle._Get (), _FileDispositionInfoExClass, &_Info_ex, sizeof (_Info_ex))) {
515- return {true , __std_win_error::_Success};
507+ if (SetFileInformationByHandle (
508+ reinterpret_cast <HANDLE >(_Handle), _FileDispositionInfoExClass, &_Info_ex, sizeof (_Info_ex))) {
509+ return __std_win_error::_Success;
516510 }
517511
518- _Last_error = __std_win_error{GetLastError ()};
512+ const auto _Last_error = __std_win_error{GetLastError ()};
519513 switch (_Last_error) {
520514 case __std_win_error::_Invalid_parameter: // Older Windows versions
521515 case __std_win_error::_Invalid_function: // Windows 10 1607
522516 case __std_win_error::_Not_supported: // POSIX delete not supported by the file system
523517 break ; // try non-POSIX delete below
518+ case __std_win_error::_Access_denied: // This might be due to the read-only bit, try to clear it and try again
524519 default :
525- return { false , _Last_error} ;
520+ return _Last_error;
526521 }
527522
528523 FILE_DISPOSITION_INFO _Info{/* .Delete= */ TRUE };
529- if (SetFileInformationByHandle (_Handle._Get (), FileDispositionInfo, &_Info, sizeof (_Info))) {
524+ if (SetFileInformationByHandle (reinterpret_cast <HANDLE >(_Handle), FileDispositionInfo, &_Info, sizeof (_Info))) {
525+ return __std_win_error::_Success;
526+ }
527+
528+ return __std_win_error{GetLastError ()};
529+ }
530+
531+ [[nodiscard]] __std_fs_remove_result __stdcall __std_fs_remove (_In_z_ const wchar_t * const _Target) noexcept {
532+ // remove _Target without caring whether _Target is a file or directory
533+ __std_win_error _Last_error;
534+ bool _Able_to_change_attributes = false ;
535+
536+ constexpr auto _Flags = __std_fs_file_flags::_Backup_semantics | __std_fs_file_flags::_Open_reparse_point;
537+ _STD _Fs_file _Handle (_Target,
538+ __std_access_rights::_Delete | __std_access_rights::_File_read_attributes
539+ | __std_access_rights::_File_write_attributes,
540+ _Flags, &_Last_error);
541+ if (_Last_error == __std_win_error::_Success) {
542+ _Able_to_change_attributes = true ;
543+ } else if (_Last_error == __std_win_error::_Access_denied) {
544+ // change the underlying HANDLE of _Handle
545+ // this might not be a good way of doing it (comment to be removed post review)
546+ _Last_error = __std_fs_open_handle (&_Handle._Raw , _Target, __std_access_rights::_Delete, _Flags);
547+ if (_Last_error != __std_win_error::_Success) {
548+ return {false , _Last_error};
549+ }
550+ } else {
551+ return {false , _Translate_not_found_to_success (_Last_error)};
552+ }
553+
554+ // For Windows 10 1809 or later we have this flag -> FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE 0x10
555+ // This flag also deletes read-only files
556+ // NOTE: This is currently undocumented in MSDN. Refer WinBase.h for declarations
557+
558+ // The following bits are set here
559+ // FILE_DISPOSITION_FLAG_DELETE, FILE_DISPOSITION_FLAG_POSIX_SEMANTICS,
560+ // FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE
561+ _File_disposition_info_ex _Info_ex{0x3 | 0x10 };
562+ constexpr auto _FileDispositionInfoExClass = static_cast <FILE_INFO_BY_HANDLE_CLASS >(21 );
563+ if (SetFileInformationByHandle (
564+ reinterpret_cast <HANDLE >(_Handle._Get ()), _FileDispositionInfoExClass, &_Info_ex, sizeof (_Info_ex))) {
530565 return {true , __std_win_error::_Success};
531566 }
532567
568+ _Last_error = __std_win_error{GetLastError ()};
569+ switch (_Last_error) {
570+ // Windows versions older than 1809
571+ case __std_win_error::_Invalid_parameter:
572+ case __std_win_error::_Invalid_function:
573+ case __std_win_error::_Not_supported:
574+ break ;
575+ default :
576+ // If we fail for reasons other than those mentioned above just return to the caller
577+ return {false , _Last_error};
578+ }
579+
580+ // code path for versions older than 1809
581+ _Last_error = _Set_delete_flag (_Handle._Raw );
582+ if (_Last_error == __std_win_error::_Success) {
583+ return {true , __std_win_error::_Success};
584+ }
585+
586+ if (_Last_error == __std_win_error::_Access_denied && _Able_to_change_attributes) {
587+
588+ FILE_BASIC_INFO _Basic_info;
589+ if (!GetFileInformationByHandleEx (_Handle._Get (), FileBasicInfo, &_Basic_info, sizeof (_Basic_info))) {
590+ return {false , __std_win_error{GetLastError ()}};
591+ }
592+ // check if FILE_ATTRIBUTE_READONLY is set
593+ if (_Basic_info.FileAttributes & FILE_ATTRIBUTE_READONLY ) {
594+ // try to remove FILE_ATTRIBUTE_READONLY
595+ _Basic_info.FileAttributes ^= FILE_ATTRIBUTE_READONLY ;
596+ if (!SetFileInformationByHandle (_Handle._Get (), FileBasicInfo, &_Basic_info, sizeof (_Basic_info))) {
597+ return {false , __std_win_error{GetLastError ()}};
598+ }
599+ // removed FILE_ATTRIBUTE_READONLY, now try to set the delete flag again
600+ _Last_error = _Set_delete_flag (_Handle._Raw );
601+ if (_Last_error == __std_win_error::_Success) {
602+ return {true , __std_win_error::_Success};
603+ } else if (_Last_error == __std_win_error::_Access_denied) {
604+ // looks like we failed to set the delete flag, after clearing the FILE_ATTRIBUTE_READONLY flag
605+ // perform rollback
606+ _Basic_info.FileAttributes |= FILE_ATTRIBUTE_READONLY ;
607+ if (!SetFileInformationByHandle (_Handle._Get (), FileBasicInfo, &_Basic_info, sizeof (_Basic_info))) {
608+ return {false , __std_win_error{GetLastError ()}};
609+ }
610+ return {false , __std_win_error{GetLastError ()}};
611+ }
612+ } else {
613+ return {false , _Last_error};
614+ }
615+ } else {
616+ return {false , _Last_error};
617+ }
618+
619+
533620 return {false , __std_win_error{GetLastError ()}};
534621
535622#undef _SetFileInformationByHandle
0 commit comments