use std::{fmt, fs, io, num, path};
use std::io::BufRead;
use super::regex;
use super::ProcessId;
#[derive(Clone)]
pub struct Permissions {
read: bool,
write: bool,
execute: bool,
shared: bool,
}
impl Permissions {
pub fn read(&self) -> bool {
self.read
}
pub fn write(&self) -> bool {
self.write
}
pub fn execute(&self) -> bool {
self.execute
}
pub fn shared(&self) -> bool {
self.shared
}
pub fn private(&self) -> bool {
!self.shared
}
}
impl fmt::Debug for Permissions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let read = if self.read { 'r' } else { '-' };
let write = if self.write { 'w' } else { '-' };
let execute = if self.execute { 'x' } else { '-' };
let shared = if self.shared { 's' } else { 'p' };
write!(f, "{}{}{}{}", read, write, execute, shared)
}
}
#[derive(Clone, Debug)]
pub struct MemoryRegion {
pub start_address: usize,
pub end_address: usize,
pub permissions: Permissions,
pub offset: usize,
pub dev_major: u32,
pub dev_minor: u32,
pub inode: Option<u64>,
pub pathname: Option<String>,
}
#[derive(Debug)]
pub enum Error {
ParseFailed,
IoError(io::Error),
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::IoError(e)
}
}
impl From<num::ParseIntError> for Error {
fn from(_: num::ParseIntError) -> Self {
Error::ParseFailed
}
}
pub struct MemoryRegionIter {
reader: io::BufReader<fs::File>,
buf: String,
finished: bool,
}
impl MemoryRegionIter {
fn parse_buf(&mut self) -> Result<MemoryRegion, Error> {
lazy_static! {
static ref RE: regex::Regex = regex::Regex::new(
"^([0-9a-f]+)-([0-9a-f]+) ([a-z-]{4}) ([0-9a-f]+) (\\d+):(\\d+) (\\d+) +(.*)$")
.expect("regex is valid");
}
let captures = RE.captures(self.buf.trim_right_matches('\n'))
.ok_or(Error::ParseFailed)?;
let start_address = usize::from_str_radix(&captures[1], 16)?;
let end_address = usize::from_str_radix(&captures[2], 16)?;
let p_bytes = &captures[3].as_bytes();
let permissions = Permissions {
read: p_bytes[0] == b'r',
write: p_bytes[1] == b'w',
execute: p_bytes[2] == b'x',
shared: p_bytes[3] == b's',
};
let offset = usize::from_str_radix(&captures[4], 16)?;
let dev_major = u32::from_str_radix(&captures[5], 16)?;
let dev_minor = u32::from_str_radix(&captures[6], 16)?;
let inode = u64::from_str_radix(&captures[7], 10)?;
let inode = if inode == 0 { None } else { Some(inode) };
let pathname = &captures[8];
let pathname = if pathname.is_empty() {
None
} else {
Some(String::from(pathname))
};
Ok(MemoryRegion {
start_address,
end_address,
permissions,
offset,
dev_major,
dev_minor,
inode,
pathname,
})
}
}
impl Iterator for MemoryRegionIter {
type Item = Result<MemoryRegion, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
self.buf.clear();
match self.reader.read_line(&mut self.buf) {
Ok(bytes_read) => {
if bytes_read == 0 {
self.finished = true;
None
} else {
Some(self.parse_buf())
}
}
Err(e) => Some(Err(Error::IoError(e))),
}
}
}
pub fn iter_mappings(pid: ProcessId) -> io::Result<MemoryRegionIter> {
let path = match pid {
ProcessId::SelfPid => String::from("/proc/self/maps"),
ProcessId::Num(n) => format!("/proc/{}/maps", n),
};
iter_mapping_path(path)
}
fn iter_mapping_path<P: AsRef<path::Path>>(path: P) -> io::Result<MemoryRegionIter> {
let f = fs::File::open(path)?;
let reader = io::BufReader::new(f);
Ok(MemoryRegionIter {
reader,
buf: String::new(),
finished: false,
})
}
#[cfg(test)]
mod tests {
extern crate libc;
use std::path;
use super::*;
#[test]
fn sample_maps_file() {
let path = path::Path::new("src/test-data/obexd-map.txt");
let mappings = iter_mapping_path(path)
.unwrap()
.map(|r| r.unwrap())
.collect::<Vec<MemoryRegion>>();
assert_eq!(80, mappings.len());
let first = &mappings[0];
assert_eq!(94858956906496, first.start_address);
assert_eq!(94858957352960, first.end_address);
assert!(first.permissions.read());
assert!(!first.permissions.write());
assert!(first.permissions.execute());
assert!(!first.permissions.shared());
assert!(first.permissions.private());
assert_eq!(0, first.offset);
assert_eq!(8, first.dev_major);
assert_eq!(3, first.dev_minor);
assert_eq!(Some(58219319), first.inode);
assert_eq!(
"/usr/libexec/bluetooth/obexd",
first.pathname.as_ref().unwrap()
);
let anon = &mappings[12];
assert_eq!(0, anon.offset);
assert_eq!(0, anon.dev_major);
assert_eq!(0, anon.dev_minor);
assert_eq!(None, anon.inode);
assert_eq!(None, anon.pathname.as_ref());
let libpthread = &mappings[50];
assert!(!libpthread.permissions.read());
assert!(!libpthread.permissions.write());
assert!(!libpthread.permissions.execute());
assert!(!libpthread.permissions.shared());
assert!(libpthread.permissions.private());
assert_eq!(106496, libpthread.offset);
let gconv = &mappings[71];
assert!(gconv.permissions.read());
assert!(!gconv.permissions.write());
assert!(!gconv.permissions.execute());
assert!(gconv.permissions.shared());
assert!(!gconv.permissions.private());
assert_eq!(3, gconv.dev_minor);
assert_eq!(Some(55705632), gconv.inode);
let last = &mappings[79];
assert_eq!(18446744073699065856, last.start_address);
assert_eq!(18446744073699069952, last.end_address);
assert!(last.permissions.read());
assert!(!last.permissions.write());
assert!(last.permissions.execute());
assert!(!last.permissions.shared());
assert!(last.permissions.private());
assert_eq!(0, last.offset);
assert_eq!(0, last.dev_major);
assert_eq!(0, last.dev_minor);
assert_eq!(None, last.inode);
assert_eq!("[vsyscall]", last.pathname.as_ref().unwrap());
}
#[test]
fn read_self() {
assert_eq!(
1,
iter_mappings(ProcessId::SelfPid)
.unwrap()
.filter_map(|r| r.unwrap().pathname.clone())
.filter(|p| p == "[stack]")
.count()
)
}
#[test]
fn read_own_pid() {
let pid = unsafe { libc::getpid() as u32 };
assert_eq!(
1,
iter_mappings(ProcessId::Num(pid))
.unwrap()
.filter_map(|r| r.unwrap().pathname.clone())
.filter(|p| p == "[stack]")
.count()
)
}
#[test]
fn permissions_debug() {
let p1 = Permissions {
read: false,
write: false,
execute: false,
shared: false,
};
assert_eq!("---p", format!("{:?}", p1));
let p2 = Permissions {
read: true,
write: true,
execute: true,
shared: true,
};
assert_eq!("rwxs", format!("{:?}", p2));
}
}