use crate::error::SearchError;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
pub(crate) fn atomic_write(target: &Path, bytes: &[u8]) -> Result<(), SearchError> {
let parent = target.parent().unwrap_or_else(|| Path::new("."));
let name = target.file_name().ok_or(SearchError::InvalidConfig(
"snapshot path needs a file name",
))?;
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |duration| duration.as_nanos());
let temporary = parent.join(format!(
".{}.{}-{nonce}.tmp",
name.to_string_lossy(),
std::process::id()
));
let backup = parent.join(format!(
".{}.{}-{nonce}.bak",
name.to_string_lossy(),
std::process::id()
));
let result = write_and_replace(target, bytes, &temporary, &backup);
if result.is_err() {
let _ = std::fs::remove_file(&temporary);
}
result
}
fn write_and_replace(
target: &Path,
bytes: &[u8],
temporary: &Path,
backup: &Path,
) -> Result<(), SearchError> {
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(temporary)
.map_err(|error| SearchError::storage("create snapshot", &error))?;
file.write_all(bytes)
.map_err(|error| SearchError::storage("write snapshot", &error))?;
file.sync_all()
.map_err(|error| SearchError::storage("flush snapshot", &error))?;
drop(file);
if !target.exists() {
return std::fs::rename(temporary, target)
.map_err(|error| SearchError::storage("install snapshot", &error));
}
std::fs::rename(target, backup)
.map_err(|error| SearchError::storage("backup snapshot", &error))?;
match std::fs::rename(temporary, target) {
Ok(()) => {
std::fs::remove_file(backup)
.map_err(|error| SearchError::storage("remove snapshot backup", &error))?;
Ok(())
}
Err(error) => {
let _ = std::fs::rename(backup, target);
Err(SearchError::storage("install snapshot", &error))
}
}
}