@@ -226,8 +226,6 @@ private void LogDiagnostic(string message, params object[] messageArgs)
226
226
FileState sourceFileState , // The source file
227
227
FileState destinationFileState ) // The destination file
228
228
{
229
- bool destinationFileExists = false ;
230
-
231
229
if ( destinationFileState . DirectoryExists )
232
230
{
233
231
Log . LogErrorWithCodeFromResources ( "Copy.DestinationIsDirectory" , sourceFileState . Name , destinationFileState . Name ) ;
@@ -269,7 +267,14 @@ private void LogDiagnostic(string message, params object[] messageArgs)
269
267
if ( OverwriteReadOnlyFiles )
270
268
{
271
269
MakeFileWriteable ( destinationFileState , true ) ;
272
- destinationFileExists = destinationFileState . FileExists ;
270
+ }
271
+
272
+ // If the destination file is a hard or symbolic link, File.Copy would overwrite the source.
273
+ // To prevent this, we need to delete the existing entry before we Copy or create a link.
274
+ // We could try to figure out if the file is a link, but I can't think of a reason to not simply delete it always.
275
+ if ( ChangeWaves . AreFeaturesEnabled ( ChangeWaves . Wave17_6 ) && destinationFileState . FileExists && ! destinationFileState . IsReadOnly )
276
+ {
277
+ FileUtilities . DeleteNoThrow ( destinationFileState . Name ) ;
273
278
}
274
279
275
280
bool symbolicLinkCreated = false ;
@@ -279,7 +284,7 @@ private void LogDiagnostic(string message, params object[] messageArgs)
279
284
// Create hard links if UseHardlinksIfPossible is true
280
285
if ( UseHardlinksIfPossible )
281
286
{
282
- TryCopyViaLink ( HardLinkComment , MessageImportance . Normal , sourceFileState , destinationFileState , ref destinationFileExists , out hardLinkCreated , ref errorMessage , ( source , destination , errMessage ) => NativeMethods . MakeHardLink ( destination , source , ref errorMessage , Log ) ) ;
287
+ TryCopyViaLink ( HardLinkComment , MessageImportance . Normal , sourceFileState , destinationFileState , out hardLinkCreated , ref errorMessage , ( source , destination , errMessage ) => NativeMethods . MakeHardLink ( destination , source , ref errorMessage , Log ) ) ;
283
288
if ( ! hardLinkCreated )
284
289
{
285
290
if ( UseSymboliclinksIfPossible )
@@ -297,13 +302,14 @@ private void LogDiagnostic(string message, params object[] messageArgs)
297
302
// Create symbolic link if UseSymboliclinksIfPossible is true and hard link is not created
298
303
if ( ! hardLinkCreated && UseSymboliclinksIfPossible )
299
304
{
300
- TryCopyViaLink ( SymbolicLinkComment , MessageImportance . Normal , sourceFileState , destinationFileState , ref destinationFileExists , out symbolicLinkCreated , ref errorMessage , ( source , destination , errMessage ) => NativeMethodsShared . MakeSymbolicLink ( destination , source , ref errorMessage ) ) ;
301
- if ( ! NativeMethodsShared . IsWindows )
302
- {
303
- errorMessage = Log . FormatResourceString ( "Copy.NonWindowsLinkErrorMessage" , "symlink()" , errorMessage ) ;
304
- }
305
+ TryCopyViaLink ( SymbolicLinkComment , MessageImportance . Normal , sourceFileState , destinationFileState , out symbolicLinkCreated , ref errorMessage , ( source , destination , errMessage ) => NativeMethodsShared . MakeSymbolicLink ( destination , source , ref errorMessage ) ) ;
305
306
if ( ! symbolicLinkCreated )
306
307
{
308
+ if ( ! NativeMethodsShared . IsWindows )
309
+ {
310
+ errorMessage = Log . FormatResourceString ( "Copy.NonWindowsLinkErrorMessage" , "symlink()" , errorMessage ) ;
311
+ }
312
+
307
313
Log . LogMessage ( MessageImportance . Normal , RetryingAsFileCopy , sourceFileState . Name , destinationFileState . Name , errorMessage ) ;
308
314
}
309
315
}
@@ -324,41 +330,28 @@ private void LogDiagnostic(string message, params object[] messageArgs)
324
330
Log . LogMessage ( MessageImportance . Normal , FileComment , sourceFilePath , destinationFilePath ) ;
325
331
326
332
File . Copy ( sourceFileState . Name , destinationFileState . Name , true ) ;
333
+
334
+ // If the destinationFile file exists, then make sure it's read-write.
335
+ // The File.Copy command copies attributes, but our copy needs to
336
+ // leave the file writeable.
337
+ if ( sourceFileState . IsReadOnly )
338
+ {
339
+ destinationFileState . Reset ( ) ;
340
+ MakeFileWriteable ( destinationFileState , false ) ;
341
+ }
327
342
}
328
343
329
344
// Files were successfully copied or linked. Those are equivalent here.
330
345
WroteAtLeastOneFile = true ;
331
346
332
- destinationFileState . Reset ( ) ;
333
-
334
- // If the destinationFile file exists, then make sure it's read-write.
335
- // The File.Copy command copies attributes, but our copy needs to
336
- // leave the file writeable.
337
- if ( sourceFileState . IsReadOnly )
338
- {
339
- MakeFileWriteable ( destinationFileState , false ) ;
340
- }
341
-
342
347
return true ;
343
348
}
344
349
345
- private void TryCopyViaLink ( string linkComment , MessageImportance messageImportance , FileState sourceFileState , FileState destinationFileState , ref bool destinationFileExists , out bool linkCreated , ref string errorMessage , Func < string , string , string , bool > createLink )
350
+ private void TryCopyViaLink ( string linkComment , MessageImportance messageImportance , FileState sourceFileState , FileState destinationFileState , out bool linkCreated , ref string errorMessage , Func < string , string , string , bool > createLink )
346
351
{
347
352
// Do not log a fake command line as well, as it's superfluous, and also potentially expensive
348
353
Log . LogMessage ( MessageImportance . Normal , linkComment , sourceFileState . Name , destinationFileState . Name ) ;
349
354
350
- if ( ! OverwriteReadOnlyFiles )
351
- {
352
- destinationFileExists = destinationFileState . FileExists ;
353
- }
354
-
355
- // CreateHardLink and CreateSymbolicLink cannot overwrite an existing file or link
356
- // so we need to delete the existing entry before we create the hard or symbolic link.
357
- if ( destinationFileExists )
358
- {
359
- FileUtilities . DeleteNoThrow ( destinationFileState . Name ) ;
360
- }
361
-
362
355
linkCreated = createLink ( sourceFileState . Name , destinationFileState . Name , errorMessage ) ;
363
356
}
364
357
@@ -826,6 +819,11 @@ private bool DoCopyWithRetries(FileState sourceFileState, FileState destinationF
826
819
LogDiagnostic ( "Retrying on ERROR_ACCESS_DENIED because MSBUILDALWAYSRETRY = 1" ) ;
827
820
}
828
821
}
822
+ else if ( code == NativeMethods . ERROR_INVALID_FILENAME )
823
+ {
824
+ // Invalid characters used in file name, no point retrying.
825
+ throw ;
826
+ }
829
827
830
828
if ( e is UnauthorizedAccessException )
831
829
{
0 commit comments