use super::{
PageSize,
err::{self, raw_error},
interface::MemMapInterface,
};
use crate::{error::FrozenResult, hints};
use core::ptr;
use std::time::Duration;
use windows_sys::Win32::{
Foundation::{
CloseHandle, ERROR_ACCESS_DENIED, ERROR_COMMITMENT_LIMIT, ERROR_INVALID_ADDRESS,
ERROR_INVALID_HANDLE, ERROR_INVALID_PARAMETER, ERROR_IO_DEVICE, ERROR_LOCK_VIOLATION,
ERROR_NOT_ENOUGH_MEMORY, ERROR_NOT_LOCKED, ERROR_OUTOFMEMORY, ERROR_SHARING_VIOLATION,
HANDLE, INVALID_HANDLE_VALUE,
},
System::{
Memory::{
CreateFileMappingW, FlushViewOfFile, MEM_PRESERVE_PLACEHOLDER, MEM_RELEASE,
MEM_REPLACE_PLACEHOLDER, MEM_RESERVE, MEM_RESERVE_PLACEHOLDER,
MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile3, PAGE_NOACCESS, PAGE_READWRITE,
PrefetchVirtualMemory, UnmapViewOfFile, VirtualAlloc2, VirtualFreeEx, VirtualUnlock,
WIN32_MEMORY_RANGE_ENTRY,
},
ProcessStatus::{
K32QueryWorkingSetEx, PSAPI_WORKING_SET_EX_BLOCK, PSAPI_WORKING_SET_EX_INFORMATION,
},
SystemInformation::{GetSystemInfo, SYSTEM_INFO},
Threading::{
GetCurrentProcess, MEMORY_PRIORITY_INFORMATION, MEMORY_PRIORITY_LOW,
ProcessMemoryPriority, SetProcessInformation,
},
},
};
pub(super) type FileId = isize;
type TPtr = *mut u8;
const CLOSED_HANDLE: isize = -1isize;
const ERROR_FILE_INVALID: u32 = 0x3EE;
const MAX_RETRIES: usize = 0x80;
#[derive(Debug)]
pub(super) struct WINMemMap {
ptr: TPtr,
length: usize,
max_len: usize,
views: Vec<(TPtr, usize)>,
}
unsafe impl Send for WINMemMap {}
unsafe impl Sync for WINMemMap {}
impl Drop for WINMemMap {
fn drop(&mut self) {
if !self.ptr.is_null() && self.max_len > 0 {
for &(view_ptr, len) in &self.views {
let _ = munmap_raw(view_ptr, len);
}
if self.length < self.max_len {
let remaining_placeholder = unsafe { self.ptr.add(self.length) };
let _ = unsafe {
VirtualFreeEx(
GetCurrentProcess(),
remaining_placeholder as *mut core::ffi::c_void,
0,
MEM_RELEASE,
)
};
}
self.ptr = ptr::null_mut();
self.length = 0;
self.max_len = 0;
self.views.clear();
}
}
}
impl MemMapInterface for WINMemMap {
type Id = FileId;
fn new(handle: FileId, length: usize, max_len: usize) -> FrozenResult<Self> {
let (ptr, views) = mmap_raw(handle, length, max_len)?;
Ok(Self { ptr, length, max_len, views })
}
fn grow(&mut self, handle: FileId, curr_len: usize, added_len: usize) -> FrozenResult<()> {
let view = mmap_grow_raw(self.ptr, handle, curr_len, added_len, self.max_len)?;
if view.1 > 0 {
self.views.push(view);
self.length = curr_len + added_len;
}
Ok(())
}
fn unmap(mut self) -> FrozenResult<()> {
if !self.ptr.is_null() && self.max_len > 0 {
let mut first_err = None;
for &(view_ptr, len) in &self.views {
let res = munmap_raw(view_ptr, len);
if hints::unlikely(res.is_err() && first_err.is_none()) {
first_err = Some(res);
}
}
if self.length < self.max_len {
let remaining_placeholder = unsafe { self.ptr.add(self.length) };
let ok = unsafe {
VirtualFreeEx(
GetCurrentProcess(),
remaining_placeholder as *mut core::ffi::c_void,
0,
MEM_RELEASE,
)
};
if hints::unlikely(ok == 0 && first_err.is_none()) {
let code = last_error();
first_err = Some(raw_error(err::UNK, err_msg(code)));
}
}
self.ptr = ptr::null_mut();
self.length = 0;
self.max_len = 0;
self.views.clear();
return first_err.unwrap_or(Ok(()));
}
Ok(())
}
#[inline]
fn length(&self) -> usize {
self.length
}
#[inline]
fn max_len(&self) -> usize {
self.max_len
}
fn page_size() -> FrozenResult<PageSize> {
let mut sys_info = unsafe { core::mem::zeroed::<SYSTEM_INFO>() };
unsafe { GetSystemInfo(&mut sys_info) };
let page_size = PageSize::try_from(sys_info.dwPageSize as usize)?;
page_size.validate_platform()
}
fn active_page_indices(&self) -> FrozenResult<Vec<usize>> {
if hints::unlikely(self.ptr.is_null() || self.length == 0) {
return Ok(Vec::new());
}
let page_size = Self::page_size()?.as_usize();
let num_pages = self.length.div_ceil(page_size);
let mut ws_info: Vec<PSAPI_WORKING_SET_EX_INFORMATION> = (0..num_pages)
.map(|i| PSAPI_WORKING_SET_EX_INFORMATION {
VirtualAddress: unsafe { self.ptr.add(i * page_size) as *mut core::ffi::c_void },
VirtualAttributes: PSAPI_WORKING_SET_EX_BLOCK { Flags: 0 },
})
.collect();
let cb = match u32::try_from(
ws_info.len() * core::mem::size_of::<PSAPI_WORKING_SET_EX_INFORMATION>(),
) {
Ok(v) => v,
Err(_) => return raw_error(err::HCF, "working set buffer size exceeds u32 capacity"),
};
let mut retries = 0;
loop {
let ok = unsafe {
K32QueryWorkingSetEx(
GetCurrentProcess(),
ws_info.as_mut_ptr() as *mut core::ffi::c_void,
cb,
)
};
if hints::likely(ok != 0) {
let indices = ws_info
.iter()
.enumerate()
.filter_map(|(idx, info)| {
if unsafe { info.VirtualAttributes.Flags } & 1 != 0 {
Some(idx)
} else {
None
}
})
.collect();
return Ok(indices);
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_LOCK_VIOLATION | ERROR_SHARING_VIOLATION => {
if retries < MAX_RETRIES {
retries += 1;
std::thread::sleep(Duration::from_millis(1));
continue;
}
return raw_error(err::UNK, err_msg);
}
ERROR_INVALID_PARAMETER | ERROR_INVALID_ADDRESS => {
return raw_error(err::HCF, err_msg);
}
ERROR_NOT_ENOUGH_MEMORY | ERROR_OUTOFMEMORY => return raw_error(err::NMM, err_msg),
_ => return raw_error(err::UNK, err_msg),
}
}
}
fn resident_bytes(&self) -> FrozenResult<usize> {
if hints::unlikely(self.ptr.is_null() || self.length == 0) {
return Ok(0);
}
let page_size = Self::page_size()?.as_usize();
let num_pages = self.length.div_ceil(page_size);
let mut ws_info: Vec<PSAPI_WORKING_SET_EX_INFORMATION> = (0..num_pages)
.map(|i| PSAPI_WORKING_SET_EX_INFORMATION {
VirtualAddress: unsafe { self.ptr.add(i * page_size) as *mut core::ffi::c_void },
VirtualAttributes: PSAPI_WORKING_SET_EX_BLOCK { Flags: 0 },
})
.collect();
let cb = match u32::try_from(
ws_info.len() * core::mem::size_of::<PSAPI_WORKING_SET_EX_INFORMATION>(),
) {
Ok(v) => v,
Err(_) => return raw_error(err::HCF, "working set buffer size exceeds u32 capacity"),
};
let mut retries = 0;
loop {
let ok = unsafe {
K32QueryWorkingSetEx(
GetCurrentProcess(),
ws_info.as_mut_ptr() as *mut core::ffi::c_void,
cb,
)
};
if hints::likely(ok != 0) {
let resident_pages = ws_info
.iter()
.filter(|info| unsafe { info.VirtualAttributes.Flags } & 1 != 0)
.count();
return Ok(resident_pages.saturating_mul(page_size));
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_LOCK_VIOLATION | ERROR_SHARING_VIOLATION => {
if retries < MAX_RETRIES {
retries += 1;
std::thread::sleep(Duration::from_millis(1));
continue;
}
return raw_error(err::UNK, err_msg);
}
ERROR_INVALID_PARAMETER | ERROR_INVALID_ADDRESS => {
return raw_error(err::HCF, err_msg);
}
ERROR_NOT_ENOUGH_MEMORY | ERROR_OUTOFMEMORY => return raw_error(err::NMM, err_msg),
_ => return raw_error(err::UNK, err_msg),
}
}
}
fn evict_pages(&self, indices: &[usize]) -> FrozenResult<()> {
if hints::unlikely(indices.is_empty()) {
return Ok(());
}
if hints::unlikely(self.ptr.is_null() || self.length == 0) {
return raw_error(err::HCF, "cannot evict pages on unmapped or zero-length mapping");
}
let page_size = Self::page_size()?.as_usize();
let num_pages = self.length.div_ceil(page_size);
let mut sorted_indices = indices.to_vec();
sorted_indices.sort_unstable();
sorted_indices.dedup();
for &idx in &sorted_indices {
if hints::unlikely(idx >= num_pages) {
return raw_error(
err::HCF,
format!("page index {idx} out of bounds for mapping of {num_pages} pages"),
);
}
}
let mut i = 0;
while i < sorted_indices.len() {
let start_idx = sorted_indices[i];
let mut end_idx = start_idx;
while i + 1 < sorted_indices.len() && sorted_indices[i + 1] == end_idx + 1 {
i += 1;
end_idx = sorted_indices[i];
}
let run_count = end_idx - start_idx + 1;
let offset = start_idx * page_size;
let len = run_count * page_size;
let target_ptr = unsafe { self.ptr.add(offset) as *mut core::ffi::c_void };
discard_raw(target_ptr, len)?;
i += 1;
}
Ok(())
}
#[inline]
fn apply_memory_priority_hint() -> FrozenResult<()> {
set_memory_priority_raw()
}
fn advise_sequential(&self, offset: usize, length: usize) -> FrozenResult<()> {
if hints::unlikely(length == 0 || self.ptr.is_null()) {
return Ok(());
}
if offset.checked_add(length).is_none_or(|end| end > self.length) {
return raw_error(err::HCF, "range out of bounds for memory mapping");
}
self.prefetch(offset, length)
}
fn prefetch(&self, offset: usize, length: usize) -> FrozenResult<()> {
if hints::unlikely(length == 0 || self.ptr.is_null()) {
return Ok(());
}
if offset.checked_add(length).is_none_or(|end| end > self.length) {
return raw_error(err::HCF, "range out of bounds for memory mapping");
}
let mut entries = Vec::new();
let mut curr_view_offset = 0;
let req_end = offset + length;
for &(view_ptr, view_len) in &self.views {
let view_start = curr_view_offset;
let view_end = curr_view_offset + view_len;
if req_end > view_start && offset < view_end {
let overlap_start = offset.max(view_start);
let overlap_end = req_end.min(view_end);
let rel_offset = overlap_start - view_start;
let rel_len = overlap_end - overlap_start;
entries.push(WIN32_MEMORY_RANGE_ENTRY {
VirtualAddress: unsafe { view_ptr.add(rel_offset) as *mut _ },
NumberOfBytes: rel_len,
});
}
curr_view_offset = view_end;
}
prefetch_raw(&entries)
}
fn sync(&self, length: usize) -> FrozenResult<()> {
if hints::unlikely(length == 0 || self.ptr.is_null()) {
return Ok(());
}
let mut remaining = length;
for &(view_ptr, view_len) in &self.views {
if remaining == 0 {
break;
}
let flush_len = remaining.min(view_len);
msync_raw(view_ptr, flush_len)?;
remaining -= flush_len;
}
Ok(())
}
#[inline]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn as_mut_ptr<T>(&self, offset: usize) -> *mut T
where
T: Sized,
{
unsafe { self.ptr.add(offset) as *mut T }
}
#[inline]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn as_ptr<T>(&self, offset: usize) -> *const T
where
T: Sized,
{
unsafe { self.ptr.add(offset) as *const T }
}
}
fn mmap_raw(
handle: isize,
length: usize,
max_len: usize,
) -> FrozenResult<(TPtr, Vec<(TPtr, usize)>)> {
if hints::unlikely(
handle == CLOSED_HANDLE
|| (handle as usize as HANDLE) == INVALID_HANDLE_VALUE
|| handle == 0,
) {
return raw_error(err::HCF, "invalid handle for memory mapping");
}
if hints::unlikely(length == 0 || max_len == 0 || length > max_len) {
return raw_error(err::HCF, "invalid mapping length or max_len");
}
let h_process = unsafe { GetCurrentProcess() };
let base_addr = unsafe {
VirtualAlloc2(
h_process,
core::ptr::null(),
max_len,
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
PAGE_NOACCESS,
core::ptr::null_mut(),
0,
)
};
if hints::unlikely(base_addr.is_null()) {
let code = last_error();
let err_msg = err_msg(code);
return match code {
ERROR_NOT_ENOUGH_MEMORY | ERROR_OUTOFMEMORY | ERROR_COMMITMENT_LIMIT => {
raw_error(err::NMM, err_msg)
}
_ => raw_error(err::UNK, err_msg),
};
}
let base_ptr = base_addr as *mut u8;
if length < max_len {
let ok = unsafe {
VirtualFreeEx(
h_process,
base_ptr as *mut core::ffi::c_void,
length,
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER,
)
};
if hints::unlikely(ok == 0) {
let code = last_error();
let err_msg = err_msg(code);
unsafe { VirtualFreeEx(h_process, base_addr, 0, MEM_RELEASE) };
return raw_error(err::UNK, err_msg);
}
}
let h_file = handle as usize as HANDLE;
let mut retries = 0;
loop {
let h_map = unsafe {
CreateFileMappingW(h_file, core::ptr::null(), PAGE_READWRITE, 0, 0, core::ptr::null())
};
if hints::unlikely(h_map.is_null()) {
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_LOCK_VIOLATION | ERROR_SHARING_VIOLATION => {
if retries < MAX_RETRIES {
retries += 1;
std::thread::sleep(Duration::from_millis(1));
continue;
}
unsafe { VirtualFreeEx(h_process, base_addr, 0, MEM_RELEASE) };
return raw_error(err::UNK, err_msg);
}
ERROR_ACCESS_DENIED => {
unsafe { VirtualFreeEx(h_process, base_addr, 0, MEM_RELEASE) };
return raw_error(err::PRM, err_msg);
}
ERROR_INVALID_HANDLE | ERROR_INVALID_PARAMETER | ERROR_FILE_INVALID => {
unsafe { VirtualFreeEx(h_process, base_addr, 0, MEM_RELEASE) };
return raw_error(err::HCF, err_msg);
}
ERROR_NOT_ENOUGH_MEMORY | ERROR_OUTOFMEMORY | ERROR_COMMITMENT_LIMIT => {
unsafe { VirtualFreeEx(h_process, base_addr, 0, MEM_RELEASE) };
return raw_error(err::NMM, err_msg);
}
_ => {
unsafe { VirtualFreeEx(h_process, base_addr, 0, MEM_RELEASE) };
return raw_error(err::UNK, err_msg);
}
}
}
let view = unsafe {
MapViewOfFile3(
h_map,
h_process,
base_ptr as *const core::ffi::c_void,
0,
length,
MEM_REPLACE_PLACEHOLDER,
PAGE_READWRITE,
core::ptr::null_mut(),
0,
)
};
unsafe { CloseHandle(h_map) };
if hints::unlikely(view.Value.is_null()) {
let code = last_error();
let err_msg = err_msg(code);
unsafe { VirtualFreeEx(h_process, base_addr, 0, MEM_RELEASE) };
return match code {
ERROR_NOT_ENOUGH_MEMORY | ERROR_OUTOFMEMORY | ERROR_COMMITMENT_LIMIT => {
raw_error(err::NMM, err_msg)
}
_ => raw_error(err::UNK, err_msg),
};
}
return Ok((base_ptr, vec![(base_ptr, length)]));
}
}
fn mmap_grow_raw(
base_ptr: TPtr,
handle: isize,
curr_len: usize,
added_len: usize,
max_len: usize,
) -> FrozenResult<(TPtr, usize)> {
if hints::unlikely(added_len == 0) {
return Ok((base_ptr, 0));
}
let h_process = unsafe { GetCurrentProcess() };
let target_addr = unsafe { base_ptr.add(curr_len) };
if curr_len + added_len < max_len {
let ok = unsafe {
VirtualFreeEx(
h_process,
target_addr as *mut core::ffi::c_void,
added_len,
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER,
)
};
if hints::unlikely(ok == 0) {
let code = last_error();
return raw_error(err::UNK, err_msg(code));
}
}
let h_file = handle as usize as HANDLE;
let mut retries = 0;
loop {
let h_map = unsafe {
CreateFileMappingW(h_file, core::ptr::null(), PAGE_READWRITE, 0, 0, core::ptr::null())
};
if hints::unlikely(h_map.is_null()) {
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_LOCK_VIOLATION | ERROR_SHARING_VIOLATION => {
if retries < MAX_RETRIES {
retries += 1;
std::thread::sleep(Duration::from_millis(1));
continue;
}
return raw_error(err::UNK, err_msg);
}
_ => return raw_error(err::UNK, err_msg),
}
}
let view = unsafe {
MapViewOfFile3(
h_map,
h_process,
target_addr as *const core::ffi::c_void,
curr_len as u64,
added_len,
MEM_REPLACE_PLACEHOLDER,
PAGE_READWRITE,
core::ptr::null_mut(),
0,
)
};
unsafe { CloseHandle(h_map) };
if hints::unlikely(view.Value.is_null()) {
let code = last_error();
return raw_error(err::UNK, err_msg(code));
}
return Ok((target_addr, added_len));
}
}
fn munmap_raw(ptr: TPtr, length: usize) -> FrozenResult<()> {
if hints::unlikely(length == 0) {
return raw_error(err::HCF, "unmap length must be greater than zero");
}
if hints::unlikely(ptr.is_null()) {
return raw_error(err::HCF, "cannot unmap null pointer");
}
let ok = unsafe {
UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS { Value: ptr as *mut core::ffi::c_void })
};
if hints::likely(ok != 0) {
return Ok(());
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_INVALID_ADDRESS | ERROR_INVALID_PARAMETER => raw_error(err::HCF, err_msg),
_ => raw_error(err::UNK, err_msg),
}
}
fn msync_raw(ptr: TPtr, length: usize) -> FrozenResult<()> {
if hints::unlikely(length == 0) {
return Ok(());
}
if hints::unlikely(ptr.is_null()) {
return raw_error(err::NMM, "cannot sync null pointer");
}
let page_size = WINMemMap::page_size()?.as_usize();
if hints::unlikely(ptr as usize % page_size != 0) {
return raw_error(err::HCF, "address is not page-aligned");
}
let mut retries = 0;
loop {
let ok = unsafe { FlushViewOfFile(ptr as *const core::ffi::c_void, length) };
if hints::likely(ok != 0) {
return Ok(());
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_LOCK_VIOLATION | ERROR_SHARING_VIOLATION => {
if retries < MAX_RETRIES {
retries += 1;
std::thread::sleep(Duration::from_millis(1));
continue;
}
return raw_error(err::SYN, err_msg);
}
ERROR_IO_DEVICE => return raw_error(err::SYN, err_msg),
ERROR_INVALID_PARAMETER | ERROR_INVALID_ADDRESS => return raw_error(err::HCF, err_msg),
ERROR_NOT_ENOUGH_MEMORY | ERROR_OUTOFMEMORY => return raw_error(err::NMM, err_msg),
_ => return raw_error(err::UNK, err_msg),
}
}
}
fn discard_raw(ptr: *mut core::ffi::c_void, length: usize) -> FrozenResult<()> {
if hints::unlikely(ptr.is_null()) {
return raw_error(err::NMM, "cannot advise unmapped null pointer");
}
let page_size = WINMemMap::page_size()?.as_usize();
if hints::unlikely(ptr as usize % page_size != 0) {
return raw_error(err::HCF, "address is not page-aligned");
}
let aligned_len = length.div_ceil(page_size) * page_size;
let mut retries = 0;
loop {
let ok = unsafe { VirtualUnlock(ptr, aligned_len) };
if hints::likely(ok != 0) {
return Ok(());
}
let code = last_error();
if hints::likely(code == ERROR_NOT_LOCKED) {
return Ok(());
}
let err_msg = err_msg(code);
match code {
ERROR_LOCK_VIOLATION | ERROR_SHARING_VIOLATION => {
if retries < MAX_RETRIES {
retries += 1;
std::thread::sleep(Duration::from_millis(1));
continue;
}
return raw_error(err::UNK, err_msg);
}
ERROR_INVALID_PARAMETER => return raw_error(err::HCF, err_msg),
ERROR_INVALID_ADDRESS => return raw_error(err::NMM, err_msg),
ERROR_NOT_ENOUGH_MEMORY | ERROR_OUTOFMEMORY => return raw_error(err::NMM, err_msg),
_ => return raw_error(err::UNK, err_msg),
}
}
}
fn set_memory_priority_raw() -> FrozenResult<()> {
let priority_info = MEMORY_PRIORITY_INFORMATION { MemoryPriority: MEMORY_PRIORITY_LOW };
let ok = unsafe {
SetProcessInformation(
GetCurrentProcess(),
ProcessMemoryPriority,
&priority_info as *const _ as *const core::ffi::c_void,
core::mem::size_of::<MEMORY_PRIORITY_INFORMATION>() as u32,
)
};
if hints::likely(ok != 0) {
return Ok(());
}
let code = last_error();
let msg = err_msg(code);
match code {
ERROR_ACCESS_DENIED => raw_error(err::PRM, msg),
ERROR_INVALID_PARAMETER => raw_error(err::HCF, msg),
_ => raw_error(err::UNK, msg),
}
}
fn prefetch_raw(entries: &[WIN32_MEMORY_RANGE_ENTRY]) -> FrozenResult<()> {
if hints::unlikely(entries.is_empty()) {
return Ok(());
}
let ok =
unsafe { PrefetchVirtualMemory(GetCurrentProcess(), entries.len(), entries.as_ptr(), 0) };
if hints::likely(ok != 0) {
return Ok(());
}
let code = last_error();
let msg = err_msg(code);
match code {
ERROR_ACCESS_DENIED => raw_error(err::PRM, msg),
ERROR_INVALID_PARAMETER => raw_error(err::HCF, msg),
_ => raw_error(err::UNK, msg),
}
}
#[cfg(test)]
fn get_memory_priority_raw() -> FrozenResult<u32> {
use windows_sys::Win32::System::Threading::GetProcessInformation;
let mut priority_info = MEMORY_PRIORITY_INFORMATION { MemoryPriority: 0 };
let ok = unsafe {
GetProcessInformation(
GetCurrentProcess(),
ProcessMemoryPriority,
&mut priority_info as *mut _ as *mut core::ffi::c_void,
core::mem::size_of::<MEMORY_PRIORITY_INFORMATION>() as u32,
)
};
if hints::likely(ok != 0) {
return Ok(priority_info.MemoryPriority);
}
let code = last_error();
let msg = err_msg(code);
match code {
ERROR_ACCESS_DENIED => raw_error(err::PRM, msg),
ERROR_INVALID_PARAMETER => raw_error(err::HCF, msg),
_ => raw_error(err::UNK, msg),
}
}
#[cfg(test)]
fn set_memory_priority_explicit_raw(priority: u32) -> FrozenResult<()> {
let priority_info = MEMORY_PRIORITY_INFORMATION { MemoryPriority: priority };
let ok = unsafe {
SetProcessInformation(
GetCurrentProcess(),
ProcessMemoryPriority,
&priority_info as *const _ as *const core::ffi::c_void,
core::mem::size_of::<MEMORY_PRIORITY_INFORMATION>() as u32,
)
};
if hints::likely(ok != 0) {
return Ok(());
}
let code = last_error();
let msg = err_msg(code);
match code {
ERROR_ACCESS_DENIED => raw_error(err::PRM, msg),
ERROR_INVALID_PARAMETER => raw_error(err::HCF, msg),
_ => raw_error(err::UNK, msg),
}
}
#[inline]
fn last_error() -> u32 {
unsafe { windows_sys::Win32::Foundation::GetLastError() }
}
#[inline]
fn err_msg(code: u32) -> String {
std::io::Error::from_raw_os_error(code as i32).to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::file::{File, FileCfg};
const MOD_ID: u8 = 0;
const BUFFER_SIZE: usize = 0x10;
const INIT_BUFFERS: usize = 0x0A;
const LENGTH: usize = BUFFER_SIZE * INIT_BUFFERS;
fn new_tmp() -> (tempfile::TempDir, File) {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tmp_map");
let file = File::new(FileCfg {
path,
module_id: MOD_ID,
buffer_size: BUFFER_SIZE,
initial_available_buffers: INIT_BUFFERS,
sync_interval: None,
sequential_access_hint: false,
})
.expect("new FF");
(dir, file)
}
mod utils {
use super::*;
#[test]
fn ok_last_error() {
unsafe {
windows_sys::Win32::Foundation::SetLastError(ERROR_INVALID_HANDLE);
assert_eq!(last_error(), ERROR_INVALID_HANDLE);
}
}
#[test]
fn ok_err_msg() {
let msg = err_msg(windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND);
assert!(!msg.is_empty(), "ERROR_FILE_NOT_FOUND must produce message");
}
}
mod priority {
use super::*;
use windows_sys::Win32::System::Threading::MEMORY_PRIORITY_NORMAL;
#[test]
fn ok_query_active_memory_priority() {
let priority = get_memory_priority_raw().expect("query memory priority");
assert!(priority > 0, "active memory priority should be non-zero");
}
#[test]
fn ok_apply_memory_priority_hint() {
WINMemMap::apply_memory_priority_hint().expect("apply memory priority hint");
let active = get_memory_priority_raw().expect("query active memory priority");
assert_eq!(
active, MEMORY_PRIORITY_LOW,
"active priority must equal MEMORY_PRIORITY_LOW after hint"
);
}
#[test]
fn ok_memory_priority_state_transition() {
set_memory_priority_explicit_raw(MEMORY_PRIORITY_NORMAL)
.expect("reset priority to normal");
let before = get_memory_priority_raw().expect("query priority before hint");
assert_eq!(before, MEMORY_PRIORITY_NORMAL);
WINMemMap::apply_memory_priority_hint().expect("apply memory priority hint");
let after = get_memory_priority_raw().expect("query priority after hint");
assert_eq!(after, MEMORY_PRIORITY_LOW, "priority must transition from normal to low");
}
#[test]
fn ok_memory_priority_hint_idempotent() {
for _ in 0..0x0A {
WINMemMap::apply_memory_priority_hint().expect("apply memory priority hint");
let active = get_memory_priority_raw().expect("query memory priority");
assert_eq!(active, MEMORY_PRIORITY_LOW);
}
}
}
mod page_size {
use super::*;
#[test]
fn ok_page_size_returns_valid_power_of_two() {
let size = WINMemMap::page_size().expect("page_size must succeed");
assert!(size.is_power_of_two(), "page size must be a power of two, got {size}");
assert!(
size.as_usize() >= 0x1000,
"page size must be at least 4096 bytes on supported 64-bit systems, got {size}"
);
}
#[test]
fn ok_page_size_matches_system_info() {
let size = WINMemMap::page_size().expect("page_size must succeed");
let mut sys_info = unsafe { core::mem::zeroed::<SYSTEM_INFO>() };
unsafe { GetSystemInfo(&mut sys_info) };
assert_eq!(size.as_usize(), sys_info.dwPageSize as usize);
}
#[test]
fn ok_page_size_consistency() {
let first = WINMemMap::page_size().unwrap();
for _ in 0..0x3E8 {
assert_eq!(WINMemMap::page_size().unwrap(), first);
}
}
#[test]
fn ok_page_size_across_threads() {
let expected = WINMemMap::page_size().unwrap();
let mut handles = Vec::new();
for _ in 0..0x10 {
let handle = std::thread::spawn(move || {
for _ in 0..0x64 {
assert_eq!(WINMemMap::page_size().unwrap(), expected);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
}
mod active_pages {
use super::*;
fn new_paged_tmp(num_pages: usize) -> (tempfile::TempDir, File, usize) {
let page_size = WINMemMap::page_size().expect("page_size").as_usize();
let total_len = page_size * num_pages;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tmp_paged_map");
let file = File::new(FileCfg {
path,
module_id: MOD_ID,
buffer_size: BUFFER_SIZE,
initial_available_buffers: total_len / BUFFER_SIZE,
sync_interval: None,
sequential_access_hint: false,
})
.expect("new paged file");
(dir, file, total_len)
}
#[test]
fn ok_active_pages_after_touch() {
let (_dir, file, total_len) = new_paged_tmp(4);
let page_size = WINMemMap::page_size().unwrap().as_usize();
unsafe {
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
let p0 = mmap.as_mut_ptr::<u8>(0);
*p0 = 0xAA;
let p2 = mmap.as_mut_ptr::<u8>(page_size * 2);
*p2 = 0xBB;
let active = mmap.active_page_indices().unwrap();
assert!(active.contains(&0), "page 0 must be active after write");
assert!(active.contains(&2), "page 2 must be active after write");
for &idx in &active {
assert!(idx < 4, "active page index must be within mapping range");
}
mmap.unmap().unwrap();
}
}
#[test]
fn ok_active_pages_all_touched() {
let (_dir, file, total_len) = new_paged_tmp(3);
let page_size = WINMemMap::page_size().unwrap().as_usize();
unsafe {
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
for i in 0..3 {
let ptr = mmap.as_mut_ptr::<u8>(i * page_size);
*ptr = (i + 1) as u8;
}
let active = mmap.active_page_indices().unwrap();
assert_eq!(active, vec![0, 1, 2]);
mmap.unmap().unwrap();
}
}
#[test]
fn ok_active_pages_read_faults_page() {
let (_dir, file, total_len) = new_paged_tmp(2);
let page_size = WINMemMap::page_size().unwrap().as_usize();
unsafe {
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
let p1 = mmap.as_ptr::<u8>(page_size);
let _val = ptr::read_volatile(p1);
let active = mmap.active_page_indices().unwrap();
assert!(active.contains(&1), "reading page 1 must bring it into memory");
mmap.unmap().unwrap();
}
}
#[test]
fn ok_active_pages_consecutive_consistency() {
let (_dir, file, total_len) = new_paged_tmp(2);
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
let first = mmap.active_page_indices().unwrap();
let second = mmap.active_page_indices().unwrap();
assert_eq!(first, second);
mmap.unmap().unwrap();
}
#[test]
fn ok_resident_bytes_all_touched() {
let (_dir, file, total_len) = new_paged_tmp(3);
let page_size = WINMemMap::page_size().unwrap().as_usize();
unsafe {
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
for i in 0..3 {
let ptr = mmap.as_mut_ptr::<u8>(i * page_size);
*ptr = (i + 1) as u8;
}
let resident = mmap.resident_bytes().unwrap();
assert_eq!(resident, 3 * page_size);
mmap.unmap().unwrap();
}
}
}
mod eviction {
use super::*;
fn new_paged_tmp(num_pages: usize) -> (tempfile::TempDir, File, usize) {
let page_size = WINMemMap::page_size().expect("page_size").as_usize();
let total_len = page_size * num_pages;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tmp_evict_map");
let file = File::new(FileCfg {
path,
module_id: MOD_ID,
buffer_size: BUFFER_SIZE,
initial_available_buffers: total_len / BUFFER_SIZE,
sync_interval: None,
sequential_access_hint: false,
})
.expect("new paged file");
(dir, file, total_len)
}
#[test]
fn ok_evict_empty_indices() {
let (_dir, file, total_len) = new_paged_tmp(2);
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
assert!(mmap.evict_pages(&[]).is_ok());
mmap.unmap().unwrap();
}
#[test]
fn ok_evict_single_page() {
let (_dir, file, total_len) = new_paged_tmp(2);
unsafe {
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
let p0 = mmap.as_mut_ptr::<u8>(0);
*p0 = 0xAA;
mmap.sync(total_len).unwrap();
assert!(mmap.evict_pages(&[0]).is_ok());
mmap.unmap().unwrap();
}
}
#[test]
fn ok_evict_contiguous_runs() {
let (_dir, file, total_len) = new_paged_tmp(4);
let page_size = WINMemMap::page_size().unwrap().as_usize();
unsafe {
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
for i in 0..4 {
let ptr = mmap.as_mut_ptr::<u8>(i * page_size);
*ptr = (i + 1) as u8;
}
mmap.sync(total_len).unwrap();
assert!(mmap.evict_pages(&[0, 1]).is_ok());
assert!(mmap.evict_pages(&[2, 3]).is_ok());
mmap.unmap().unwrap();
}
}
#[test]
fn ok_evict_unsorted_and_duplicate_indices() {
let (_dir, file, total_len) = new_paged_tmp(3);
let page_size = WINMemMap::page_size().unwrap().as_usize();
unsafe {
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
for i in 0..3 {
let ptr = mmap.as_mut_ptr::<u8>(i * page_size);
*ptr = 0xFF;
}
mmap.sync(total_len).unwrap();
assert!(mmap.evict_pages(&[2, 0, 2, 1, 0]).is_ok());
mmap.unmap().unwrap();
}
}
#[test]
fn ok_evict_all_using_active_page_indices() {
let (_dir, file, total_len) = new_paged_tmp(3);
let page_size = WINMemMap::page_size().unwrap().as_usize();
unsafe {
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
for i in 0..3 {
let ptr = mmap.as_mut_ptr::<u8>(i * page_size);
*ptr = 0x55;
}
mmap.sync(total_len).unwrap();
let active = mmap.active_page_indices().unwrap();
assert!(mmap.evict_pages(&active).is_ok());
mmap.unmap().unwrap();
}
}
#[test]
fn err_evict_out_of_bounds() {
let (_dir, file, total_len) = new_paged_tmp(2);
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
let err = mmap.evict_pages(&[2]).unwrap_err();
assert_eq!(err.reason, err::HCF.reason);
let err_large = mmap.evict_pages(&[0x64]).unwrap_err();
assert_eq!(err_large.reason, err::HCF.reason);
mmap.unmap().unwrap();
}
#[test]
fn err_evict_unmapped_and_unaligned_ptr() {
let err_unmapped = discard_raw(ptr::null_mut(), BUFFER_SIZE).unwrap_err();
assert_eq!(err_unmapped.reason, err::NMM.reason);
let (_dir, file, total_len) = new_paged_tmp(1);
unsafe {
let mmap = WINMemMap::new(file.fd(), total_len, total_len).unwrap();
let unaligned = (mmap.as_ptr::<u8>(0) as usize + 1) as *mut core::ffi::c_void;
let err_unaligned = discard_raw(unaligned, BUFFER_SIZE).unwrap_err();
assert_eq!(err_unaligned.reason, err::HCF.reason);
mmap.unmap().unwrap();
}
}
}
mod map_unmap {
use super::*;
#[test]
fn ok_map_unmap_cycle() {
let (_dir, file) = new_tmp();
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
mmap.unmap().unwrap();
}
#[test]
fn ok_map_zero_bytes_on_new() {
let (_dir, file) = new_tmp();
const BUF: [u8; LENGTH] = [0; LENGTH];
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let ptr = mmap.as_ptr::<[u8; LENGTH]>(0);
assert_eq!(*ptr, BUF);
mmap.unmap().unwrap();
}
}
#[test]
fn ok_unmap_consumes_self() {
let (_dir, file) = new_tmp();
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
assert!(mmap.unmap().is_ok());
}
#[test]
fn ok_advise_sequential_and_prefetch() {
let (_dir, file) = new_tmp();
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
assert!(mmap.advise_sequential(0, 0).is_ok());
assert!(mmap.prefetch(0, 0).is_ok());
assert!(mmap.advise_sequential(0, LENGTH).is_ok());
assert!(mmap.prefetch(0, LENGTH).is_ok());
assert!(mmap.advise_sequential(0, BUFFER_SIZE).is_ok());
assert!(mmap.prefetch(BUFFER_SIZE, BUFFER_SIZE).is_ok());
let err_seq = mmap.advise_sequential(0, LENGTH + 1).unwrap_err();
assert_eq!(err_seq.reason, err::HCF.reason);
let err_pref = mmap.prefetch(LENGTH, 1).unwrap_err();
assert_eq!(err_pref.reason, err::HCF.reason);
mmap.unmap().unwrap();
}
#[test]
fn ok_multiple_shared_mappings_reflect_writes() {
const VAL: u64 = 0x1234_5678_9ABC_DEF0;
let (_dir, file) = new_tmp();
unsafe {
let mmap1 = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let mmap2 = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let wptr = mmap1.as_mut_ptr::<u64>(0);
*wptr = VAL;
let rptr = mmap2.as_ptr::<u64>(0);
assert_eq!(*rptr, VAL);
mmap1.unmap().unwrap();
mmap2.unmap().unwrap();
}
}
#[test]
fn err_map_on_invalid_length() {
let (_dir, file) = new_tmp();
let err = WINMemMap::new(file.fd(), 0, 0).unwrap_err();
assert_eq!(err.reason, err::HCF.reason);
}
#[test]
fn err_map_on_invalid_handle() {
let (_dir, _) = new_tmp();
let err = WINMemMap::new(-1isize, LENGTH, LENGTH).unwrap_err();
assert_eq!(err.reason, err::HCF.reason);
let err_zero = WINMemMap::new(0isize, LENGTH, LENGTH).unwrap_err();
assert_eq!(err_zero.reason, err::HCF.reason);
}
#[test]
fn err_map_on_read_only_handle() {
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::{
Foundation::GENERIC_READ,
Storage::FileSystem::{
CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_DELETE, FILE_SHARE_READ,
FILE_SHARE_WRITE, OPEN_EXISTING,
},
};
let (dir, file) = new_tmp();
drop(file);
let path = dir.path().join("tmp_map");
let mut wide: Vec<u16> = path.as_os_str().encode_wide().collect();
wide.push(0);
let handle = unsafe {
CreateFileW(
wide.as_ptr(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
core::ptr::null(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
core::ptr::null_mut(),
)
};
assert_ne!(handle, INVALID_HANDLE_VALUE, "open read-only must succeed");
let err = WINMemMap::new(handle as isize, LENGTH, LENGTH).unwrap_err();
assert_eq!(err.reason, err::PRM.reason);
unsafe {
CloseHandle(handle);
}
}
#[test]
fn err_unmap_on_invalid_length() {
let (_dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let err = munmap_raw(mmap.as_ptr::<u8>(0) as *mut u8, 0).unwrap_err();
assert_eq!(err.reason, err::HCF.reason);
mmap.unmap().unwrap();
}
}
}
mod map_sync {
use super::*;
#[test]
fn ok_sync() {
let (_dir, file) = new_tmp();
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
mmap.sync(LENGTH).unwrap();
mmap.unmap().unwrap();
}
#[test]
fn ok_sync_after_sync() {
let (_dir, file) = new_tmp();
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
mmap.sync(LENGTH).unwrap();
mmap.sync(LENGTH).unwrap();
mmap.sync(LENGTH).unwrap();
mmap.sync(LENGTH).unwrap();
mmap.unmap().unwrap();
}
#[test]
fn ok_sync_zero_length() {
let (_dir, file) = new_tmp();
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
assert!(mmap.sync(0).is_ok());
mmap.unmap().unwrap();
}
#[test]
fn ok_sync_partial_range() {
const VAL: u64 = 0xA5A5_A5A5_A5A5_A5A5;
let (_dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let wptr = mmap.as_mut_ptr::<u64>(0);
*wptr = VAL;
assert!(mmap.sync(BUFFER_SIZE).is_ok());
mmap.unmap().unwrap();
}
}
#[test]
fn err_sync_on_unaligned_pointer() {
let (_dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let unaligned = (mmap.as_ptr::<u8>(0) as usize + 1) as *mut u8;
let err = msync_raw(unaligned, BUFFER_SIZE).unwrap_err();
assert_eq!(err.reason, err::HCF.reason);
mmap.unmap().unwrap();
}
}
}
mod map_write_read {
use super::*;
#[test]
fn ok_write_read_cycle() {
const VAL: u64 = 0xDEADC0DE;
let (_dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let wptr = mmap.as_mut_ptr::<u64>(0);
*wptr = VAL;
let rptr = mmap.as_ptr::<u64>(0);
assert_eq!(*rptr, VAL);
mmap.unmap().unwrap();
}
}
#[test]
fn ok_write_read_with_offset() {
const VAL: u64 = 0xDEADC0DE;
let (_dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let wptr = mmap.as_mut_ptr::<u64>(8);
*wptr = VAL;
let rptr = mmap.as_ptr::<u64>(8);
assert_eq!(*rptr, VAL);
mmap.unmap().unwrap();
}
}
#[test]
fn ok_write_read_at_boundary() {
const VAL: u64 = 0xFEEDFACECAFEC0DE;
const OFFSET: usize = LENGTH - std::mem::size_of::<u64>();
let (_dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let wptr = mmap.as_mut_ptr::<u64>(OFFSET);
*wptr = VAL;
let rptr = mmap.as_ptr::<u64>(OFFSET);
assert_eq!(*rptr, VAL);
mmap.unmap().unwrap();
}
}
#[test]
fn ok_write_read_sync_cycle() {
const VAL: [u32; 0x0A] = [0xDEADC0DE; 0x0A];
let (_dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let wptr = mmap.as_mut_ptr::<[u32; 0x0A]>(0);
*wptr = VAL;
mmap.sync(LENGTH).unwrap();
let rptr = mmap.as_ptr::<[u32; 0x0A]>(0);
assert_eq!(*rptr, VAL);
mmap.unmap().unwrap();
}
}
#[test]
fn ok_read_zero_bytes() {
let (_dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let rptr = mmap.as_ptr::<u64>(0);
assert_eq!(*rptr, 0);
mmap.unmap().unwrap();
}
}
#[test]
fn ok_write_read_heterogeneous_types() {
let (_dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let p_u8 = mmap.as_mut_ptr::<u8>(0);
*p_u8 = 0xAA;
let p_u16 = mmap.as_mut_ptr::<u16>(2);
*p_u16 = 0xBBCC;
let p_u32 = mmap.as_mut_ptr::<u32>(4);
*p_u32 = 0xDDEEFF00;
let p_u64 = mmap.as_mut_ptr::<u64>(8);
*p_u64 = 0x11223344_55667788;
assert_eq!(*mmap.as_ptr::<u8>(0), 0xAA);
assert_eq!(*mmap.as_ptr::<u16>(2), 0xBBCC);
assert_eq!(*mmap.as_ptr::<u32>(4), 0xDDEEFF00);
assert_eq!(*mmap.as_ptr::<u64>(8), 0x11223344_55667788);
mmap.unmap().unwrap();
}
}
}
mod map_durability {
use super::*;
#[test]
fn ok_map_durability_after_unmap() {
const VAL: u64 = 0xCAFEBABEDEADC0DE;
let (_dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let ptr = mmap.as_mut_ptr::<u64>(0);
*ptr = VAL;
mmap.sync(LENGTH).unwrap();
mmap.unmap().unwrap();
}
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let ptr = mmap.as_ptr::<u64>(0);
assert_eq!(*ptr, VAL);
mmap.unmap().unwrap();
}
}
#[test]
fn ok_map_durability_after_unmap_and_close() {
const VAL: u64 = 0xDEADC0DEDEADC0DE;
let (dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let ptr = mmap.as_mut_ptr::<u64>(0);
*ptr = VAL;
mmap.sync(LENGTH).unwrap();
mmap.unmap().unwrap();
drop(file);
}
unsafe {
let path = dir.path().join("tmp_map");
let cfg = FileCfg {
path,
module_id: MOD_ID,
buffer_size: BUFFER_SIZE,
initial_available_buffers: INIT_BUFFERS,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::open(cfg).expect("open FF");
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let ptr = mmap.as_ptr::<u64>(0);
assert_eq!(*ptr, VAL);
mmap.unmap().unwrap();
}
}
#[test]
fn ok_map_durability_on_drop_and_reopen() {
const VAL: u64 = 0x0123_4567_89AB_CDEF;
let (dir, file) = new_tmp();
unsafe {
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let ptr = mmap.as_mut_ptr::<u64>(0);
*ptr = VAL;
mmap.sync(LENGTH).unwrap();
drop(mmap);
drop(file);
}
unsafe {
let path = dir.path().join("tmp_map");
let cfg = FileCfg {
path,
module_id: MOD_ID,
buffer_size: BUFFER_SIZE,
initial_available_buffers: INIT_BUFFERS,
sync_interval: None,
sequential_access_hint: false,
};
let file = File::open(cfg).expect("open FF");
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let ptr = mmap.as_ptr::<u64>(0);
assert_eq!(*ptr, VAL);
mmap.unmap().unwrap();
}
}
}
mod map_drop {
use super::*;
#[test]
fn ok_drop_tracks_length() {
let (_dir, file) = new_tmp();
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
assert_eq!(mmap.length(), LENGTH);
}
#[test]
fn ok_explicit_unmap_prevents_drop_double_unmap() {
let (_dir, file) = new_tmp();
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
assert!(mmap.unmap().is_ok());
}
#[test]
fn ok_drop_persists_written_data() {
const VAL: u64 = 0xCAFEBABE_DEADC0DE;
let (_dir, file) = new_tmp();
unsafe {
{
let mmap = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let wptr = mmap.as_mut_ptr::<u64>(0);
*wptr = VAL;
mmap.sync(LENGTH).unwrap();
}
let mmap2 = WINMemMap::new(file.fd(), LENGTH, LENGTH).unwrap();
let rptr = mmap2.as_ptr::<u64>(0);
assert_eq!(*rptr, VAL);
mmap2.unmap().unwrap();
}
}
}
}