mmap-rs 0.7.0

A cross-platform and safe Rust API to create and manage memory mappings in the virtual address space of the calling process.
Documentation
use crate::PageSizes;
use crate::areas::{MemoryArea, Protection, ShareMode};
use crate::error::Error;
use crate::os_impl::unix::MmapOptions;
use combine::{
    EasyParser, Parser, Stream,
    error::ParseError,
    parser::{
        char::{digit, hex_digit, string},
        repeat::many1,
    },
    token,
};
use std::fs::File;
use std::io::Lines;
use std::io::{BufRead, BufReader};
use std::ops::Range;
use std::path::PathBuf;

fn hugepage_path<Input>() -> impl Parser<Input, Output = u32>
where
    Input: Stream<Token = char>,
    <Input::Error as ParseError<Input::Token, Input::Range, Input::Position>>::StreamError:
        From<::std::num::ParseIntError>,
{
    (
        string("hugepages-"),
        many1(digit()).and_then(|s: String| s.parse::<usize>()),
        string("kB"),
    )
        .map(|(_, size, _)| size.ilog2() + 10)
}

fn hex_digit1<Input>() -> impl Parser<Input, Output = String>
where
    Input: Stream<Token = char>,
{
    many1(hex_digit())
}

fn address_range<Input>() -> impl Parser<Input, Output = Range<usize>>
where
    Input: Stream<Token = char>,
    <Input::Error as ParseError<Input::Token, Input::Range, Input::Position>>::StreamError:
        From<::std::num::ParseIntError>,
{
    (
        hex_digit1().and_then(|s| usize::from_str_radix(s.as_str(), 16)),
        token('-'),
        hex_digit1().and_then(|s| usize::from_str_radix(s.as_str(), 16)),
    )
        .map(|(start, _, end)| start..end)
}

fn permissions<Input>() -> impl Parser<Input, Output = (Protection, ShareMode)>
where
    Input: Stream<Token = char>,
{
    use combine::parser::{char::char, choice::or};

    (
        or(
            char('r').map(|_| Protection::READ),
            char('-').map(|_| Protection::empty()),
        ),
        or(
            char('w').map(|_| Protection::WRITE),
            char('-').map(|_| Protection::empty()),
        ),
        or(
            char('x').map(|_| Protection::EXECUTE),
            char('-').map(|_| Protection::empty()),
        ),
        or(
            char('s').map(|_| ShareMode::Shared),
            char('p').map(|_| ShareMode::Private),
        ),
    )
        .map(|(r, w, x, s)| (r | w | x, s))
}

fn device_id<Input>() -> impl Parser<Input, Output = (u16, u8)>
where
    Input: Stream<Token = char>,
    <Input::Error as ParseError<Input::Token, Input::Range, Input::Position>>::StreamError:
        From<::std::num::ParseIntError>,
{
    (
        hex_digit1().and_then(|s| u16::from_str_radix(s.as_str(), 16)),
        token(':'),
        hex_digit1().and_then(|s| u8::from_str_radix(s.as_str(), 16)),
    )
        .map(|(major, _, minor)| (major, minor))
}

fn path<Input>() -> impl Parser<Input, Output = PathBuf>
where
    Input: Stream<Token = char>,
{
    use combine::parser::token::satisfy;

    many1(satisfy(|c| c != '\n')).map(|s: String| PathBuf::from(s))
}

fn memory_region<Input>() -> impl Parser<Input, Output = MemoryArea>
where
    Input: Stream<Token = char>,
    <Input::Error as ParseError<Input::Token, Input::Range, Input::Position>>::StreamError:
        From<::std::num::ParseIntError>,
{
    use combine::parser::{char::spaces, choice::optional};

    (
        address_range(),
        spaces(),
        permissions(),
        spaces(),
        hex_digit1().and_then(|s| u64::from_str_radix(s.as_str(), 16)),
        spaces(),
        device_id(),
        spaces(),
        hex_digit1(),
        spaces(),
        optional(path()),
    )
        .map(
            |(range, _, (protection, share_mode), _, offset, _, _, _, _, _, path)| MemoryArea {
                allocation_base: range.start,
                range,
                protection,
                share_mode,
                path: path.map(|path| (path, offset)),
            },
        )
}

impl MmapOptions<'_> {
    pub fn page_sizes() -> Result<PageSizes, Error> {
        let mut sizes = 1 << Self::page_size().ilog2();

        if let Ok(dir) = std::fs::read_dir("/sys/kernel/mm/hugepages") {
            for entry in dir {
                let entry = match entry {
                    Ok(entry) => entry,
                    _ => continue,
                };

                let name = match entry.file_name().into_string() {
                    Ok(name) => name,
                    _ => continue,
                };

                use combine::stream::position::Stream;

                let size = match hugepage_path().easy_parse(Stream::new(name.as_str())) {
                    Ok((size, _)) => size,
                    _ => continue,
                };

                sizes |= 1 << size;
            }
        }

        Ok(PageSizes::from_bits_truncate(sizes))
    }
}

pub struct MemoryAreas<B> {
    lines: Lines<B>,
    range: Option<Range<usize>>,
}

impl MemoryAreas<BufReader<File>> {
    pub fn open(pid: Option<u32>, range: Option<Range<usize>>) -> Result<Self, Error> {
        let path = match pid {
            Some(pid) => format!("/proc/{}/maps", pid),
            _ => "/proc/self/maps".to_string(),
        };

        let file = File::open(path)?;
        let reader = BufReader::new(file);
        let lines = reader.lines();

        Ok(Self { lines, range })
    }
}

impl<B: BufRead> Iterator for MemoryAreas<B> {
    type Item = Result<MemoryArea, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let line = match self.lines.next() {
                Some(Ok(line)) => line,
                Some(Err(e)) => return Some(Err(Error::Io(e))),
                None => return None,
            };

            use combine::stream::position::Stream;

            let region = match memory_region().easy_parse(Stream::new(line.as_str())) {
                Ok((region, _)) => region,
                _ => return None,
            };

            if let Some(ref range) = self.range {
                if region.end() <= range.start {
                    continue;
                }

                if range.end <= region.start() {
                    break;
                }
            }

            return Some(Ok(region));
        }

        None
    }
}