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 let r = unsafe {
245 libc::mmap(
246 addr as *mut c_void,
247 len as libc::size_t,
248 prot,
249 flags,
250 fd,
251 offset,
252 )
253 };
254 if r == libc::MAP_FAILED {
255 Err(match errno() {
256 libc::EACCES => ErrFdNotAvail,
257 libc::EBADF => ErrInvalidFd,
258 libc::EINVAL => ErrUnaligned,
259 libc::ENODEV => ErrNoMapSupport,
260 libc::ENOMEM => ErrNoMem,
261 code => ErrUnknown(code as isize),
262 })
263 } else {
264 Ok(MemoryMap {
265 data: r as *mut u8,
266 len: len,
267 kind: if fd == -1 {
268 MapVirtual
269 } else {
270 MapFile(ptr::null())
271 },
272 })
273 }
274 }
275
276 pub fn granularity() -> usize {
279 page_size()
280 }
281}
282
283#[cfg(unix)]
284impl Drop for MemoryMap {
285 fn drop(&mut self) {
287 if self.len == 0 {
288 return;
290 }
291
292 unsafe {
293 libc::munmap(self.data as *mut c_void, self.len as libc::size_t);
295 }
296 }
297}
298
299#[cfg(windows)]
300impl MemoryMap {
301 #[allow(non_snake_case)]
303 pub fn new(min_len: usize, options: &[MapOption]) -> Result<MemoryMap, MapError> {
304 use winapi::shared::minwindef::{DWORD, LPVOID};
305
306 let mut lpAddress: LPVOID = ptr::null_mut();
307 let mut readable = false;
308 let mut writable = false;
309 let mut executable = false;
310 let mut handle = None;
311 let mut offset: usize = 0;
312 let len = round_up(min_len, page_size());
313
314 for &o in options {
315 match o {
316 MapReadable => {
317 readable = true;
318 }
319 MapWritable => {
320 writable = true;
321 }
322 MapExecutable => {
323 executable = true;
324 }
325 MapAddr(addr_) => {
326 lpAddress = addr_ as LPVOID;
327 }
328 MapFd(handle_) => {
329 handle = Some(handle_);
330 }
331 MapOffset(offset_) => {
332 offset = offset_;
333 }
334 MapNonStandardFlags(..) => {}
335 }
336 }
337
338 let flProtect = match (executable, readable, writable) {
339 (false, false, false) if handle.is_none() => winapi::um::winnt::PAGE_NOACCESS,
340 (false, true, false) => winapi::um::winnt::PAGE_READONLY,
341 (false, true, true) => winapi::um::winnt::PAGE_READWRITE,
342 (true, false, false) if handle.is_none() => winapi::um::winnt::PAGE_EXECUTE,
343 (true, true, false) => winapi::um::winnt::PAGE_EXECUTE_READ,
344 (true, true, true) => winapi::um::winnt::PAGE_EXECUTE_READWRITE,
345 _ => return Err(ErrUnsupProt),
346 };
347
348 if let Some(handle) = handle {
349 let dwDesiredAccess = match (executable, readable, writable) {
350 (false, true, false) => winapi::um::memoryapi::FILE_MAP_READ,
351 (false, true, true) => winapi::um::memoryapi::FILE_MAP_WRITE,
352 (true, true, false) => {
353 winapi::um::memoryapi::FILE_MAP_READ | winapi::um::memoryapi::FILE_MAP_EXECUTE
354 }
355 (true, true, true) => {
356 winapi::um::memoryapi::FILE_MAP_WRITE | winapi::um::memoryapi::FILE_MAP_EXECUTE
357 }
358 _ => return Err(ErrUnsupProt), };
361 unsafe {
362 let hFile = handle;
363 let mapping = winapi::um::memoryapi::CreateFileMappingW(
364 hFile,
365 ptr::null_mut(),
366 flProtect,
367 0,
368 0,
369 ptr::null(),
370 );
371 if mapping == ptr::null_mut() {
372 return Err(ErrCreateFileMappingW(errno()));
373 }
374 if errno() == winapi::shared::winerror::ERROR_ALREADY_EXISTS as i32 {
375 return Err(ErrAlreadyExists);
376 }
377 let r = winapi::um::memoryapi::MapViewOfFile(
378 mapping,
379 dwDesiredAccess,
380 ((len as u64) >> 32) as DWORD,
381 (offset & 0xffff_ffff) as DWORD,
382 0,
383 );
384 match r as usize {
385 0 => Err(ErrMapViewOfFile(errno())),
386 _ => Ok(MemoryMap {
387 data: r as *mut u8,
388 len: len,
389 kind: MapFile(mapping as *const u8),
390 }),
391 }
392 }
393 } else {
394 if offset != 0 {
395 return Err(ErrUnsupOffset);
396 }
397
398 let r = unsafe {
399 winapi::um::memoryapi::VirtualAlloc(
400 lpAddress,
401 len,
402 winapi::um::winnt::MEM_COMMIT | winapi::um::winnt::MEM_RESERVE,
403 flProtect,
404 )
405 };
406 match r as usize {
407 0 => Err(ErrVirtualAlloc(errno())),
408 _ => Ok(MemoryMap {
409 data: r as *mut u8,
410 len: len,
411 kind: MapVirtual,
412 }),
413 }
414 }
415 }
416
417 pub fn granularity() -> usize {
420 unsafe {
421 let mut info = mem::zeroed();
422 winapi::um::sysinfoapi::GetSystemInfo(&mut info);
423
424 return info.dwAllocationGranularity as usize;
425 }
426 }
427}
428
429#[cfg(windows)]
430impl Drop for MemoryMap {
431 fn drop(&mut self) {
434 use winapi::shared::minwindef::LPCVOID;
435 use winapi::shared::ntdef::HANDLE;
436 if self.len == 0 {
437 return;
438 }
439
440 unsafe {
441 match self.kind {
442 MapVirtual => {
443 if winapi::um::memoryapi::VirtualFree(
444 self.data as *mut _,
445 0,
446 winapi::um::winnt::MEM_RELEASE,
447 ) == 0
448 {
449 println!("VirtualFree failed: {}", errno());
450 }
451 }
452 MapFile(mapping) => {
453 if winapi::um::memoryapi::UnmapViewOfFile(self.data as LPCVOID) == 0 {
454 println!("UnmapViewOfFile failed: {}", errno());
455 }
456 if winapi::um::handleapi::CloseHandle(mapping as HANDLE) == 0 {
457 println!("CloseHandle failed: {}", errno());
458 }
459 }
460 }
461 }
462 }
463}
464
465impl MemoryMap {
466 #[inline(always)]
468 pub fn data(&self) -> *mut u8 {
469 self.data
470 }
471
472 #[inline(always)]
474 pub fn len(&self) -> usize {
475 self.len
476 }
477
478 pub fn kind(&self) -> MemoryMapKind {
480 self.kind
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 #[cfg(unix)]
487 extern crate libc;
488 extern crate tempdir;
489 #[cfg(windows)]
490 extern crate winapi;
491
492 use super::{MapOption, MemoryMap};
493
494 #[test]
495 fn memory_map_rw() {
496 let chunk = match MemoryMap::new(16, &[MapOption::MapReadable, MapOption::MapWritable]) {
497 Ok(chunk) => chunk,
498 Err(msg) => panic!("{:?}", msg),
499 };
500 assert!(chunk.len >= 16);
501
502 unsafe {
503 *chunk.data = 0xBE;
504 assert!(*chunk.data == 0xBE);
505 }
506 }
507
508 #[test]
509 fn memory_map_file() {
510 use std::fs;
511 use std::io::{Seek, SeekFrom, Write};
512
513 #[cfg(unix)]
514 fn get_fd(file: &fs::File) -> libc::c_int {
515 use std::os::unix::io::AsRawFd;
516 file.as_raw_fd()
517 }
518
519 #[cfg(windows)]
520 fn get_fd(file: &fs::File) -> winapi::shared::ntdef::HANDLE {
521 use std::os::windows::io::AsRawHandle;
522 file.as_raw_handle() as winapi::shared::ntdef::HANDLE
523 }
524
525 let tmpdir = tempdir::TempDir::new("").unwrap();
526 let mut path = tmpdir.path().to_path_buf();
527 path.push("mmap_file.tmp");
528 let size = MemoryMap::granularity() * 2;
529
530 let mut file = fs::OpenOptions::new()
531 .create(true)
532 .read(true)
533 .write(true)
534 .open(&path)
535 .unwrap();
536 file.seek(SeekFrom::Start(size as u64)).unwrap();
537 file.write(b"\0").unwrap();
538 let fd = get_fd(&file);
539
540 let chunk = MemoryMap::new(
541 size / 2,
542 &[
543 MapOption::MapReadable,
544 MapOption::MapWritable,
545 MapOption::MapFd(fd),
546 MapOption::MapOffset(size / 2),
547 ],
548 )
549 .unwrap();
550 assert!(chunk.len > 0);
551
552 unsafe {
553 *chunk.data = 0xbe;
554 assert!(*chunk.data == 0xbe);
555 }
556 drop(chunk);
557
558 fs::remove_file(&path).unwrap();
559 }
560}