use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use std::sync::Arc;
use memmap2::Mmap;
pub(crate) const SMALL_FILE_LIMIT: u64 = 16 * 1024;
#[derive(Debug)]
pub(crate) enum Backing {
Mapped(Mmap),
Owned(Box<[u8]>),
Shared(Arc<[u8]>),
}
impl Backing {
#[inline]
pub(crate) fn bytes(&self) -> &[u8] {
match self {
Self::Mapped(map) => map,
Self::Owned(bytes) => bytes,
Self::Shared(bytes) => bytes,
}
}
pub(crate) fn is_mapped(&self) -> bool {
matches!(self, Self::Mapped(_))
}
}
pub(crate) fn load(path: &Path) -> io::Result<Backing> {
let mut file = File::open(path)?;
let metadata = file.metadata()?;
if metadata.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"is a directory",
));
}
if metadata.is_file() && metadata.len() >= SMALL_FILE_LIMIT {
if let Ok(map) = unsafe { Mmap::map(&file) } {
return Ok(Backing::Mapped(map));
}
}
let mut buffer = Vec::new();
if metadata.is_file() {
buffer.reserve(usize::try_from(metadata.len()).unwrap_or(0));
}
file.read_to_end(&mut buffer)?;
Ok(Backing::Owned(buffer.into_boxed_slice()))
}
#[cfg(test)]
#[allow(clippy::arithmetic_side_effects)] mod tests {
use super::*;
#[test]
fn small_files_are_read_and_large_files_mapped() {
let dir = std::env::temp_dir().join(format!("qld-input-map-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let small = dir.join("small");
let large = dir.join("large");
std::fs::write(&small, b"hello").unwrap();
let big = vec![0xa5u8; 64 * 1024];
std::fs::write(&large, &big).unwrap();
let loaded = load(&small).unwrap();
assert!(!loaded.is_mapped());
assert_eq!(loaded.bytes(), b"hello");
let loaded = load(&large).unwrap();
assert_eq!(loaded.bytes(), &big[..]);
#[cfg(target_os = "linux")]
assert!(loaded.is_mapped());
drop(loaded);
assert!(load(&dir).is_err());
assert!(load(&dir.join("missing")).is_err());
let _ = std::fs::remove_dir_all(&dir);
}
}