extern crate libc;
use std::error::Error;
use std::io;
use std::fmt;
use std::ptr;
use std::ops::Drop;
use libc::{c_void, c_int};
#[cfg(windows)]
use std::mem;
fn errno() -> i32 {
io::Error::last_os_error().raw_os_error().unwrap_or(-1)
}
#[cfg(unix)]
fn page_size() -> usize {
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
}
#[cfg(windows)]
fn page_size() -> usize {
unsafe {
let mut info = mem::zeroed();
libc::GetSystemInfo(&mut info);
info.dwPageSize as usize
}
}
fn round_up(from: usize, to: usize) -> usize {
let r = if from % to == 0 {
from
} else {
from + to - (from % to)
};
if r == 0 {
to
} else {
r
}
}
#[derive(Copy,Clone)]
pub enum MemoryMapKind {
File(*const u8),
Virtual,
}
#[derive(Copy,Clone)]
pub enum MemoryMapOption {
Readable,
Writable,
Executable,
Addr(*const u8),
#[cfg(windows)]
Fd(libc::HANDLE),
#[cfg(not(windows))]
Fd(c_int),
Offset(usize),
NonStandardFlags(c_int),
}
#[derive(Debug,Clone,Copy)]
pub enum MemoryMapError {
FdNotAvail,
InvalidFd,
Unaligned,
NoMapSupport,
NoMem,
ZeroLength,
Unknown(isize),
UnsupProt,
UnsupOffset,
AlreadyExists,
VirtualAlloc(i32),
CreateFileMappingW(i32),
MapViewOfFile(i32),
}
impl fmt::Display for MemoryMapError {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
let str = match *self {
MemoryMapError::FdNotAvail => "fd not available for reading or writing",
MemoryMapError::InvalidFd => "Invalid fd",
MemoryMapError::Unaligned => {
"Unaligned address, invalid flags, negative length or unaligned offset"
}
MemoryMapError::NoMapSupport => "File doesn't support mapping",
MemoryMapError::NoMem => "Invalid address, or not enough available memory",
MemoryMapError::UnsupProt => "Protection mode unsupported",
MemoryMapError::UnsupOffset => "Offset in virtual memory mode is unsupported",
MemoryMapError::AlreadyExists => "File mapping for specified file already exists",
MemoryMapError::ZeroLength => "Zero-length mapping not allowed",
MemoryMapError::Unknown(code) => return write!(out, "Unknown error = {}", code),
MemoryMapError::VirtualAlloc(code) => {
return write!(out, "VirtualAlloc failure = {}", code)
}
MemoryMapError::CreateFileMappingW(code) => {
return write!(out, "CreateFileMappingW failure = {}", code)
}
MemoryMapError::MapViewOfFile(code) => {
return write!(out, "MapViewOfFile failure = {}", code)
}
};
write!(out, "{}", str)
}
}
impl Error for MemoryMapError {
fn description(&self) -> &str {
"memory map error"
}
}
pub struct MemoryMap {
data: *mut u8,
len: usize,
kind: MemoryMapKind,
}
#[cfg(unix)]
impl MemoryMap {
pub fn new(min_len: usize, options: &[MemoryMapOption]) -> Result<MemoryMap, MemoryMapError> {
use libc::off_t;
if min_len == 0 {
return Err(MemoryMapError::ZeroLength);
}
let mut addr: *const u8 = ptr::null();
let mut prot = 0;
let mut flags = libc::MAP_PRIVATE;
let mut fd = -1;
let mut offset = 0;
let mut custom_flags = false;
let len = round_up(min_len, page_size());
for &option in options {
match option {
MemoryMapOption::Readable => prot |= libc::PROT_READ,
MemoryMapOption::Writable => prot |= libc::PROT_WRITE,
MemoryMapOption::Executable => prot |= libc::PROT_EXEC,
MemoryMapOption::Addr(addr_) => {
flags |= libc::MAP_FIXED;
addr = addr_;
}
MemoryMapOption::Fd(fd_) => {
flags |= libc::MAP_FILE;
fd = fd_;
}
MemoryMapOption::Offset(offset_) => offset = offset_ as off_t,
MemoryMapOption::NonStandardFlags(f) => {
custom_flags = true;
flags = f;
}
}
}
if fd == -1 && !custom_flags {
flags |= libc::MAP_ANON;
}
let r: *mut libc::c_void = unsafe {
libc::mmap(addr as *mut c_void,
len as libc::size_t,
prot,
flags,
fd,
offset)
};
if r == libc::MAP_FAILED {
Err(match errno() {
libc::EACCES => MemoryMapError::FdNotAvail,
libc::EBADF => MemoryMapError::InvalidFd,
libc::EINVAL => MemoryMapError::Unaligned,
libc::ENODEV => MemoryMapError::NoMapSupport,
libc::ENOMEM => MemoryMapError::NoMem,
code => MemoryMapError::Unknown(code as isize),
})
} else {
let mut kind = MemoryMapKind::File(ptr::null());
if fd == -1 {
kind = MemoryMapKind::Virtual;
}
Ok(MemoryMap {
data: r as *mut u8,
len: len,
kind: kind,
})
}
}
pub fn granularity() -> usize {
page_size()
}
pub fn flush(&self, offset: usize, len: usize) -> Result<(), MemoryMapError> {
let flags = libc::MS_SYNC | libc::MS_INVALIDATE;
let alignment = (self.data as usize + offset) % page_size();
let aligned_offset = offset as isize - alignment as isize;
let aligned_len = len + alignment;
let result = unsafe {
libc::msync(self.data.offset(aligned_offset) as *mut c_void,
aligned_len as libc::size_t,
flags)
};
match result {
0 => Ok(()),
_ => Err(MemoryMapError::Unknown(result as isize)),
}
}
pub fn flush_async(&self, offset: usize, len: usize) -> Result<(), MemoryMapError> {
let flags = libc::MS_ASYNC | libc::MS_INVALIDATE;
let alignment = (self.data as usize + offset) % page_size();
let aligned_offset = offset - alignment;
let aligned_len = len + alignment;
let result = unsafe {
libc::msync(self.data.offset(aligned_offset as isize) as *mut c_void,
aligned_len as libc::size_t,
flags)
};
match result {
0 => Ok(()),
_ => Err(MemoryMapError::Unknown(result as isize)),
}
}
}
#[cfg(unix)]
impl Drop for MemoryMap {
fn drop(&mut self) {
if self.len == 0 {
return;
}
unsafe {
libc::munmap(self.data as *mut c_void, self.len as libc::size_t);
}
}
}
#[cfg(windows)]
impl MemoryMap {
pub fn new(min_len: usize, options: &[MemoryMapOption]) -> Result<MemoryMap, MemoryMapError> {
use libc::types::os::arch::extra::{LPVOID, DWORD, SIZE_T};
let mut lp_address: LPVOID = ptr::null_mut();
let (mut readable, mut writable, mut executable) = (false, false, false);
let mut handle = None;
let mut offset: usize = 0;
let mut len = round_up(min_len, page_size());
for &option in options {
match option {
MemoryMapOption::MapReadable => readable = true,
MemoryMapOption::MapWritable => writable = true,
MemoryMapOption::MapExecutable => executable = true,
MemoryMapOption::MapAddr(addr_) => lp_address = addr_ as LPVOID,
MemoryMapOption::MapFd(handle_) => handle = Some(handle_),
MemoryMapOption::MapOffset(offset_) => offset = offset_,
MemoryMapOption::MapNonStandardFlags(..) => {}
}
}
let fl_protect = match (executable, readable, writable) {
(false, false, false) if handle.is_none() => libc::PAGE_NOACCESS,
(false, true, false) => libc::PAGE_READONLY,
(false, true, true) => libc::PAGE_READWRITE,
(true, false, false) if handle.is_none() => libc::PAGE_EXECUTE,
(true, true, false) => libc::PAGE_EXECUTE_READ,
(true, true, true) => libc::PAGE_EXECUTE_READWRITE,
_ => return Err(MemoryMapError::ErrUnsupProt),
};
if let Some(handle) = handle {
let dw_desired_access = match (executable, readable, writable) {
(false, true, false) => libc::FILE_MAP_READ,
(false, true, true) => libc::FILE_MAP_WRITE,
(true, true, false) => libc::FILE_MAP_READ | libc::FILE_MAP_EXECUTE,
(true, true, true) => libc::FILE_MAP_WRITE | libc::FILE_MAP_EXECUTE,
_ => return Err(MemoryMapError::ErrUnsupProt),
};
unsafe {
let h_file = handle;
let mapping = libc::CreateFileMapping(h_file,
ptr::null_mut(),
fl_protect,
0,
0,
ptr::null());
if mapping == ptr::null_mut() {
return Err(MemoryMapError::ErrCreateFileMappingW(errno()));
}
if errno() as c_int == libc::ERROR_ALREADY_EXISTS {
return Err(MemoryMapError::ErrAlreadyExists);
}
let r = libc::MapViewOfFile(mapping,
dw_desired_access,
((len as u64) >> 32) as DWORD,
(offset & 0xffff_ffff) as DWORD,
0);
match r as usize {
0 => return Err(MemoryMapError::ErrMapViewOfFile(errno())),
_ => {
return Ok(MemoryMap {
data: r as *mut u8,
len: len,
kind: MapFile(mapping as *const u8),
})
}
}
}
} else {
if offset != 0 {
return Err(MemoryMapError::ErrUnsupOffset);
}
let r = unsafe {
libc::VirtualAlloc(lp_address,
len as SIZE_T,
libc::MEM_COMMIT | libc::MEM_RESERVE,
fl_protect)
};
match r as usize {
0 => return Err(MemoryMapError::ErrVirtualAlloc()),
_ => {
return Ok(MemoryMap {
data: r as *mut u8,
len: len,
kind: MemoryMapKind::MapVirtual,
})
}
}
}
}
pub fn granularity() -> usize {
use std::mem;
unsafe {
let mut info = mem::zeroed();
libc::GetSystemInfo(&mut info);
return info.dwAllocationGranularity as usize;
}
}
}
#[cfg(windows)]
impl Drop for MemoryMap {
fn drop(&mut self) {
use libc::types::os::arch::extra::{LPCVOID, HANDLE};
use libc::consts::os::extra::FALSE;
if self.len == 0 {
return;
}
unsafe {
match self.kind {
MemoryMapKind::MapVirtual => {
if libc::VirtualFree(self.data as *mut c_void, 0, libc::MEM_RELEASE) == 0 {
println!("VirtualFree failed: {}", errno());
}
}
MemoryMapKind::MapFile => {
if libc::UnmapViewOfFile(self.data as LPCVOID) == FALSE {
println!("UnmapViewOfFile failed: {}", errno());
}
if libc::CloseHandle(mapping as HANDLE) == FALSE {
println!("CloseHandle failed: {}", errno());
}
}
}
}
}
}
impl MemoryMap {
pub fn data(&self) -> *mut u8 {
self.data
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn kind(&self) -> MemoryMapKind {
self.kind
}
pub fn set_data(&mut self, data: *mut u8) {
self.data = data;
}
}