use memmap2::MmapOptions;
use std::fs::File;
use std::path::Path;
use super::raw::open_file_safe;
fn read_capped_open_file(
file: File,
path: &Path,
cap: u64,
live_size: u64,
) -> std::io::Result<Vec<u8>> {
if live_size > cap {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"compressed file '{}' exceeds {} byte cap",
path.display(),
cap
),
));
}
let read = crate::capped_read::read_to_cap(file, cap, Some(live_size))?;
if read.truncated {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"compressed file '{}' grew beyond {} byte cap while reading",
path.display(),
cap
),
));
}
Ok(read.bytes)
}
pub(in crate::filesystem) enum FileBytes {
Mmap(memmap2::Mmap),
Owned(Vec<u8>),
}
impl FileBytes {
pub(in crate::filesystem) fn as_slice(&self) -> &[u8] {
match self {
FileBytes::Mmap(m) => m,
FileBytes::Owned(v) => v,
}
}
#[cfg(test)]
pub(in crate::filesystem) fn len(&self) -> usize {
self.as_slice().len()
}
}
pub(in crate::filesystem) fn read_file_for_compressed_input(
path: &Path,
size_cap: u64,
) -> Option<FileBytes> {
let effective_size_cap = if size_cap == 0 {
super::MMAP_TOCTOU_SANITY_CAP_BYTES
} else {
size_cap
};
let file = match open_file_safe(path) {
Ok(f) => f,
Err(error) => {
tracing::warn!(
path = %path.display(),
%error,
"cannot open compressed file; skipping"
);
let _event = crate::record_skip_event(crate::SourceSkipEvent::Unreadable);
return None;
}
};
let metadata = match file.metadata() {
Ok(m) => m,
Err(error) => {
tracing::warn!(
path = %path.display(),
%error,
"cannot stat compressed file; skipping"
);
let _event = crate::record_skip_event(crate::SourceSkipEvent::Unreadable);
return None;
}
};
if metadata.len() > effective_size_cap {
tracing::warn!(
path = %path.display(),
size = metadata.len(),
cap = effective_size_cap,
"compressed file exceeds size cap; refusing to map"
);
let _event = crate::record_skip_event(crate::SourceSkipEvent::OverMaxSize);
return None;
}
if metadata.len() == 0 {
return Some(FileBytes::Owned(Vec::new()));
}
match unsafe { MmapOptions::new().map(&file) } {
Ok(mmap) => {
#[cfg(unix)]
{
unsafe {
libc::madvise(
mmap.as_ptr() as *mut libc::c_void,
mmap.len(),
libc::MADV_SEQUENTIAL,
);
}
use std::os::unix::io::AsRawFd;
unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
}
Some(FileBytes::Mmap(mmap))
}
Err(error) => {
tracing::warn!(
path = %path.display(),
%error,
"cannot mmap compressed file; falling back to buffered read"
);
match read_capped_open_file(file, path, effective_size_cap, metadata.len()) {
Ok(bytes) => Some(FileBytes::Owned(bytes)),
Err(error) => {
tracing::warn!(
path = %path.display(),
%error,
"cannot read compressed file after mmap failure; skipping"
);
let skip = if error.kind() == std::io::ErrorKind::InvalidData {
crate::SourceSkipEvent::OverMaxSize
} else {
crate::SourceSkipEvent::Unreadable
};
let _event = crate::record_skip_event(skip);
None
}
}
}
}
}