#![allow(non_camel_case_types)]
use core::ffi::{c_int, c_long, c_void};
use core::time::Duration;
use crate::os::types::ThreadHandle;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "linux")]
pub(super) use linux::*;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
pub(super) use macos::*;
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
compile_error!("osal-rs: the `posix` backend supports only Linux and macOS");
#[repr(C, align(8))]
#[derive(Copy, Clone)]
pub(super) struct pthread_attr_t {
_opaque: [u8; PTHREAD_ATTR_T_SIZE],
}
impl Default for pthread_attr_t {
fn default() -> Self {
Self {
_opaque: [0u8; PTHREAD_ATTR_T_SIZE],
}
}
}
#[repr(C, align(8))]
#[derive(Copy, Clone)]
pub struct pthread_mutex_t {
_opaque: [u8; PTHREAD_MUTEX_T_SIZE],
}
impl Default for pthread_mutex_t {
fn default() -> Self {
Self {
_opaque: [0u8; PTHREAD_MUTEX_T_SIZE],
}
}
}
impl pthread_mutex_t {
pub(super) fn is_empty(&self) -> bool {
self._opaque.iter().all(|&b| b == 0)
}
}
#[cfg_attr(target_os = "linux", repr(C, align(4)))]
#[cfg_attr(target_os = "macos", repr(C, align(8)))]
#[derive(Copy, Clone)]
pub(super) struct pthread_mutexattr_t {
_opaque: [u8; PTHREAD_MUTEXATTR_T_SIZE],
}
impl Default for pthread_mutexattr_t {
fn default() -> Self {
Self {
_opaque: [0u8; PTHREAD_MUTEXATTR_T_SIZE],
}
}
}
#[repr(C, align(8))]
#[derive(Copy, Clone)]
pub(super) struct pthread_cond_t {
_opaque: [u8; PTHREAD_COND_T_SIZE],
}
impl Default for pthread_cond_t {
fn default() -> Self {
Self {
_opaque: [0u8; PTHREAD_COND_T_SIZE],
}
}
}
impl pthread_cond_t {
pub(super) fn is_empty(&self) -> bool {
self._opaque.iter().all(|&b| b == 0)
}
}
#[cfg_attr(target_os = "linux", repr(C, align(4)))]
#[cfg_attr(target_os = "macos", repr(C, align(8)))]
#[derive(Copy, Clone)]
pub(super) struct pthread_condattr_t {
_opaque: [u8; PTHREAD_CONDATTR_T_SIZE],
}
impl Default for pthread_condattr_t {
fn default() -> Self {
Self {
_opaque: [0u8; PTHREAD_CONDATTR_T_SIZE],
}
}
}
pub(super) type PthreadOnceRoutine = unsafe extern "C" fn();
pub(super) type ThreadStartRoutine = unsafe extern "C" fn(arg: *mut c_void) -> *mut c_void;
pub(super) type sighandler_t = usize;
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub(super) struct timespec {
pub(super) tv_sec: c_long,
pub(super) tv_nsec: c_long,
}
#[cfg(feature = "real_time")]
pub(super) const EPERM: c_int = 1;
pub(super) const SIGINT: c_int = 2;
pub(super) const SIGTERM: c_int = 15;
pub(super) const PTHREAD_PRIO_INHERIT: c_int = 1;
unsafe extern "C" {
pub(super) fn pthread_attr_init(attr: *mut pthread_attr_t) -> c_int;
pub(super) fn pthread_attr_setstacksize(attr: *mut pthread_attr_t, stacksize: usize) -> c_int;
#[cfg(feature = "real_time")]
pub(super) fn pthread_attr_setinheritsched(attr: *mut pthread_attr_t, inheritsched: c_int) -> c_int;
#[cfg(feature = "real_time")]
pub(super) fn pthread_attr_setschedpolicy(attr: *mut pthread_attr_t, policy: c_int) -> c_int;
#[cfg(feature = "real_time")]
pub(super) fn pthread_attr_setschedparam(attr: *mut pthread_attr_t, param: *const sched_param) -> c_int;
#[cfg(feature = "real_time")]
pub(super) fn sched_get_priority_min(policy: c_int) -> c_int;
pub(super) fn pthread_create(
thread: *mut ThreadHandle,
attr: *const pthread_attr_t,
start_routine: Option<ThreadStartRoutine>,
arg: *mut c_void,
) -> c_int;
pub(super) fn pthread_self() -> ThreadHandle;
pub(super) fn pthread_join(thread: ThreadHandle, retval: *mut *mut c_void) -> c_int;
pub(super) fn pthread_detach(thread: ThreadHandle) -> c_int;
pub(super) fn pthread_kill(thread: ThreadHandle, sig: c_int) -> c_int;
pub(super) fn sigfillset(set: *mut sigset_t) -> c_int;
pub(super) fn sigdelset(set: *mut sigset_t, signum: c_int) -> c_int;
pub(super) fn sigsuspend(mask: *const sigset_t) -> c_int;
pub(super) fn signal(signum: c_int, handler: sighandler_t) -> sighandler_t;
pub(super) fn sigaction(signum: c_int, act: *const sigaction, oldact: *mut sigaction) -> c_int;
pub(super) fn pthread_sigmask(how: c_int, set: *const sigset_t, oldset: *mut sigset_t) -> c_int;
pub(super) fn sigemptyset(set: *mut sigset_t) -> c_int;
pub(super) fn sigaddset(set: *mut sigset_t, signum: c_int) -> c_int;
pub(super) fn sysconf(name: c_int) -> c_long;
pub(super) fn clock_gettime(clock_id: c_int, tp: *mut timespec) -> c_int;
pub(super) fn nanosleep(req: *const timespec, rem: *mut timespec) -> c_int;
pub(super) fn sched_yield() -> c_int;
pub(super) fn pthread_mutexattr_init(attr: *mut pthread_mutexattr_t) -> c_int;
pub(super) fn pthread_mutexattr_settype(attr: *mut pthread_mutexattr_t, kind: c_int) -> c_int;
pub(super) fn pthread_mutexattr_setprotocol(attr: *mut pthread_mutexattr_t, protocol: c_int) -> c_int;
pub(super) fn pthread_mutex_init(mutex: *mut pthread_mutex_t, attr: *const pthread_mutexattr_t) -> c_int;
pub(super) fn pthread_mutex_destroy(mutex: *mut pthread_mutex_t) -> c_int;
pub(super) fn pthread_mutex_lock(mutex: *mut pthread_mutex_t) -> c_int;
pub(super) fn pthread_mutex_trylock(mutex: *mut pthread_mutex_t) -> c_int;
pub(super) fn pthread_mutex_unlock(mutex: *mut pthread_mutex_t) -> c_int;
pub(super) fn pthread_once(once_control: *mut pthread_once_t, init_routine: Option<PthreadOnceRoutine>) -> c_int;
pub(super) fn pthread_condattr_init(attr: *mut pthread_condattr_t) -> c_int;
pub(super) fn pthread_cond_init(cond: *mut pthread_cond_t, attr: *const pthread_condattr_t) -> c_int;
pub(super) fn pthread_cond_destroy(cond: *mut pthread_cond_t) -> c_int;
pub(super) fn pthread_cond_wait(cond: *mut pthread_cond_t, mutex: *mut pthread_mutex_t) -> c_int;
pub(super) fn pthread_cond_broadcast(cond: *mut pthread_cond_t) -> c_int;
}
pub(super) unsafe fn cond_init_monotonic(cond: *mut pthread_cond_t) -> c_int {
let mut attr = pthread_condattr_t::default();
unsafe {
pthread_condattr_init(&mut attr);
condattr_set_monotonic(&mut attr);
pthread_cond_init(cond, &attr)
}
}
pub(super) fn monotonic_deadline(timeout: Duration) -> timespec {
let mut now = timespec::default();
unsafe {
clock_gettime(CLOCK_MONOTONIC, &mut now);
}
let mut tv_sec = now.tv_sec + timeout.as_secs() as c_long;
let mut tv_nsec = now.tv_nsec + timeout.subsec_nanos() as c_long;
if tv_nsec >= 1_000_000_000 {
tv_sec += 1;
tv_nsec -= 1_000_000_000;
}
timespec { tv_sec, tv_nsec }
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::process::Command;
const PROBE: &str = r#"
#define _GNU_SOURCE
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#if defined(__APPLE__)
#include <mach/mach.h>
#endif
#define S(t) printf("sizeof %s=%ld\n", #t, (long)sizeof(t))
#define V(c) printf("%s=%ld\n", #c, (long)(c))
int main(void) {
S(pthread_attr_t); S(pthread_mutex_t); S(pthread_mutexattr_t);
S(pthread_cond_t); S(pthread_condattr_t); S(pthread_once_t);
S(sigset_t); S(pthread_t); S(struct timespec); S(struct sched_param);
S(struct sigaction); V(SA_RESTART);
printf("alignof sigset_t=%ld\n", (long)_Alignof(sigset_t));
V(CLOCK_MONOTONIC); V(ETIMEDOUT); V(_SC_PAGESIZE); V(SIG_BLOCK); V(SIG_SETMASK);
V(PTHREAD_MUTEX_RECURSIVE); V(PTHREAD_PRIO_INHERIT);
V(SCHED_FIFO); V(PTHREAD_EXPLICIT_SCHED); V(EPERM);
V(SIGINT); V(SIGTERM);
#if defined(__linux__)
V(_SC_AVPHYS_PAGES);
#endif
#if defined(__APPLE__)
V(SIGUSR1); V(SIGUSR2); V(HOST_VM_INFO64_COUNT);
#endif
return 0;
}
"#;
fn c_values() -> HashMap<String, i64> {
let dir = std::env::temp_dir().join(format!("osal_rs_ffi_layout_{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("create probe dir");
let src = dir.join("probe.c");
let exe = dir.join("probe");
std::fs::write(&src, PROBE).expect("write probe source");
let cc = std::env::var("CC").unwrap_or_else(|_| "cc".into());
let status = Command::new(&cc).arg(&src).arg("-o").arg(&exe).status().expect("run C compiler");
assert!(status.success(), "{cc} failed to compile the layout probe");
let output = Command::new(&exe).output().expect("run layout probe");
let _ = std::fs::remove_dir_all(&dir);
String::from_utf8_lossy(&output.stdout)
.lines()
.filter_map(|line| line.split_once('='))
.map(|(name, value)| (name.to_string(), value.parse().expect("numeric probe value")))
.collect()
}
#[test]
fn layout_matches_c_headers() {
let c = c_values();
let check = |name: &str, rust: i64| assert_eq!(c[name], rust, "`{name}` differs from the C headers");
check("sizeof pthread_attr_t", size_of::<pthread_attr_t>() as i64);
check("sizeof pthread_mutex_t", size_of::<pthread_mutex_t>() as i64);
check("sizeof pthread_mutexattr_t", size_of::<pthread_mutexattr_t>() as i64);
check("sizeof pthread_cond_t", size_of::<pthread_cond_t>() as i64);
check("sizeof pthread_condattr_t", size_of::<pthread_condattr_t>() as i64);
check("sizeof pthread_once_t", size_of::<pthread_once_t>() as i64);
check("sizeof sigset_t", size_of::<sigset_t>() as i64);
check("alignof sigset_t", align_of::<sigset_t>() as i64);
check("sizeof struct sigaction", size_of::<sigaction>() as i64);
check("SA_RESTART", SA_RESTART as i64);
check("sizeof pthread_t", size_of::<ThreadHandle>() as i64);
check("sizeof struct timespec", size_of::<timespec>() as i64);
#[cfg(feature = "real_time")]
check("sizeof struct sched_param", size_of::<sched_param>() as i64);
check("CLOCK_MONOTONIC", CLOCK_MONOTONIC as i64);
check("ETIMEDOUT", ETIMEDOUT as i64);
check("_SC_PAGESIZE", _SC_PAGESIZE as i64);
check("SIG_BLOCK", SIG_BLOCK as i64);
check("SIG_SETMASK", SIG_SETMASK as i64);
check("PTHREAD_MUTEX_RECURSIVE", PTHREAD_MUTEX_RECURSIVE as i64);
check("PTHREAD_PRIO_INHERIT", PTHREAD_PRIO_INHERIT as i64);
#[cfg(feature = "real_time")]
{
check("SCHED_FIFO", SCHED_FIFO as i64);
check("PTHREAD_EXPLICIT_SCHED", PTHREAD_EXPLICIT_SCHED as i64);
check("EPERM", EPERM as i64);
}
check("SIGINT", SIGINT as i64);
check("SIGTERM", SIGTERM as i64);
#[cfg(target_os = "linux")]
{
check("_SC_AVPHYS_PAGES", _SC_AVPHYS_PAGES as i64);
}
#[cfg(target_os = "macos")]
{
check("SIGUSR1", SIGUSR1 as i64);
check("SIGUSR2", SIGUSR2 as i64);
assert!(
VM_STATISTICS64_WORDS as i64 >= c["HOST_VM_INFO64_COUNT"],
"`VM_STATISTICS64_WORDS` ({VM_STATISTICS64_WORDS}) is smaller than the SDK's HOST_VM_INFO64_COUNT ({})",
c["HOST_VM_INFO64_COUNT"]
);
}
}
}