#[cfg(test)]
mod tests;
use std::{
ffi::OsString,
fs::{self, File, OpenOptions},
io::{self, Write},
path::{Path, PathBuf},
sync::atomic::{AtomicU64, Ordering},
};
static TEMP_SEQUENCE: AtomicU64 = AtomicU64::new(0);
pub fn write_bytes(path: &Path, bytes: &[u8]) -> io::Result<()> {
let parent = path
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
.unwrap_or_else(|| Path::new("."));
fs::create_dir_all(parent)?;
let (temp_path, mut temp_file) = create_sibling_temp(path, parent)?;
if let Err(error) = temp_file
.write_all(bytes)
.and_then(|()| temp_file.sync_all())
{
drop(temp_file);
let _ = fs::remove_file(&temp_path);
return Err(error);
}
drop(temp_file);
if let Err(error) = fs::rename(&temp_path, path) {
let _ = fs::remove_file(&temp_path);
return Err(error);
}
File::open(parent)?.sync_all()
}
fn create_sibling_temp(path: &Path, parent: &Path) -> io::Result<(PathBuf, File)> {
let file_name = path.file_name().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("durable write target has no file name: {}", path.display()),
)
})?;
for _ in 0..64 {
let sequence = TEMP_SEQUENCE.fetch_add(1, Ordering::Relaxed);
let mut temp_name = OsString::from(".");
temp_name.push(file_name);
temp_name.push(format!(".canic-tmp-{}-{sequence}", std::process::id()));
let temp_path = parent.join(temp_name);
match OpenOptions::new()
.write(true)
.create_new(true)
.open(&temp_path)
{
Ok(file) => return Ok((temp_path, file)),
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}
Err(error) => return Err(error),
}
}
Err(io::Error::new(
io::ErrorKind::AlreadyExists,
format!(
"could not allocate a unique sibling temporary file for {}",
path.display()
),
))
}