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

Perform atomic write to avoid corrupting files #12362

Closed

Conversation

marcospb19
Copy link

Fix #11386 with @epage's suggestion, atomic writes.

Instead of applying the fix just to cargo-add, I applied it to everything, we might want to review each specific case to tell if cases.

this fixes issue where Ctrl+C (SIGINT or any other interruption) leaves
files (like `Cargo.toml`) corrupted, and potentially empty
@rustbot
Copy link
Collaborator

rustbot commented Jul 15, 2023

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @weihanglo (or someone else) soon.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@rustbot rustbot added A-build-scripts Area: build.rs scripts A-cache-messages Area: caching of compiler messages A-cfg-expr Area: Platform cfg expressions A-cli Area: Command-line interface, option parsing, etc. A-manifest Area: Cargo.toml issues A-rebuild-detection Area: rebuild detection and fingerprinting Command-fix Command-new Command-remove Command-vendor S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 15, 2023
@marcospb19
Copy link
Author

marcospb19 commented Jul 15, 2023

So @epage I'm getting this error in local testing when trying to copy from /tmp (generated by tempfile crate) to current folder:

`Result::unwrap()` on an `Err` value: Os { code: 18, kind: CrossesDevices, message: "Invalid cross-device link" }'

So I need a temporary file in the same filesystem? Where is a good place for that?

EDIT: I guess we could pick target/temporary_file and we should be OK? File disappears after renaming anyways.
EDIT2: nevermind, target doesn't exist after cargo new and cargo add.

.write_all(contents.as_ref())
.with_context(|| format!("failed to write to `{}`", temporary_file.path().display()))?;

fs::rename(temporary_file.path(), path)
Copy link
Contributor

Choose a reason for hiding this comment

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

So @epage I'm getting this error in local testing when trying to copy from /tmp (generated by tempfile crate) to current folder:

`Result::unwrap()` on an `Err` value: Os { code: 18, kind: CrossesDevices, message: "Invalid cross-device link" }'

So I need a temporary file in the same filesystem? Where is a good place for that?

EDIT: I guess we could pick target/temporary_file and we should be OK? File disappears after renaming anyways. EDIT2: nevermind, target doesn't exist after cargo new and cargo add.

Atomic renames requires doing so on the same filesystem.

Comment on lines +167 to +175
pub fn atomic_write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
let path = path.as_ref();
fs::write(path, contents.as_ref())
.with_context(|| format!("failed to write `{}`", path.display()))

let mut temporary_file = tempfile::Builder::new().suffix(path).tempfile()?;
temporary_file
.write_all(contents.as_ref())
.with_context(|| format!("failed to write to `{}`", temporary_file.path().display()))?;

fs::rename(temporary_file.path(), path)
Copy link
Contributor

Choose a reason for hiding this comment

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

I know enough about atomic file writes to know that it is complicated. atomicwrites seems to be a popular crate so there might be things to learn from it (or maybe to use it).

Overall, I'd still want multiple eyes on this to make sure the approach is correct and its the right choice in each case.

Copy link
Contributor

Choose a reason for hiding this comment

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

The bug is "wiped my file clean on ctrl-c" (#11386). I assume these "atomic writes" would instead be "I've got a random temp file on ctrl-c". Only slightly better. If that is the case, then I would expect this to only be applied to the cases where the empty file causes problems.

/// are identical to the given contents.
pub fn write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
pub fn atomic_write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit confusing of a name. Its "'atomic write' if changed" but could be read as "atomic 'write if changed'". which can't be atomic.

@weihanglo
Copy link
Member

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 14, 2023
@tompscanlan
Copy link
Contributor

@marcospb19 do you plan on getting back to this?

@marcospb19
Copy link
Author

@marcospb19 do you plan on getting back to this?

Not right now, feel free to pick it up if you want to.

@tompscanlan
Copy link
Contributor

Thanks, and I hope you're doing well.

@marcospb19
Copy link
Author

Ty! Closing in favor of #12744.

@marcospb19 marcospb19 closed this Sep 27, 2023
bors added a commit that referenced this pull request Oct 2, 2023
fix bug: corruption when cargo killed while writing

### What does this PR try to resolve?

fix  #11386, superseding #12362

### How should we test and review this PR?

Added unit test showing basic equivalency to existing `write(path, content)`. Full test suite should exercise write.
Added tests for cargo add and remove. These are timing tests, so take a bit of time to run. 5-10s each.  They may not fail every time, but do so regularly.  Making the change to these two writes seems to prevent me from failing these tests at all.

### Additional information

This uses tempfile::persist which was an existing dependency. atomicwrites crate, an alternative option for this fix, indicates `tempfile::persist` is the same thing.  Since we already use tempfile as a dep, I stuck with that.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-build-scripts Area: build.rs scripts A-cache-messages Area: caching of compiler messages A-cfg-expr Area: Platform cfg expressions A-cli Area: Command-line interface, option parsing, etc. A-manifest Area: Cargo.toml issues A-rebuild-detection Area: rebuild detection and fingerprinting Command-fix Command-new Command-remove Command-vendor S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

cargo add and ctrl + c
5 participants