mmap_fixed/
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
8#[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
47/// A memory mapped file or chunk of memory. This is a very system-specific
48/// interface to the OS's memory mapping facilities (`mmap` on POSIX,
49/// `VirtualAlloc`/`CreateFileMapping` on Windows). It makes no attempt at
50/// abstracting platform differences, besides in error values returned. Consider
51/// yourself warned.
52///
53/// The memory map is released (unmapped) when the destructor is run, so don't
54/// let it leave scope by accident if you want it to stick around.
55pub struct MemoryMap {
56    data: *mut u8,
57    len: usize,
58    kind: MemoryMapKind,
59}
60
61/// Type of memory map
62#[derive(Copy, Clone)]
63pub enum MemoryMapKind {
64    /// Virtual memory map. Usually used to change the permissions of a given
65    /// chunk of memory.  Corresponds to `VirtualAlloc` on Windows.
66    MapFile(*const u8),
67    /// Virtual memory map. Usually used to change the permissions of a given
68    /// chunk of memory, or for allocation. Corresponds to `VirtualAlloc` on
69    /// Windows.
70    MapVirtual,
71}
72
73/// Options the memory map is created with
74#[derive(Copy, Clone)]
75pub enum MapOption {
76    /// The memory should be readable
77    MapReadable,
78    /// The memory should be writable
79    MapWritable,
80    /// The memory should be executable
81    MapExecutable,
82    /// Create a map for a specific address range. Corresponds to `MAP_FIXED` on
83    /// POSIX.
84    MapAddr(*const u8),
85    /// Create a memory mapping for a file with a given HANDLE.
86    #[cfg(windows)]
87    MapFd(winapi::shared::ntdef::HANDLE),
88    /// Create a memory mapping for a file with a given fd.
89    #[cfg(not(windows))]
90    MapFd(c_int),
91    /// When using `MapFd`, the start of the map is `usize` bytes from the start
92    /// of the file.
93    MapOffset(usize),
94    /// On POSIX, this can be used to specify the default flags passed to
95    /// `mmap`. By default it uses `MAP_PRIVATE` and, if not using `MapFd`,
96    /// `MAP_ANON`. This will override both of those. This is platform-specific
97    /// (the exact values used) and ignored on Windows.
98    MapNonStandardFlags(i32),
99}
100
101/// Possible errors when creating a map.
102#[derive(Copy, Clone, Debug)]
103pub enum MapError {
104    /// # The following are POSIX-specific
105    ///
106    /// fd was not open for reading or, if using `MapWritable`, was not open for
107    /// writing.
108    ErrFdNotAvail,
109    /// fd was not valid
110    ErrInvalidFd,
111    /// Either the address given by `MapAddr` or offset given by `MapOffset` was
112    /// not a multiple of `MemoryMap::granularity` (unaligned to page size).
113    ErrUnaligned,
114    /// With `MapFd`, the fd does not support mapping.
115    ErrNoMapSupport,
116    /// If using `MapAddr`, the address + `min_len` was outside of the process's
117    /// address space. If using `MapFd`, the target of the fd didn't have enough
118    /// resources to fulfill the request.
119    ErrNoMem,
120    /// A zero-length map was requested. This is invalid according to
121    /// [POSIX](http://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html).
122    /// Not all platforms obey this, but this wrapper does.
123    ErrZeroLength,
124    /// Unrecognized error. The inner value is the unrecognized errno.
125    ErrUnknown(isize),
126    /// # The following are Windows-specific
127    ///
128    /// Unsupported combination of protection flags
129    /// (`MapReadable`/`MapWritable`/`MapExecutable`).
130    ErrUnsupProt,
131    /// When using `MapFd`, `MapOffset` was given (Windows does not support this
132    /// at all)
133    ErrUnsupOffset,
134    /// When using `MapFd`, there was already a mapping to the file.
135    ErrAlreadyExists,
136    /// Unrecognized error from `VirtualAlloc`. The inner value is the return
137    /// value of GetLastError.
138    ErrVirtualAlloc(i32),
139    /// Unrecognized error from `CreateFileMapping`. The inner value is the
140    /// return value of `GetLastError`.
141    ErrCreateFileMappingW(i32),
142    /// Unrecognized error from `MapViewOfFile`. The inner value is the return
143    /// value of `GetLastError`.
144    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
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 => {
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        // This adjustment ensures that the behavior of memory protection is
245        // orthogonal across all platforms by aligning NetBSD's protection flags
246        // with those of other operating systems.
247        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    /// Granularity that the offset or address must be for `MapOffset` and
285    /// `MapAddr` respectively.
286    pub fn granularity() -> usize {
287        page_size()
288    }
289}
290
291#[cfg(unix)]
292impl Drop for MemoryMap {
293    /// Unmap the mapping. Panics the task if `munmap` panics.
294    fn drop(&mut self) {
295        if self.len == 0 {
296            /* workaround for dummy_stack */
297            return;
298        }
299
300        unsafe {
301            // `munmap` only panics due to logic errors
302            libc::munmap(self.data as *mut c_void, self.len as libc::size_t);
303        }
304    }
305}
306
307#[cfg(windows)]
308impl MemoryMap {
309    /// Create a new mapping with the given `options`, at least `min_len` bytes long.
310    #[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), // Actually, because of the check above,
367                                               // we should never get here.
368            };
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    /// Granularity of MapAddr() and MapOffset() parameter values.
426    /// This may be greater than the value returned by page_size().
427    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    /// Unmap the mapping. Panics the task if any of `VirtualFree`,
440    /// `UnmapViewOfFile`, or `CloseHandle` fail.
441    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    /// Returns the pointer to the memory created or modified by this map.
475    #[inline(always)]
476    pub fn data(&self) -> *mut u8 {
477        self.data
478    }
479
480    /// Returns the number of bytes this map applies to.
481    #[inline(always)]
482    pub fn len(&self) -> usize {
483        self.len
484    }
485
486    /// Returns the type of mapping this represents.
487    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}