Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue8273 corrupt nu get cache #8275

Merged
merged 4 commits into from
Mar 15, 2023

Conversation

manfred-brands
Copy link
Contributor

Fixes #8273

Context

Prevent overwriting the source of a hard/symbolic link.

Changes Made

Always delete the destination file (unless readonly and OverwriteReadOnlyFiles is false)

Testing

Added unit test.

Notes

@manfred-brands manfred-brands force-pushed the issue8273_corruptNuGetCache branch from 35b3ddd to c88d6a3 Compare January 4, 2023 02:04
@Forgind
Copy link
Member

Forgind commented Jan 19, 2023

I opened dotnet/runtime#80832

Jozkee labeled it 'bug', so I'm guessing this is a runtime bug rather than an MSBuild bug. He also noted that File.Move does not have the same issue. People using MSBuild's Copy often don't care whether the source file exists afterwards, but we can't guarantee that, so I don't think we can use File.Move, unfortunately.

We're still talking internally about whether we want to:

  1. Take this
  2. Close this in favor of the runtime fixing it
  3. Take this but consider reverting it if the runtime changes File.Copy's behavior
  4. Possible other option?

What happens on .NET Framework (and older .NET Core) may be an important question, but I haven't tested that yet.

Sorry we're being a bit slow; I just wanted to give you something of an update 🙂

@Forgind
Copy link
Member

Forgind commented Feb 13, 2023

It looks like runtime isn't planning to change anything in the immediate future.

We're thinking we will likely take this, but our primary concern is performance. @rokonec volunteered to take a quick look into that to see if he can find anything problematic. Additionally, would you mind putting this behind a change wave? That will turn it on by default for all users but provide a way for users to opt out should they hit perf problems.

@Forgind Forgind requested a review from rokonec February 13, 2023 17:14
@@ -324,41 +329,28 @@ private void LogDiagnostic(string message, params object[] messageArgs)
Log.LogMessage(MessageImportance.Normal, FileComment, sourceFilePath, destinationFilePath);

File.Copy(sourceFileState.Name, destinationFileState.Name, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the destination is always deleted, I think it would be a good idea to set the overwrite flag to false to protect us from accidental overwrites.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is probably better to fail in case someone gets in between than overwriting a link. I will update the PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not believe it is necessary. Sometimes logic might change to not delete it, or new Copy task argument will be introduced, or some weird concurrent logic is relaying with last win. I would recommend to keep it as is, but if you decide to change it, it has to go under same ChangeWave or same conditions as deleting files.

@Forgind
Copy link
Member

Forgind commented Feb 27, 2023

Ping @rokonec

@marcin-krystianc
Copy link
Contributor

It looks like runtime isn't planning to change anything in the immediate future.

We're thinking we will likely take this, but our primary concern is performance. @rokonec volunteered to take a quick look into that to see if he can find anything problematic. Additionally, would you mind putting this behind a change wave? That will turn it on by default for all users but provide a way for users to opt out should they hit perf problems.

FWIW, I've run some benchmarks (building a large solution, with and without a change from this PR), and here are my conclusions:

  • When building on Windows, the variant with forced FileUtilities.DeleteNoThrow is actually faster (2m 25s vs 2m 35s). Unfortunately, I cannot share the tested solution here as it contains IP. I attribute this difference to AntiVirus software as I suspect that it handles file deletes and file overwrites differently.
  • On Linux I didn't notice any real difference, in both cases, the build time was around 4 minutes:
Case 1 (forced `FileUtilities.DeleteNoThrow`)
Time Elapsed 00:03:59.28
Time Elapsed 00:03:58.44
Time Elapsed 00:04:01.78
Time Elapsed 00:03:57.04
Time Elapsed 00:04:02.73

Case 2:
Time Elapsed 00:03:59.87
Time Elapsed 00:03:56.23
Time Elapsed 00:03:58.41
Time Elapsed 00:03:58.95
Time Elapsed 00:04:01.58

Tested solution:
LargeAppWithPrivatePackagesCentralisedNGBVRemoved.zip
Test command:

  • cd ~/LargeAppWithPrivatePackagesCentralisedNGBVRemoved/solution$
  • echo "" >> Directory.Build.props && ~/dotnet/dotnet build -clp:summary --no-restore /bl /p:UseLinks=false

rokonec

This comment was marked as outdated.

Copy link
Member

@rokonec rokonec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have spent some time to measure it and although it is very slightly slower, it is, IMO, acceptable. It took less then 0.1 % on my tests.
I went away and created some minor suggestions...
Good job.

src/Tasks.UnitTests/Copy_Tests.cs Outdated Show resolved Hide resolved
src/Tasks/Copy.cs Outdated Show resolved Hide resolved
@manfred-brands manfred-brands force-pushed the issue8273_corruptNuGetCache branch from 0cdf3d7 to cb5d0de Compare March 13, 2023 04:39
Co-authored-by: Roman Konecny <rokonecn@microsoft.com>
@manfred-brands
Copy link
Contributor Author

@rokonec I have applied your changes and rebased the branch on current main branch.

Copy link
Member

@Forgind Forgind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

// If the destination file is a hard or symbolic link, File.Copy would overwrite the source.
// To prevent this, we need to delete the existing entry before we Copy or create a link.
// 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.
if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_6) && destinationFileState.FileExists && !destinationFileState.IsReadOnly)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also take into account OverwriteReadOnlyFiles and also delete them in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if OverwriteReadOnlyFiles is set, the file is made writable and hence deleted here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and now I see it 6 lines up. Stupid question, thanks!

@Forgind Forgind added the merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now. label Mar 14, 2023
@JaynieBai JaynieBai merged commit a93882f into dotnet:main Mar 15, 2023
JanKrivanek pushed a commit to JanKrivanek/msbuild that referenced this pull request Mar 27, 2023
Fixes dotnet#8273

Context
Prevent overwriting the source of a hard/symbolic link.

Changes Made
Always delete the destination file (unless readonly and OverwriteReadOnlyFiles is false)

Testing
Added unit test.
Forgind added a commit to Forgind/msbuild that referenced this pull request Apr 20, 2023
JaynieBai pushed a commit that referenced this pull request Apr 23, 2023
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 <Forgind@users.noreply.github.com>
Forgind added a commit that referenced this pull request May 5, 2023
Fixes #8684
Fixes #8273

Context
After #8275, we delete any destination file as part of the Copy task if we determine that we really should copy onto it. Unfortunately, if we try to copy a file onto itself, we delete it before we can copy onto itself, which just means it's gone. Fortunately, we have a check earlier that ensures that we skip any copy operation from a location to the same location. Unfortunately, it's a direct string comparison that doesn't evaluate to full paths first, so it misses slightly more complicated examples.

Changes Made
Take into account full paths

Testing
Unit tests + manual test that it doesn't delete the file anymore

Notes
This implementation tries to remove now-unnecessary full path derivations downstream, hence some added complexity, but it still means extra computation on the happy path if we end up creating a hard/symbolic link. An alternate direction eliminating any full path derivations on the happy path would save about 4% of Copy's execution time, per a quick perf test. (With how many samples I used, "no change" is within a standard deviation.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Usage of hard or symbolic linking leads to NuGet cache corruption
5 participants