use std::{
ffi::CString,
ops::RangeInclusive,
os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd},
};
use libc::GRND_RANDOM;
use memchr::memchr;
use nix::{
errno::Errno,
fcntl::OFlag,
sys::{
mman::{shm_open, shm_unlink},
resource::{getrlimit, Resource},
stat::Mode,
},
unistd::UnlinkatFlags,
};
use crate::{
compat::{dup3, ResolveFlag},
cookie::safe_unlinkat,
fd::{close, is_active_fd, SafeOwnedFd},
log::{now, Tm},
lookup::safe_open,
path::{XPathBuf, PATH_MAX},
retry::retry_on_eintr,
};
#[cfg(not(target_os = "android"))]
#[must_use = "hold the guard to keep cancellation disabled"]
struct CancelGuard(libc::c_int);
#[cfg(not(target_os = "android"))]
const _PTHREAD_CANCEL_ENABLE: libc::c_int = 0;
#[cfg(not(target_os = "android"))]
const PTHREAD_CANCEL_DISABLE: libc::c_int = 1;
#[cfg(not(target_os = "android"))]
extern "C" {
fn pthread_setcancelstate(state: libc::c_int, oldstate: *mut libc::c_int) -> libc::c_int;
}
#[cfg(not(target_os = "android"))]
impl CancelGuard {
pub fn acquire() -> Result<Self, Errno> {
let mut old: libc::c_int = 0;
let err = unsafe { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &raw mut old) };
if err == 0 {
Ok(Self(old))
} else {
Err(Errno::from_raw(err))
}
}
}
#[cfg(not(target_os = "android"))]
impl Drop for CancelGuard {
fn drop(&mut self) {
unsafe {
pthread_setcancelstate(self.0, std::ptr::null_mut());
}
}
}
pub trait RandUint: Copy + Ord {
const ZERO: Self;
const ONE: Self;
const MAX: Self;
fn rand_from_os() -> Result<Self, Errno>;
fn checked_add(self, rhs: Self) -> Option<Self>;
fn checked_sub(self, rhs: Self) -> Option<Self>;
fn checked_mul(self, rhs: Self) -> Option<Self>;
fn div_euclid_opt(self, rhs: Self) -> Option<Self>;
}
macro_rules! impl_rand_uint {
($($t:ty),* $(,)?) => {$(
impl RandUint for $t {
const ZERO: Self = 0;
const ONE: Self = 1;
const MAX: Self = <$t>::MAX;
#[inline]
fn rand_from_os() -> Result<Self, Errno> {
let mut buf = [0u8; { size_of::<$t>() }];
fillrandom(&mut buf)?;
Ok(<$t>::from_ne_bytes(buf))
}
#[inline] fn checked_add(self, rhs: Self) -> Option<Self> { self.checked_add(rhs) }
#[inline] fn checked_sub(self, rhs: Self) -> Option<Self> { self.checked_sub(rhs) }
#[inline] fn checked_mul(self, rhs: Self) -> Option<Self> { self.checked_mul(rhs) }
#[inline]
fn div_euclid_opt(self, rhs: Self) -> Option<Self> {
if rhs == 0 { None } else { Some(self.div_euclid(rhs)) }
}
}
)*};
}
impl_rand_uint!(u8, u16, u32, u64, u128, usize);
pub fn randint<T>(range: RangeInclusive<T>) -> Result<T, Errno>
where
T: RandUint,
{
let (lo, hi) = range.into_inner();
if lo >= hi {
return Err(Errno::EINVAL);
}
if lo == T::ZERO && hi == T::MAX {
return T::rand_from_os();
}
let span = hi
.checked_sub(lo)
.ok_or(Errno::EOVERFLOW)?
.checked_add(T::ONE)
.ok_or(Errno::EOVERFLOW)?;
let k = T::MAX.div_euclid_opt(span).ok_or(Errno::EOVERFLOW)?;
let accept_top = k.checked_mul(span).ok_or(Errno::EOVERFLOW)?;
loop {
let r = T::rand_from_os()?;
if r < accept_top {
let q = r.div_euclid_opt(span).ok_or(Errno::EOVERFLOW)?;
let qspan = q.checked_mul(span).ok_or(Errno::EOVERFLOW)?;
let off = r.checked_sub(qspan).ok_or(Errno::EOVERFLOW)?;
let v = lo.checked_add(off).ok_or(Errno::EOVERFLOW)?;
return Ok(v);
}
}
}
#[inline]
pub fn randport() -> Result<u16, Errno> {
randint(1025u16..=0xFFFF)
}
pub fn getrandom(size: usize) -> Result<Vec<u8>, Errno> {
if size == 0 {
return Err(Errno::EINVAL);
}
let mut buf = Vec::new();
buf.try_reserve(size).or(Err(Errno::ENOMEM))?;
buf.resize(size, 0);
fillrandom(&mut buf)?;
Ok(buf)
}
pub fn fillrandom(buf: &mut [u8]) -> Result<(), Errno> {
let siz = buf.len();
if siz == 0 {
return Err(Errno::EINVAL);
}
#[cfg(not(target_os = "android"))]
let guard = CancelGuard::acquire()?;
let mut n = 0;
while n < siz {
let ptr = &mut buf[n..];
let ptr = ptr.as_mut_ptr().cast();
let siz = siz.checked_sub(n).ok_or(Errno::EOVERFLOW)?;
n = n
.checked_add(
retry_on_eintr(|| {
Errno::result(unsafe { libc::getrandom(ptr, siz, GRND_RANDOM) })
})?
.try_into()
.or(Err(Errno::EINVAL))?,
)
.ok_or(Errno::EOVERFLOW)?;
}
#[cfg(not(target_os = "android"))]
drop(guard);
Ok(())
}
pub fn fillrandom_pod<T>(pod: &mut T) -> Result<(), Errno> {
let siz = size_of::<T>();
let ptr = std::ptr::from_mut(pod).cast::<u8>();
fillrandom(unsafe { std::slice::from_raw_parts_mut(ptr, siz) })
}
pub fn duprand(oldfd: RawFd, mut flags: OFlag) -> Result<SafeOwnedFd, Errno> {
let range_start = 7u64;
let (range_end, _) = getrlimit(Resource::RLIMIT_NOFILE)?;
#[expect(clippy::unnecessary_cast)]
let range_end = range_end.saturating_sub(1) as u64;
let range_end = range_end.min(0x10000);
if range_end <= range_start {
return Err(Errno::EMFILE);
}
let range = range_start..=range_end;
let close_old = flags.contains(OFlag::O_EXCL);
flags.remove(OFlag::O_EXCL);
for _ in range.clone() {
#[expect(clippy::cast_possible_truncation)]
let fd_rand = randint(range.clone())? as RawFd;
let fd_rand = unsafe { BorrowedFd::borrow_raw(fd_rand) };
if is_active_fd(fd_rand) {
continue;
}
match dup3(oldfd, fd_rand.as_raw_fd(), flags.bits()) {
Ok(fd_rand) => {
if close_old {
let _ = close(oldfd);
}
return Ok(fd_rand);
}
Err(Errno::EMFILE) => return Err(Errno::EMFILE),
Err(_) => {}
}
}
Err(Errno::EBADF)
}
pub fn mkstempat<Fd: AsFd>(dirfd: Fd, prefix: &[u8]) -> Result<SafeOwnedFd, Errno> {
const MAX_TCOUNT: usize = 8;
const SUFFIX_LEN: usize = 128;
const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let mut flags = OFlag::O_TMPFILE | OFlag::O_EXCL | OFlag::O_RDWR;
match safe_open(&dirfd, c".", flags, ResolveFlag::empty()) {
Ok(fd) => return Ok(fd),
Err(Errno::EISDIR | Errno::ENOENT | Errno::EOPNOTSUPP) => {}
Err(errno) => return Err(errno),
}
flags.remove(OFlag::O_TMPFILE);
flags.insert(OFlag::O_CREAT);
if memchr(b'/', prefix).is_some() {
return Err(Errno::EINVAL);
} else if prefix.len().saturating_sub(SUFFIX_LEN) > PATH_MAX {
return Err(Errno::ENAMETOOLONG);
}
let mut attempts = 0;
let mut rng_data = [0u8; SUFFIX_LEN];
loop {
attempts = attempts.checked_add(1).ok_or(Errno::EOVERFLOW)?;
if attempts > MAX_TCOUNT {
return Err(Errno::EEXIST);
}
fillrandom(&mut rng_data)?;
let size = prefix
.len()
.checked_add(SUFFIX_LEN)
.ok_or(Errno::EOVERFLOW)?;
let mut base = XPathBuf::new();
base.try_reserve(size).or(Err(Errno::ENOMEM))?;
base.append_bytes(prefix);
for &b in &rng_data {
let idx = (b as usize)
.checked_rem(CHARSET.len())
.ok_or(Errno::EOVERFLOW)?;
let chr = CHARSET.get(idx).copied().ok_or(Errno::EOVERFLOW)?;
base.append_byte(chr);
}
match safe_open(&dirfd, &base, flags, ResolveFlag::empty()) {
Ok(fd) => {
safe_unlinkat(dirfd, &base, UnlinkatFlags::NoRemoveDir)?;
return Ok(fd);
}
Err(Errno::EEXIST) => {
continue;
}
Err(errno) => return Err(errno),
}
}
}
pub fn shm_open_anon(flags: OFlag, mode: Mode) -> Result<SafeOwnedFd, Errno> {
const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const SUFFIX_LEN: usize = 128;
const PREFIX: &[u8] = b"/syd-bpf-";
const MAX_TRIES: u32 = 64;
let last = CHARSET.len().checked_sub(1).ok_or(Errno::EINVAL)?;
let mut attempts: u32 = 0;
loop {
attempts = attempts.checked_add(1).ok_or(Errno::EOVERFLOW)?;
if attempts > MAX_TRIES {
return Err(Errno::EEXIST);
}
let mut name = Vec::new();
#[expect(clippy::arithmetic_side_effects)]
name.try_reserve(PREFIX.len() + SUFFIX_LEN + 1)
.or(Err(Errno::ENOMEM))?;
name.extend_from_slice(PREFIX);
for _ in 0..SUFFIX_LEN {
let idx: usize = randint(0..=last)?;
name.push(CHARSET[idx]);
}
let cname = CString::new(name).map_err(|_| Errno::EINVAL)?;
let cname = cname.as_c_str();
let mut flags = flags;
flags.insert(OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_CLOEXEC | OFlag::O_NOFOLLOW);
match shm_open(cname, flags, mode) {
Ok(fd) => {
let _ = shm_unlink(cname);
return Ok(SafeOwnedFd::from(fd));
}
Err(Errno::EEXIST) => continue,
Err(errno) => return Err(errno),
}
}
}
pub fn rand_version() -> Result<String, Errno> {
const VERMAGICS: &[&str] = &[
"SMP",
"SMP PREEMPT",
"SMP PREEMPT_DYNAMIC",
"SMP PREEMPT_RT",
];
const MONTHS: &[&str] = &[
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];
const WKDAYS: &[&str] = &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const TS_WINDOW: u64 = 366 * 86_400;
let now = now();
let offset: u64 = randint(0..=TS_WINDOW)?;
let target: i64 = now
.saturating_sub(offset)
.try_into()
.or(Err(Errno::EOVERFLOW))?;
let tm = Tm::try_from(target)?;
let build_no = randint(1u8..=64)?;
#[expect(clippy::arithmetic_side_effects)]
let vermagic = VERMAGICS[randint(0usize..=(VERMAGICS.len() - 1))?];
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_sign_loss)]
let mon = MONTHS[(tm.month() - 1) as usize];
#[expect(clippy::cast_sign_loss)]
let wday = WKDAYS[tm.weekday() as usize];
let mday = tm.day();
let hh = tm.hour();
let mm = tm.minute();
let ss = tm.second();
let year = tm.year();
Ok(format!(
"#{build_no} {vermagic} {wday} {mon} {mday:>2} {hh:02}:{mm:02}:{ss:02} UTC {year}",
))
}
#[cfg(test)]
mod tests {
use std::fmt::Debug;
use super::*;
fn draw<T: RandUint + Debug>(lo: T, hi: T) -> T {
match randint::<T>(lo..=hi) {
Ok(v) => v,
Err(e) => panic!("randint failed for [{:?},{:?}] -> {:?}", lo, hi, e),
}
}
fn sample<T: RandUint + Debug>(lo: T, hi: T, n: usize) -> Vec<T> {
(0..n).map(|_| draw::<T>(lo, hi)).collect()
}
fn all_in_range<T: RandUint + Debug>(xs: &[T], lo: T, hi: T) -> bool {
xs.iter().all(|&v| v >= lo && v <= hi)
}
#[test]
fn test_fillrandom_1() {
assert_eq!(fillrandom(&mut []), Err(Errno::EINVAL));
assert_eq!(fillrandom(&mut [0u8; 257]), Ok(()));
}
#[test]
fn test_fillrandom_2() {
let mut buf = [0u8; 64];
fillrandom(&mut buf).unwrap();
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn test_fillrandom_3() {
let mut buf = [0u8; 1024];
fillrandom(&mut buf).unwrap();
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn test_getrandom_1() {
assert_eq!(getrandom(0), Err(Errno::EINVAL));
let result = getrandom(257);
assert!(result.is_ok(), "result:{result:?}");
}
#[test]
fn test_getrandom_2() {
let buf = getrandom(32).unwrap();
assert_eq!(buf.len(), 32);
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn test_getrandom_3() {
let buf = getrandom(1).unwrap();
assert_eq!(buf.len(), 1);
}
#[test]
fn test_randint_1() {
assert!(matches!(randint::<u8>(200..=100), Err(Errno::EINVAL)));
}
#[test]
fn test_randint_2() {
assert!(matches!(randint::<u16>(5000..=4999), Err(Errno::EINVAL)));
}
#[test]
fn test_randint_3() {
assert!(matches!(randint::<u32>(42..=41), Err(Errno::EINVAL)));
}
#[test]
fn test_randint_4() {
assert!(matches!(randint::<u64>(999..=998), Err(Errno::EINVAL)));
}
#[test]
fn test_randint_5() {
assert!(matches!(randint::<u128>(500..=499), Err(Errno::EINVAL)));
}
#[test]
fn test_randint_6() {
assert!(matches!(randint::<usize>(100..=99), Err(Errno::EINVAL)));
}
#[test]
fn test_randint_7() {
assert!(matches!(randint::<u8>(77..=77), Err(Errno::EINVAL)));
}
#[test]
fn test_randint_8() {
assert!(matches!(randint::<u16>(31337..=31337), Err(Errno::EINVAL)));
}
#[test]
fn test_randint_9() {
assert!(matches!(
randint::<u32>(1_000_000..=1_000_000),
Err(Errno::EINVAL)
));
}
#[test]
fn test_randint_10() {
assert!(matches!(
randint::<u64>(123456789..=123456789),
Err(Errno::EINVAL)
));
}
#[test]
fn test_randint_11() {
assert!(matches!(randint::<u128>(999..=999), Err(Errno::EINVAL)));
}
#[test]
fn test_randint_12() {
assert!(matches!(randint::<usize>(4242..=4242), Err(Errno::EINVAL)));
}
#[test]
fn test_randint_13() {
let xs = sample::<u8>(u8::MIN, u8::MAX, 4096);
assert!(all_in_range(&xs, u8::MIN, u8::MAX));
}
#[test]
fn test_randint_14() {
let xs = sample::<u16>(u16::MIN, u16::MAX, 2048);
assert!(all_in_range(&xs, u16::MIN, u16::MAX));
}
#[test]
fn test_randint_15() {
let xs = sample::<u32>(u32::MIN, u32::MAX, 2048);
assert!(all_in_range(&xs, u32::MIN, u32::MAX));
}
#[test]
fn test_randint_16() {
let xs = sample::<u64>(u64::MIN, u64::MAX, 1024);
assert!(all_in_range(&xs, u64::MIN, u64::MAX));
}
#[test]
fn test_randint_17() {
let xs = sample::<u128>(u128::MIN, u128::MAX, 256);
assert!(all_in_range(&xs, u128::MIN, u128::MAX));
}
#[test]
fn test_randint_18() {
let xs = sample::<usize>(usize::MIN, usize::MAX, 1024);
assert!(all_in_range(&xs, usize::MIN, usize::MAX));
}
#[test]
fn test_randint_19() {
let lo = u8::MAX.saturating_sub(15);
let xs = sample::<u8>(lo, u8::MAX, 2000);
assert!(all_in_range(&xs, lo, u8::MAX));
}
#[test]
fn test_randint_20() {
let lo = u16::MAX.saturating_sub(1023);
let xs = sample::<u16>(lo, u16::MAX, 4000);
assert!(all_in_range(&xs, lo, u16::MAX));
}
#[test]
fn test_randint_21() {
let lo = u32::MAX.saturating_sub(1000);
let xs = sample::<u32>(lo, u32::MAX, 3000);
assert!(all_in_range(&xs, lo, u32::MAX));
}
#[test]
fn test_randint_22() {
let lo = u64::MAX.saturating_sub(1000);
let xs = sample::<u64>(lo, u64::MAX, 3000);
assert!(all_in_range(&xs, lo, u64::MAX));
}
#[test]
fn test_randint_23() {
let lo = u128::MAX.saturating_sub(1000);
let xs = sample::<u128>(lo, u128::MAX, 2000);
assert!(all_in_range(&xs, lo, u128::MAX));
}
#[test]
fn test_randint_24() {
let lo = usize::MAX.saturating_sub(1000);
let xs = sample::<usize>(lo, usize::MAX, 3000);
assert!(all_in_range(&xs, lo, usize::MAX));
}
#[test]
fn test_randport_1() {
let port = randport().unwrap();
assert!(port >= 1025);
}
#[test]
fn test_randport_2() {
for _ in 0..100 {
let port = randport().unwrap();
assert!((1025..=65535).contains(&port));
}
}
#[test]
fn test_rand_version_1() {
let ver = rand_version().unwrap();
assert!(ver.starts_with('#'));
assert!(ver.contains("UTC"));
}
#[test]
fn test_rand_version_2() {
let ver = rand_version().unwrap();
assert!(
ver.contains("SMP PREEMPT_DYNAMIC")
|| ver.contains("SMP PREEMPT_RT")
|| ver.contains("SMP PREEMPT")
|| ver.contains("SMP")
);
}
}