use std::fs;
#[cfg(unix)]
use std::fs::File;
use std::io::{self, Write};
use std::path::Path;
use std::process;
use std::sync::atomic::{AtomicU64, Ordering};
use tempfile::NamedTempFile;
static TEMP_SEQ: AtomicU64 = AtomicU64::new(0);
pub(crate) fn write_content_synced(final_path: &Path, bytes: &[u8]) -> io::Result<()> {
let parent = final_path
.parent()
.expect("atomic::write_content_synced: path has parent");
let file_name = final_path
.file_name()
.expect("atomic::write_content_synced: path has file name")
.to_string_lossy();
let pid = process::id();
let seq = TEMP_SEQ.fetch_add(1, Ordering::Relaxed);
let tmp_name = format!(".{file_name}.tmp.{pid}.{seq}");
let mut tmp = NamedTempFile::with_prefix_in(tmp_name, parent)?;
tmp.as_file_mut().write_all(bytes)?;
tmp.as_file_mut().sync_all()?;
tmp.persist(final_path).map_err(|e| e.error)?;
Ok(())
}
pub(crate) fn sync_dir(dir: &Path) -> io::Result<()> {
sync_parent_dir(dir)
}
pub(crate) fn write_atomic(final_path: &Path, bytes: &[u8], make_parents: bool) -> io::Result<()> {
let parent = final_path
.parent()
.expect("atomic::write_atomic: path has parent");
if make_parents {
fs::create_dir_all(parent)?;
}
let file_name = final_path
.file_name()
.expect("atomic::write_atomic: path has file name")
.to_string_lossy();
let pid = process::id();
let seq = TEMP_SEQ.fetch_add(1, Ordering::Relaxed);
let tmp_name = format!(".{file_name}.tmp.{pid}.{seq}");
let mut tmp = NamedTempFile::with_prefix_in(tmp_name, parent)?;
tmp.as_file_mut().write_all(bytes)?;
tmp.as_file_mut().sync_all()?;
tmp.persist(final_path).map_err(|e| e.error)?;
sync_parent_dir(parent)?;
Ok(())
}
pub(crate) fn write_create_new(
final_path: &Path,
bytes: &[u8],
make_parents: bool,
) -> io::Result<bool> {
let parent = final_path
.parent()
.expect("atomic::write_create_new: path has parent");
if make_parents {
fs::create_dir_all(parent)?;
}
let file_name = final_path
.file_name()
.expect("atomic::write_create_new: path has file name")
.to_string_lossy();
let pid = process::id();
let seq = TEMP_SEQ.fetch_add(1, Ordering::Relaxed);
let tmp_name = format!(".{file_name}.tmp.{pid}.{seq}");
let mut tmp = NamedTempFile::with_prefix_in(tmp_name, parent)?;
tmp.as_file_mut().write_all(bytes)?;
tmp.as_file_mut().sync_all()?;
match tmp.persist_noclobber(final_path) {
Ok(_file) => {
sync_parent_dir(parent)?;
Ok(true)
}
Err(e) if e.error.kind() == io::ErrorKind::AlreadyExists => {
Ok(false)
}
Err(e) => Err(e.error),
}
}
#[cfg(unix)]
pub(crate) fn sync_parent_dir(parent: &Path) -> io::Result<()> {
#[cfg(test)]
testing::record_dir_sync_call();
match File::open(parent) {
Ok(dir) => dir.sync_all(),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(e),
}
}
#[cfg(not(unix))]
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn sync_parent_dir(_parent: &Path) -> io::Result<()> {
#[cfg(test)]
testing::record_dir_sync_call();
Ok(())
}
#[cfg(test)]
pub(crate) mod testing {
use std::cell::Cell;
thread_local! {
static DIR_SYNC_CALLS: Cell<u64> = const { Cell::new(0) };
}
pub(crate) fn record_dir_sync_call() {
DIR_SYNC_CALLS.with(|c| c.set(c.get() + 1));
}
pub(crate) fn reset_dir_sync_calls() {
DIR_SYNC_CALLS.with(|c| c.set(0));
}
#[must_use]
pub(crate) fn dir_sync_calls() -> u64 {
DIR_SYNC_CALLS.with(Cell::get)
}
}