13 个版本 (1 个稳定版)

1.0.0 2023 年 2 月 14 日
0.10.3 2023 年 2 月 10 日
0.9.3 2023 年 2 月 7 日

文件系统 中排名 #583

Download history 6/week @ 2024-07-04 505/week @ 2024-07-25

每月下载次数 511

MIT 许可证

57KB
909

atomic-file

Rust 文档在此


AtomicFile crate 提供了一个包装器,用于对 async_std::File 进行操作,以实现更方便、更安全的磁盘数据交互。对 AtomicFile 的所有操作都是 ACID,AtomicFile 类型包括一个不可见的 4096 字节头,用于管理版本号和文件标识符等详细信息。

版本号和文件标识符的主要用途是提供 AtomicFile 的简易升级功能,并确保用户在错误地将文件从一个位置移动到另一个位置时不会打开错误的文件。

使用 AtomicFile 的主要优点是其 ACID 保证,这确保了在突然断电的情况下数据不会被损坏。典型的文件使用模式会让用户容易受到损坏,尤其是在更新文件时。AtomicFile 通过使用双写方案来防止损坏,以确保磁盘上存在正确数据,并在启动时使用校验和来验证加载的是双重写入文件的正确实例。这意味着每个 AtomicFile 都会在磁盘上存在两个文件 - 一个 .atomic_file 和一个 .atomic_file_backup。

AtomicFile 使用的校验和是 6 字节。我们使用 6 字节校验和是因为我们的威胁模型是任意磁盘故障,而不是人类对手。人类对手可以写入任何校验和来击败我们的损坏检测。校验和以十六进制形式写入文件的第一个 12 个字节。

如果需要手动修改文件,可以覆盖校验和。将校验和更改为 'ffffffffffff'(12 个字符)后,校验和将根据文件内容被接受。对标识符的检查仍然会触发。

即使在极端情况下,例如硬盘物理损坏时,也可能发生数据损坏,但恢复的机会更好,并且用户可以防止所有常见的损坏形式(这些损坏通常源于意外断电)。

AtomicFile 的 'Atomic' 属性是它只执行完全读取或完全写入文件的操作。

// Basic file operations

use std::path::PathBuf;
use atomic_file::{
    open, open_file,
    OpenSettings::CreateIfNotExists,
};

#[async_std::main]
async fn main() {
    // Create a version 1 file with open_file. We pass in an empty vector for the upgrade path,
    // and 'CreateIfNotExists' to indicate that we want to create the non-existing file.
    let mut path = PathBuf::new();
    path.push("target");
    path.push("docs-example-1");
    let identifier = "AtomicFileDocs::docs-example-1";
    let mut file = open_file(&path, identifier, 1, &Vec::new(), CreateIfNotExists).await.unwrap();

    // Use 'contents' and 'write_file' to read and write the logical data of the file. Each
    // one will always read or write the full contents of the file.
    file.write_file(b"hello, world!").await.unwrap();
    let file_data = file.contents();
    if file_data != b"hello, world!" {
        panic!("example did not read correctly");
    }
    drop(file);

    // Now that we have created a file, we can use 'open(path, identifier)' as an alias for:
    // 'open_file(path, identifier, 1, Vec::new(), ErrorIfNotExists)'
    let file = open(&path, identifier);
    # drop(file);
    # atomic_file::delete_file(&path).await.unwrap();
}

AtomicFile 使用版本控制和升级方案来简化发布新版本文件的过程。在打开文件时,您将传递版本号和升级路径,这将允许文件打开过程自动将您的文件从当前版本升级到最新版本。

// Simple upgrade example
use std::path::PathBuf;

use anyhow::{bail, Result, Error};
use atomic_file::{open, open_file, AtomicFile, Upgrade};
use atomic_file::OpenSettings::ErrorIfNotExists;
# use atomic_file::OpenSettings::CreateIfNotExists;

// An example of a function that upgrades a file from version 1 to version 2, while making
// changes to the body of the file.
fn example_upgrade(
    data: Vec<u8>,
    initial_version: u8,
    updated_version: u8,
) -> Result<Vec<u8>, Error> {
    // Check that the version is okay.
    if initial_version != 1 || updated_version != 2 {
        bail!("wrong version");
    }

    // Return updated contents for the file.
    Ok((b"hello, update!".to_vec()))
}

#[async_std::main]
async fn main() {
    # let mut p = PathBuf::new();
    # p.push("target");
    # p.push("docs-example-2");
    # let i = "AtomicFileDocs::docs-example-2";
    # let mut f = atomic_file::open_file(&p, i, 1, &Vec::new(), CreateIfNotExists).await.unwrap();
    # f.write_file(b"hello, world!").await.unwrap();
    # drop(f);
    let mut path = PathBuf::new();
    path.push("target");
    path.push("docs-example-2");
    let identifier = "AtomicFileDocs::docs-example-2";
    let upgrade = Upgrade {
        initial_version: 1,
        updated_version: 2,
        process: example_upgrade,
    };
    let mut file = open_file(&path, identifier, 2, &vec![upgrade], ErrorIfNotExists).await.unwrap();
    // Note that the upgrades are passed in as a vector, allowing the caller to
    // define entire upgrade chains, e.g. 1->2 and 2->3. The final file that gets returned
    // will have been upgraded through the chain to the latest version.
    let file_data = file.contents();
    if file_data != b"hello, update!" {
        panic!("upgrade appears to have failed: \n{:?}\n{:?}", file_data, b"hello, update!");
    }

    // Perform cleanup.
    drop(file);
    atomic_file::delete_file(&path).await.unwrap();
}

如果您想为此 crate 贡献,我们正在寻找一种使升级函数异步+Send 的方法,因为之前的尝试都未能成功。

依赖项

~7-15MB
~207K SLoC