use memmap2::MmapOptions;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use super::decode::{decode_text_file, decode_text_file_owned};
use super::MMAP_TOCTOU_SANITY_CAP_BYTES;
const MAX_BUFFERED_READ_BYTES: u64 = MMAP_TOCTOU_SANITY_CAP_BYTES;
pub(in crate::filesystem) fn read_file_buffered(path: &Path, size_hint: u64) -> Option<String> {
let bytes = read_file_safe(path, size_hint).ok()?;
decode_text_file_owned(bytes)
}
pub(in crate::filesystem) fn open_file_safe(path: &Path) -> std::io::Result<File> {
let mut options = std::fs::OpenOptions::new();
options.read(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.custom_flags(libc::O_NOFOLLOW);
}
#[cfg(windows)]
{
if let Ok(meta) = std::fs::symlink_metadata(path) {
if meta.file_type().is_symlink() {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"refusing to follow symlink (Windows safety guard)",
));
}
}
}
options.open(path)
}
pub(in crate::filesystem) fn read_file_safe(
path: &Path,
size_hint: u64,
) -> std::io::Result<Vec<u8>> {
let mut file = open_file_safe(path)?;
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
let fd = file.as_raw_fd();
unsafe { libc::posix_fadvise(fd, 0, 0, libc::POSIX_FADV_SEQUENTIAL) };
}
let cap = size_hint.min(MAX_BUFFERED_READ_BYTES);
if cap == 0 {
let mut bytes = Vec::new();
file.take(MAX_BUFFERED_READ_BYTES).read_to_end(&mut bytes)?;
return Ok(bytes);
}
let cap = cap as usize;
let mut bytes = vec![0u8; cap];
let mut filled = 0;
while filled < cap {
match file.read(&mut bytes[filled..]) {
Ok(0) => break, Ok(n) => filled += n,
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
bytes.truncate(filled);
Ok(bytes)
}
pub(in crate::filesystem) fn read_file_mmap(path: &Path) -> Option<String> {
let mut 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 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 {
let mut bytes = Vec::new();
if std::io::Read::read_to_end(&mut file, &mut bytes).is_ok() {
return decode_text_file(&bytes);
}
return None;
}
}
let mmap = match unsafe { MmapOptions::new().map(&file) } {
Ok(m) => m,
Err(_) => {
let mut bytes = Vec::new();
if std::io::Read::read_to_end(&mut file, &mut bytes).is_ok() {
return decode_text_file(&bytes);
}
return None;
}
};
#[cfg(unix)]
{
unsafe {
libc::madvise(
mmap.as_ptr() as *mut libc::c_void,
mmap.len(),
libc::MADV_SEQUENTIAL,
);
}
}
let result = decode_text_file(&mmap);
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let fd = file.as_raw_fd();
unsafe { libc::flock(fd, libc::LOCK_UN) };
}
result
}