use crate::*;
use nix::{fcntl::OFlag, sys::stat::Mode};
pub struct SemaphoreLock {
sem: safe_wrapper::NamedSem,
have_lock: bool,
}
const INITIAL_VALUE: i32 = 1;
const FLAGS: OFlag = OFlag::O_CREAT;
fn verify_name(name: impl Into<String>) -> SimpleLockResult<String> {
let mut test = name.into();
if !test.starts_with("/") {
test.insert_str(0, "/");
}
if test.len() < 2 || test.len() > 250 || test.split('/').count() > 2 {
Err(SimpleLockError::InvalidName)
} else {
Ok(test)
}
}
impl SemaphoreLock {
pub fn new(name: impl Into<String>) -> SimpleLockResult<SemaphoreLock> {
let name = verify_name(name)?;
let mode = Mode::S_IRWXU | Mode::S_IRWXO;
let sem = safe_wrapper::sem_open(&name, &FLAGS, mode, INITIAL_VALUE)?;
Ok(SemaphoreLock {
sem,
have_lock: false,
})
}
pub fn name(&self) -> String {
self.sem.name()
}
}
impl ConcreteLock for SemaphoreLock {
fn status(&self) -> SimpleLockResult<LockStatus> {
if self.have_lock {
Ok(LockStatus::Mine)
} else if safe_wrapper::sem_getvalue(&self.sem)? != INITIAL_VALUE {
Ok(LockStatus::Taken)
} else {
Ok(LockStatus::Open)
}
}
fn try_lock(&mut self) -> SimpleLockResult<()> {
if self.have_lock {
return Ok(());
}
safe_wrapper::sem_trywait(&self.sem)
.map_err(|e| e.into())
.and_then(|_| {
self.have_lock = true;
Ok(())
})
}
fn hang_lock(&mut self) -> SimpleLockResult<()> {
if self.have_lock {
return Ok(());
}
safe_wrapper::sem_wait(&self.sem)
.map_err(|e| e.into())
.and_then(|_| {
self.have_lock = true;
Ok(())
})
}
fn try_unlock(&mut self) -> SimpleLockResult<()> {
if !self.have_lock {
return Ok(());
}
safe_wrapper::sem_post(&self.sem)
.map_err(|e| e.into())
.and_then(|_| {
self.have_lock = false;
Ok(())
})
}
}
mod safe_wrapper {
use crate::SimpleLockError;
use nix::{errno::Errno, fcntl::OFlag, sys::stat::Mode, Error, NixPath};
impl From<Error> for SimpleLockError {
fn from(e: Error) -> Self {
match e {
Error::InvalidPath => SimpleLockError::InvalidName,
Error::InvalidUtf8 => SimpleLockError::InvalidName,
Error::Sys(errno) => errno.into(),
Error::UnsupportedOperation => {
SimpleLockError::UnknownError("nix::UnsupportedOperation".into())
}
}
}
}
impl From<Errno> for SimpleLockError {
fn from(e: Errno) -> Self {
match e {
Errno::EACCES => SimpleLockError::PermissionDenied,
Errno::EMFILE => SimpleLockError::PermissionDenied,
Errno::ENFILE => SimpleLockError::PermissionDenied,
Errno::ENOMEM => SimpleLockError::PermissionDenied,
Errno::EINVAL => SimpleLockError::InvalidName,
Errno::ENAMETOOLONG => SimpleLockError::InvalidName,
Errno::EINTR => SimpleLockError::UnknownError("Signal interrupt.".into()),
_ => SimpleLockError::UnknownError(format!("{:?}: {}", e, e.desc())),
}
}
}
pub struct NamedSem {
name: String,
sem: *mut libc::sem_t,
}
impl Drop for NamedSem {
fn drop(&mut self) {
let _ = sem_post(self);
let _ = sem_close(self);
}
}
impl NamedSem {
pub fn name(&self) -> String {
self.name.clone()
}
}
pub fn sem_getvalue(sem: &NamedSem) -> nix::Result<i32> {
let mut res: i32 = 0;
if unsafe { libc::sem_getvalue(sem.sem, &mut res) == libc::EXIT_SUCCESS } {
Ok(res)
} else {
Err(nix::Error::Sys(Errno::last()).into())
}
}
pub fn sem_open(name: &String, oflag: &OFlag, mode: Mode, value: i32) -> nix::Result<NamedSem> {
name.as_bytes().with_nix_path(|cstr| unsafe {
let sem: *mut libc::sem_t = libc::sem_open(
cstr.as_ptr(),
oflag.bits(),
mode.bits() as libc::mode_t,
value,
);
if sem == libc::SEM_FAILED {
Err(nix::Error::Sys(Errno::last()))
} else {
Ok(NamedSem {
name: name.clone(),
sem,
})
}
})?
}
pub fn sem_wait(sem: &NamedSem) -> nix::Result<()> {
if unsafe { libc::sem_wait(sem.sem) == libc::EXIT_SUCCESS } {
Ok(())
} else {
Err(nix::Error::Sys(Errno::last()).into())
}
}
pub fn sem_trywait(sem: &NamedSem) -> nix::Result<()> {
if unsafe { libc::sem_trywait(sem.sem) == libc::EXIT_SUCCESS } {
Ok(())
} else {
Err(nix::Error::Sys(Errno::last()).into())
}
}
pub fn sem_post(sem: &NamedSem) -> nix::Result<()> {
if unsafe { libc::sem_post(sem.sem) == libc::EXIT_SUCCESS } {
Ok(())
} else {
Err(nix::Error::Sys(Errno::last()).into())
}
}
pub fn sem_close(sem: &mut NamedSem) -> nix::Result<()> {
if unsafe { libc::sem_close(sem.sem) == libc::EXIT_SUCCESS } {
Ok(())
} else {
Err(nix::Error::Sys(Errno::last()).into())
}
}
}
#[cfg(test)]
mod _test {
use super::*;
#[test]
fn good_names() {
assert_eq!(true, verify_name("abc").is_ok(), "lowercase");
assert_eq!(true, verify_name("123").is_ok(), "numbers");
assert_eq!(true, verify_name("ABC").is_ok(), "capitals");
assert_eq!(true, verify_name("a-c").is_ok(), "dash");
assert_eq!(true, verify_name("a_c").is_ok(), "underscore");
assert_eq!(true, verify_name("/ab").is_ok(), "prefix-slash");
}
#[test]
fn bad_names() {
assert_eq!(false, verify_name("").is_ok(), "too short");
assert_eq!(false, verify_name("a".repeat(251)).is_ok(), "too long");
assert_eq!(false, verify_name("a/").is_ok(), "slash within name");
}
}