mmap_io/
utils.rs

1//! Utility helpers for alignment, page size, and safe range calculations.
2
3use crate::errors::{MmapIoError, Result};
4
5/// Get the system page size in bytes.
6#[must_use]
7pub fn page_size() -> usize {
8    // Use Rust standard library when available; otherwise fallback to libc via page_size crate pattern.
9    // On modern Rust and platforms, std::io::Write doesn't expose page size; use `page_size` crate approach inline:
10    // However, to keep pure dependencies, use cfg to call platform-specific APIs.
11    cfg_if::cfg_if! {
12        if #[cfg(target_os = "windows")] {
13            windows_page_size()
14        } else {
15            unix_page_size()
16        }
17    }
18}
19
20#[cfg(target_os = "windows")]
21fn windows_page_size() -> usize {
22    use std::mem::MaybeUninit;
23    #[allow(non_snake_case)]
24    #[repr(C)]
25    struct SYSTEM_INFO {
26        wProcessorArchitecture: u16,
27        wReserved: u16,
28        dwPageSize: u32,
29        lpMinimumApplicationAddress: *mut core::ffi::c_void,
30        lpMaximumApplicationAddress: *mut core::ffi::c_void,
31        dwActiveProcessorMask: usize,
32        dwNumberOfProcessors: u32,
33        dwProcessorType: u32,
34        dwAllocationGranularity: u32,
35        wProcessorLevel: u16,
36        wProcessorRevision: u16,
37    }
38    extern "system" {
39        fn GetSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
40    }
41    let mut sysinfo = MaybeUninit::<SYSTEM_INFO>::uninit();
42    unsafe {
43        GetSystemInfo(sysinfo.as_mut_ptr());
44        let s = sysinfo.assume_init();
45        s.dwPageSize as usize
46    }
47}
48
49#[cfg(not(target_os = "windows"))]
50#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
51fn unix_page_size() -> usize {
52    // SAFETY: sysconf with _SC_PAGESIZE is safe to call.
53    unsafe {
54        let page_size = libc::sysconf(libc::_SC_PAGESIZE);
55        // Page size should always be positive and fit in usize
56        // Cast is safe because page sizes are always reasonable values
57        page_size.max(0) as usize
58    }
59}
60
61/// Align a value up to the nearest multiple of `alignment`.
62#[must_use]
63pub fn align_up(value: u64, alignment: u64) -> u64 {
64    if alignment == 0 {
65        return value;
66    }
67    // Fast path for power-of-2 alignments (common case for page sizes)
68    if alignment.is_power_of_two() {
69        let mask = alignment - 1;
70        (value + mask) & !mask
71    } else {
72        value.div_ceil(alignment) * alignment
73    }
74}
75
76/// Ensure the requested [offset, offset+len) range is within [0, total).
77/// Returns `Ok(())` if valid; otherwise an `OutOfBounds` error.
78///
79/// # Errors
80///
81/// Returns `MmapIoError::OutOfBounds` if the range exceeds bounds.
82pub fn ensure_in_bounds(offset: u64, len: u64, total: u64) -> Result<()> {
83    if offset > total {
84        return Err(MmapIoError::OutOfBounds { offset, len, total });
85    }
86    let end = offset.saturating_add(len);
87    if end > total {
88        return Err(MmapIoError::OutOfBounds { offset, len, total });
89    }
90    Ok(())
91}
92
93/// Compute a safe byte slice range for a given total length, returning start..end as usize tuple.
94///
95/// # Errors
96///
97/// Returns `MmapIoError::OutOfBounds` if the requested range exceeds the total length.
98#[allow(clippy::cast_possible_truncation)]
99pub fn slice_range(offset: u64, len: u64, total: u64) -> Result<(usize, usize)> {
100    ensure_in_bounds(offset, len, total)?;
101    // Safe to cast because we've already validated bounds against total
102    // which itself must fit in memory (and thus usize)
103    let start = offset as usize;
104    let end = (offset + len) as usize;
105    Ok((start, end))
106}