about summary refs log tree commit diff
path: root/src/libstd
diff options
context:
space:
mode:
authorFedor Indutny <fedor.indutny@gmail.com>2013-06-26 17:42:11 +0200
committerFedor Indutny <fedor.indutny@gmail.com>2013-07-09 19:20:26 +0400
commitdb24ee9db02acf0912a89626e09801665bbb365a (patch)
treed8f0a542f08db501d88ec74fa7da332e81494eeb /src/libstd
parent4a868c7c1df6a0e68b03c1710474e83c057d69c0 (diff)
downloadrust-db24ee9db02acf0912a89626e09801665bbb365a.tar.gz
rust-db24ee9db02acf0912a89626e09801665bbb365a.zip
os: introduce cross-platform MemoryMap bindings
Basically, one may just do:

    MemoryMap::new(16, ~[
      MapExecutable,
      MapReadable,
      MapWritable
    ])

And executable+readable+writable chunk of at least 16 bytes size will be
allocated and freed with the result of `MemoryMap::new`.
Diffstat (limited to 'src/libstd')
-rw-r--r--src/libstd/libc.rs9
-rw-r--r--src/libstd/os.rs353
2 files changed, 358 insertions, 4 deletions
diff --git a/src/libstd/libc.rs b/src/libstd/libc.rs
index 9cb81e76663..2518ba1a73d 100644
--- a/src/libstd/libc.rs
+++ b/src/libstd/libc.rs
@@ -732,8 +732,8 @@ pub mod types {
                             wProcessorArchitecture: 0,
                             wReserved: 0,
                             dwPageSize: 0,
-                            lpMinimumApplicationAddress: ptr::null(),
-                            lpMaximumApplicationAddress: ptr::null(),
+                            lpMinimumApplicationAddress: ptr::mut_null(),
+                            lpMaximumApplicationAddress: ptr::mut_null(),
                             dwActiveProcessorMask: 0,
                             dwNumberOfProcessors: 0,
                             dwProcessorType: 0,
@@ -1011,7 +1011,7 @@ pub mod consts {
         }
         pub mod extra {
             use libc::types::os::arch::c95::c_int;
-            use libc::types::os::arch::extra::{DWORD, BOOL};
+            use libc::types::os::arch::extra::{WORD, DWORD, BOOL};
 
             pub static TRUE : BOOL = 1;
             pub static FALSE : BOOL = 0;
@@ -3002,7 +3002,8 @@ pub mod funcs {
                                                LPCVOID};
             use libc::types::os::arch::extra::{LPSECURITY_ATTRIBUTES, LPSTARTUPINFO,
                                                LPPROCESS_INFORMATION,
-                                               LPMEMORY_BASIC_INFORMATION};
+                                               LPMEMORY_BASIC_INFORMATION,
+                                               LPSYSTEM_INFO};
             use libc::types::os::arch::extra::{HANDLE, LPHANDLE};
 
             #[abi = "stdcall"]
diff --git a/src/libstd/os.rs b/src/libstd/os.rs
index be0c504885b..50acbee697f 100644
--- a/src/libstd/os.rs
+++ b/src/libstd/os.rs
@@ -41,6 +41,7 @@ use os;
 use prelude::*;
 use ptr;
 use str;
+use to_str;
 use uint;
 use unstable::finally::Finally;
 use vec;
@@ -1335,6 +1336,288 @@ extern {
     pub fn _NSGetArgv() -> ***c_char;
 }
 
+// Round up `from` to be divisible by `to`
+fn round_up(from: uint, to: uint) -> uint {
+    let r = if from % to == 0 {
+        from
+    } else {
+        from + to - (from % to)
+    };
+    if r == 0 {
+        to
+    } else {
+        r
+    }
+}
+
+#[cfg(unix)]
+pub fn page_size() -> uint {
+    unsafe {
+        libc::sysconf(libc::_SC_PAGESIZE) as uint
+    }
+}
+
+#[cfg(windows)]
+pub fn page_size() -> uint {
+  unsafe {
+    let mut info = libc::SYSTEM_INFO::new();
+    libc::GetSystemInfo(&mut info);
+
+    return info.dwPageSize as uint;
+  }
+}
+
+pub struct MemoryMap {
+    data: *mut u8,
+    len: size_t,
+    kind: MemoryMapKind
+}
+
+pub enum MemoryMapKind {
+    MapFile(*c_void),
+    MapVirtual
+}
+
+pub enum MapOption {
+    MapReadable,
+    MapWritable,
+    MapExecutable,
+    MapAddr(*c_void),
+    MapFd(c_int),
+    MapOffset(uint)
+}
+
+pub enum MapError {
+    // Linux-specific errors
+    ErrFdNotAvail,
+    ErrInvalidFd,
+    ErrUnaligned,
+    ErrNoMapSupport,
+    ErrNoMem,
+    ErrUnknown(libc::c_int),
+
+    // Windows-specific errors
+    ErrUnsupProt,
+    ErrUnsupOffset,
+    ErrNeedRW,
+    ErrAlreadyExists,
+    ErrVirtualAlloc(uint),
+    ErrCreateFileMappingW(uint),
+    ErrMapViewOfFile(uint)
+}
+
+impl to_str::ToStr for MapError {
+    fn to_str(&self) -> ~str {
+        match *self {
+            ErrFdNotAvail => ~"fd not available for reading or writing",
+            ErrInvalidFd => ~"Invalid fd",
+            ErrUnaligned => ~"Unaligned address, invalid flags, \
+                              negative length or unaligned offset",
+            ErrNoMapSupport=> ~"File doesn't support mapping",
+            ErrNoMem => ~"Invalid address, or not enough available memory",
+            ErrUnknown(code) => fmt!("Unknown error=%?", code),
+            ErrUnsupProt => ~"Protection mode unsupported",
+            ErrUnsupOffset => ~"Offset in virtual memory mode is unsupported",
+            ErrNeedRW => ~"File mapping should be at least readable/writable",
+            ErrAlreadyExists => ~"File mapping for specified file already exists",
+            ErrVirtualAlloc(code) => fmt!("VirtualAlloc failure=%?", code),
+            ErrCreateFileMappingW(code) => fmt!("CreateFileMappingW failure=%?", code),
+            ErrMapViewOfFile(code) => fmt!("MapViewOfFile failure=%?", code)
+        }
+    }
+}
+
+#[cfg(unix)]
+impl MemoryMap {
+    pub fn new(min_len: uint, options: ~[MapOption]) -> Result<~MemoryMap, MapError> {
+        use libc::off_t;
+
+        let mut addr: *c_void = ptr::null();
+        let mut prot: c_int = 0;
+        let mut flags: c_int = libc::MAP_PRIVATE;
+        let mut fd: c_int = -1;
+        let mut offset: off_t = 0;
+        let len = round_up(min_len, page_size()) as size_t;
+
+        for options.iter().advance |&o| {
+            match o {
+                MapReadable => { prot |= libc::PROT_READ; },
+                MapWritable => { prot |= libc::PROT_WRITE; },
+                MapExecutable => { prot |= libc::PROT_EXEC; },
+                MapAddr(addr_) => {
+                    flags |= libc::MAP_FIXED;
+                    addr = addr_;
+                },
+                MapFd(fd_) => {
+                    flags |= libc::MAP_FILE;
+                    fd = fd_;
+                },
+                MapOffset(offset_) => { offset = offset_ as off_t; }
+            }
+        }
+        if fd == -1 { flags |= libc::MAP_ANON; }
+
+        let r = unsafe {
+            libc::mmap(addr, len, prot, flags, fd, offset)
+        };
+        if r == libc::MAP_FAILED {
+            Err(match errno() as c_int {
+                libc::EACCES => ErrFdNotAvail,
+                libc::EBADF => ErrInvalidFd,
+                libc::EINVAL => ErrUnaligned,
+                libc::ENODEV => ErrNoMapSupport,
+                libc::ENOMEM => ErrNoMem,
+                code => ErrUnknown(code)
+            })
+        } else {
+            Ok(~MemoryMap {
+               data: r as *mut u8,
+               len: len,
+               kind: if fd == -1 {
+                   MapVirtual
+               } else {
+                   MapFile(ptr::null())
+               }
+            })
+        }
+    }
+}
+
+#[cfg(unix)]
+impl Drop for MemoryMap {
+    fn drop(&self) {
+        unsafe {
+            match libc::munmap(self.data as *c_void, self.len) {
+                0 => (),
+                -1 => error!(match errno() as c_int {
+                    libc::EINVAL => ~"invalid addr or len",
+                    e => fmt!("unknown errno=%?", e)
+                }),
+                r => error!(fmt!("Unexpected result %?", r))
+            }
+        }
+    }
+}
+
+#[cfg(windows)]
+impl MemoryMap {
+    pub fn new(min_len: uint, options: ~[MapOption]) -> Result<~MemoryMap, MapError> {
+        use libc::types::os::arch::extra::{LPVOID, DWORD, SIZE_T, HANDLE};
+
+        let mut lpAddress: LPVOID = ptr::mut_null();
+        let mut readable = false;
+        let mut writable = false;
+        let mut executable = false;
+        let mut fd: c_int = -1;
+        let mut offset: uint = 0;
+        let len = round_up(min_len, page_size()) as SIZE_T;
+
+        for options.iter().advance |&o| {
+            match o {
+                MapReadable => { readable = true; },
+                MapWritable => { writable = true; },
+                MapExecutable => { executable = true; }
+                MapAddr(addr_) => { lpAddress = addr_ as LPVOID; },
+                MapFd(fd_) => { fd = fd_; },
+                MapOffset(offset_) => { offset = offset_; }
+            }
+        }
+
+        let flProtect = match (executable, readable, writable) {
+            (false, false, false) if fd == -1 => libc::PAGE_NOACCESS,
+            (false, true, false) => libc::PAGE_READONLY,
+            (false, true, true) => libc::PAGE_READWRITE,
+            (true, false, false) if fd == -1 => libc::PAGE_EXECUTE,
+            (true, true, false) => libc::PAGE_EXECUTE_READ,
+            (true, true, true) => libc::PAGE_EXECUTE_READWRITE,
+            _ => return Err(ErrUnsupProt)
+        };
+
+        if fd == -1 {
+            if offset != 0 {
+                return Err(ErrUnsupOffset);
+            }
+            let r = unsafe {
+                libc::VirtualAlloc(lpAddress,
+                                   len,
+                                   libc::MEM_COMMIT | libc::MEM_RESERVE,
+                                   flProtect)
+            };
+            match r as uint {
+                0 => Err(ErrVirtualAlloc(errno())),
+                _ => Ok(~MemoryMap {
+                   data: r as *mut u8,
+                   len: len,
+                   kind: MapVirtual
+                })
+            }
+        } else {
+            let dwDesiredAccess = match (readable, writable) {
+                (true, true) => libc::FILE_MAP_ALL_ACCESS,
+                (true, false) => libc::FILE_MAP_READ,
+                (false, true) => libc::FILE_MAP_WRITE,
+                _ => {
+                    return Err(ErrNeedRW);
+                }
+            };
+            unsafe {
+                let hFile = libc::get_osfhandle(fd) as HANDLE;
+                let mapping = libc::CreateFileMappingW(hFile,
+                                                       ptr::mut_null(),
+                                                       flProtect,
+                                                       (len >> 32) as DWORD,
+                                                       (len & 0xffff_ffff) as DWORD,
+                                                       ptr::null());
+                if mapping == ptr::mut_null() {
+                    return Err(ErrCreateFileMappingW(errno()));
+                }
+                if errno() as c_int == libc::ERROR_ALREADY_EXISTS {
+                    return Err(ErrAlreadyExists);
+                }
+                let r = libc::MapViewOfFile(mapping,
+                                            dwDesiredAccess,
+                                            (offset >> 32) as DWORD,
+                                            (offset & 0xffff_ffff) as DWORD,
+                                            0);
+                match r as uint {
+                    0 => Err(ErrMapViewOfFile(errno())),
+                    _ => Ok(~MemoryMap {
+                       data: r as *mut u8,
+                       len: len,
+                       kind: MapFile(mapping as *c_void)
+                    })
+                }
+            }
+        }
+    }
+}
+
+#[cfg(windows)]
+impl Drop for MemoryMap {
+    fn drop(&self) {
+        use libc::types::os::arch::extra::{LPCVOID, HANDLE};
+
+        unsafe {
+            match self.kind {
+                MapVirtual => match libc::VirtualFree(self.data as *mut c_void,
+                                                      self.len,
+                                                      libc::MEM_RELEASE) {
+                    0 => error!(fmt!("VirtualFree failed: %?", errno())),
+                    _ => ()
+                },
+                MapFile(mapping) => {
+                    if libc::UnmapViewOfFile(self.data as LPCVOID) != 0 {
+                        error!(fmt!("UnmapViewOfFile failed: %?", errno()));
+                    }
+                    if libc::CloseHandle(mapping as HANDLE) != 0 {
+                        error!(fmt!("CloseHandle failed: %?", errno()));
+                    }
+                }
+            }
+        }
+    }
+}
+
 pub mod consts {
 
     #[cfg(unix)]
@@ -1715,5 +1998,75 @@ mod tests {
         assert!(!os::mkdir_recursive(&path, (S_IRUSR | S_IWUSR | S_IXUSR) as i32));
     }
 
+    #[test]
+    fn memory_map_rw() {
+        use result::{Ok, Err};
+
+        let chunk = match os::MemoryMap::new(16, ~[
+            os::MapReadable,
+            os::MapWritable
+        ]) {
+            Ok(chunk) => chunk,
+            Err(msg) => fail!(msg.to_str())
+        };
+        assert!(chunk.len >= 16);
+
+        unsafe {
+            *chunk.data = 0xBE;
+            assert!(*chunk.data == 0xBE);
+        }
+    }
+
+    #[test]
+    fn memory_map_file() {
+        use result::{Ok, Err};
+        use os::*;
+        use libc::*;
+
+        #[cfg(unix)]
+        fn lseek_(fd: c_int, size: uint) {
+            unsafe {
+                assert!(lseek(fd, size as off_t, SEEK_SET) == size as off_t);
+            }
+        }
+        #[cfg(windows)]
+        fn lseek_(fd: c_int, size: uint) {
+           unsafe {
+               assert!(lseek(fd, size as c_long, SEEK_SET) == size as c_long);
+           }
+        }
+
+        let p = tmpdir().push("mmap_file.tmp");
+        let size = page_size() * 2;
+        remove_file(&p);
+
+        let fd = unsafe {
+            let fd = do as_c_charp(p.to_str()) |path| {
+                open(path, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)
+            };
+            lseek_(fd, size);
+            do as_c_charp("x") |x| {
+                assert!(write(fd, x as *c_void, 1) == 1);
+            }
+            fd
+        };
+        let chunk = match MemoryMap::new(size / 2, ~[
+            MapReadable,
+            MapWritable,
+            MapFd(fd),
+            MapOffset(size / 2)
+        ]) {
+            Ok(chunk) => chunk,
+            Err(msg) => fail!(msg.to_str())
+        };
+        assert!(chunk.len > 0);
+
+        unsafe {
+            *chunk.data = 0xbe;
+            assert!(*chunk.data == 0xbe);
+            close(fd);
+        }
+    }
+
     // More recursive_mkdir tests are in extra::tempfile
 }