use std::io::Write;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::error::DetoxError;
pub fn rename_with_fallback(src: &Path, tgt: &Path) -> Result<(), DetoxError> {
match std::fs::rename(src, tgt) {
Ok(_) => Ok(()),
Err(e) if is_cross_device(&e) => exdev_fallback(src, tgt),
Err(e) => Err(DetoxError::Io {
path: src.to_path_buf(),
source: e,
}),
}
}
fn is_cross_device(e: &std::io::Error) -> bool {
matches!(e.kind(), std::io::ErrorKind::CrossesDevices)
}
fn exdev_fallback(src: &Path, tgt: &Path) -> Result<(), DetoxError> {
let parent = tgt.parent().unwrap_or_else(|| Path::new("."));
let tmp = unique_temp_path(parent);
{
let mut reader = std::fs::File::open(src).map_err(|e| DetoxError::CrossDevice {
source_path: src.to_path_buf(),
target: tgt.to_path_buf(),
source_err: e,
})?;
let mut writer = std::fs::File::create(&tmp).map_err(|e| DetoxError::CrossDevice {
source_path: src.to_path_buf(),
target: tgt.to_path_buf(),
source_err: e,
})?;
if let Err(e) = std::io::copy(&mut reader, &mut writer) {
let _ = std::fs::remove_file(&tmp);
return Err(DetoxError::CrossDevice {
source_path: src.to_path_buf(),
target: tgt.to_path_buf(),
source_err: e,
});
}
if let Err(e) = writer.flush() {
let _ = std::fs::remove_file(&tmp);
return Err(DetoxError::CrossDevice {
source_path: src.to_path_buf(),
target: tgt.to_path_buf(),
source_err: e,
});
}
if let Err(e) = writer.sync_all() {
let _ = std::fs::remove_file(&tmp);
return Err(DetoxError::CrossDevice {
source_path: src.to_path_buf(),
target: tgt.to_path_buf(),
source_err: e,
});
}
}
if let Err(e) = std::fs::rename(&tmp, tgt) {
let _ = std::fs::remove_file(&tmp);
return Err(DetoxError::CrossDevice {
source_path: src.to_path_buf(),
target: tgt.to_path_buf(),
source_err: e,
});
}
let _ = copy_metadata(src, tgt);
if let Err(e) = std::fs::remove_file(src) {
eprintln!(
"rusty-detox: warning: rename succeeded but could not unlink source '{}': {e}",
src.display()
);
}
Ok(())
}
fn unique_temp_path(parent: &Path) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let pid = std::process::id();
parent.join(format!(".rusty-detox-tmp-{pid}-{nanos}"))
}
pub fn copy_metadata(src: &Path, tgt: &Path) -> Result<(), DetoxError> {
let meta = std::fs::metadata(src).map_err(|e| DetoxError::Io {
path: src.to_path_buf(),
source: e,
})?;
if let Err(e) = std::fs::set_permissions(tgt, meta.permissions()) {
eprintln!(
"rusty-detox: warning: could not preserve permissions on '{}': {e}",
tgt.display()
);
}
if let (Ok(mtime), Ok(atime)) = (meta.modified(), meta.accessed()) {
let times = std::fs::FileTimes::new()
.set_modified(mtime)
.set_accessed(atime);
if let Ok(tgt_file) = std::fs::File::options().write(true).open(tgt) {
if let Err(e) = tgt_file.set_times(times) {
eprintln!(
"rusty-detox: warning: could not preserve timestamps on '{}': {e}",
tgt.display()
);
}
}
}
Ok(())
}