use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU8, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DirSyncPolicy {
#[default]
Safe,
Off,
}
static DIR_SYNC_OFF: AtomicU8 = AtomicU8::new(0);
pub fn set_dir_sync_policy(policy: DirSyncPolicy) {
DIR_SYNC_OFF.store(matches!(policy, DirSyncPolicy::Off) as u8, Ordering::SeqCst);
}
pub fn dir_sync_policy() -> DirSyncPolicy {
if DIR_SYNC_OFF.load(Ordering::SeqCst) == 0 {
DirSyncPolicy::Safe
} else {
DirSyncPolicy::Off
}
}
fn dir_sync_active() -> bool {
matches!(dir_sync_policy(), DirSyncPolicy::Safe)
}
#[cfg(feature = "power-log")]
mod journal {
use std::fs;
use std::io::Write;
use std::path::Path;
use std::sync::{Mutex, OnceLock};
static LOG: OnceLock<Mutex<Option<fs::File>>> = OnceLock::new();
fn writer() -> &'static Mutex<Option<fs::File>> {
LOG.get_or_init(|| {
let file = std::env::var("VOLE_POWER_LOG").ok().and_then(|p| {
fs::OpenOptions::new()
.create(true)
.append(true)
.open(p)
.ok()
});
Mutex::new(file)
})
}
pub fn rel(path: &Path) -> String {
if let Ok(root) = std::env::var("VOLE_POWER_ROOT")
&& let Ok(rest) = path.strip_prefix(&root)
{
return rest.to_string_lossy().into_owned();
}
path.to_string_lossy().into_owned()
}
pub fn event(line: &str) {
if let Ok(mut guard) = writer().lock()
&& let Some(f) = guard.as_mut()
{
let _ = f.write_all(line.as_bytes());
let _ = f.write_all(b"\n");
let _ = f.flush();
}
}
}
pub fn create_dir_all(path: &Path) -> io::Result<()> {
let mut missing: Vec<PathBuf> = Vec::new();
let mut cur = path.to_path_buf();
loop {
if cur.is_dir() {
break;
}
missing.push(cur.clone());
match cur.parent() {
Some(p) if !p.as_os_str().is_empty() => cur = p.to_path_buf(),
_ => break,
}
}
for dir in missing.iter().rev() {
match fs::create_dir(dir) {
Ok(()) => {
#[cfg(feature = "power-log")]
journal::event(&format!("mkdir\t{}", journal::rel(dir)));
if let Some(parent) = dir.parent()
&& !parent.as_os_str().is_empty()
{
sync_dir(parent)?;
}
}
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
Err(e) => return Err(e),
}
}
Ok(())
}
pub fn create_file(path: &Path) -> io::Result<fs::File> {
let f = fs::OpenOptions::new()
.create(true)
.truncate(true)
.read(true)
.write(true)
.open(path)?;
#[cfg(feature = "power-log")]
journal::event(&format!("create\t{}", journal::rel(path)));
#[cfg(not(feature = "power-log"))]
let _ = path;
Ok(f)
}
pub fn write_all(f: &mut fs::File, path: &Path, bytes: &[u8]) -> io::Result<()> {
f.write_all(bytes)?;
#[cfg(feature = "power-log")]
{
let len = f.metadata().map(|m| m.len()).unwrap_or(0);
journal::event(&format!("write\t{}\t{len}", journal::rel(path)));
}
#[cfg(not(feature = "power-log"))]
let _ = path;
Ok(())
}
pub fn sync_all(f: &fs::File, path: &Path) -> io::Result<()> {
f.sync_all()?;
#[cfg(feature = "power-log")]
{
let len = f.metadata().map(|m| m.len()).unwrap_or(0);
journal::event(&format!("fsync\t{}\t{len}", journal::rel(path)));
}
#[cfg(not(feature = "power-log"))]
let _ = path;
Ok(())
}
pub fn sync_data(f: &fs::File, path: &Path) -> io::Result<()> {
f.sync_data()?;
#[cfg(feature = "power-log")]
{
let len = f.metadata().map(|m| m.len()).unwrap_or(0);
journal::event(&format!("fsync_data\t{}\t{len}", journal::rel(path)));
}
#[cfg(not(feature = "power-log"))]
let _ = path;
Ok(())
}
pub fn sync_dir(path: &Path) -> io::Result<()> {
#[cfg(not(feature = "power-log"))]
let _ = path;
if !dir_sync_active() {
return Ok(());
}
#[cfg(unix)]
{
let d = fs::File::open(path)?;
d.sync_all()?;
}
#[cfg(feature = "power-log")]
journal::event(&format!("dirsync\t{}", journal::rel(path)));
Ok(())
}
pub fn rename(from: &Path, to: &Path) -> io::Result<()> {
fs::rename(from, to)?;
#[cfg(feature = "power-log")]
journal::event(&format!(
"rename\t{}\t{}",
journal::rel(from),
journal::rel(to)
));
#[cfg(not(feature = "power-log"))]
let _ = (from, to);
Ok(())
}
pub fn set_len(f: &fs::File, path: &Path, len: u64) -> io::Result<()> {
f.set_len(len)?;
#[cfg(feature = "power-log")]
journal::event(&format!("setlen\t{}\t{len}", journal::rel(path)));
#[cfg(not(feature = "power-log"))]
let _ = (path, len);
Ok(())
}