#![cfg(unix)]
use std::ffi::CString;
use std::fs::OpenOptions;
use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::os::unix::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use std::ptr::NonNull;
pub const SHM_NAMESPACE: &str = "orbit";
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ShmAccessPolicy {
#[default]
OwnerOnly,
GroupRead { gid: u32 },
GroupReadWrite { gid: u32 },
}
impl ShmAccessPolicy {
pub const fn mode(self) -> u32 {
match self {
Self::OwnerOnly => 0o600,
Self::GroupRead { .. } => 0o640,
Self::GroupReadWrite { .. } => 0o660,
}
}
pub const fn gid(self) -> Option<u32> {
match self {
Self::OwnerOnly => None,
Self::GroupRead { gid } | Self::GroupReadWrite { gid } => Some(gid),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ShmValidation {
Missing,
Valid { actual_size: usize },
}
pub struct ShmRegion {
name: CString,
lock_path: PathBuf,
process_lock: bool,
ptr: NonNull<u8>,
len: usize,
created: bool,
}
impl ShmRegion {
pub fn validate_existing(
name: &str,
minimum_size: usize,
) -> io::Result<ShmValidation> {
Self::validate_existing_with_policy(
name,
minimum_size,
unsafe { libc::geteuid() },
ShmAccessPolicy::default(),
)
}
pub fn validate_existing_with_policy(
name: &str,
minimum_size: usize,
owner_uid: u32,
policy: ShmAccessPolicy,
) -> io::Result<ShmValidation> {
let cname = CString::new(name).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidInput, "shm name has nul byte")
})?;
let raw_fd = loop {
let fd = unsafe {
libc::shm_open(cname.as_ptr(), libc::O_RDONLY, 0o600)
};
if fd >= 0 {
break fd;
}
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::Interrupted {
continue;
}
if error.raw_os_error() == Some(libc::ENOENT) {
return Ok(ShmValidation::Missing);
}
return Err(error);
};
let fd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
let actual_size = shm_object_size(&fd, name, owner_uid, policy)?;
validate_minimum_size(name, actual_size, minimum_size)?;
Ok(ShmValidation::Valid { actual_size })
}
pub(crate) fn open_existing_read_only(
name: &str,
minimum_size: usize,
owner_uid: u32,
policy: ShmAccessPolicy,
) -> io::Result<Self> {
Self::open_existing(name, minimum_size, owner_uid, policy, false)
}
pub fn open_existing_read_write(
name: &str,
minimum_size: usize,
owner_uid: u32,
policy: ShmAccessPolicy,
) -> io::Result<Self> {
Self::open_existing(name, minimum_size, owner_uid, policy, true)
}
fn open_existing(
name: &str,
minimum_size: usize,
owner_uid: u32,
policy: ShmAccessPolicy,
writable: bool,
) -> io::Result<Self> {
let cname = CString::new(name).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidInput, "shm name has nul byte")
})?;
let raw_fd = loop {
let fd = unsafe {
libc::shm_open(
cname.as_ptr(),
if writable { libc::O_RDWR } else { libc::O_RDONLY },
0o600,
)
};
if fd >= 0 {
break fd;
}
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(error);
};
let fd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
let actual_size = shm_object_size(&fd, name, owner_uid, policy)?;
validate_minimum_size(name, actual_size, minimum_size)?;
let ptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
actual_size,
libc::PROT_READ | if writable { libc::PROT_WRITE } else { 0 },
libc::MAP_SHARED,
fd.as_raw_fd(),
0,
)
};
if ptr == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
let ptr = NonNull::new(ptr.cast::<u8>())
.expect("mmap returned non-null on success");
Ok(Self {
lock_path: lock_dir_for_uid(owner_uid)
.join(format!("{}.lock", name.trim_start_matches('/'))),
name: cname,
process_lock: false,
ptr,
len: actual_size,
created: false,
})
}
pub fn open_or_create(
name: &str,
size: usize,
) -> io::Result<Self> {
Self::open_or_create_with_policy(name, size, ShmAccessPolicy::default())
}
pub fn open_or_create_with_policy(
name: &str,
size: usize,
policy: ShmAccessPolicy,
) -> io::Result<Self> {
let (region, initialization_lock) =
Self::open_or_create_inner(name, size, false, policy)?;
debug_assert!(initialization_lock.is_none());
Ok(region)
}
pub fn open_or_create_locked(
name: &str,
size: usize,
) -> io::Result<(Self, ShmRegionLock)> {
Self::open_or_create_locked_with_policy(
name,
size,
ShmAccessPolicy::default(),
)
}
pub fn open_or_create_locked_with_policy(
name: &str,
size: usize,
policy: ShmAccessPolicy,
) -> io::Result<(Self, ShmRegionLock)> {
let (region, initialization_lock) =
Self::open_or_create_inner(name, size, true, policy)?;
Ok((
region,
initialization_lock
.expect("locked SHM open must return its initialization lock"),
))
}
fn open_or_create_inner(
name: &str,
size: usize,
process_lock: bool,
policy: ShmAccessPolicy,
) -> io::Result<(Self, Option<ShmRegionLock>)> {
let cname = CString::new(name).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidInput, "shm name has nul byte")
})?;
let lock_path = lock_file_path(name);
let initialization_lock = if process_lock {
Some(lock_path_exclusive(&lock_path)?)
} else {
None
};
#[cfg(target_os = "macos")]
let can_create =
policy.gid().is_none_or(|gid| gid == unsafe { libc::getegid() });
#[cfg(target_os = "macos")]
let creation_mode = policy.mode();
#[cfg(not(target_os = "macos"))]
let (can_create, creation_mode) = (true, 0o600u32);
#[cfg(not(target_vendor = "apple"))]
let creation_mode = creation_mode as libc::mode_t;
let (raw_fd, created) = unsafe {
let fd = if can_create {
libc::shm_open(
cname.as_ptr(),
libc::O_RDWR | libc::O_CREAT | libc::O_EXCL,
creation_mode,
)
} else {
libc::shm_open(cname.as_ptr(), libc::O_RDWR, 0)
};
if fd >= 0 {
(fd, can_create)
} else {
let err = io::Error::last_os_error();
if !can_create && err.raw_os_error() == Some(libc::ENOENT) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"creating group-shared SHM requires the selected effective gid",
));
}
if err.raw_os_error() != Some(libc::EEXIST) {
return Err(err);
}
let fd = libc::shm_open(cname.as_ptr(), libc::O_RDWR, 0o600);
if fd < 0 {
return Err(io::Error::last_os_error());
}
(fd, false)
}
};
let fd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
#[cfg(not(target_os = "macos"))]
if created && let Err(error) = configure_new_shm(&fd, policy) {
let _ = unsafe { libc::shm_unlink(cname.as_ptr()) };
return Err(error);
}
let owner_uid = unsafe { libc::geteuid() };
if let Err(error) = shm_object_size(&fd, name, owner_uid, policy) {
if created {
let _ = unsafe { libc::shm_unlink(cname.as_ptr()) };
}
return Err(error);
}
if created {
let rc =
unsafe { libc::ftruncate(fd.as_raw_fd(), size as libc::off_t) };
if rc != 0 {
let err = io::Error::last_os_error();
let _ = unsafe { libc::shm_unlink(cname.as_ptr()) };
return Err(err);
}
}
let actual_size = match shm_object_size(&fd, name, owner_uid, policy) {
Ok(actual_size) => actual_size,
Err(error) => {
if created {
let _ = unsafe { libc::shm_unlink(cname.as_ptr()) };
}
return Err(error);
}
};
if let Err(error) = validate_minimum_size(name, actual_size, size) {
if created {
let _ = unsafe { libc::shm_unlink(cname.as_ptr()) };
}
return Err(error);
}
let ptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd.as_raw_fd(),
0,
)
};
if ptr == libc::MAP_FAILED {
let err = io::Error::last_os_error();
if created {
let _ = unsafe { libc::shm_unlink(cname.as_ptr()) };
}
return Err(err);
}
let ptr = NonNull::new(ptr.cast::<u8>())
.expect("mmap returned non-null on success");
Ok((
Self {
name: cname,
lock_path,
process_lock,
ptr,
len: size,
created,
},
initialization_lock,
))
}
pub fn as_ptr(&self) -> *mut u8 {
self.ptr.as_ptr()
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn created(&self) -> bool {
self.created
}
pub fn lock_exclusive(&self) -> io::Result<ShmRegionLock> {
if !self.process_lock {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"SHM region was opened without a process lock",
));
}
lock_path_exclusive(&self.lock_path)
}
#[cfg(test)]
pub(crate) fn try_lock_exclusive(&self) -> io::Result<ShmRegionLock> {
if !self.process_lock {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"SHM region was opened without a process lock",
));
}
let lock_fd = open_lock_file(&self.lock_path)?;
let rc = unsafe {
libc::flock(lock_fd.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB)
};
if rc == 0 {
Ok(ShmRegionLock { lock_fd })
} else {
Err(io::Error::last_os_error())
}
}
pub fn unlink(&self) -> io::Result<()> {
let rc = unsafe { libc::shm_unlink(self.name.as_ptr()) };
let shm_error = if rc != 0 {
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(libc::ENOENT) {
None
} else {
Some(err)
}
} else {
None
};
let mut lock_error = match std::fs::remove_file(&self.lock_path) {
Ok(()) => None,
Err(error) if error.kind() == io::ErrorKind::NotFound => None,
Err(error) => Some(error),
};
let name = self.name.to_string_lossy();
let uid = unsafe { libc::geteuid() };
match companion_lock_files(&name, uid) {
Ok(files) => {
for file in files {
if let Err(error) = std::fs::remove_file(&file)
&& error.kind() != io::ErrorKind::NotFound
{
lock_error.get_or_insert(error);
}
}
}
Err(error) => {
lock_error.get_or_insert(error);
}
}
if let Some(error) = shm_error.or(lock_error) {
return Err(error);
}
Ok(())
}
}
#[cfg(not(target_os = "macos"))]
fn configure_new_shm(
fd: &OwnedFd,
policy: ShmAccessPolicy,
) -> io::Result<()> {
let Some(gid) = policy.gid() else {
return Ok(());
};
if gid == !0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid SHM group id",
));
}
if unsafe { libc::fchown(fd.as_raw_fd(), !0, gid) } != 0 {
return Err(io::Error::last_os_error());
}
if unsafe { libc::fchmod(fd.as_raw_fd(), policy.mode() as _) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn shm_object_size(
fd: &OwnedFd,
name: &str,
owner_uid: u32,
policy: ShmAccessPolicy,
) -> io::Result<usize> {
let mut stat = std::mem::MaybeUninit::<libc::stat>::uninit();
let stat_rc = unsafe { libc::fstat(fd.as_raw_fd(), stat.as_mut_ptr()) };
if stat_rc != 0 {
return Err(io::Error::last_os_error());
}
let stat = unsafe { stat.assume_init() };
let mode = u64::from(stat.st_mode) & 0o7777;
if stat.st_uid != owner_uid
|| policy.gid().is_some_and(|gid| gid != stat.st_gid)
|| mode & !u64::from(policy.mode()) != 0
{
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"SHM segment {name} owner/group/mode do not satisfy {policy:?} for uid {owner_uid}"
),
));
}
let actual_size = stat.st_size;
usize::try_from(actual_size).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("SHM segment {name} reported invalid size {actual_size}"),
)
})
}
fn validate_minimum_size(
name: &str,
actual_size: usize,
minimum_size: usize,
) -> io::Result<()> {
if actual_size < minimum_size {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"SHM segment {name} size {actual_size} is smaller than requested mapping {minimum_size}"
),
));
}
Ok(())
}
pub struct ShmRegionLock {
lock_fd: OwnedFd,
}
impl Drop for ShmRegionLock {
fn drop(&mut self) {
let _ = unsafe { libc::flock(self.lock_fd.as_raw_fd(), libc::LOCK_UN) };
}
}
fn lock_path_exclusive(lock_path: &Path) -> io::Result<ShmRegionLock> {
lock_fd_exclusive(open_lock_file(lock_path)?)
}
fn open_lock_file(lock_path: &Path) -> io::Result<OwnedFd> {
use std::os::unix::fs::MetadataExt;
if let Some(dir) = lock_path.parent() {
ensure_lock_dir(dir)?;
}
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.mode(0o600)
.custom_flags(libc::O_CLOEXEC | libc::O_NOFOLLOW)
.open(lock_path)?;
let uid = unsafe { libc::geteuid() };
let owner = file.metadata()?.uid();
if owner != uid {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"{} is owned by uid {owner} rather than {uid}",
lock_path.display()
),
));
}
Ok(file.into())
}
fn lock_fd_exclusive(lock_fd: OwnedFd) -> io::Result<ShmRegionLock> {
loop {
let rc = unsafe { libc::flock(lock_fd.as_raw_fd(), libc::LOCK_EX) };
if rc == 0 {
return Ok(ShmRegionLock { lock_fd });
}
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::Interrupted {
return Err(error);
}
}
}
impl Drop for ShmRegion {
fn drop(&mut self) {
unsafe {
libc::munmap(self.ptr.as_ptr().cast(), self.len);
}
}
}
unsafe impl Send for ShmRegion {}
unsafe impl Sync for ShmRegion {}
pub fn ring_segment_name(
fleet_name: &str,
kind: u8,
) -> String {
let uid = unsafe { libc::geteuid() };
ring_segment_name_for_uid(fleet_name, kind, uid)
}
pub fn ring_segment_name_for_uid(
fleet_name: &str,
kind: u8,
uid: u32,
) -> String {
format!("/{SHM_NAMESPACE}-{fleet_name}-{kind}-{uid}")
}
pub fn fleet_lock_path(fleet_name: &str) -> PathBuf {
lock_dir().join(format!("{SHM_NAMESPACE}-{fleet_name}.fleet"))
}
pub fn fleet_lock_path_for_uid(
fleet_name: &str,
uid: u32,
) -> PathBuf {
lock_dir_for_uid(uid).join(format!("{SHM_NAMESPACE}-{fleet_name}.fleet"))
}
pub struct FleetMembership {
lock_fd: OwnedFd,
}
impl Drop for FleetMembership {
fn drop(&mut self) {
let _ = unsafe { libc::flock(self.lock_fd.as_raw_fd(), libc::LOCK_UN) };
}
}
pub fn join_fleet_membership(fleet_name: &str) -> io::Result<FleetMembership> {
let lock_fd = open_lock_file(&fleet_lock_path(fleet_name))?;
let rc = unsafe { libc::flock(lock_fd.as_raw_fd(), libc::LOCK_SH) };
if rc != 0 {
return Err(io::Error::last_os_error());
}
Ok(FleetMembership { lock_fd })
}
pub fn hold_existing_fleet(fleet_name: &str) -> io::Result<FleetMembership> {
use std::os::unix::fs::MetadataExt;
let path = fleet_lock_path(fleet_name);
let file = OpenOptions::new()
.read(true)
.custom_flags(libc::O_CLOEXEC | libc::O_NOFOLLOW)
.open(&path)?;
let metadata = file.metadata()?;
if !metadata.is_file() || metadata.uid() != unsafe { libc::geteuid() } {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"fleet membership file has an unexpected owner or type",
));
}
let lock_fd: OwnedFd = file.into();
loop {
let rc = unsafe { libc::flock(lock_fd.as_raw_fd(), libc::LOCK_SH) };
if rc == 0 {
return Ok(FleetMembership { lock_fd });
}
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::Interrupted {
return Err(error);
}
}
}
pub fn try_lock_fleet_exclusive(
fleet_name: &str,
uid: u32,
) -> io::Result<Option<ShmRegionLock>> {
let lock_fd = open_lock_file(&fleet_lock_path_for_uid(fleet_name, uid))?;
let rc = unsafe {
libc::flock(lock_fd.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB)
};
if rc == 0 {
return Ok(Some(ShmRegionLock { lock_fd }));
}
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::WouldBlock {
return Ok(None);
}
Err(error)
}
pub struct LaneHold {
lock_fd: OwnedFd,
}
impl Drop for LaneHold {
fn drop(&mut self) {
let _ = unsafe { libc::flock(self.lock_fd.as_raw_fd(), libc::LOCK_UN) };
}
}
pub fn try_hold_lane(
shm_name: &str,
lane: usize,
) -> io::Result<Option<LaneHold>> {
let name = format!("{}.lane{lane}", shm_name.trim_start_matches('/'));
let lock_fd = open_lock_file(&lock_file_path(&name))?;
let rc = unsafe {
libc::flock(lock_fd.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB)
};
if rc == 0 {
return Ok(Some(LaneHold { lock_fd }));
}
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::WouldBlock {
return Ok(None);
}
Err(error)
}
pub struct WatcherHold {
lock_fd: OwnedFd,
}
impl Drop for WatcherHold {
fn drop(&mut self) {
let _ = unsafe { libc::flock(self.lock_fd.as_raw_fd(), libc::LOCK_UN) };
}
}
pub fn try_hold_watcher(
shm_name: &str,
slot: usize,
) -> io::Result<Option<WatcherHold>> {
let name = format!("{}.watch{slot}", shm_name.trim_start_matches('/'));
let lock_fd = open_lock_file(&lock_file_path(&name))?;
let rc = unsafe {
libc::flock(lock_fd.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB)
};
if rc == 0 {
return Ok(Some(WatcherHold { lock_fd }));
}
let error = io::Error::last_os_error();
if error.kind() == io::ErrorKind::WouldBlock {
return Ok(None);
}
Err(error)
}
pub fn companion_lock_files(
shm_name: &str,
uid: u32,
) -> io::Result<Vec<PathBuf>> {
let base = shm_name.trim_start_matches('/');
let own = format!("{base}.lock");
let lane = format!("{base}.lane");
let watch = format!("{base}.watch");
let dir = lock_dir_for_uid(uid);
let entries = match std::fs::read_dir(&dir) {
Ok(entries) => entries,
Err(error) if error.kind() == io::ErrorKind::NotFound => {
return Ok(Vec::new());
}
Err(error) => return Err(error),
};
let mut found = Vec::new();
for entry in entries {
let entry = entry?;
let Some(file) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if file == own
|| ((file.starts_with(&lane) || file.starts_with(&watch))
&& file.ends_with(".lock"))
{
found.push(entry.path());
}
}
Ok(found)
}
fn lock_file_path(shm_name: &str) -> PathBuf {
lock_dir().join(format!("{}.lock", shm_name.trim_start_matches('/')))
}
fn lock_dir() -> PathBuf {
let uid = unsafe { libc::geteuid() };
lock_dir_for_uid(uid)
}
pub fn lock_dir_for_uid(uid: u32) -> PathBuf {
let base = std::env::var_os("XDG_RUNTIME_DIR")
.map(PathBuf::from)
.filter(|dir| dir.is_absolute())
.unwrap_or_else(|| PathBuf::from("/tmp"));
base.join(format!("{SHM_NAMESPACE}-{uid}"))
}
fn ensure_lock_dir(dir: &Path) -> io::Result<()> {
use std::os::unix::fs::DirBuilderExt;
let uid = unsafe { libc::geteuid() };
match std::fs::DirBuilder::new().mode(0o700).create(dir) {
Ok(()) => {}
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}
Err(error) => return Err(error),
}
ensure_private_dir(dir, uid)
}
fn ensure_private_dir(
dir: &Path,
uid: u32,
) -> io::Result<()> {
use std::os::unix::fs::{MetadataExt, PermissionsExt};
let metadata = std::fs::symlink_metadata(dir)?;
if !metadata.is_dir() {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!("{} is not a directory", dir.display()),
));
}
if metadata.uid() != uid {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"{} is owned by uid {} rather than {uid}; refusing to lock in a directory \
another user controls",
dir.display(),
metadata.uid()
),
));
}
if metadata.permissions().mode() & 0o077 != 0 {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"{} is mode {:o}; refusing to lock in a directory others can write to",
dir.display(),
metadata.permissions().mode() & 0o777
),
));
}
Ok(())
}
#[cfg(test)]
mod fleet_lock_tests {
use super::{join_fleet_membership, try_lock_fleet_exclusive};
#[test]
fn a_member_holds_the_fleet_against_exclusive_takers() {
let fleet = format!("fl{:x}", std::process::id());
let uid = unsafe { libc::geteuid() };
let member = join_fleet_membership(&fleet).expect("join");
assert!(try_lock_fleet_exclusive(&fleet, uid).expect("try").is_none());
drop(member);
let exclusive = try_lock_fleet_exclusive(&fleet, uid).expect("try");
assert!(exclusive.is_some());
drop(exclusive);
let _ =
std::fs::remove_file(super::fleet_lock_path_for_uid(&fleet, uid));
}
}