use crate::SemaphoreRef;
use core::{ffi::{c_uint, CStr},
fmt::{self, Display, Formatter}};
#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
const SEM_FAILED: *mut libc::sem_t = libc::SEM_FAILED;
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
#[allow(clippy::as_conversions)]
const SEM_FAILED: *mut libc::sem_t = -1_isize as *mut libc::sem_t;
#[must_use]
#[derive(Debug)]
pub struct Semaphore {
ptr: *mut libc::sem_t,
}
unsafe impl Sync for Semaphore {}
unsafe impl Send for Semaphore {}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OpenFlags {
AccessOnly,
Create {
exclusive: bool,
mode: libc::mode_t,
value: c_uint,
},
}
impl Semaphore {
#[inline]
pub fn open(name: &CStr, open_flags: OpenFlags) -> Result<Self, ()> {
let name_as = name.as_ptr();
loop {
let ptr = match open_flags {
OpenFlags::AccessOnly => {
let oflag = 0;
unsafe { libc::sem_open(name_as, oflag) }
},
OpenFlags::Create { exclusive, mode, value } => {
let oflag = libc::O_CREAT | if exclusive { libc::O_EXCL } else { 0 };
let mode = c_uint::from(mode);
unsafe { libc::sem_open(name_as, oflag, mode, value) }
},
};
if ptr == SEM_FAILED {
let errno = errno::errno().0;
if errno == libc::EINTR {
continue;
}
break Err(());
}
break Ok(Self { ptr });
}
}
#[must_use]
#[inline]
pub fn sem_ref(&self) -> SemaphoreRef<'_> {
unsafe { SemaphoreRef::named(self.ptr) }
}
#[inline]
pub fn unlink(name: &CStr) -> Result<(), ()> {
let name = name.as_ptr();
let r = unsafe { libc::sem_unlink(name) };
if r == 0 { Ok(()) } else { Err(()) }
}
#[inline]
pub unsafe fn close(self) -> Result<(), ()> {
let sem = self.ptr;
let r = unsafe { libc::sem_close(sem) };
if r == 0 { Ok(()) } else { Err(()) }
}
#[cfg(feature = "anonymous")]
#[inline]
pub fn anonymous() -> Result<Self, ()> { Self::anonymous_with(0) }
#[cfg(feature = "anonymous")]
#[inline]
#[allow(clippy::expect_used)]
pub fn anonymous_with(sem_count: c_uint) -> Result<Self, ()> {
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use core::ops::Range;
use getrandom::getrandom;
struct UniqueName {
name: [u8; Self::NAME_LEN],
}
impl UniqueName {
const RAND_LEN: usize = 16; const INIT_UNIQUE: [u8; Self::RAND_LEN] = [
0xCA, 0xFF, 0xDD, 0xCE, 0x94, 0x57, 0x7A, 0xF4, 0xCC, 0xAC, 0x86, 0xFF, 0x42,
0x99, 0x12, 0xA6,
];
const NAME_LEN: usize = match base64::encoded_len(Self::RAND_LEN, false) {
Some(len) => 1 + len + 1,
#[allow(clippy::unreachable)]
None => unreachable!(), };
const B64_RANGE: Range<usize> = 1 .. (Self::NAME_LEN - 1);
fn new() -> Self {
let mut it = Self { name: [0; Self::NAME_LEN] };
it.name[0] = b'/'; it.name[Self::NAME_LEN - 1] = b'\0'; it
}
fn generate(&mut self) -> &CStr {
let mut random: [u8; Self::RAND_LEN] = Self::INIT_UNIQUE;
let _ignore_err = getrandom(&mut random);
let _b64_size = URL_SAFE_NO_PAD
.encode_slice(random, &mut self.name[Self::B64_RANGE])
.expect("output size is always enough");
{
#![allow(clippy::used_underscore_binding)]
debug_assert_eq!(self.name[0], b'/', "path slash is preserved");
debug_assert_eq!(self.name[Self::NAME_LEN - 1], b'\0', "nul is preserved");
debug_assert_eq!(_b64_size, Self::NAME_LEN - 2, "all other bytes filled");
}
CStr::from_bytes_with_nul(&self.name).expect("nul byte is at end")
}
}
const TRY_LIMIT: u32 = 10;
let mut unique_name = UniqueName::new();
let open_flags = OpenFlags::Create {
exclusive: true,
mode: 0o600, value: sem_count,
};
for _ in 0 .. TRY_LIMIT {
let name = unique_name.generate();
if let Ok(sem) = Semaphore::open(name, open_flags) {
let r = Self::unlink(name);
debug_assert!(r.is_ok(), "name unlink will succeed");
return Ok(sem);
}
}
Err(())
}
}
impl Display for Semaphore {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.sem_ref(), f) }
}
#[cfg(all(doctest, any(target_os = "illumos", target_os = "solaris")))]
mod compile_fail_tests {
#[allow(non_snake_case)]
fn SEM_FAILED_is_missing() {}
}