1#[cfg(unix)]
9extern crate libc;
10#[cfg(windows)]
11extern crate winapi;
12
13use std::error::Error;
14use std::fmt;
15use std::io;
16use std::ops::Drop;
17use std::ptr;
18
19#[cfg(unix)]
20use libc::{c_int, c_void};
21
22use self::MapError::*;
23use self::MapOption::*;
24use self::MemoryMapKind::*;
25
26#[cfg(windows)]
27use std::mem;
28
29fn errno() -> i32 {
30 io::Error::last_os_error().raw_os_error().unwrap_or(-1)
31}
32
33#[cfg(unix)]
34fn page_size() -> usize {
35 unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
36}
37
38#[cfg(windows)]
39fn page_size() -> usize {
40 unsafe {
41 let mut info = mem::zeroed();
42 winapi::um::sysinfoapi::GetSystemInfo(&mut info);
43 return info.dwPageSize as usize;
44 }
45}
46
47pub struct MemoryMap {
56 data: *mut u8,
57 len: usize,
58 kind: MemoryMapKind,
59}
60
61#[derive(Copy, Clone)]
63pub enum MemoryMapKind {
64 MapFile(*const u8),
67 MapVirtual,
71}
72
73#[derive(Copy, Clone)]
75pub enum MapOption {
76 MapReadable,
78 MapWritable,
80 MapExecutable,
82 MapAddr(*const u8),
85 #[cfg(windows)]
87 MapFd(winapi::shared::ntdef::HANDLE),
88 #[cfg(not(windows))]
90 MapFd(c_int),
91 MapOffset(usize),
94 MapNonStandardFlags(i32),
99}
100
101#[derive(Copy, Clone, Debug)]
103pub enum MapError {
104 ErrFdNotAvail,
109 ErrInvalidFd,
111 ErrUnaligned,
114 ErrNoMapSupport,
116 ErrNoMem,
120 ErrZeroLength,
124 ErrUnknown(isize),
126 ErrUnsupProt,
131 ErrUnsupOffset,
134 ErrAlreadyExists,
136 ErrVirtualAlloc(i32),
139 ErrCreateFileMappingW(i32),
142 ErrMapViewOfFile(i32),
145}
146
147impl fmt::Display for MapError {
148 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
149 let str = match *self {
150 ErrFdNotAvail => "fd not available for reading or writing",
151 ErrInvalidFd => "Invalid fd",
152 ErrUnaligned => {
153 "Unaligned address, invalid flags, negative length or \
154 unaligned offset"
155 }
156 ErrNoMapSupport => "File doesn't support mapping",
157 ErrNoMem => "Invalid address, or not enough available memory",
158 ErrUnsupProt => "Protection mode unsupported",
159 ErrUnsupOffset => "Offset in virtual memory mode is unsupported",
160 ErrAlreadyExists => "File mapping for specified file already exists",
161 ErrZeroLength => "Zero-length mapping not allowed",
162 ErrUnknown(code) => return write!(out, "Unknown error = {}", code),
163 ErrVirtualAlloc(code) => return write!(out, "VirtualAlloc failure = {}", code),
164 ErrCreateFileMappingW(code) => {
165 return write!(out, "CreateFileMappingW failure = {}", code)
166 }
167 ErrMapViewOfFile(code) => return write!(out, "MapViewOfFile failure = {}", code),
168 };
169 write!(out, "{}", str)
170 }
171}
172
173impl Error for MapError {
174 fn description(&self) -> &str {
175 "memory map error"
176 }
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 => {
215 prot |= libc::PROT_READ;
216 }
217 MapWritable => {
218 prot |= libc::PROT_WRITE;
219 }
220 MapExecutable => {
221 prot |= libc::PROT_EXEC;
222 }
223 MapAddr(addr_) => {
224 flags |= libc::MAP_FIXED;
225 addr = addr_;
226 }
227 MapFd(fd_) => {
228 flags |= libc::MAP_FILE;
229 fd = fd_;
230 }
231 MapOffset(offset_) => {
232 offset = offset_ as off_t;
233 }
234 MapNonStandardFlags(f) => {
235 custom_flags = true;
236 flags = f
237 }
238 }
239 }
240 if fd == -1 && !custom_flags {
241 flags |= libc::MAP_ANON;
242 }
243
244 if cfg!(target_os = "netbsd") {
248 let max_protection = (libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC) << 3;
249 prot = prot | max_protection;
250 }
251
252 let r = unsafe {
253 libc::mmap(
254 addr as *mut c_void,
255 len as libc::size_t,
256 prot,
257 flags,
258 fd,
259 offset,
260 )
261 };
262 if r == libc::MAP_FAILED {
263 Err(match errno() {
264 libc::EACCES => ErrFdNotAvail,
265 libc::EBADF => ErrInvalidFd,
266 libc::EINVAL => ErrUnaligned,
267 libc::ENODEV => ErrNoMapSupport,
268 libc::ENOMEM => ErrNoMem,
269 code => ErrUnknown(code as isize),
270 })
271 } else {
272 Ok(MemoryMap {
273 data: r as *mut u8,
274 len: len,
275 kind: if fd == -1 {
276 MapVirtual
277 } else {
278 MapFile(ptr::null())
279 },
280 })
281 }
282 }
283
284 pub fn granularity() -> usize {
287 page_size()
288 }
289}
290
291#[cfg(unix)]
292impl Drop for MemoryMap {
293 fn drop(&mut self) {
295 if self.len == 0 {
296 return;
298 }
299
300 unsafe {
301 libc::munmap(self.data as *mut c_void, self.len as libc::size_t);
303 }
304 }
305}
306
307#[cfg(windows)]
308impl MemoryMap {
309 #[allow(non_snake_case)]
311 pub fn new(min_len: usize, options: &[MapOption]) -> Result<MemoryMap, MapError> {
312 use winapi::shared::minwindef::{DWORD, LPVOID};
313
314 let mut lpAddress: LPVOID = ptr::null_mut();
315 let mut readable = false;
316 let mut writable = false;
317 let mut executable = false;
318 let mut handle = None;
319 let mut offset: usize = 0;
320 let len = round_up(min_len, page_size());
321
322 for &o in options {
323 match o {
324 MapReadable => {
325 readable = true;
326 }
327 MapWritable => {
328 writable = true;
329 }
330 MapExecutable => {
331 executable = true;
332 }
333 MapAddr(addr_) => {
334 lpAddress = addr_ as LPVOID;
335 }
336 MapFd(handle_) => {
337 handle = Some(handle_);
338 }
339 MapOffset(offset_) => {
340 offset = offset_;
341 }
342 MapNonStandardFlags(..) => {}
343 }
344 }
345
346 let flProtect = match (executable, readable, writable) {
347 (false, false, false) if handle.is_none() => winapi::um::winnt::PAGE_NOACCESS,
348 (false, true, false) => winapi::um::winnt::PAGE_READONLY,
349 (false, true, true) => winapi::um::winnt::PAGE_READWRITE,
350 (true, false, false) if handle.is_none() => winapi::um::winnt::PAGE_EXECUTE,
351 (true, true, false) => winapi::um::winnt::PAGE_EXECUTE_READ,
352 (true, true, true) => winapi::um::winnt::PAGE_EXECUTE_READWRITE,
353 _ => return Err(ErrUnsupProt),
354 };
355
356 if let Some(handle) = handle {
357 let dwDesiredAccess = match (executable, readable, writable) {
358 (false, true, false) => winapi::um::memoryapi::FILE_MAP_READ,
359 (false, true, true) => winapi::um::memoryapi::FILE_MAP_WRITE,
360 (true, true, false) => {
361 winapi::um::memoryapi::FILE_MAP_READ | winapi::um::memoryapi::FILE_MAP_EXECUTE
362 }
363 (true, true, true) => {
364 winapi::um::memoryapi::FILE_MAP_WRITE | winapi::um::memoryapi::FILE_MAP_EXECUTE
365 }
366 _ => return Err(ErrUnsupProt), };
369 unsafe {
370 let hFile = handle;
371 let mapping = winapi::um::memoryapi::CreateFileMappingW(
372 hFile,
373 ptr::null_mut(),
374 flProtect,
375 0,
376 0,
377 ptr::null(),
378 );
379 if mapping == ptr::null_mut() {
380 return Err(ErrCreateFileMappingW(errno()));
381 }
382 if errno() == winapi::shared::winerror::ERROR_ALREADY_EXISTS as i32 {
383 return Err(ErrAlreadyExists);
384 }
385 let r = winapi::um::memoryapi::MapViewOfFile(
386 mapping,
387 dwDesiredAccess,
388 ((len as u64) >> 32) as DWORD,
389 (offset & 0xffff_ffff) as DWORD,
390 0,
391 );
392 match r as usize {
393 0 => Err(ErrMapViewOfFile(errno())),
394 _ => Ok(MemoryMap {
395 data: r as *mut u8,
396 len: len,
397 kind: MapFile(mapping as *const u8),
398 }),
399 }
400 }
401 } else {
402 if offset != 0 {
403 return Err(ErrUnsupOffset);
404 }
405
406 let r = unsafe {
407 winapi::um::memoryapi::VirtualAlloc(
408 lpAddress,
409 len,
410 winapi::um::winnt::MEM_COMMIT | winapi::um::winnt::MEM_RESERVE,
411 flProtect,
412 )
413 };
414 match r as usize {
415 0 => Err(ErrVirtualAlloc(errno())),
416 _ => Ok(MemoryMap {
417 data: r as *mut u8,
418 len: len,
419 kind: MapVirtual,
420 }),
421 }
422 }
423 }
424
425 pub fn granularity() -> usize {
428 unsafe {
429 let mut info = mem::zeroed();
430 winapi::um::sysinfoapi::GetSystemInfo(&mut info);
431
432 return info.dwAllocationGranularity as usize;
433 }
434 }
435}
436
437#[cfg(windows)]
438impl Drop for MemoryMap {
439 fn drop(&mut self) {
442 use winapi::shared::minwindef::LPCVOID;
443 use winapi::shared::ntdef::HANDLE;
444 if self.len == 0 {
445 return;
446 }
447
448 unsafe {
449 match self.kind {
450 MapVirtual => {
451 if winapi::um::memoryapi::VirtualFree(
452 self.data as *mut _,
453 0,
454 winapi::um::winnt::MEM_RELEASE,
455 ) == 0
456 {
457 println!("VirtualFree failed: {}", errno());
458 }
459 }
460 MapFile(mapping) => {
461 if winapi::um::memoryapi::UnmapViewOfFile(self.data as LPCVOID) == 0 {
462 println!("UnmapViewOfFile failed: {}", errno());
463 }
464 if winapi::um::handleapi::CloseHandle(mapping as HANDLE) == 0 {
465 println!("CloseHandle failed: {}", errno());
466 }
467 }
468 }
469 }
470 }
471}
472
473impl MemoryMap {
474 #[inline(always)]
476 pub fn data(&self) -> *mut u8 {
477 self.data
478 }
479
480 #[inline(always)]
482 pub fn len(&self) -> usize {
483 self.len
484 }
485
486 pub fn kind(&self) -> MemoryMapKind {
488 self.kind
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 #[cfg(unix)]
495 extern crate libc;
496 extern crate tempdir;
497 #[cfg(windows)]
498 extern crate winapi;
499
500 use super::{MapOption, MemoryMap};
501
502 #[test]
503 fn memory_map_rw() {
504 let chunk = match MemoryMap::new(16, &[MapOption::MapReadable, MapOption::MapWritable]) {
505 Ok(chunk) => chunk,
506 Err(msg) => panic!("{:?}", msg),
507 };
508 assert!(chunk.len >= 16);
509
510 unsafe {
511 *chunk.data = 0xBE;
512 assert!(*chunk.data == 0xBE);
513 }
514 }
515
516 #[test]
517 fn memory_map_file() {
518 use std::fs;
519 use std::io::{Seek, SeekFrom, Write};
520
521 #[cfg(unix)]
522 fn get_fd(file: &fs::File) -> libc::c_int {
523 use std::os::unix::io::AsRawFd;
524 file.as_raw_fd()
525 }
526
527 #[cfg(windows)]
528 fn get_fd(file: &fs::File) -> winapi::shared::ntdef::HANDLE {
529 use std::os::windows::io::AsRawHandle;
530 file.as_raw_handle() as winapi::shared::ntdef::HANDLE
531 }
532
533 let tmpdir = tempdir::TempDir::new("").unwrap();
534 let mut path = tmpdir.path().to_path_buf();
535 path.push("mmap_file.tmp");
536 let size = MemoryMap::granularity() * 2;
537
538 let mut file = fs::OpenOptions::new()
539 .create(true)
540 .read(true)
541 .write(true)
542 .open(&path)
543 .unwrap();
544 file.seek(SeekFrom::Start(size as u64)).unwrap();
545 file.write(b"\0").unwrap();
546 let fd = get_fd(&file);
547
548 let chunk = MemoryMap::new(
549 size / 2,
550 &[
551 MapOption::MapReadable,
552 MapOption::MapWritable,
553 MapOption::MapFd(fd),
554 MapOption::MapOffset(size / 2),
555 ],
556 )
557 .unwrap();
558 assert!(chunk.len > 0);
559
560 unsafe {
561 *chunk.data = 0xbe;
562 assert!(*chunk.data == 0xbe);
563 }
564 drop(chunk);
565
566 fs::remove_file(&path).unwrap();
567 }
568}