zebra_chain/
common.rs

1//! Common functions used in Zebra.
2
3use std::{
4    ffi::OsString,
5    fs,
6    io::{self, Write},
7    path::PathBuf,
8};
9
10use tempfile::PersistError;
11
12/// Returns Zebra's default cache directory path.
13pub fn default_cache_dir() -> PathBuf {
14    dirs::cache_dir()
15        .unwrap_or_else(|| std::env::current_dir().unwrap().join("cache"))
16        .join("zebra")
17}
18
19/// Accepts a target file path and a byte-slice.
20///
21/// Atomically writes the byte-slice to a file to avoid corrupting the file if Zebra
22/// panics, crashes, or exits while the file is being written, or if multiple Zebra instances
23/// try to read and write the same file.
24///
25/// Returns the provided file path if successful.
26///
27/// # Concurrency
28///
29/// This function blocks on filesystem operations and should be called in a blocking task
30/// when calling from an async environment.
31///
32/// # Panics
33///
34/// If the provided `file_path` is a directory path.
35pub fn atomic_write(
36    file_path: PathBuf,
37    data: &[u8],
38) -> io::Result<Result<PathBuf, PersistError<fs::File>>> {
39    // Get the file's parent directory, or use Zebra's default cache directory
40    let file_dir = file_path
41        .parent()
42        .map(|p| p.to_owned())
43        .unwrap_or_else(default_cache_dir);
44
45    // Create the directory if needed.
46    fs::create_dir_all(&file_dir)?;
47
48    // Give the temporary file a similar name to the permanent file,
49    // but hide it in directory listings.
50    let mut tmp_file_prefix: OsString = ".tmp.".into();
51    tmp_file_prefix.push(
52        file_path
53            .file_name()
54            .expect("file path must have a file name"),
55    );
56
57    // Create the temporary file in the same directory as the permanent file,
58    // so atomic filesystem operations are possible.
59    let mut tmp_file = tempfile::Builder::new()
60        .prefix(&tmp_file_prefix)
61        .tempfile_in(file_dir)?;
62
63    tmp_file.write_all(data)?;
64
65    // Atomically write the temp file to `file_path`.
66    let persist_result = tmp_file
67        .persist(&file_path)
68        // Drops the temp file and returns the file path.
69        .map(|_| file_path);
70    Ok(persist_result)
71}