use std::fs;
use std::io::Write;
use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::error::CloudResult;
static TEMP_NONCE: AtomicU64 = AtomicU64::new(0);
pub(super) fn write_private_atomic(path: &Path, content: &str) -> CloudResult<()> {
let dir = path.parent().unwrap_or_else(|| Path::new("."));
let name = path
.file_name()
.map_or_else(|| "file".to_owned(), |n| n.to_string_lossy().into_owned());
let nonce = TEMP_NONCE.fetch_add(1, Ordering::Relaxed);
let temp_path = dir.join(format!("{name}.{}.{nonce}.tmp", std::process::id()));
let written = create_private(&temp_path, content).and_then(|()| {
fs::rename(&temp_path, path)?;
Ok(())
});
if written.is_err()
&& let Err(cleanup) = fs::remove_file(&temp_path)
&& cleanup.kind() != std::io::ErrorKind::NotFound
{
tracing::debug!(path = %temp_path.display(), error = %cleanup, "temp file not removed");
}
written
}
fn create_private(path: &Path, content: &str) -> CloudResult<()> {
let mut options = fs::OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600);
}
let mut file = options.open(path)?;
file.write_all(content.as_bytes())?;
file.sync_all()?;
Ok(())
}
pub(super) fn ensure_private_dir(dir: &Path) -> CloudResult<()> {
fs::create_dir_all(dir)?;
let gitignore_path = dir.join(".gitignore");
if !gitignore_path.exists() {
fs::write(&gitignore_path, "*\n")?;
}
Ok(())
}