use crate::privileged;
use anyhow::{Context, Result};
use std::fs::{File, OpenOptions, TryLockError};
use std::path::Path;
pub enum Mode {
Shared,
Exclusive,
}
pub struct StateLock {
_file: Option<File>,
}
impl StateLock {
pub fn acquire(prefix: &Path, mode: &Mode) -> Result<Self> {
let path = prefix.join("share/cargo-lbin/lock");
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&path)
.or_else(|_| OpenOptions::new().read(true).open(&path));
let file = match (file, mode) {
(Ok(f), _) => f,
(Err(_), Mode::Exclusive) => {
eprintln!(
"initializing state for {}: creating {}",
prefix.display(),
path.display()
);
privileged::ensure_lock_file(privileged::Escalation::for_prefix(prefix), &path)
.with_context(|| format!("preparing state lock {}", path.display()))?;
OpenOptions::new()
.read(true)
.open(&path)
.with_context(|| format!("opening state lock {}", path.display()))?
}
(Err(_), Mode::Shared) => {
eprintln!(
"warning: cannot open {} — proceeding without a state lock \
(the file is created by the first install/update/remove)",
path.display()
);
return Ok(Self { _file: None });
}
};
let probe = match mode {
Mode::Shared => file.try_lock_shared(),
Mode::Exclusive => file.try_lock(),
};
match probe {
Ok(()) => {}
Err(TryLockError::WouldBlock) => {
eprintln!("another cargo-lbin instance holds the state lock; waiting...");
match mode {
Mode::Shared => file.lock_shared(),
Mode::Exclusive => file.lock(),
}
.context("acquiring state lock")?;
}
Err(TryLockError::Error(e)) => return Err(e).context("acquiring state lock"),
}
Ok(Self { _file: Some(file) })
}
}