mmap/
lib.rs

1//
2// Copyright 2015 Richard W. Branson
3// Copyright 2015 The Rust Project Developers.
4//
5// See LICENSE file at top level directory.
6//
7
8extern 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
41/// A memory mapped file or chunk of memory. This is a very system-specific
42/// interface to the OS's memory mapping facilities (`mmap` on POSIX,
43/// `VirtualAlloc`/`CreateFileMapping` on Windows). It makes no attempt at
44/// abstracting platform differences, besides in error values returned. Consider
45/// yourself warned.
46///
47/// The memory map is released (unmapped) when the destructor is run, so don't
48/// let it leave scope by accident if you want it to stick around.
49pub struct MemoryMap {
50    data: *mut u8,
51    len: usize,
52    kind: MemoryMapKind,
53}
54
55/// Type of memory map
56#[allow(raw_pointer_derive)]
57#[derive(Copy, Clone)]
58pub enum MemoryMapKind {
59    /// Virtual memory map. Usually used to change the permissions of a given
60    /// chunk of memory.  Corresponds to `VirtualAlloc` on Windows.
61    MapFile(*const u8),
62    /// Virtual memory map. Usually used to change the permissions of a given
63    /// chunk of memory, or for allocation. Corresponds to `VirtualAlloc` on
64    /// Windows.
65    MapVirtual
66}
67
68/// Options the memory map is created with
69#[allow(raw_pointer_derive)]
70#[derive(Copy, Clone)]
71pub enum MapOption {
72    /// The memory should be readable
73    MapReadable,
74    /// The memory should be writable
75    MapWritable,
76    /// The memory should be executable
77    MapExecutable,
78    /// Create a map for a specific address range. Corresponds to `MAP_FIXED` on
79    /// POSIX.
80    MapAddr(*const u8),
81    /// Create a memory mapping for a file with a given HANDLE.
82    #[cfg(windows)]
83    MapFd(libc::HANDLE),
84    /// Create a memory mapping for a file with a given fd.
85    #[cfg(not(windows))]
86    MapFd(c_int),
87    /// When using `MapFd`, the start of the map is `usize` bytes from the start
88    /// of the file.
89    MapOffset(usize),
90    /// On POSIX, this can be used to specify the default flags passed to
91    /// `mmap`. By default it uses `MAP_PRIVATE` and, if not using `MapFd`,
92    /// `MAP_ANON`. This will override both of those. This is platform-specific
93    /// (the exact values used) and ignored on Windows.
94    MapNonStandardFlags(c_int),
95}
96
97/// Possible errors when creating a map.
98#[derive(Copy, Clone, Debug)]
99pub enum MapError {
100    /// # The following are POSIX-specific
101    ///
102    /// fd was not open for reading or, if using `MapWritable`, was not open for
103    /// writing.
104    ErrFdNotAvail,
105    /// fd was not valid
106    ErrInvalidFd,
107    /// Either the address given by `MapAddr` or offset given by `MapOffset` was
108    /// not a multiple of `MemoryMap::granularity` (unaligned to page size).
109    ErrUnaligned,
110    /// With `MapFd`, the fd does not support mapping.
111    ErrNoMapSupport,
112    /// If using `MapAddr`, the address + `min_len` was outside of the process's
113    /// address space. If using `MapFd`, the target of the fd didn't have enough
114    /// resources to fulfill the request.
115    ErrNoMem,
116    /// A zero-length map was requested. This is invalid according to
117    /// [POSIX](http://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html).
118    /// Not all platforms obey this, but this wrapper does.
119    ErrZeroLength,
120    /// Unrecognized error. The inner value is the unrecognized errno.
121    ErrUnknown(isize),
122    /// # The following are Windows-specific
123    ///
124    /// Unsupported combination of protection flags
125    /// (`MapReadable`/`MapWritable`/`MapExecutable`).
126    ErrUnsupProt,
127    /// When using `MapFd`, `MapOffset` was given (Windows does not support this
128    /// at all)
129    ErrUnsupOffset,
130    /// When using `MapFd`, there was already a mapping to the file.
131    ErrAlreadyExists,
132    /// Unrecognized error from `VirtualAlloc`. The inner value is the return
133    /// value of GetLastError.
134    ErrVirtualAlloc(i32),
135    /// Unrecognized error from `CreateFileMapping`. The inner value is the
136    /// return value of `GetLastError`.
137    ErrCreateFileMappingW(i32),
138    /// Unrecognized error from `MapViewOfFile`. The inner value is the return
139    /// value of `GetLastError`.
140    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
179// Round up `from` to be divisible by `to`
180fn 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    /// Create a new mapping with the given `options`, at least `min_len` bytes
196    /// long. `min_len` must be greater than zero; see the note on
197    /// `ErrZeroLength`.
198    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    /// Granularity that the offset or address must be for `MapOffset` and
258    /// `MapAddr` respectively.
259    pub fn granularity() -> usize {
260        page_size()
261    }
262}
263
264#[cfg(unix)]
265impl Drop for MemoryMap {
266    /// Unmap the mapping. Panics the task if `munmap` panics.
267    fn drop(&mut self) {
268        if self.len == 0 { /* workaround for dummy_stack */ return; }
269
270        unsafe {
271            // `munmap` only panics due to logic errors
272            libc::munmap(self.data as *mut c_void, self.len as libc::size_t);
273        }
274    }
275}
276
277#[cfg(windows)]
278impl MemoryMap {
279    /// Create a new mapping with the given `options`, at least `min_len` bytes long.
280    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) // Actually, because of the check above,
338                                              // we should never get here.
339            };
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    /// Granularity of MapAddr() and MapOffset() parameter values.
372    /// This may be greater than the value returned by page_size().
373    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    /// Unmap the mapping. Panics the task if any of `VirtualFree`,
387    /// `UnmapViewOfFile`, or `CloseHandle` fail.
388    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    /// Returns the pointer to the memory created or modified by this map.
416    #[inline(always)]
417    pub fn data(&self) -> *mut u8 { self.data }
418
419    /// Returns the number of bytes this map applies to.
420    #[inline(always)]
421    pub fn len(&self) -> usize { self.len }
422
423    /// Returns the type of mapping this represents.
424    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}