1#[cfg(unix)]
9extern crate libc;
10
11#[cfg(windows)]
12use windows::Win32::Foundation::*;
13
14use std::error::Error;
15use std::ffi::c_void;
16use std::fmt;
17use std::io;
18use std::ops::Drop;
19use std::ptr;
20
21#[cfg(unix)]
22use libc::c_int;
23
24use self::MapError::*;
25use self::MapOption::*;
26use self::MemoryMapKind::*;
27
28#[cfg(windows)]
29use std::mem;
30
31fn errno() -> i32 {
32 io::Error::last_os_error().raw_os_error().unwrap_or(-1)
33}
34
35#[cfg(unix)]
36fn page_size() -> usize {
37 unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
38}
39
40#[cfg(windows)]
41fn page_size() -> usize {
42 use windows::Win32::System::SystemInformation::GetSystemInfo;
43
44 unsafe {
45 let mut info = mem::zeroed();
46 GetSystemInfo(&mut info);
47 return info.dwPageSize as usize;
48 }
49}
50
51pub struct MemoryMap {
60 data: *mut u8,
61 len: usize,
62 kind: MemoryMapKind,
63}
64
65#[derive(Copy, Clone)]
67pub enum MemoryMapKind {
68 MapFile(*const u8),
71 MapVirtual,
75}
76
77#[derive(Copy, Clone)]
79pub enum MapOption {
80 MapReadable,
82 MapWritable,
84 MapExecutable,
86 MapAddr(*const u8),
89 #[cfg(windows)]
91 MapFd(HANDLE),
92 #[cfg(not(windows))]
94 MapFd(c_int),
95 MapOffset(usize),
98 MapNonStandardFlags(i32),
103}
104
105#[derive(Copy, Clone, Debug)]
107pub enum MapError {
108 ErrFdNotAvail,
113 ErrInvalidFd,
115 ErrUnaligned,
118 ErrNoMapSupport,
120 ErrNoMem,
124 ErrZeroLength,
128 ErrUnknown(isize),
130 ErrUnsupProt,
135 ErrUnsupOffset,
138 ErrAlreadyExists,
140 ErrVirtualAlloc(i32),
143 ErrCreateFileMappingW(i32),
146 ErrMapViewOfFile(i32),
149}
150
151impl fmt::Display for MapError {
152 fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
153 let str = match *self {
154 ErrFdNotAvail => "fd not available for reading or writing",
155 ErrInvalidFd => "Invalid fd",
156 ErrUnaligned => {
157 "Unaligned address, invalid flags, negative length or \
158 unaligned offset"
159 }
160 ErrNoMapSupport => "File doesn't support mapping",
161 ErrNoMem => "Invalid address, or not enough available memory",
162 ErrUnsupProt => "Protection mode unsupported",
163 ErrUnsupOffset => "Offset in virtual memory mode is unsupported",
164 ErrAlreadyExists => "File mapping for specified file already exists",
165 ErrZeroLength => "Zero-length mapping not allowed",
166 ErrUnknown(code) => return write!(out, "Unknown error = {}", code),
167 ErrVirtualAlloc(code) => return write!(out, "VirtualAlloc failure = {}", code),
168 ErrCreateFileMappingW(code) => {
169 return write!(out, "CreateFileMappingW failure = {}", code);
170 }
171 ErrMapViewOfFile(code) => return write!(out, "MapViewOfFile failure = {}", code),
172 };
173 write!(out, "{}", str)
174 }
175}
176
177impl Error for MapError {
178 fn description(&self) -> &str {
179 "memory map error"
180 }
181}
182
183fn round_up(from: usize, to: usize) -> usize {
185 let r = if from % to == 0 {
186 from
187 } else {
188 from + to - (from % to)
189 };
190 if r == 0 { to } else { r }
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 windows::Win32::System::Memory::*;
305
306 let mut lpAddress: *const c_void = 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 *const c_void;
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() => PAGE_NOACCESS,
340 (false, true, false) => PAGE_READONLY,
341 (false, true, true) => PAGE_READWRITE,
342 (true, false, false) if handle.is_none() => PAGE_EXECUTE,
343 (true, true, false) => PAGE_EXECUTE_READ,
344 (true, true, true) => 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) => FILE_MAP_READ,
351 (false, true, true) => FILE_MAP_WRITE,
352 (true, true, false) => FILE_MAP_READ | FILE_MAP_EXECUTE,
353 (true, true, true) => FILE_MAP_WRITE | FILE_MAP_EXECUTE,
354 _ => return Err(ErrUnsupProt), };
357 unsafe {
358 let hFile = handle;
359 let mapping = CreateFileMappingW(hFile, None, flProtect, 0, 0, None);
360 if mapping.is_err() {
361 return Err(ErrCreateFileMappingW(errno()));
362 }
363 let mapping = mapping.unwrap();
364 if errno() == ERROR_ALREADY_EXISTS.0 as i32 {
365 return Err(ErrAlreadyExists);
366 }
367 let r = MapViewOfFile(
368 mapping,
369 dwDesiredAccess,
370 ((len as u64) >> 32) as u32,
371 (offset & 0xffff_ffff) as u32,
372 0,
373 );
374 match r.Value as usize {
375 0 => Err(ErrMapViewOfFile(errno())),
376 _ => Ok(MemoryMap {
377 data: r.Value as *mut u8,
378 len: len,
379 kind: MapFile(mapping.0 as *const u8),
380 }),
381 }
382 }
383 } else {
384 if offset != 0 {
385 return Err(ErrUnsupOffset);
386 }
387
388 let r =
389 unsafe { VirtualAlloc(Some(lpAddress), len, MEM_COMMIT | MEM_RESERVE, flProtect) };
390 match r as usize {
391 0 => Err(ErrVirtualAlloc(errno())),
392 _ => Ok(MemoryMap {
393 data: r as *mut u8,
394 len: len,
395 kind: MapVirtual,
396 }),
397 }
398 }
399 }
400
401 pub fn granularity() -> usize {
404 use windows::Win32::System::SystemInformation::GetSystemInfo;
405
406 unsafe {
407 let mut info = mem::zeroed();
408 GetSystemInfo(&mut info);
409
410 return info.dwAllocationGranularity as usize;
411 }
412 }
413}
414
415#[cfg(windows)]
416impl Drop for MemoryMap {
417 fn drop(&mut self) {
420 use windows::Win32::System::Memory::*;
421
422 if self.len == 0 {
423 return;
424 }
425
426 unsafe {
427 match self.kind {
428 MapVirtual => {
429 if VirtualFree(self.data as *mut _, 0, MEM_RELEASE).is_err() {
430 println!("VirtualFree failed: {}", errno());
431 }
432 }
433 MapFile(mapping) => {
434 if UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
435 Value: self.data as *mut c_void,
436 })
437 .is_err()
438 {
439 println!("UnmapViewOfFile failed: {}", errno());
440 }
441 if CloseHandle(HANDLE(mapping as *mut c_void)).is_err() {
442 println!("CloseHandle failed: {}", errno());
443 }
444 }
445 }
446 }
447 }
448}
449
450impl MemoryMap {
451 #[inline(always)]
453 pub fn data(&self) -> *mut u8 {
454 self.data
455 }
456
457 #[inline(always)]
459 pub fn len(&self) -> usize {
460 self.len
461 }
462
463 pub fn kind(&self) -> MemoryMapKind {
465 self.kind
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 #[cfg(unix)]
472 extern crate libc;
473 extern crate tempdir;
474
475 use super::*;
476
477 #[test]
478 fn memory_map_rw() {
479 let chunk = match MemoryMap::new(16, &[MapOption::MapReadable, MapOption::MapWritable]) {
480 Ok(chunk) => chunk,
481 Err(msg) => panic!("{:?}", msg),
482 };
483 assert!(chunk.len >= 16);
484
485 unsafe {
486 *chunk.data = 0xBE;
487 assert!(*chunk.data == 0xBE);
488 }
489 }
490
491 #[test]
492 fn memory_map_file() {
493 use std::fs;
494 use std::io::{Seek, SeekFrom, Write};
495
496 #[cfg(unix)]
497 fn get_fd(file: &fs::File) -> libc::c_int {
498 use std::os::unix::io::AsRawFd;
499 file.as_raw_fd()
500 }
501
502 #[cfg(windows)]
503 fn get_fd(file: &fs::File) -> HANDLE {
504 use std::os::windows::io::AsRawHandle;
505 HANDLE(file.as_raw_handle())
506 }
507
508 let tmpdir = tempdir::TempDir::new("").unwrap();
509 let mut path = tmpdir.path().to_path_buf();
510 path.push("mmap_file.tmp");
511 let size = MemoryMap::granularity() * 2;
512
513 let mut file = fs::OpenOptions::new()
514 .create(true)
515 .read(true)
516 .write(true)
517 .open(&path)
518 .unwrap();
519 file.seek(SeekFrom::Start(size as u64)).unwrap();
520 file.write(b"\0").unwrap();
521 let fd = get_fd(&file);
522
523 let chunk = MemoryMap::new(
524 size / 2,
525 &[
526 MapOption::MapReadable,
527 MapOption::MapWritable,
528 MapOption::MapFd(fd),
529 MapOption::MapOffset(size / 2),
530 ],
531 )
532 .unwrap();
533 assert!(chunk.len > 0);
534
535 unsafe {
536 *chunk.data = 0xbe;
537 assert!(*chunk.data == 0xbe);
538 }
539 drop(chunk);
540
541 fs::remove_file(&path).unwrap();
542 }
543}