#[cfg(unix)]
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use crate::config::FsyncPolicy;
use crate::error::{PersistError, Result};
pub(crate) trait Storage: Send + Sync {
fn read_all(&self, path: &Path) -> Result<Vec<u8>>;
fn write_atomic(&self, target: &Path, payload: &[u8], policy: FsyncPolicy) -> Result<()>;
}
pub(crate) struct StdFsStorage;
impl Storage for StdFsStorage {
fn read_all(&self, path: &Path) -> Result<Vec<u8>> {
std::fs::read(path).map_err(|source| PersistError::Io {
path: path.to_path_buf(),
source,
})
}
fn write_atomic(&self, target: &Path, payload: &[u8], policy: FsyncPolicy) -> Result<()> {
let target_dir = target.parent().unwrap_or_else(|| Path::new("."));
let file_name = target.file_name().ok_or_else(|| PersistError::Io {
path: target.to_path_buf(),
source: std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"target has no file name",
),
})?;
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let pid = std::process::id();
let temp_name = format!("{}.tmp.{pid}.{nanos}", file_name.to_string_lossy());
let temp_path = target_dir.join(&temp_name);
{
let mut file = OpenOptions::new()
.create_new(true)
.write(true)
.open(&temp_path)
.map_err(|source| PersistError::Io {
path: temp_path.clone(),
source,
})?;
if let Err(source) = file.write_all(payload) {
drop(file);
let _cleanup = std::fs::remove_file(&temp_path);
return Err(PersistError::Io {
path: temp_path,
source,
});
}
if policy != FsyncPolicy::Never {
if let Err(source) = file.sync_all() {
drop(file);
let _cleanup = std::fs::remove_file(&temp_path);
return Err(PersistError::Io {
path: temp_path,
source,
});
}
}
}
if let Err(source) = std::fs::rename(&temp_path, target) {
let _cleanup = std::fs::remove_file(&temp_path);
return Err(PersistError::Io {
path: temp_path,
source,
});
}
#[cfg(unix)]
{
if policy != FsyncPolicy::Never {
let dir = File::open(target_dir).map_err(|source| PersistError::Io {
path: target_dir.to_path_buf(),
source,
})?;
dir.sync_all().map_err(|source| PersistError::Io {
path: target_dir.to_path_buf(),
source,
})?;
}
}
#[cfg(not(unix))]
{
let _ = policy;
let _ = target_dir;
}
Ok(())
}
}