Index: llvm/include/llvm/Support/FileSystem.h =================================================================== --- llvm/include/llvm/Support/FileSystem.h +++ llvm/include/llvm/Support/FileSystem.h @@ -343,7 +343,11 @@ /// platform-specific error code. std::error_code remove_directories(const Twine &path, bool IgnoreErrors = true); -/// @brief Rename \a from to \a to. Files are renamed as if by POSIX rename(). +/// @brief Rename \a from to \a to. +/// +/// Files are renamed as if by POSIX rename(), except that on Windows there may +/// be a short interval of time during which the destination file does not +/// exist. /// /// @param from The path to rename from. /// @param to The path to rename to. This is created. Index: llvm/lib/Support/Windows/Path.inc =================================================================== --- llvm/lib/Support/Windows/Path.inc +++ llvm/lib/Support/Windows/Path.inc @@ -359,65 +359,112 @@ return is_local_internal(FinalPath, Result); } -std::error_code rename(const Twine &from, const Twine &to) { - // Convert to utf-16. - SmallVector wide_from; - SmallVector wide_to; - if (std::error_code ec = widenPath(from, wide_from)) - return ec; - if (std::error_code ec = widenPath(to, wide_to)) - return ec; - - std::error_code ec = std::error_code(); +static std::error_code rename_internal(HANDLE FromHandle, const Twine &To, + bool ReplaceIfExists) { + SmallVector ToWide; + if (auto EC = widenPath(To, ToWide)) + return EC; - // Retry while we see recoverable errors. - // System scanners (eg. indexer) might open the source file when it is written - // and closed. + std::vector RenameInfoBuf(sizeof(FILE_RENAME_INFO) + + (ToWide.size() * sizeof(wchar_t))); + FILE_RENAME_INFO &RenameInfo = + *reinterpret_cast(RenameInfoBuf.data()); + RenameInfo.ReplaceIfExists = ReplaceIfExists; + RenameInfo.RootDirectory = 0; + RenameInfo.FileNameLength = ToWide.size(); + std::copy(ToWide.begin(), ToWide.end(), RenameInfo.FileName); + + if (!SetFileInformationByHandle(FromHandle, FileRenameInfo, &RenameInfo, + RenameInfoBuf.size())) + return mapWindowsError(GetLastError()); - bool TryReplace = true; + return std::error_code(); +} - for (int i = 0; i < 2000; i++) { - if (i > 0) - ::Sleep(1); +std::error_code rename(const Twine &From, const Twine &To) { + // Convert to utf-16. + SmallVector WideFrom; + SmallVector WideTo; + if (std::error_code EC = widenPath(From, WideFrom)) + return EC; + if (std::error_code EC = widenPath(To, WideTo)) + return EC; - if (TryReplace) { - // Try ReplaceFile first, as it is able to associate a new data stream - // with the destination even if the destination file is currently open. - if (::ReplaceFileW(wide_to.data(), wide_from.data(), NULL, 0, NULL, NULL)) - return std::error_code(); + ScopedFileHandle FromHandle( + ::CreateFileW(WideFrom.begin(), GENERIC_READ | DELETE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + if (!FromHandle) + return mapWindowsError(GetLastError()); - DWORD ReplaceError = ::GetLastError(); - ec = mapWindowsError(ReplaceError); + while (1) { + auto EC = rename_internal(FromHandle, To, true); + if (!EC || EC != errc::permission_denied) + return EC; - // If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or - // ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW(). - if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT || - ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) { - TryReplace = false; - continue; - } - // If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry - // using ReplaceFileW(). - if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED) + // The destination file probably exists and is currently open in another + // process. Rename it to move it out of the way of the source file. Use + // FILE_FLAG_DELETE_ON_CLOSE to arrange for the destination file to be + // deleted when the other process closes it. + ScopedFileHandle ToHandle( + ::CreateFileW(WideTo.begin(), GENERIC_READ | DELETE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL)); + if (!ToHandle) { + auto EC = mapWindowsError(GetLastError()); + // Another process might have raced with us and moved the existing file + // out of the way before we had a chance to open it. If that happens, try + // to rename the source file again. + if (EC == errc::no_such_file_or_directory) continue; - // We get ERROR_FILE_NOT_FOUND if the destination file is missing. - // MoveFileEx can handle this case. - if (ReplaceError != ERROR_ACCESS_DENIED && - ReplaceError != ERROR_FILE_NOT_FOUND && - ReplaceError != ERROR_SHARING_VIOLATION) - break; + return EC; } - if (::MoveFileExW(wide_from.begin(), wide_to.begin(), - MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) - return std::error_code(); + BY_HANDLE_FILE_INFORMATION FI; + if (!GetFileInformationByHandle(ToHandle, &FI)) + return mapWindowsError(GetLastError()); + + // Try to find a unique new name for the destination file. + int UniqueId = 0; + while (1) { + std::string TmpFilename = (To + ".tmp" + utostr(UniqueId)).str(); + if (auto EC = rename_internal(ToHandle, TmpFilename, false)) { + if (EC == errc::file_exists || EC == errc::permission_denied) { + // Again, another process might have raced with us and moved the file + // before we could move it. Check whether this is the case, as it + // might have caused the permission denied error. If that was the + // case, we don't need to move it ourselves. + ScopedFileHandle ToHandle2(::CreateFileW( + WideTo.begin(), 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + if (!ToHandle2) { + auto EC = mapWindowsError(GetLastError()); + if (EC == errc::no_such_file_or_directory) + break; + return EC; + } + BY_HANDLE_FILE_INFORMATION FI2; + if (!GetFileInformationByHandle(ToHandle2, &FI2)) + return mapWindowsError(GetLastError()); + if (FI.nFileIndexHigh != FI2.nFileIndexHigh || + FI.nFileIndexLow != FI2.nFileIndexLow || + FI.dwVolumeSerialNumber != FI2.dwVolumeSerialNumber) + break; + ++UniqueId; + continue; + } + return EC; + } + break; + } - DWORD MoveError = ::GetLastError(); - ec = mapWindowsError(MoveError); - if (MoveError != ERROR_ACCESS_DENIED) break; + // Okay, the old destination file has probably been moved out of the way at + // this point, so try to rename the source file again. Still, another + // process might have raced with us to create and open the destination + // file, so we need to keep doing this until we succeed. } - - return ec; } std::error_code resize_file(int FD, uint64_t Size) {