use std::{
fs::{File, OpenOptions, TryLockError},
io,
path::Path,
sync::Arc,
};
use tracing::warn;
const HOLD_NAME: &str = ".hold";
pub(crate) struct Hold {
_file: File,
}
impl Hold {
pub(crate) fn acquire(dir: &Path) -> io::Result<Arc<Self>> {
std::fs::create_dir_all(dir)?;
let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(false)
.open(dir.join(HOLD_NAME))?;
match file.try_lock() {
Ok(()) => {}
Err(TryLockError::WouldBlock) => {
warn!(
directory = %dir.display(),
"waiting for storage directory hold (operations from a previous run may still be finishing)"
);
loop {
match file.lock() {
Ok(()) => break,
Err(err) if err.kind() == io::ErrorKind::Interrupted => {}
Err(err) => return Err(err),
}
}
}
Err(TryLockError::Error(err)) => return Err(err),
}
Ok(Arc::new(Self { _file: file }))
}
}
#[cfg(test)]
mod tests {
use super::{HOLD_NAME, Hold};
use crate::storage::validate_partition_name;
use std::fs;
#[test]
fn test_hold_name_rejected_as_partition() {
assert!(validate_partition_name(HOLD_NAME).is_err());
}
#[test]
fn test_acquire_fails_on_unusable_root() {
let dir =
std::env::temp_dir().join(format!("commonware_hold_occupied_{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
fs::write(&dir, b"not a directory").unwrap();
assert!(Hold::acquire(&dir).is_err());
let _ = fs::remove_file(&dir);
}
}