use memmap2::MmapOptions;
use std::path::Path;
use super::raw::open_file_safe;
use super::MMAP_TOCTOU_SANITY_CAP_BYTES;
pub(in crate::filesystem) struct FileWindow {
pub offset: usize,
pub base_line: usize,
pub text: String,
}
pub(in crate::filesystem) fn read_file_windowed_mmap(
path: &Path,
window_size: usize,
overlap: usize,
) -> Option<Vec<FileWindow>> {
debug_assert!(window_size > overlap, "window must exceed overlap");
let file = open_file_safe(path).ok()?;
if let Ok(meta) = file.metadata() {
if meta.len() > MMAP_TOCTOU_SANITY_CAP_BYTES {
tracing::warn!(
path = %path.display(),
live_size = meta.len(),
cap = MMAP_TOCTOU_SANITY_CAP_BYTES,
"refusing to windowed-mmap file: live size exceeds sanity cap (likely TOCTOU growth)"
);
return None;
}
}
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let fd = file.as_raw_fd();
if unsafe { libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB) } != 0 {
return None;
}
}
let mmap = match unsafe { MmapOptions::new().map(&file) } {
Ok(m) => m,
Err(_) => {
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
}
return None;
}
};
#[cfg(unix)]
{
unsafe {
libc::madvise(
mmap.as_ptr() as *mut libc::c_void,
mmap.len(),
libc::MADV_SEQUENTIAL,
);
}
}
let windows = slice_into_windows(&mmap, window_size, overlap);
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
}
Some(windows)
}
#[inline]
fn bytecount_newlines(slice: &[u8]) -> usize {
memchr::memchr_iter(b'\n', slice).count()
}
pub(in crate::filesystem) fn slice_into_windows(
bytes: &[u8],
window_size: usize,
overlap: usize,
) -> Vec<FileWindow> {
assert!(window_size > overlap, "window must exceed overlap");
if bytes.is_empty() {
return Vec::new();
}
let stride = window_size - overlap;
let total = bytes.len();
let mut out = Vec::with_capacity(total.div_ceil(stride));
let mut offset = 0usize;
let mut base_line = 0usize;
while offset < total {
let end = (offset + window_size).min(total);
let slice = &bytes[offset..end];
let text = String::from_utf8_lossy(slice).into_owned();
out.push(FileWindow {
offset,
base_line,
text,
});
if end >= total {
break;
}
let next = offset + stride;
base_line += bytecount_newlines(&bytes[offset..next]);
offset = next;
}
out
}