#[cfg(unix)]
use std::fs;
use std::io::{BufWriter, Write};
use std::path::Path;
use anyhow::{Context, Result};
use crate::platform;
#[cfg(windows)]
use super::persist_retry::persist_with_retry;
#[cfg(unix)]
use super::inplace::copy_tempfile_to_target;
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)
.with_context(|| format!("cannot create tempfile in {}", parent.display()))?;
{
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_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])
.with_context(|| format!("write error for {}", target.display()))?;
offset = end;
}
writer
.flush()
.with_context(|| format!("flush error for {}", target.display()))?;
writer.into_inner().map_err(|e| {
anyhow::anyhow!(
"BufWriter into_inner error for {}: {}",
target.display(),
e.error()
)
})?;
}
platform::fsync_file_with_durability(temp.as_file(), durability)
.with_context(|| format!("fsync error for {}", target.display()))?;
#[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(),
}
.into());
}
tracing::warn!(
path = %target.display(),
"EXDEV detected, falling back to copy + fsync + cleanup"
);
let recovered = e.file;
copy_tempfile_to_target(recovered.as_file(), target, content)?;
return Ok((true, "copy_exdev"));
}
}
return Err(e.error)
.with_context(|| format!("rename error for {}", target.display()));
}
}
}
}