use std::{fs::File, io::Result, os::fd::AsRawFd};
use crate::cutils::cerr;
pub trait Lockable {
fn lock_exclusive(&self, nonblocking: bool) -> Result<()>;
fn lock_shared(&self, nonblocking: bool) -> Result<()>;
fn unlock(&self) -> Result<()>;
}
#[derive(Clone, Copy, Debug)]
enum LockOp {
LockExclusive,
LockShared,
Unlock,
}
impl LockOp {
fn as_flock_operation(self) -> libc::c_int {
match self {
LockOp::LockExclusive => libc::LOCK_EX,
LockOp::LockShared => libc::LOCK_SH,
LockOp::Unlock => libc::LOCK_UN,
}
}
}
fn flock(fd: &impl AsRawFd, action: LockOp, nonblocking: bool) -> Result<()> {
let mut operation = action.as_flock_operation();
if nonblocking {
operation |= libc::LOCK_NB;
}
cerr(unsafe { libc::flock(fd.as_raw_fd(), operation) })?;
Ok(())
}
impl Lockable for File {
fn lock_exclusive(&self, nonblocking: bool) -> Result<()> {
flock(self, LockOp::LockExclusive, nonblocking)
}
fn lock_shared(&self, nonblocking: bool) -> Result<()> {
flock(self, LockOp::LockShared, nonblocking)
}
fn unlock(&self) -> Result<()> {
flock(self, LockOp::Unlock, false)
}
}
#[cfg(test)]
mod tests {
use super::*;
impl Lockable for std::io::Cursor<Vec<u8>> {
fn lock_exclusive(&self, _: bool) -> Result<()> {
Ok(())
}
fn lock_shared(&self, _: bool) -> Result<()> {
Ok(())
}
fn unlock(&self) -> Result<()> {
Ok(())
}
}
impl Lockable for std::io::Cursor<&mut Vec<u8>> {
fn lock_exclusive(&self, _: bool) -> Result<()> {
Ok(())
}
fn lock_shared(&self, _: bool) -> Result<()> {
Ok(())
}
fn unlock(&self) -> Result<()> {
Ok(())
}
}
fn tempfile() -> std::io::Result<std::fs::File> {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("Failed to get system time")
.as_nanos();
let pid = std::process::id();
let filename = format!("sudo_rs_test_{}_{}", pid, timestamp);
let path = std::path::PathBuf::from("/tmp").join(filename);
std::fs::File::create(path)
}
#[test]
fn test_locking_of_tmp_file() {
let f = tempfile().unwrap();
assert!(f.lock_shared(false).is_ok());
assert!(f.unlock().is_ok());
assert!(f.lock_exclusive(false).is_ok());
assert!(f.unlock().is_ok());
}
}