@@ -359,65 +359,126 @@ std::error_code is_local(int FD, bool &Result) {
359
359
return is_local_internal (FinalPath, Result);
360
360
}
361
361
362
- std::error_code rename (const Twine &from, const Twine &to) {
363
- // Convert to utf-16.
364
- SmallVector<wchar_t , 128 > wide_from;
365
- SmallVector<wchar_t , 128 > wide_to;
366
- if (std::error_code ec = widenPath (from, wide_from))
367
- return ec;
368
- if (std::error_code ec = widenPath (to, wide_to))
369
- return ec;
370
-
371
- std::error_code ec = std::error_code ();
362
+ static std::error_code rename_internal (HANDLE FromHandle, const Twine &To,
363
+ bool ReplaceIfExists) {
364
+ SmallVector<wchar_t , 0 > ToWide;
365
+ if (auto EC = widenPath (To, ToWide))
366
+ return EC;
372
367
373
- // Retry while we see recoverable errors.
374
- // System scanners (eg. indexer) might open the source file when it is written
375
- // and closed.
368
+ std::vector<char > RenameInfoBuf (sizeof (FILE_RENAME_INFO) - sizeof (wchar_t ) +
369
+ (ToWide.size () * sizeof (wchar_t )));
370
+ FILE_RENAME_INFO &RenameInfo =
371
+ *reinterpret_cast <FILE_RENAME_INFO *>(RenameInfoBuf.data ());
372
+ RenameInfo.ReplaceIfExists = ReplaceIfExists;
373
+ RenameInfo.RootDirectory = 0 ;
374
+ RenameInfo.FileNameLength = ToWide.size ();
375
+ std::copy (ToWide.begin (), ToWide.end (), RenameInfo.FileName );
376
+
377
+ if (!SetFileInformationByHandle (FromHandle, FileRenameInfo, &RenameInfo,
378
+ RenameInfoBuf.size ()))
379
+ return mapWindowsError (GetLastError ());
376
380
377
- bool TryReplace = true ;
381
+ return std::error_code ();
382
+ }
378
383
379
- for (int i = 0 ; i < 2000 ; i++) {
380
- if (i > 0 )
381
- ::Sleep (1 );
384
+ std::error_code rename (const Twine &From, const Twine &To) {
385
+ // Convert to utf-16.
386
+ SmallVector<wchar_t , 128 > WideFrom;
387
+ SmallVector<wchar_t , 128 > WideTo;
388
+ if (std::error_code EC = widenPath (From, WideFrom))
389
+ return EC;
390
+ if (std::error_code EC = widenPath (To, WideTo))
391
+ return EC;
382
392
383
- if (TryReplace) {
384
- // Try ReplaceFile first, as it is able to associate a new data stream
385
- // with the destination even if the destination file is currently open.
386
- if (::ReplaceFileW (wide_to.data (), wide_from.data (), NULL , 0 , NULL , NULL ))
387
- return std::error_code ();
393
+ ScopedFileHandle FromHandle;
394
+ // Retry this a few times to defeat badly behaved file system scanners.
395
+ for (unsigned Retry = 0 ; Retry != 200 ; ++Retry) {
396
+ if (Retry != 0 )
397
+ ::Sleep (10 );
398
+ FromHandle =
399
+ ::CreateFileW (WideFrom.begin(), GENERIC_READ | DELETE,
400
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
401
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
402
+ if (FromHandle)
403
+ break ;
404
+ }
405
+ if (!FromHandle)
406
+ return mapWindowsError (GetLastError ());
388
407
389
- DWORD ReplaceError = ::GetLastError ();
390
- ec = mapWindowsError (ReplaceError);
408
+ // We normally expect this loop to succeed after a few iterations. If it
409
+ // requires more than 200 tries, it's more likely that the failures are due to
410
+ // a true error, so stop trying.
411
+ for (unsigned Retry = 0 ; Retry != 200 ; ++Retry) {
412
+ auto EC = rename_internal (FromHandle, To, true );
413
+ if (!EC || EC != errc::permission_denied)
414
+ return EC;
391
415
392
- // If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or
393
- // ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW().
394
- if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT ||
395
- ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) {
396
- TryReplace = false ;
416
+ // The destination file probably exists and is currently open in another
417
+ // process, either because the file was opened without FILE_SHARE_DELETE or
418
+ // it is mapped into memory (e.g. using MemoryBuffer). Rename it in order to
419
+ // move it out of the way of the source file. Use FILE_FLAG_DELETE_ON_CLOSE
420
+ // to arrange for the destination file to be deleted when the other process
421
+ // closes it.
422
+ ScopedFileHandle ToHandle (
423
+ ::CreateFileW (WideTo.begin(), GENERIC_READ | DELETE,
424
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
425
+ NULL, OPEN_EXISTING,
426
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL));
427
+ if (!ToHandle) {
428
+ auto EC = mapWindowsError (GetLastError ());
429
+ // Another process might have raced with us and moved the existing file
430
+ // out of the way before we had a chance to open it. If that happens, try
431
+ // to rename the source file again.
432
+ if (EC == errc::no_such_file_or_directory)
397
433
continue ;
398
- }
399
- // If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry
400
- // using ReplaceFileW().
401
- if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED)
402
- continue ;
403
- // We get ERROR_FILE_NOT_FOUND if the destination file is missing.
404
- // MoveFileEx can handle this case.
405
- if (ReplaceError != ERROR_ACCESS_DENIED &&
406
- ReplaceError != ERROR_FILE_NOT_FOUND &&
407
- ReplaceError != ERROR_SHARING_VIOLATION)
408
- break ;
434
+ return EC;
409
435
}
410
436
411
- if (::MoveFileExW (wide_from.begin (), wide_to.begin (),
412
- MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING))
413
- return std::error_code ();
437
+ BY_HANDLE_FILE_INFORMATION FI;
438
+ if (!GetFileInformationByHandle (ToHandle, &FI))
439
+ return mapWindowsError (GetLastError ());
440
+
441
+ // Try to find a unique new name for the destination file.
442
+ for (unsigned UniqueId = 0 ; UniqueId != 200 ; ++UniqueId) {
443
+ std::string TmpFilename = (To + " .tmp" + utostr (UniqueId)).str ();
444
+ if (auto EC = rename_internal (ToHandle, TmpFilename, false )) {
445
+ if (EC == errc::file_exists || EC == errc::permission_denied) {
446
+ // Again, another process might have raced with us and moved the file
447
+ // before we could move it. Check whether this is the case, as it
448
+ // might have caused the permission denied error. If that was the
449
+ // case, we don't need to move it ourselves.
450
+ ScopedFileHandle ToHandle2 (::CreateFileW (
451
+ WideTo.begin (), 0 ,
452
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL ,
453
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ));
454
+ if (!ToHandle2) {
455
+ auto EC = mapWindowsError (GetLastError ());
456
+ if (EC == errc::no_such_file_or_directory)
457
+ break ;
458
+ return EC;
459
+ }
460
+ BY_HANDLE_FILE_INFORMATION FI2;
461
+ if (!GetFileInformationByHandle (ToHandle2, &FI2))
462
+ return mapWindowsError (GetLastError ());
463
+ if (FI.nFileIndexHigh != FI2.nFileIndexHigh ||
464
+ FI.nFileIndexLow != FI2.nFileIndexLow ||
465
+ FI.dwVolumeSerialNumber != FI2.dwVolumeSerialNumber )
466
+ break ;
467
+ continue ;
468
+ }
469
+ return EC;
470
+ }
471
+ break ;
472
+ }
414
473
415
- DWORD MoveError = ::GetLastError ();
416
- ec = mapWindowsError (MoveError);
417
- if (MoveError != ERROR_ACCESS_DENIED) break ;
474
+ // Okay, the old destination file has probably been moved out of the way at
475
+ // this point, so try to rename the source file again. Still, another
476
+ // process might have raced with us to create and open the destination
477
+ // file, so we need to keep doing this until we succeed.
418
478
}
419
479
420
- return ec;
480
+ // The most likely root cause.
481
+ return errc::permission_denied;
421
482
}
422
483
423
484
std::error_code resize_file (int FD, uint64_t Size) {
0 commit comments