at54321
at54321

Reputation: 11766

Atomic file create & write

I want to create a new file and write some data into it (there will be no other writes after that). Modern Rust provides a convenient function fs::write(), but I want to make sure no other process would ever be able to read that file with partially written data.

Is there a function in Rust that does that directly or should I have to do that "manually" like this:

let mut f = File::create(FILE_TEMP)?;
f.write_all(some_data)?;
fs::rename(FILE_TEMP, FILE_FINAL)?;

I generally target Linux, but a cross-platform solution would be best.

Upvotes: 5

Views: 1430

Answers (4)

at54321
at54321

Reputation: 11766

It seems to me there is no better solution than the one posted in the question, but in a slightly shortened form (thanks to user4815162342 for the note):

fs::write(FILE_TEMP, some_data)?;
fs::rename(FILE_TEMP, FILE_FINAL)?;

The lock-based solutions don't really seem to solve anything for this particular case. Also, they require a lot more code and are generally more error-prone.

The only potential side effect of my approach that I can think of is that if a problem during write occurs, we might end up having some dangling temp files. Some might see that as a problem, some might see it as a good thing, which might help in troubleshooting, etc. Also, if we prefer the OS to automatically clean up those dangling temp files for us, we could simply make FILE_TEMP point to the system's temp folder (given it is on the same filesystem /see the comments below/, otherwise the fs::rename() function will fail). That's why I personally see that more as a good thing.

Upvotes: 4

jmou
jmou

Reputation: 544

The tempfile crate is a convenient option. NamedTempFile has a persist method that performs the atomic rename.

let final_path = std::path::Path::new("some/dir/file");
let mut file = tempfile::NamedTempFile::new_in(final_path.parent().unwrap())?;
file.write_all(b"some data")?;
file.persist(final_path)?;

This just does the same thing as your solution, although it is also implemented to work on Windows.

The temp file and final file must be on the same filesystem, which is why new_in explicitly specifies the temp directory.

Upvotes: 0

matkv
matkv

Reputation: 702

You can lock the file using the fs2 crate. Here is an example that locks a file, taken from this answer.

//! This program tries to lock a file, sleeps for N seconds, and then unlocks the file.

// cargo-deps: fs2
extern crate fs2;

use fs2::FileExt;
use std::io::Result;
use std::env::args;
use std::fs::File;
use std::time::Duration;
use std::thread::sleep;

fn main() {
    run().unwrap();
}

fn run() -> Result<()> {
    let sleep_seconds = args().nth(1).and_then(|arg| arg.parse().ok()).unwrap_or(0);
    let sleep_duration = Duration::from_secs(sleep_seconds);

    let file = File::open("file.lock")?;

    println!("{}: Preparing to lock file.", sleep_seconds);
    file.lock_exclusive()?; // block until this process can lock the file
    println!("{}: Obtained lock.", sleep_seconds);

    sleep(sleep_duration);

    println!("{}: Sleep completed", sleep_seconds);
    file.unlock()?;
    println!("{}: Released lock, returning", sleep_seconds);

    Ok(())
}

Upvotes: 0

Marcus Ilgner
Marcus Ilgner

Reputation: 7231

The fs2 crate has a function that can lock a file for exclusive access:

https://docs.rs/fs2/0.4.3/fs2/trait.FileExt.html

File locks are implemented with flock(2) on Unix and LockFile on Windows.

Upvotes: 0

Related Questions