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
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
51/// A memory mapped file or chunk of memory. This is a very system-specific
52/// interface to the OS's memory mapping facilities (`mmap` on POSIX,
53/// `VirtualAlloc`/`CreateFileMapping` on Windows). It makes no attempt at
54/// abstracting platform differences, besides in error values returned. Consider
55/// yourself warned.
56///
57/// The memory map is released (unmapped) when the destructor is run, so don't
58/// let it leave scope by accident if you want it to stick around.
59pub struct MemoryMap {
60    data: *mut u8,
61    len: usize,
62    kind: MemoryMapKind,
63}
64
65/// Type of memory map
66#[derive(Copy, Clone)]
67pub enum MemoryMapKind {
68    /// Virtual memory map. Usually used to change the permissions of a given
69    /// chunk of memory.  Corresponds to `VirtualAlloc` on Windows.
70    MapFile(*const u8),
71    /// Virtual memory map. Usually used to change the permissions of a given
72    /// chunk of memory, or for allocation. Corresponds to `VirtualAlloc` on
73    /// Windows.
74    MapVirtual,
75}
76
77/// Options the memory map is created with
78#[derive(Copy, Clone)]
79pub enum MapOption {
80    /// The memory should be readable
81    MapReadable,
82    /// The memory should be writable
83    MapWritable,
84    /// The memory should be executable
85    MapExecutable,
86    /// Create a map for a specific address range. Corresponds to `MAP_FIXED` on
87    /// POSIX.
88    MapAddr(*const u8),
89    /// Create a memory mapping for a file with a given HANDLE.
90    #[cfg(windows)]
91    MapFd(HANDLE),
92    /// Create a memory mapping for a file with a given fd.
93    #[cfg(not(windows))]
94    MapFd(c_int),
95    /// When using `MapFd`, the start of the map is `usize` bytes from the start
96    /// of the file.
97    MapOffset(usize),
98    /// On POSIX, this can be used to specify the default flags passed to
99    /// `mmap`. By default it uses `MAP_PRIVATE` and, if not using `MapFd`,
100    /// `MAP_ANON`. This will override both of those. This is platform-specific
101    /// (the exact values used) and ignored on Windows.
102    MapNonStandardFlags(i32),
103}
104
105/// Possible errors when creating a map.
106#[derive(Copy, Clone, Debug)]
107pub enum MapError {
108    /// # The following are POSIX-specific
109    ///
110    /// fd was not open for reading or, if using `MapWritable`, was not open for
111    /// writing.
112    ErrFdNotAvail,
113    /// fd was not valid
114    ErrInvalidFd,
115    /// Either the address given by `MapAddr` or offset given by `MapOffset` was
116    /// not a multiple of `MemoryMap::granularity` (unaligned to page size).
117    ErrUnaligned,
118    /// With `MapFd`, the fd does not support mapping.
119    ErrNoMapSupport,
120    /// If using `MapAddr`, the address + `min_len` was outside of the process's
121    /// address space. If using `MapFd`, the target of the fd didn't have enough
122    /// resources to fulfill the request.
123    ErrNoMem,
124    /// A zero-length map was requested. This is invalid according to
125    /// [POSIX](http://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html).
126    /// Not all platforms obey this, but this wrapper does.
127    ErrZeroLength,
128    /// Unrecognized error. The inner value is the unrecognized errno.
129    ErrUnknown(isize),
130    /// # The following are Windows-specific
131    ///
132    /// Unsupported combination of protection flags
133    /// (`MapReadable`/`MapWritable`/`MapExecutable`).
134    ErrUnsupProt,
135    /// When using `MapFd`, `MapOffset` was given (Windows does not support this
136    /// at all)
137    ErrUnsupOffset,
138    /// When using `MapFd`, there was already a mapping to the file.
139    ErrAlreadyExists,
140    /// Unrecognized error from `VirtualAlloc`. The inner value is the return
141    /// value of GetLastError.
142    ErrVirtualAlloc(i32),
143    /// Unrecognized error from `CreateFileMapping`. The inner value is the
144    /// return value of `GetLastError`.
145    ErrCreateFileMappingW(i32),
146    /// Unrecognized error from `MapViewOfFile`. The inner value is the return
147    /// value of `GetLastError`.
148    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
183// Round up `from` to be divisible by `to`
184fn 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    /// 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 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), // Actually, because of the check above,
355                                               // we should never get here.
356            };
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    /// Granularity of MapAddr() and MapOffset() parameter values.
402    /// This may be greater than the value returned by page_size().
403    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    /// Unmap the mapping. Panics the task if any of `VirtualFree`,
418    /// `UnmapViewOfFile`, or `CloseHandle` fail.
419    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    /// Returns the pointer to the memory created or modified by this map.
452    #[inline(always)]
453    pub fn data(&self) -> *mut u8 {
454        self.data
455    }
456
457    /// Returns the number of bytes this map applies to.
458    #[inline(always)]
459    pub fn len(&self) -> usize {
460        self.len
461    }
462
463    /// Returns the type of mapping this represents.
464    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}