Skip to content

Commit f6feb85

Browse files
authored
Revert "Issue8273 corrupt nu get cache (#8275)" (#8686)
This reverts commit a93882f. This is a temporary fix for #8684 The current plan is to revert #8275 in 17.6, as it caused some difficulties, and try to bring it back in 17.7 via #8685. Summary #8275 fixed a longstanding confusing and unfortunate behavior in MSBuild in which passing the Copy task a symlink as its destination would copy the source file onto the destination of the symlink rather than overwriting the symlink. Unfortunately, it also introduced a new issue in which copying a file onto itself could often just delete the file instead of copying anything. Customers reported this issue. Customer Impact Projects that copy a file onto itself using the Copy task without passing identical paths for source and destination instead delete the file without necessarily even logging an error. Regression? Yes, from #8275. Testing Unit tests and manually tested that the repro described in #8684 no longer works. Risk Minimal (straight revert of the commit that caused the bug) --------- Co-authored-by: Forgind <[email protected]>
1 parent 8ffc3fe commit f6feb85

File tree

4 files changed

+33
-113
lines changed

4 files changed

+33
-113
lines changed

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the MIT license. See License.txt in the project root for full license information. -->
33
<Project>
44
<PropertyGroup>
5-
<VersionPrefix>17.6.1</VersionPrefix><DotNetFinalVersionKind>release</DotNetFinalVersionKind>
5+
<VersionPrefix>17.6.2</VersionPrefix><DotNetFinalVersionKind>release</DotNetFinalVersionKind>
66
<PackageValidationBaselineVersion>17.5.0</PackageValidationBaselineVersion>
77
<AssemblyVersion>15.1.0.0</AssemblyVersion>
88
<PreReleaseVersionLabel>preview</PreReleaseVersionLabel>

src/Tasks.UnitTests/Copy_Tests.cs

Lines changed: 0 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,87 +2096,6 @@ public void InvalidErrorIfLinkFailed()
20962096
Assert.False(result);
20972097
engine.AssertLogContains("MSB3892");
20982098
}
2099-
2100-
/// <summary>
2101-
/// An existing link source should not be modified.
2102-
/// </summary>
2103-
/// <remarks>
2104-
/// Related to issue [#8273](https://github.com/dotnet/msbuild/issues/8273)
2105-
/// </remarks>
2106-
[Theory]
2107-
[InlineData(false, false)]
2108-
[InlineData(false, true)]
2109-
[InlineData(true, false)]
2110-
public void DoNotCorruptSourceOfLink(bool useHardLink, bool useSymbolicLink)
2111-
{
2112-
string sourceFile1 = FileUtilities.GetTemporaryFile();
2113-
string sourceFile2 = FileUtilities.GetTemporaryFile();
2114-
string temp = Path.GetTempPath();
2115-
string destFolder = Path.Combine(temp, "2A333ED756AF4dc392E728D0F864A398");
2116-
string destFile = Path.Combine(destFolder, "The Destination");
2117-
2118-
try
2119-
{
2120-
File.WriteAllText(sourceFile1, "This is the first source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
2121-
File.WriteAllText(sourceFile2, "This is the second source temp file."); // HIGHCHAR: Test writes in UTF8 without preamble.
2122-
2123-
// Don't create the dest folder, let task do that
2124-
2125-
ITaskItem[] sourceFiles = { new TaskItem(sourceFile1) };
2126-
ITaskItem[] destinationFiles = { new TaskItem(destFile) };
2127-
2128-
var me = new MockEngine(true);
2129-
var t = new Copy
2130-
{
2131-
RetryDelayMilliseconds = 1, // speed up tests!
2132-
BuildEngine = me,
2133-
SourceFiles = sourceFiles,
2134-
DestinationFiles = destinationFiles,
2135-
SkipUnchangedFiles = true,
2136-
UseHardlinksIfPossible = useHardLink,
2137-
UseSymboliclinksIfPossible = useSymbolicLink,
2138-
};
2139-
2140-
bool success = t.Execute();
2141-
2142-
Assert.True(success); // "success"
2143-
Assert.True(File.Exists(destFile)); // "destination exists"
2144-
2145-
string destinationFileContents = File.ReadAllText(destFile);
2146-
Assert.Equal("This is the first source temp file.", destinationFileContents);
2147-
2148-
sourceFiles = new TaskItem[] { new TaskItem(sourceFile2) };
2149-
2150-
t = new Copy
2151-
{
2152-
RetryDelayMilliseconds = 1, // speed up tests!
2153-
BuildEngine = me,
2154-
SourceFiles = sourceFiles,
2155-
DestinationFiles = destinationFiles,
2156-
SkipUnchangedFiles = true,
2157-
UseHardlinksIfPossible = false,
2158-
UseSymboliclinksIfPossible = false,
2159-
};
2160-
2161-
success = t.Execute();
2162-
2163-
Assert.True(success); // "success"
2164-
Assert.True(File.Exists(destFile)); // "destination exists"
2165-
2166-
destinationFileContents = File.ReadAllText(destFile);
2167-
Assert.Equal("This is the second source temp file.", destinationFileContents);
2168-
2169-
// Read the source file (it should not have been overwritten)
2170-
string sourceFileContents = File.ReadAllText(sourceFile1);
2171-
Assert.Equal("This is the first source temp file.", sourceFileContents);
2172-
2173-
((MockEngine)t.BuildEngine).AssertLogDoesntContain("MSB3026"); // Didn't do retries
2174-
}
2175-
finally
2176-
{
2177-
Helpers.DeleteFiles(sourceFile1, sourceFile2, destFile);
2178-
}
2179-
}
21802099
}
21812100

21822101
public class CopyHardLink_Tests : Copy_Tests

src/Tasks/Copy.cs

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ private void LogDiagnostic(string message, params object[] messageArgs)
226226
FileState sourceFileState, // The source file
227227
FileState destinationFileState) // The destination file
228228
{
229+
bool destinationFileExists = false;
230+
229231
if (destinationFileState.DirectoryExists)
230232
{
231233
Log.LogErrorWithCodeFromResources("Copy.DestinationIsDirectory", sourceFileState.Name, destinationFileState.Name);
@@ -267,14 +269,7 @@ private void LogDiagnostic(string message, params object[] messageArgs)
267269
if (OverwriteReadOnlyFiles)
268270
{
269271
MakeFileWriteable(destinationFileState, true);
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);
272+
destinationFileExists = destinationFileState.FileExists;
278273
}
279274

280275
bool symbolicLinkCreated = false;
@@ -284,7 +279,7 @@ private void LogDiagnostic(string message, params object[] messageArgs)
284279
// Create hard links if UseHardlinksIfPossible is true
285280
if (UseHardlinksIfPossible)
286281
{
287-
TryCopyViaLink(HardLinkComment, MessageImportance.Normal, sourceFileState, destinationFileState, out hardLinkCreated, ref errorMessage, (source, destination, errMessage) => NativeMethods.MakeHardLink(destination, source, ref errorMessage, Log));
282+
TryCopyViaLink(HardLinkComment, MessageImportance.Normal, sourceFileState, destinationFileState, ref destinationFileExists, out hardLinkCreated, ref errorMessage, (source, destination, errMessage) => NativeMethods.MakeHardLink(destination, source, ref errorMessage, Log));
288283
if (!hardLinkCreated)
289284
{
290285
if (UseSymboliclinksIfPossible)
@@ -302,14 +297,13 @@ private void LogDiagnostic(string message, params object[] messageArgs)
302297
// Create symbolic link if UseSymboliclinksIfPossible is true and hard link is not created
303298
if (!hardLinkCreated && UseSymboliclinksIfPossible)
304299
{
305-
TryCopyViaLink(SymbolicLinkComment, MessageImportance.Normal, sourceFileState, destinationFileState, out symbolicLinkCreated, ref errorMessage, (source, destination, errMessage) => NativeMethodsShared.MakeSymbolicLink(destination, source, ref errorMessage));
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+
}
306305
if (!symbolicLinkCreated)
307306
{
308-
if (!NativeMethodsShared.IsWindows)
309-
{
310-
errorMessage = Log.FormatResourceString("Copy.NonWindowsLinkErrorMessage", "symlink()", errorMessage);
311-
}
312-
313307
Log.LogMessage(MessageImportance.Normal, RetryingAsFileCopy, sourceFileState.Name, destinationFileState.Name, errorMessage);
314308
}
315309
}
@@ -330,28 +324,41 @@ private void LogDiagnostic(string message, params object[] messageArgs)
330324
Log.LogMessage(MessageImportance.Normal, FileComment, sourceFilePath, destinationFilePath);
331325

332326
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-
}
342327
}
343328

344329
// Files were successfully copied or linked. Those are equivalent here.
345330
WroteAtLeastOneFile = true;
346331

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+
347342
return true;
348343
}
349344

350-
private void TryCopyViaLink(string linkComment, MessageImportance messageImportance, FileState sourceFileState, FileState destinationFileState, out bool linkCreated, ref string errorMessage, Func<string, string, string, bool> createLink)
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)
351346
{
352347
// Do not log a fake command line as well, as it's superfluous, and also potentially expensive
353348
Log.LogMessage(MessageImportance.Normal, linkComment, sourceFileState.Name, destinationFileState.Name);
354349

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+
355362
linkCreated = createLink(sourceFileState.Name, destinationFileState.Name, errorMessage);
356363
}
357364

@@ -819,11 +826,6 @@ private bool DoCopyWithRetries(FileState sourceFileState, FileState destinationF
819826
LogDiagnostic("Retrying on ERROR_ACCESS_DENIED because MSBUILDALWAYSRETRY = 1");
820827
}
821828
}
822-
else if (code == NativeMethods.ERROR_INVALID_FILENAME)
823-
{
824-
// Invalid characters used in file name, no point retrying.
825-
throw;
826-
}
827829

828830
if (e is UnauthorizedAccessException)
829831
{

src/Tasks/NativeMethods.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,6 @@ internal static class NativeMethods
537537

538538
internal const int HRESULT_E_CLASSNOTREGISTERED = -2147221164;
539539

540-
internal const int ERROR_INVALID_FILENAME = -2147024773; // Illegal characters in name
541540
internal const int ERROR_ACCESS_DENIED = -2147024891; // ACL'd or r/o
542541
internal const int ERROR_SHARING_VIOLATION = -2147024864; // File locked by another use
543542

0 commit comments

Comments
 (0)