use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr};
use crate::{StarryError, StarryResult, mm::vm_write_slice};
const MINCORE_BATCH_PAGES: usize = 32;
fn validate_mincore_request(
addr: usize,
length: usize,
vec_is_null: bool,
user_base: usize,
user_end: usize,
) -> StarryResult<usize> {
let start = VirtAddr::from(addr);
if !start.is_aligned(PAGE_SIZE_4K) {
return Err(StarryError::InvalidInput);
}
if length == 0 {
return Ok(0);
}
let end = addr.checked_add(length).ok_or(StarryError::NoMemory)?;
if addr < user_base || end > user_end {
return Err(StarryError::NoMemory);
}
let pages = length.div_ceil(PAGE_SIZE_4K);
if vec_is_null {
return Err(StarryError::BadAddress);
}
Ok(pages)
}
pub fn sys_mincore(
current: &crate::task::UserTaskRef,
addr: usize,
length: usize,
vec: *mut u8,
) -> crate::StarryResult<isize> {
let start_addr = VirtAddr::from(addr);
let curr = current;
let cred = curr.as_thread().cred();
let aspace_pin = curr.as_thread().proc_data.pin_aspace()?;
let (user_base, user_end) = {
let aspace = aspace_pin.lock();
(aspace.base().as_usize(), aspace.end().as_usize())
};
let page_count = validate_mincore_request(addr, length, vec.is_null(), user_base, user_end)?;
debug!("sys_mincore <= addr: {addr:#x}, length: {length:#x}, vec: {vec:?}");
if page_count == 0 {
return Ok(0);
}
crate::mm::check_access(vec.addr(), page_count)?;
let mut completed = 0;
while completed < page_count {
let batch_pages = (page_count - completed).min(MINCORE_BATCH_PAGES);
let mut result = [0u8; MINCORE_BATCH_PAGES];
let mut cache_queries = heapless::Vec::<_, MINCORE_BATCH_PAGES>::new();
let mut filled = 0;
let mut range_error = None;
{
let aspace = aspace_pin.lock();
while filled < batch_pages {
let address = start_addr + (completed + filled) * PAGE_SIZE_4K;
let Some(probe) = aspace.mincore_probe(address) else {
range_error = Some(StarryError::NoMemory);
break;
};
if let Some(bytes) = aspace.resident_bytes_from(address) {
let pages = (bytes / PAGE_SIZE_4K).min(batch_pages - filled);
debug_assert!(pages != 0);
result[filled..filled + pages].fill(1);
filled += pages;
} else {
if cache_queries.push((filled, address, probe)).is_err() {
unreachable!("one cache query per bounded output byte");
}
filled += 1;
}
}
}
for (index, address, probe) in cache_queries {
if probe.mincore_resident(address, &cred) {
result[index] = 1;
}
}
if filled != 0 {
vm_write_slice(current, vec.wrapping_add(completed), &result[..filled])?;
}
if let Some(error) = range_error {
return Err(error);
}
completed += filled;
}
Ok(0)
}
#[cfg(all(test, not(axtest)))]
mod tests {
use crate::StarryError;
#[test]
fn zero_length_does_not_validate_output_pointer() {
assert_eq!(
super::validate_mincore_request(0x1000, 0, true, 0x1000, 0x20_0000).unwrap(),
0
);
}
#[test]
fn overflowing_range_precedes_output_pointer_validation() {
assert!(matches!(
super::validate_mincore_request(
usize::MAX & !(4096 - 1),
4096,
true,
0x1000,
0x20_0000,
),
Err(StarryError::NoMemory)
));
}
}