原子文件创建和写入

Atomic file create & write

我想创建一个新文件并向其中写入一些数据(之后不会有其他写入)。 Modern Rust 提供了一个方便的函数 fs::write(),但我想确保没有其他进程能够读取具有 部分 写入数据的文件。

Rust 中是否有一个函数可以直接执行此操作,或者我是否应该像这样“手动”执行此操作:

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

我通常以 Linux 为目标,但最好是跨平台解决方案。

fs2 crate 有一个可以锁定文件以进行独占访问的功能:

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

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

您可以使用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(())
}

在我看来,没有比问题中发布的解决方案更好的解决方案了,但形式略有缩短(感谢 user4815162342 的注释):

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

对于这种特殊情况,基于锁的解决方案似乎并没有真正解决任何问题。此外,它们需要更多的代码并且通常更容易出错。

我能想到的我的方法的唯一潜在副作用是,如果在写入过程中出现问题,我们最终可能会有一些悬空的临时文件。有些人可能认为这是一个问题,有些人可能认为这是一件好事,这可能有助于故障排除等。另外,如果我们更喜欢 OS 为我们自动清理那些悬空的临时文件,我们可以简单地使 FILE_TEMP 指向系统的临时文件夹(假设它在同一文件系统上/请参阅下面的注释/,否则 fs::rename() 函数将失败)。这就是为什么我个人认为这是一件好事。

执行原子重命名的 tempfile crate is a convenient option. NamedTempFile has a persist 方法。

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)?;

这与您的解决方案做同样的事情,尽管它也可以在 Windows 上运行。

临时文件和最终文件必须在同一个文件系统上,这就是 new_in 明确指定临时目录的原因。