#![warn(missing_docs)]
use std::cmp;
use std::io;
use std::fs;
use std::ops::Deref;
use std::path::Path;
use std::ptr;
use std::slice;
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
#[cfg(unix)]
use unix::{PlatformData, get_page_size, map_file, unmap_file, prefetch};
#[cfg(all(unix))]
use unix::get_resident;
#[cfg(windows)]
use windows::{PlatformData, get_resident, get_page_size, map_file, unmap_file, prefetch};
#[derive(Debug)]
pub struct FileBuffer {
page_size: usize,
buffer: *const u8,
length: usize,
#[allow(dead_code)] platform_data: PlatformData,
}
fn round_up_to(size: usize, power_of_two: usize) -> usize {
(size + (power_of_two - 1)) & !(power_of_two - 1)
}
#[test]
fn verify_round_up_to() {
assert_eq!(1024, round_up_to(23, 1024));
assert_eq!(1024, round_up_to(1024, 1024));
assert_eq!(2048, round_up_to(1025, 1024));
}
fn round_down_to(size: usize, power_of_two: usize) -> usize {
size & !(power_of_two - 1)
}
#[test]
fn verify_round_down_to() {
assert_eq!(0, round_down_to(23, 1024));
assert_eq!(1024, round_down_to(1024, 1024));
assert_eq!(1024, round_down_to(1025, 1024));
}
impl FileBuffer {
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<FileBuffer> {
let mut open_opts = fs::OpenOptions::new();
open_opts.read(true);
let file = open_opts.open(path)?;
let (buffer, length, platform_data) = map_file(file)?;
let fbuffer = FileBuffer {
page_size: get_page_size(),
buffer: buffer,
length: length,
platform_data: platform_data
};
Ok(fbuffer)
}
pub fn resident_len(&self, offset: usize, length: usize) -> usize {
assert!(offset + length <= self.length);
if self.buffer == ptr::null() { return 0; }
let aligned_offset = round_down_to(offset, self.page_size);
let aligned_length = round_up_to(length + (offset - aligned_offset), self.page_size);
let num_pages = aligned_length / self.page_size;
let mut residency = [false; 32];
let mut pages_checked = 0;
let mut pages_resident = 0;
while pages_checked < num_pages {
let pages_to_check = cmp::min(32, num_pages - pages_checked);
let check_offset = (aligned_offset + pages_checked * self.page_size) as isize;
let check_buffer = unsafe { self.buffer.offset(check_offset) };
let check_length = pages_to_check * self.page_size;
get_resident(check_buffer, check_length, &mut residency);
match residency[..pages_to_check].iter().position(|resident| !resident) {
Some(non_resident) => {
pages_resident += non_resident;
break;
}
None => {
pages_resident += pages_to_check;
pages_checked += pages_to_check;
}
}
}
let resident_length = pages_resident * self.page_size + aligned_offset - offset;
cmp::min(length, resident_length)
}
pub fn chunk_len_hint(&self) -> usize {
self.page_size
}
pub fn prefetch(&self, offset: usize, length: usize) {
assert!(offset + length <= self.length);
if self.buffer == ptr::null() { return; }
let aligned_offset = round_down_to(offset, self.page_size);
let aligned_length = round_up_to(length + (offset - aligned_offset), self.page_size);
let buffer = unsafe { self.buffer.offset(aligned_offset as isize) };
prefetch(buffer, aligned_length);
}
pub fn leak(mut self) -> &'static [u8] {
let buffer = if self.buffer == ptr::null() {
&[]
} else {
unsafe { slice::from_raw_parts(self.buffer, self.length) }
};
self.buffer = ptr::null();
self.length = 0;
buffer
}
}
unsafe impl Sync for FileBuffer {}
unsafe impl Send for FileBuffer {}
impl Drop for FileBuffer {
fn drop(&mut self) {
if self.buffer != ptr::null() { unmap_file(self.buffer, self.length); }
}
}
impl Deref for FileBuffer {
type Target = [u8];
fn deref(&self) -> &[u8] {
if self.buffer == ptr::null() {
&[]
} else {
unsafe { slice::from_raw_parts(self.buffer, self.length) }
}
}
}
impl AsRef<[u8]> for FileBuffer {
fn as_ref(&self) -> &[u8] {
self.deref()
}
}
#[test]
fn open_file() {
let fbuffer = FileBuffer::open("src/lib.rs");
assert!(fbuffer.is_ok());
}
#[test]
fn make_resident() {
let fbuffer = FileBuffer::open("src/lib.rs").unwrap();
assert_eq!(&fbuffer[3..13], &b"Filebuffer"[..]);
assert_eq!(fbuffer.resident_len(3, 10), 10);
}
#[test]
fn prefetch_is_not_harmful() {
let fbuffer = FileBuffer::open("src/lib.rs").unwrap();
fbuffer.prefetch(0, fbuffer.len());
assert_eq!(&fbuffer[3..13], &b"Filebuffer"[..]);
}
#[test]
fn drop_after_leak() {
let mut bytes = &[0u8][..];
assert_eq!(bytes[0], 0);
{
let fbuffer = FileBuffer::open("src/lib.rs").unwrap();
bytes = fbuffer.leak();
}
assert_eq!(&bytes[3..13], &b"Filebuffer"[..]);
}
#[test]
fn fbuffer_can_be_moved_into_thread() {
use std::thread;
let fbuffer = FileBuffer::open("src/lib.rs").unwrap();
thread::spawn(move || {
assert_eq!(&fbuffer[3..13], &b"Filebuffer"[..]);
});
}
#[test]
fn fbuffer_can_be_shared_among_threads() {
use std::sync;
use std::thread;
let fbuffer = FileBuffer::open("src/lib.rs").unwrap();
let buffer1 = sync::Arc::new(fbuffer);
let buffer2 = buffer1.clone();
thread::spawn(move || {
assert_eq!(&buffer2[3..13], &b"Filebuffer"[..]);
});
assert_eq!(&buffer1[17..45], &b"Fast and simple file reading"[..]);
}
#[test]
fn open_empty_file_is_fine() {
FileBuffer::open("src/empty_file_for_testing.rs").unwrap();
}
#[test]
fn empty_file_prefetch_is_fine() {
let fbuffer = FileBuffer::open("src/empty_file_for_testing.rs").unwrap();
fbuffer.prefetch(0, 0);
}
#[test]
fn empty_file_deref_is_fine() {
let fbuffer = FileBuffer::open("src/empty_file_for_testing.rs").unwrap();
assert_eq!(fbuffer.iter().any(|_| true), false);
}
#[test]
fn empty_file_has_zero_resident_len() {
let fbuffer = FileBuffer::open("src/empty_file_for_testing.rs").unwrap();
assert_eq!(fbuffer.resident_len(0, 0), 0);
}
#[test]
fn page_size_at_least_4096() {
assert!(get_page_size() >= 4096);
}