// SPDX-FileCopyrightText: edef // SPDX-License-Identifier: OSL-3.0 use { crate::syscall_abi::{Device, ProtFlags}, anyhow::bail, nom::{ branch::alt, bytes::complete::{tag, take_while}, combinator::map_res, sequence::tuple, IResult, }, std::{ fmt::{self, Debug}, num::NonZeroU64, }, }; fn whitespace(s: &str) -> IResult<&str, &str> { take_while(|c| c == ' ')(s) } fn hex_u32(s: &str) -> IResult<&str, u32> { map_res(take_while(|c: char| c.is_ascii_hexdigit()), |src| { u32::from_str_radix(src, 16) })(s) } fn hex_u64(s: &str) -> IResult<&str, u64> { map_res(take_while(|c: char| c.is_ascii_hexdigit()), |src| { u64::from_str_radix(src, 16) })(s) } fn dec_u64(s: &str) -> IResult<&str, u64> { map_res(take_while(|c: char| c.is_ascii_hexdigit()), |src| { u64::from_str_radix(src, 10) })(s) } fn perm(c: &'static str) -> impl Fn(&str) -> IResult<&str, bool> { move |input| { let (input, tag) = alt((tag(c), tag("-")))(input)?; Ok((input, tag != "-")) } } fn shared(input: &str) -> IResult<&str, bool> { let (input, tag) = alt((tag("p"), tag("s")))(input)?; Ok((input, tag == "s")) } fn pathname(input: &str) -> IResult<&str, &str> { take_while(|c| c != '\n')(input) } fn mapping(input: &str) -> IResult<&str, Mapping> { let ( input, ( start, _, end, _, perm_read, perm_write, perm_exec, shared, _, offset, _, dev_maj, _, dev_min, _, inode, _, pathname, ), ) = tuple(( hex_u64, tag("-"), hex_u64, whitespace, perm("r"), perm("w"), perm("x"), shared, whitespace, hex_u64, whitespace, hex_u32, tag(":"), hex_u32, whitespace, dec_u64, whitespace, pathname, ))(input)?; let dev = Device::checked_new(dev_maj, dev_min); let inode = NonZeroU64::new(inode); let inode = match (dev, inode) { (None, None) => None, (Some(dev), Some(node)) => Some(Inode { dev, node }), _ => unreachable!(), }; let mut prot = ProtFlags::empty(); if perm_read { prot |= ProtFlags::READ; } if perm_write { prot |= ProtFlags::WRITE; } if perm_exec { prot |= ProtFlags::EXEC; } Ok(( input, Mapping { start, end, prot, shared, offset, inode, pathname: pathname.to_owned(), }, )) } pub(crate) fn parse_mapping_line(line: &str) -> anyhow::Result { match mapping(line) { Ok(("", mapping)) => Ok(mapping), Ok(_) => unreachable!(), Err(err) => bail!("Cannot parse mapping: {err}"), } } #[derive(Eq, PartialEq)] pub(crate) struct Mapping { pub(crate) start: u64, pub(crate) end: u64, pub(crate) prot: ProtFlags, pub(crate) shared: bool, pub(crate) offset: u64, pub(crate) inode: Option, pub(crate) pathname: String, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct Inode { pub(crate) dev: Device, pub(crate) node: NonZeroU64, } impl Debug for Mapping { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use std::fmt::Write; let Mapping { start, end, prot, shared, offset, inode, ref pathname, } = *self; let (dev_maj, dev_min, inode): (u32, u32, u64) = inode .map(|inode| (inode.dev.major(), inode.dev.minor(), inode.node.into())) .unwrap_or_default(); write!(f, "{start:016x}-{end:016x} ")?; for (c, p) in [ ('r', ProtFlags::READ), ('w', ProtFlags::WRITE), ('x', ProtFlags::EXEC), ] { f.write_char(if prot.contains(p) { c } else { '-' })?; } f.write_char(if shared { 's' } else { 'p' })?; f.write_char(' ')?; let offset_end = self.offset + (self.end - self.start); write!( f, "{offset:016x}-{offset_end:016x} {dev_maj:04x}:{dev_min:04x} {inode:20} {pathname}" ) } } #[test] fn golden_mapping() { let line = "00400000-00407000 r--p 00000000 00:1e 7507895 /nix/store/hgl0ydlkgs6y6hx9h7k209shw3v7z77j-coreutils-9.0/bin/coreutils"; let golden = Mapping { start: 0x00400000, end: 0x00407000, prot: ProtFlags::READ, shared: false, offset: 0, inode: Some(Inode { dev: Device::new(0x00, 0x1e), node: NonZeroU64::new(7507895).unwrap(), }), pathname: "/nix/store/hgl0ydlkgs6y6hx9h7k209shw3v7z77j-coreutils-9.0/bin/coreutils" .to_owned(), }; assert_eq!(mapping(line).unwrap().1, golden); } // NOTE: this is a surjective mapping, since backslashes are not escaped // ie `escape_path("\\012") == escape_path("\n")` pub(crate) fn escape_path(path: &str) -> String { path.replace('\n', "\\012") }