1extern crate libc;
9
10use std::error::Error;
11use std::io;
12use std::fmt;
13use libc::{c_void, c_int};
14use std::ops::Drop;
15use std::ptr;
16use self::MemoryMapKind::*;
17use self::MapOption::*;
18use self::MapError::*;
19
20#[cfg(windows)]
21use std::mem;
22
23fn errno() -> i32 {
24 io::Error::last_os_error().raw_os_error().unwrap_or(-1)
25}
26
27#[cfg(unix)]
28fn page_size() -> usize {
29 unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
30}
31
32#[cfg(windows)]
33fn page_size() -> usize {
34 unsafe {
35 let mut info = mem::zeroed();
36 libc::GetSystemInfo(&mut info);
37 return info.dwPageSize as usize;
38 }
39}
40
41pub struct MemoryMap {
50 data: *mut u8,
51 len: usize,
52 kind: MemoryMapKind,
53}
54
55#[allow(raw_pointer_derive)]
57#[derive(Copy, Clone)]
58pub enum MemoryMapKind {
59 MapFile(*const u8),
62 MapVirtual
66}
67
68#[allow(raw_pointer_derive)]
70#[derive(Copy, Clone)]
71pub enum MapOption {
72 MapReadable,
74 MapWritable,
76 MapExecutable,
78 MapAddr(*const u8),
81 #[cfg(windows)]
83 MapFd(libc::HANDLE),
84 #[cfg(not(windows))]
86 MapFd(c_int),
87 MapOffset(usize),
90 MapNonStandardFlags(c_int),
95}
96
97#[derive(Copy, Clone, Debug)]
99pub enum MapError {
100 ErrFdNotAvail,
105 ErrInvalidFd,
107 ErrUnaligned,
110 ErrNoMapSupport,
112 ErrNoMem,
116 ErrZeroLength,
120 ErrUnknown(isize),
122 ErrUnsupProt,
127 ErrUnsupOffset,
130 ErrAlreadyExists,
132 ErrVirtualAlloc(i32),
135 ErrCreateFileMappingW(i32),
138 ErrMapViewOfFile(i32)
141}
142
143impl fmt::Display for MapError {
144 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
145 let str = match *self {
146 ErrFdNotAvail => "fd not available for reading or writing",
147 ErrInvalidFd => "Invalid fd",
148 ErrUnaligned => {
149 "Unaligned address, invalid flags, negative length or \
150 unaligned offset"
151 }
152 ErrNoMapSupport=> "File doesn't support mapping",
153 ErrNoMem => "Invalid address, or not enough available memory",
154 ErrUnsupProt => "Protection mode unsupported",
155 ErrUnsupOffset => "Offset in virtual memory mode is unsupported",
156 ErrAlreadyExists => "File mapping for specified file already exists",
157 ErrZeroLength => "Zero-length mapping not allowed",
158 ErrUnknown(code) => {
159 return write!(out, "Unknown error = {}", code)
160 },
161 ErrVirtualAlloc(code) => {
162 return write!(out, "VirtualAlloc failure = {}", code)
163 },
164 ErrCreateFileMappingW(code) => {
165 return write!(out, "CreateFileMappingW failure = {}", code)
166 },
167 ErrMapViewOfFile(code) => {
168 return write!(out, "MapViewOfFile failure = {}", code)
169 }
170 };
171 write!(out, "{}", str)
172 }
173}
174
175impl Error for MapError {
176 fn description(&self) -> &str { "memory map error" }
177}
178
179fn round_up(from: usize, to: usize) -> usize {
181 let r = if from % to == 0 {
182 from
183 } else {
184 from + to - (from % to)
185 };
186 if r == 0 {
187 to
188 } else {
189 r
190 }
191}
192
193#[cfg(unix)]
194impl MemoryMap {
195 pub fn new(min_len: usize, options: &[MapOption]) -> Result<MemoryMap, MapError> {
199 use libc::off_t;
200
201 if min_len == 0 {
202 return Err(ErrZeroLength)
203 }
204 let mut addr: *const u8 = ptr::null();
205 let mut prot = 0;
206 let mut flags = libc::MAP_PRIVATE;
207 let mut fd = -1;
208 let mut offset = 0;
209 let mut custom_flags = false;
210 let len = round_up(min_len, page_size());
211
212 for &o in options {
213 match o {
214 MapReadable => { prot |= libc::PROT_READ; },
215 MapWritable => { prot |= libc::PROT_WRITE; },
216 MapExecutable => { prot |= libc::PROT_EXEC; },
217 MapAddr(addr_) => {
218 flags |= libc::MAP_FIXED;
219 addr = addr_;
220 },
221 MapFd(fd_) => {
222 flags |= libc::MAP_FILE;
223 fd = fd_;
224 },
225 MapOffset(offset_) => { offset = offset_ as off_t; },
226 MapNonStandardFlags(f) => { custom_flags = true; flags = f },
227 }
228 }
229 if fd == -1 && !custom_flags { flags |= libc::MAP_ANON; }
230
231 let r = unsafe {
232 libc::mmap(addr as *mut c_void, len as libc::size_t, prot, flags,
233 fd, offset)
234 };
235 if r == libc::MAP_FAILED {
236 Err(match errno() {
237 libc::EACCES => ErrFdNotAvail,
238 libc::EBADF => ErrInvalidFd,
239 libc::EINVAL => ErrUnaligned,
240 libc::ENODEV => ErrNoMapSupport,
241 libc::ENOMEM => ErrNoMem,
242 code => ErrUnknown(code as isize)
243 })
244 } else {
245 Ok(MemoryMap {
246 data: r as *mut u8,
247 len: len,
248 kind: if fd == -1 {
249 MapVirtual
250 } else {
251 MapFile(ptr::null())
252 }
253 })
254 }
255 }
256
257 pub fn granularity() -> usize {
260 page_size()
261 }
262}
263
264#[cfg(unix)]
265impl Drop for MemoryMap {
266 fn drop(&mut self) {
268 if self.len == 0 { return; }
269
270 unsafe {
271 libc::munmap(self.data as *mut c_void, self.len as libc::size_t);
273 }
274 }
275}
276
277#[cfg(windows)]
278impl MemoryMap {
279 pub fn new(min_len: usize, options: &[MapOption]) -> Result<MemoryMap, MapError> {
281 use libc::types::os::arch::extra::{LPVOID, DWORD, SIZE_T, HANDLE};
282
283 let mut lpAddress: LPVOID = ptr::null_mut();
284 let mut readable = false;
285 let mut writable = false;
286 let mut executable = false;
287 let mut handle: HANDLE = libc::INVALID_HANDLE_VALUE;
288 let mut offset: usize = 0;
289 let len = round_up(min_len, page_size());
290
291 for &o in options {
292 match o {
293 MapReadable => { readable = true; },
294 MapWritable => { writable = true; },
295 MapExecutable => { executable = true; }
296 MapAddr(addr_) => { lpAddress = addr_ as LPVOID; },
297 MapFd(handle_) => { handle = handle_; },
298 MapOffset(offset_) => { offset = offset_; },
299 MapNonStandardFlags(..) => {}
300 }
301 }
302
303 let flProtect = match (executable, readable, writable) {
304 (false, false, false) if handle == libc::INVALID_HANDLE_VALUE => libc::PAGE_NOACCESS,
305 (false, true, false) => libc::PAGE_READONLY,
306 (false, true, true) => libc::PAGE_READWRITE,
307 (true, false, false) if handle == libc::INVALID_HANDLE_VALUE => libc::PAGE_EXECUTE,
308 (true, true, false) => libc::PAGE_EXECUTE_READ,
309 (true, true, true) => libc::PAGE_EXECUTE_READWRITE,
310 _ => return Err(ErrUnsupProt)
311 };
312
313 if handle == libc::INVALID_HANDLE_VALUE {
314 if offset != 0 {
315 return Err(ErrUnsupOffset);
316 }
317 let r = unsafe {
318 libc::VirtualAlloc(lpAddress,
319 len as SIZE_T,
320 libc::MEM_COMMIT | libc::MEM_RESERVE,
321 flProtect)
322 };
323 match r as usize {
324 0 => Err(ErrVirtualAlloc()),
325 _ => Ok(MemoryMap {
326 data: r as *mut u8,
327 len: len,
328 kind: MapVirtual
329 })
330 }
331 } else {
332 let dwDesiredAccess = match (executable, readable, writable) {
333 (false, true, false) => libc::FILE_MAP_READ,
334 (false, true, true) => libc::FILE_MAP_WRITE,
335 (true, true, false) => libc::FILE_MAP_READ | libc::FILE_MAP_EXECUTE,
336 (true, true, true) => libc::FILE_MAP_WRITE | libc::FILE_MAP_EXECUTE,
337 _ => return Err(ErrUnsupProt) };
340 unsafe {
341 let hFile = handle;
342 let mapping = libc::CreateFileMappingW(hFile,
343 ptr::null_mut(),
344 flProtect,
345 0,
346 0,
347 ptr::null());
348 if mapping == ptr::null_mut() {
349 return Err(ErrCreateFileMappingW(errno()));
350 }
351 if errno() as c_int == libc::ERROR_ALREADY_EXISTS {
352 return Err(ErrAlreadyExists);
353 }
354 let r = libc::MapViewOfFile(mapping,
355 dwDesiredAccess,
356 ((len as u64) >> 32) as DWORD,
357 (offset & 0xffff_ffff) as DWORD,
358 0);
359 match r as usize {
360 0 => Err(ErrMapViewOfFile(errno())),
361 _ => Ok(MemoryMap {
362 data: r as *mut u8,
363 len: len,
364 kind: MapFile(mapping as *const u8)
365 })
366 }
367 }
368 }
369 }
370
371 pub fn granularity() -> usize {
374 use mem;
375 unsafe {
376 let mut info = mem::zeroed();
377 libc::GetSystemInfo(&mut info);
378
379 return info.dwAllocationGranularity as usize;
380 }
381 }
382}
383
384#[cfg(windows)]
385impl Drop for MemoryMap {
386 fn drop(&mut self) {
389 use libc::types::os::arch::extra::{LPCVOID, HANDLE};
390 use libc::consts::os::extra::FALSE;
391 if self.len == 0 { return }
392
393 unsafe {
394 match self.kind {
395 MapVirtual => {
396 if libc::VirtualFree(self.data as *mut c_void, 0,
397 libc::MEM_RELEASE) == 0 {
398 println!("VirtualFree failed: {}", errno());
399 }
400 },
401 MapFile(mapping) => {
402 if libc::UnmapViewOfFile(self.data as LPCVOID) == FALSE {
403 println!("UnmapViewOfFile failed: {}", errno());
404 }
405 if libc::CloseHandle(mapping as HANDLE) == FALSE {
406 println!("CloseHandle failed: {}", errno());
407 }
408 }
409 }
410 }
411 }
412}
413
414impl MemoryMap {
415 #[inline(always)]
417 pub fn data(&self) -> *mut u8 { self.data }
418
419 #[inline(always)]
421 pub fn len(&self) -> usize { self.len }
422
423 pub fn kind(&self) -> MemoryMapKind { self.kind }
425}
426
427#[cfg(test)]
428mod tests {
429 extern crate libc;
430 extern crate tempdir;
431
432 use super::{MemoryMap, MapOption};
433
434 #[test]
435 fn memory_map_rw() {
436 let chunk = match MemoryMap::new(16, &[
437 MapOption::MapReadable,
438 MapOption::MapWritable
439 ]) {
440 Ok(chunk) => chunk,
441 Err(msg) => panic!("{:?}", msg)
442 };
443 assert!(chunk.len >= 16);
444
445 unsafe {
446 *chunk.data = 0xBE;
447 assert!(*chunk.data == 0xBE);
448 }
449 }
450
451 #[test]
452 fn memory_map_file() {
453 use std::fs;
454 use std::io::{Seek, SeekFrom, Write};
455
456 #[cfg(unix)]
457 use std::os::unix::io::AsRawFd;
458
459 #[cfg(unix)]
460 fn get_fd(file: &fs::File) -> libc::c_int {
461 file.as_raw_fd()
462 }
463
464 #[cfg(windows)]
465 fn get_fd(file: &fs::File) -> libc::HANDLE {
466 file.as_raw_handle()
467 }
468
469 let tmpdir = tempdir::TempDir::new("").unwrap();
470 let mut path = tmpdir.path().to_path_buf();
471 path.push("mmap_file.tmp");
472 let size = MemoryMap::granularity() * 2;
473
474 let mut file = fs::OpenOptions::new()
475 .create(true)
476 .read(true)
477 .write(true)
478 .open(&path)
479 .unwrap();
480 file.seek(SeekFrom::Start(size as u64)).unwrap();
481 file.write(b"\0").unwrap();
482 let fd = get_fd(&file);
483
484 let chunk = MemoryMap::new(size / 2, &[
485 MapOption::MapReadable,
486 MapOption::MapWritable,
487 MapOption::MapFd(fd),
488 MapOption::MapOffset(size / 2)
489 ]).unwrap();
490 assert!(chunk.len > 0);
491
492 unsafe {
493 *chunk.data = 0xbe;
494 assert!(*chunk.data == 0xbe);
495 }
496 drop(chunk);
497
498 fs::remove_file(&path).unwrap();
499 }
500}