use std::fs::{self, File};
use std::io::{self, Read};
use std::os::fd::{AsRawFd, FromRawFd};
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use crate::{ProcessMemory, Termination};
#[cfg(all(
any(target_os = "linux", target_os = "android"),
target_pointer_width = "64"
))]
const LOCK_OFFSET: libc::off_t = i64::MAX;
#[cfg(all(
any(target_os = "linux", target_os = "android"),
target_pointer_width = "64"
))]
pub(crate) fn try_lock_exclusive(file: &File) -> io::Result<bool> {
match lock_unreachable_byte(file) {
Err(error) if error.raw_os_error() == Some(libc::EINVAL) => flock_exclusive(file),
locked => locked,
}
}
#[cfg(not(all(
any(target_os = "linux", target_os = "android"),
target_pointer_width = "64"
)))]
pub(crate) fn try_lock_exclusive(file: &File) -> io::Result<bool> {
flock_exclusive(file)
}
#[cfg(all(
any(target_os = "linux", target_os = "android"),
target_pointer_width = "64"
))]
fn lock_unreachable_byte(file: &File) -> io::Result<bool> {
let mut range: libc::flock = unsafe { std::mem::zeroed() };
#[allow(clippy::cast_possible_truncation)]
{
range.l_type = libc::F_WRLCK as libc::c_short;
range.l_whence = libc::SEEK_SET as libc::c_short;
}
range.l_start = LOCK_OFFSET;
range.l_len = 1;
loop {
if unsafe { libc::fcntl(file.as_raw_fd(), libc::F_OFD_SETLK, &raw const range) } == 0 {
return Ok(true);
}
let error = io::Error::last_os_error();
match error.raw_os_error() {
Some(libc::EAGAIN | libc::EACCES) => return Ok(false),
Some(libc::EINTR) => {}
_ => return Err(error),
}
}
}
fn flock_exclusive(file: &File) -> io::Result<bool> {
loop {
if unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) } == 0 {
return Ok(true);
}
let error = io::Error::last_os_error();
match error.kind() {
io::ErrorKind::WouldBlock => return Ok(false),
io::ErrorKind::Interrupted => {}
_ => return Err(error),
}
}
}
pub(crate) fn same_file(file: &File, path: &Path) -> io::Result<bool> {
let held = file.metadata()?;
if held.nlink() == 0 {
return Ok(false);
}
match fs::metadata(path) {
Ok(named) => Ok(named.dev() == held.dev() && named.ino() == held.ino()),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(false),
Err(error) => Err(error),
}
}
pub(crate) fn rename_no_replace(from: &Path, to: &Path) -> io::Result<()> {
match rename_exclusive(from, to) {
Err(error) if flag_refused(&error) => {}
renamed => return renamed,
}
fs::hard_link(from, to)?;
fs::remove_file(from)
}
#[cfg(all(target_os = "linux", target_env = "gnu"))]
fn rename_exclusive(from: &Path, to: &Path) -> io::Result<()> {
let (from, to) = (c_path(from)?, c_path(to)?);
let renamed = unsafe {
libc::renameat2(
libc::AT_FDCWD,
from.as_ptr(),
libc::AT_FDCWD,
to.as_ptr(),
libc::RENAME_NOREPLACE,
)
};
if renamed == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(target_os = "macos")]
fn rename_exclusive(from: &Path, to: &Path) -> io::Result<()> {
let (from, to) = (c_path(from)?, c_path(to)?);
let renamed = unsafe { libc::renamex_np(from.as_ptr(), to.as_ptr(), libc::RENAME_EXCL) };
if renamed == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(not(any(all(target_os = "linux", target_env = "gnu"), target_os = "macos")))]
fn rename_exclusive(_from: &Path, _to: &Path) -> io::Result<()> {
Err(io::Error::from_raw_os_error(libc::ENOTSUP))
}
fn flag_refused(error: &io::Error) -> bool {
matches!(
error.raw_os_error(),
Some(libc::EINVAL | libc::ENOSYS | libc::ENOTSUP)
)
}
#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "macos"))]
fn c_path(path: &Path) -> io::Result<std::ffi::CString> {
use std::os::unix::ffi::OsStrExt;
std::ffi::CString::new(path.as_os_str().as_bytes())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contains a NUL byte"))
}
static WAKE: AtomicI32 = AtomicI32::new(-1);
static INSTALLED: AtomicBool = AtomicBool::new(false);
extern "C" fn on_signal(signal: libc::c_int) {
let saved = io::Error::last_os_error().raw_os_error().unwrap_or(0);
let fd = WAKE.load(Ordering::Relaxed);
if fd >= 0 {
let byte = u8::try_from(signal).unwrap_or(u8::MAX);
let _ = unsafe { libc::write(fd, (&raw const byte).cast(), 1) };
}
unsafe { *errno_location() = saved };
}
#[cfg(any(target_os = "linux", target_os = "android"))]
unsafe fn errno_location() -> *mut libc::c_int {
unsafe { libc::__errno_location() }
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
unsafe fn errno_location() -> *mut libc::c_int {
unsafe { libc::__error() }
}
fn termination(signal: u8) -> Termination {
let (name, number) = match i32::from(signal) {
libc::SIGINT => ("SIGINT", libc::SIGINT),
_ => ("SIGTERM", libc::SIGTERM),
};
Termination {
name,
exit_code: u8::try_from(128 + number).unwrap_or(u8::MAX),
closing: false,
}
}
pub(crate) fn on_termination(handler: Box<dyn Fn(Termination) + Send + Sync>) -> io::Result<()> {
if INSTALLED.swap(true, Ordering::AcqRel) {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"a termination handler is already installed",
));
}
let mut fds = [0 as libc::c_int; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
for fd in fds {
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } != 0 {
return Err(io::Error::last_os_error());
}
}
let mut wake = unsafe { File::from_raw_fd(fds[0]) };
WAKE.store(fds[1], Ordering::Release);
std::thread::Builder::new()
.name("alpha2-termination".to_owned())
.spawn(move || {
let mut signal = [0_u8];
loop {
match wake.read(&mut signal) {
Ok(1) => handler(termination(signal[0])),
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
_ => return,
}
}
})?;
for signal in [libc::SIGTERM, libc::SIGINT] {
let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
action.sa_sigaction = on_signal as extern "C" fn(libc::c_int) as libc::sighandler_t;
action.sa_flags = libc::SA_RESTART | libc::SA_RESETHAND;
let installed = unsafe {
libc::sigemptyset(&raw mut action.sa_mask);
libc::sigaction(signal, &raw const action, std::ptr::null_mut())
};
if installed != 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn process_memory() -> ProcessMemory {
let status = fs::read_to_string("/proc/self/status").unwrap_or_default();
let kib = |key: &str| {
status
.lines()
.find_map(|line| line.strip_prefix(key))
.and_then(|rest| rest.trim().strip_suffix("kB"))
.and_then(|value| value.trim().parse::<u64>().ok())
.map(|value| value.saturating_mul(1_024))
};
ProcessMemory {
resident_bytes: kib("VmRSS:"),
peak_resident_bytes: kib("VmHWM:"),
}
}
#[cfg(target_os = "macos")]
pub(crate) fn process_memory() -> ProcessMemory {
let mut usage: libc::rusage_info_v2 = unsafe { std::mem::zeroed() };
let resident = unsafe {
libc::proc_pid_rusage(
libc::getpid(),
libc::RUSAGE_INFO_V2,
std::ptr::addr_of_mut!(usage).cast(),
)
} == 0;
let mut own: libc::rusage = unsafe { std::mem::zeroed() };
let peak = unsafe { libc::getrusage(libc::RUSAGE_SELF, &raw mut own) } == 0;
ProcessMemory {
resident_bytes: resident.then_some(usage.ri_resident_size),
peak_resident_bytes: peak.then(|| u64::try_from(own.ru_maxrss).ok()).flatten(),
}
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
pub(crate) fn process_memory() -> ProcessMemory {
ProcessMemory::default()
}
pub(crate) fn thread_cpu_work() -> Option<u64> {
let mut now: libc::timespec = unsafe { std::mem::zeroed() };
let read = unsafe { libc::clock_gettime(libc::CLOCK_THREAD_CPUTIME_ID, &raw mut now) } == 0;
let seconds = u64::try_from(now.tv_sec).ok()?;
let nanoseconds = u64::try_from(now.tv_nsec).ok()?;
read.then(|| {
seconds
.saturating_mul(1_000_000_000)
.saturating_add(nanoseconds)
})
}