#[cfg(feature = "mmap")]
use std::fs::File;
use std::io;
#[cfg(feature = "mmap")]
const MINIMUM_MMAP_SIZE: u64 = 16 * 1024;
pub(crate) fn copy_wide(mut reader: impl io::Read, hasher: &mut crate::Hasher) -> io::Result<u64> {
let mut buffer = [0; 65536];
let mut total = 0;
loop {
match reader.read(&mut buffer) {
Ok(0) => return Ok(total),
Ok(n) => {
hasher.update(&buffer[..n]);
total += n as u64;
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
}
#[cfg(feature = "mmap")]
pub(crate) fn maybe_mmap_file(file: &mut File) -> io::Result<Option<memmap2::Mmap>> {
use io::Seek;
if cfg!(debug_assertions) {
if let Ok(position) = file.stream_position() {
assert_eq!(position, 0, "initial file offset isn't at the beginning");
}
}
let seek_offset = MINIMUM_MMAP_SIZE - 1;
let seek_target = io::SeekFrom::End(-(seek_offset as i64));
let Ok(offset_len) = file.seek(seek_target) else {
return Ok(None); };
if offset_len == 0 {
return Ok(None); }
if offset_len <= isize::MAX as u64 - seek_offset {
let mut mmap_options = memmap2::MmapOptions::new();
mmap_options.len((offset_len + seek_offset) as usize); if let Ok(mmap) = unsafe { mmap_options.map(&*file) } {
return Ok(Some(mmap));
}
}
file.rewind()?;
Ok(None)
}
#[cfg(all(test, feature = "mmap"))]
mod test {
use super::*;
use std::io;
use std::io::prelude::*;
#[test]
fn test_maybe_mmap_small_files() -> io::Result<()> {
let test_cases = [0, 1, MINIMUM_MMAP_SIZE - 2, MINIMUM_MMAP_SIZE - 1];
for len in test_cases {
dbg!(len);
let mut input = vec![0; len as usize];
crate::test::paint_test_input(&mut input);
let mut f = tempfile::NamedTempFile::new()?;
f.write_all(&input)?;
f.flush()?;
f.rewind()?;
assert!(maybe_mmap_file(f.as_file_mut())?.is_none());
assert_eq!(f.stream_position()?, 0);
assert_eq!(
crate::hash(&input),
crate::Hasher::new()
.update_reader(f.as_file_mut())?
.finalize(),
);
}
Ok(())
}
#[test]
fn test_maybe_mmap_mappable_files() -> io::Result<()> {
let test_cases = [MINIMUM_MMAP_SIZE, MINIMUM_MMAP_SIZE + 1];
for len in test_cases {
dbg!(len);
let mut input = vec![0; len as usize];
crate::test::paint_test_input(&mut input);
let mut f = tempfile::NamedTempFile::new()?;
f.write_all(&input)?;
f.flush()?;
f.rewind()?;
let Ok(Some(mmap)) = maybe_mmap_file(f.as_file_mut()) else {
panic!("mmap failed");
};
assert_ne!(f.stream_position()?, 0);
f.rewind()?;
assert_eq!(mmap[..], input[..]);
assert_eq!(crate::hash(&input), crate::hash(&mmap));
assert_eq!(
crate::hash(&input),
crate::Hasher::new()
.update_reader(f.as_file_mut())?
.finalize(),
);
}
Ok(())
}
#[test]
fn test_maybe_mmap_current_exe() -> io::Result<()> {
let mut exe_file = File::open(std::env::current_exe()?)?;
assert!(exe_file.metadata()?.len() > MINIMUM_MMAP_SIZE);
let mmap = maybe_mmap_file(&mut exe_file)?.expect("maybe_mmap_file should return Some");
exe_file.rewind()?;
assert_eq!(
crate::hash(&mmap),
crate::Hasher::new().update_reader(&exe_file)?.finalize(),
);
Ok(())
}
#[cfg(target_os = "linux")]
#[test]
fn test_unmappable_linux() -> io::Result<()> {
let unmappable_path = "/sys/kernel/btf/vmlinux";
let mut unmappable_file = File::open(unmappable_path)?;
assert!(unmappable_file.metadata()?.len() > MINIMUM_MMAP_SIZE);
assert_eq!(unmappable_file.read(&mut [0])?, 1);
unmappable_file.rewind()?;
unsafe { memmap2::Mmap::map(&unmappable_file) }.unwrap_err();
assert!(maybe_mmap_file(&mut unmappable_file)?.is_none());
Ok(())
}
}