Created
March 20, 2025 18:58
-
-
Save matthewdowney/1114c0ae679a14e4f6b98911606953bf to your computer and use it in GitHub Desktop.
mmap stdin in Rust, supports Mac OS (ARM) and Linux
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Conditional extern definitions for Linux/Android | |
#[cfg(any(target_os = "linux", target_os = "android"))] | |
#[link(name = "c")] | |
unsafe extern "C" { | |
fn mmap(addr: *mut u8, len: usize, prot: i32, flags: i32, fd: i32, offset: i64) -> *mut u8; | |
fn __errno_location() -> *const i32; | |
fn lseek(fd: i32, offset: i64, whence: i32) -> i64; | |
fn open(path: *const u8, oflag: i32) -> i32; | |
} | |
// Conditional extern definitions for macOS/iOS | |
#[cfg(any(target_os = "macos", target_os = "ios"))] | |
#[link(name = "c")] | |
unsafe extern "C" { | |
fn mmap(addr: *mut u8, len: usize, prot: i32, flags: i32, fd: i32, offset: i64) -> *mut u8; | |
// macOS uses __error instead of __errno_location | |
fn __error() -> *mut i32; | |
fn lseek(fd: i32, offset: i64, whence: i32) -> i64; | |
fn open(path: *const u8, oflag: i32) -> i32; | |
} | |
/// Helper function to get errno in a platform-independent way. | |
#[inline] | |
unsafe fn get_errno() -> i32 { | |
#[cfg(any(target_os = "linux", target_os = "android"))] | |
{ | |
unsafe { *__errno_location() } | |
} | |
#[cfg(any(target_os = "macos", target_os = "ios"))] | |
{ | |
unsafe { *__error() } | |
} | |
} | |
/// Define MAP_POPULATE conditionally: | |
#[cfg(any(target_os = "linux", target_os = "android"))] | |
const MAP_POPULATE: i32 = 0x08000; | |
#[cfg(any(target_os = "macos", target_os = "ios"))] | |
const MAP_POPULATE: i32 = 0; // MAP_POPULATE not supported on macOS | |
/// Map the file descriptor for standard input. | |
#[allow(dead_code)] | |
pub unsafe fn mmap_stdin<'a>() -> &'a [u8] { | |
unsafe { mmap_fd(0) } | |
} | |
/// Map the file at the given path. | |
#[allow(dead_code)] | |
pub unsafe fn mmap_path<'a>(path: &str) -> &'a [u8] { | |
let cpath = std::ffi::CString::new(path).expect("CString::new failed"); | |
unsafe { | |
let fd = open(cpath.as_ptr() as *const u8, 0); | |
if fd == -1 { | |
panic!("open failed, errno {}", get_errno()); | |
} | |
mmap_fd(fd) | |
} | |
} | |
/// Helper function that performs the mmap operation on a given file descriptor. | |
pub unsafe fn mmap_fd<'a>(fd: i32) -> &'a [u8] { | |
// SEEK_END is usually defined as 2 | |
let seek_end = 2; | |
unsafe { | |
let size = lseek(fd, 0, seek_end); | |
if size == -1 { | |
panic!("lseek failed, errno {}", get_errno()); | |
} | |
let prot_read = 0x01; // PROT_READ | |
let map_private = 0x02; // MAP_PRIVATE | |
let ptr = mmap( | |
std::ptr::null_mut(), | |
size as usize, | |
prot_read, | |
map_private | MAP_POPULATE, | |
fd, | |
0, | |
); | |
if ptr as isize == -1 { | |
panic!("mmap failed, errno {}", get_errno()); | |
} | |
std::slice::from_raw_parts(ptr, size as usize) | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
use std::io::Write; | |
use tempfile::NamedTempFile; | |
#[test] | |
fn test_mmap_path_success() { | |
// Create a temporary file with known content. | |
let mut tmpfile = NamedTempFile::new().expect("Failed to create temp file"); | |
let content = b"Hello, mmap!"; | |
tmpfile | |
.write_all(content) | |
.expect("Failed to write to temp file"); | |
// Flush and get the file path. | |
tmpfile.flush().expect("Failed to flush file"); | |
let path = tmpfile.path().to_str().expect("Path is not valid UTF-8"); | |
// Map the file using your function. | |
let mapped = unsafe { mmap_path(path) }; | |
// Verify that the mapped content matches. | |
assert_eq!(mapped, content); | |
} | |
#[test] | |
#[should_panic(expected = "open failed")] | |
fn test_mmap_path_nonexistent() { | |
// Attempt to map a file that doesn't exist. | |
unsafe { | |
mmap_path("nonexistent_file.txt"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment