#[cfg(unix)]
use std::os::unix::io::AsRawFd;
#[cfg(unix)]
use tokio::net::UnixStream;
#[cfg(unix)]
use super::{lock_path, recoverer_lock_path};
#[cfg(unix)]
pub(super) fn open_lock_file(path: &std::path::Path) -> std::io::Result<std::fs::File> {
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.open(path)
}
#[cfg(unix)]
fn acquire_flock_blocking(path: &std::path::Path, label: &str) -> Option<std::fs::File> {
let file = match open_lock_file(path) {
Ok(f) => f,
Err(e) => {
tracing::warn!(target: "khive_runtime::daemon", error = %e, path = ?path, "cannot open {label} lock file");
return None;
}
};
let rc = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX) };
if rc != 0 {
tracing::warn!(target: "khive_runtime::daemon", "flock LOCK_EX failed on {label} lock");
return None;
}
Some(file)
}
#[cfg(unix)]
pub fn acquire_recovery_lock() -> Option<std::fs::File> {
acquire_flock_blocking(&lock_path(), "recovery")
}
#[cfg(unix)]
fn try_acquire_flock_until(
path: &std::path::Path,
deadline: std::time::Instant,
) -> std::io::Result<Option<std::fs::File>> {
let file = open_lock_file(path)?;
let poll_interval = std::time::Duration::from_millis(10);
loop {
let rc = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
if rc == 0 {
return Ok(Some(file));
}
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::EWOULDBLOCK) {
return Err(err);
}
let now = std::time::Instant::now();
if now >= deadline {
return Ok(None);
}
std::thread::sleep(poll_interval.min(deadline - now));
}
}
#[cfg(unix)]
pub fn try_acquire_daemon_boot_guard_until(
deadline: std::time::Instant,
) -> std::io::Result<Option<DaemonBootGuard>> {
try_acquire_flock_until(&lock_path(), deadline)
}
#[cfg(unix)]
pub fn try_acquire_recoverer_lock_until(
deadline: std::time::Instant,
) -> std::io::Result<Option<std::fs::File>> {
try_acquire_flock_until(&recoverer_lock_path(), deadline)
}
#[cfg(unix)]
pub type DaemonBootGuard = std::fs::File;
#[cfg(unix)]
pub fn acquire_daemon_boot_guard() -> anyhow::Result<DaemonBootGuard> {
acquire_recovery_lock()
.ok_or_else(|| anyhow::anyhow!("failed to acquire daemon boot/recovery lock"))
}
#[cfg(unix)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) struct SocketIdentity {
dev: u64,
ino: u64,
}
#[cfg(unix)]
pub(super) fn socket_identity(path: &std::path::Path) -> Option<SocketIdentity> {
use std::os::unix::fs::MetadataExt;
let meta = std::fs::metadata(path).ok()?;
Some(SocketIdentity {
dev: meta.dev(),
ino: meta.ino(),
})
}
#[cfg(unix)]
pub(crate) fn peer_uid(stream: &UnixStream) -> std::io::Result<u32> {
use std::os::fd::AsRawFd;
let fd = stream.as_raw_fd();
#[cfg(any(target_os = "macos", target_os = "ios", target_vendor = "apple"))]
{
let mut uid: libc::uid_t = 0;
let mut gid: libc::gid_t = 0;
let rc = unsafe { libc::getpeereid(fd, &mut uid, &mut gid) };
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(uid as u32)
}
#[cfg(target_os = "linux")]
{
let mut cred = libc::ucred {
pid: 0,
uid: 0,
gid: 0,
};
let mut len = std::mem::size_of::<libc::ucred>() as libc::socklen_t;
let rc = unsafe {
libc::getsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_PEERCRED,
(&mut cred as *mut libc::ucred).cast::<libc::c_void>(),
&mut len,
)
};
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(cred.uid)
}
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_vendor = "apple"
)))]
{
let _ = fd;
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"peer-credential capture is not implemented for this platform",
))
}
}
#[cfg(unix)]
pub(crate) fn uid_is_permitted(peer: u32, daemon_euid: u32) -> bool {
peer == daemon_euid
}