#[cfg(unix)]
use std::fs;
use std::io::{BufWriter, Write};
use std::path::Path;
use anyhow::Result;
use crate::platform;
fn io_at(target: &Path, what: &str) -> impl Fn(std::io::Error) -> anyhow::Error {
let path = target.to_path_buf();
let what = what.to_string();
move |source| {
let reason = format!("{what} for {}: {source}", path.display());
crate::error::AtomwriteError::from_io_at(&path, std::io::Error::new(source.kind(), reason))
.into()
}
}
#[cfg(unix)]
use super::inplace::copy_content_to_target;
#[cfg(windows)]
use super::persist_retry::persist_with_retry;
pub(crate) fn write_rename_path(
target: &Path,
content: &[u8],
#[cfg_attr(not(unix), allow(unused_variables))] strict_atomic: bool,
durability: crate::platform::Durability,
) -> Result<(bool, &'static str)> {
let parent = target.parent().unwrap_or(Path::new("."));
let mut builder = tempfile::Builder::new();
builder
.prefix(crate::constants::TEMPFILE_PREFIX)
.suffix(crate::constants::TEMPFILE_SUFFIX);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
builder.permissions(fs::Permissions::from_mode(
crate::constants::TEMPFILE_PERMISSIONS,
));
}
let temp = builder
.tempfile_in(parent)
.map_err(io_at(parent, "cannot create tempfile"))?;
{
let mut writer = BufWriter::with_capacity(crate::constants::BUF_CAPACITY, temp.as_file());
let chunk = crate::constants::BUF_CAPACITY;
let mut offset = 0usize;
while offset < content.len() {
if crate::signal::is_global_hard_shutdown() {
return Err(crate::signal::cancelled_error(format!(
"atomic write cancelled for {}",
target.display()
))
.into());
}
let end = (offset + chunk).min(content.len());
writer
.write_all(&content[offset..end])
.map_err(io_at(target, "write error"))?;
offset = end;
}
writer.flush().map_err(io_at(target, "flush error"))?;
writer.into_inner().map_err(|e| {
io_at(target, "BufWriter into_inner error")(std::io::Error::new(
e.error().kind(),
format!(
"BufWriter into_inner error for {}: {}",
target.display(),
e.error()
),
))
})?;
}
platform::fsync_file_with_durability(temp.as_file(), durability)
.map_err(io_at(target, "fsync error"))?;
#[cfg(windows)]
{
persist_with_retry(temp, target)?;
return Ok((false, "MoveFileEx"));
}
#[cfg(not(windows))]
{
match temp.persist(target) {
Ok(_) => Ok((false, platform::platform_rename_method())),
Err(e) => {
#[cfg(unix)]
{
if e.error.raw_os_error() == Some(libc::EXDEV) {
if strict_atomic {
return Err(crate::error::AtomwriteError::ExdevFallbackDisabled {
path: target.to_path_buf(),
reason: "the temp file and the target are on different filesystems"
.into(),
}
.into());
}
tracing::warn!(
path = %target.display(),
"EXDEV detected, falling back to copy + fsync + cleanup"
);
let _recovered = e.file;
copy_content_to_target(target, content)?;
return Ok((true, "copy_exdev"));
}
}
return Err(io_at(target, "rename error")(e.error));
}
}
}
}