├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | target 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mmap" 3 | version = "0.1.1" 4 | authors = ["Rick Branson ", "The Rust Project Developers"] 5 | license = "MIT" 6 | readme = "README.md" 7 | repository = "https://github.com/rbranson/rust-mmap" 8 | homepage = "https://github.com/rbranson/rust-mmap" 9 | description = """ 10 | A library for dealing with memory-mapped I/O 11 | """ 12 | 13 | [dependencies] 14 | libc = "0.1.6" 15 | tempdir = "0.3" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Richard W. Branson 2 | Copyright (c) 2015 The Rust Project Developers 3 | 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rust-mmap 2 | ========= 3 | 4 | A Rust library for dealing with memory mapped files, originally extracted from 5 | the Rust standard library source code before it was removed. 6 | 7 | ## Usage 8 | 9 | Add this to your `Cargo.toml`: 10 | 11 | ```toml 12 | [dependencies] 13 | mmap = "*" 14 | ``` 15 | 16 | and this to your crate root: 17 | 18 | ```rust 19 | extern crate mmap; 20 | ``` 21 | -------------------------------------------------------------------------------- /src/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 | extern crate libc; 9 | 10 | use std::error::Error; 11 | use std::io; 12 | use std::fmt; 13 | use libc::{c_void, c_int}; 14 | use std::ops::Drop; 15 | use std::ptr; 16 | use self::MemoryMapKind::*; 17 | use self::MapOption::*; 18 | use self::MapError::*; 19 | 20 | #[cfg(windows)] 21 | use std::mem; 22 | 23 | fn errno() -> i32 { 24 | io::Error::last_os_error().raw_os_error().unwrap_or(-1) 25 | } 26 | 27 | #[cfg(unix)] 28 | fn page_size() -> usize { 29 | unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } 30 | } 31 | 32 | #[cfg(windows)] 33 | fn 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. 49 | pub 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)] 58 | pub 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)] 71 | pub 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)] 99 | pub 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 | 143 | impl 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 | 175 | impl Error for MapError { 176 | fn description(&self) -> &str { "memory map error" } 177 | } 178 | 179 | // Round up `from` to be divisible by `to` 180 | fn 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)] 194 | impl 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 { 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)] 265 | impl 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)] 278 | impl 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 { 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)] 385 | impl 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 | 414 | impl 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)] 428 | mod 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 | } 501 | --------------------------------------------------------------------------------