mmap_fixed_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        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    /// Granularity that the offset or address must be for `MapOffset` and
277    /// `MapAddr` respectively.
278    pub fn granularity() -> usize {
279        page_size()
280    }
281}
282
283#[cfg(unix)]
284impl Drop for MemoryMap {
285    /// Unmap the mapping. Panics the task if `munmap` panics.
286    fn drop(&mut self) {
287        if self.len == 0 {
288            /* workaround for dummy_stack */
289            return;
290        }
291
292        unsafe {
293            // `munmap` only panics due to logic errors
294            libc::munmap(self.data as *mut c_void, self.len as libc::size_t);
295        }
296    }
297}
298
299#[cfg(windows)]
300impl MemoryMap {
301    /// Create a new mapping with the given `options`, at least `min_len` bytes long.
302    #[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), // Actually, because of the check above,
359                                               // we should never get here.
360            };
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    /// Granularity of MapAddr() and MapOffset() parameter values.
418    /// This may be greater than the value returned by page_size().
419    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    /// Unmap the mapping. Panics the task if any of `VirtualFree`,
432    /// `UnmapViewOfFile`, or `CloseHandle` fail.
433    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    /// Returns the pointer to the memory created or modified by this map.
467    #[inline(always)]
468    pub fn data(&self) -> *mut u8 {
469        self.data
470    }
471
472    /// Returns the number of bytes this map applies to.
473    #[inline(always)]
474    pub fn len(&self) -> usize {
475        self.len
476    }
477
478    /// Returns the type of mapping this represents.
479    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}