use std::fs::{self, File, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use crate::error::Result;
use crate::{crypto, error::Error};
pub(crate) fn sibling_temp_path(target: &Path) -> PathBuf {
let dir = target.parent().map(Path::to_path_buf).unwrap_or_default();
let name = target
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "krypton-out".into());
let rnd = crypto::random_nonce();
dir.join(format!(
".{name}.tmp-{}-{}",
std::process::id(),
hex_prefix(&rnd)
))
}
fn hex_prefix(bytes: &[u8]) -> String {
bytes.iter().take(4).map(|b| format!("{b:02x}")).collect()
}
pub(crate) fn atomic_write(path: &Path, bytes: &[u8]) -> Result<()> {
if path.parent().is_none() {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"target has no parent directory",
)));
}
let tmp = sibling_temp_path(path);
let write_result = (|| -> Result<()> {
let mut f = OpenOptions::new().write(true).create_new(true).open(&tmp)?;
restrict_perms(&tmp);
f.write_all(bytes)?;
f.sync_all()?;
drop(f);
#[cfg(windows)]
if path.exists() {
fs::remove_file(path)?;
}
fs::rename(&tmp, path)?;
Ok(())
})();
if write_result.is_err() {
let _ = fs::remove_file(&tmp);
} else {
sync_dir(path.parent().expect("checked above"));
}
write_result
}
pub(crate) fn sync_dir(dir: &Path) {
#[cfg(unix)]
if let Ok(d) = File::open(dir) {
let _ = d.sync_all();
}
#[cfg(not(unix))]
let _ = dir;
}
pub(crate) fn restrict_perms(path: &Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o600));
}
#[cfg(not(unix))]
let _ = path;
}
pub(crate) fn restrict_dir_perms(path: &Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o700));
}
#[cfg(not(unix))]
let _ = path;
}
pub(crate) fn create_private_dir(path: &Path) -> Result<()> {
if !path.exists() {
fs::create_dir_all(path)?;
restrict_dir_perms(path);
}
Ok(())
}
pub(crate) fn remove_file_if_exists(path: &PathBuf) -> Result<()> {
match fs::remove_file(path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(e.into()),
}
}