Reputation: 11766
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
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
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
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
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