use failure::*; use std::u32; use std::fs::OpenOptions; use std::io::{BufRead, BufReader}; use std::collections::HashSet; use std::process; use std::net::{Ipv4Addr, Ipv6Addr}; use crate::tools; use lazy_static::lazy_static; use regex::Regex; use libc; /// POSIX sysconf call pub fn sysconf(name: i32) -> i64 { extern { fn sysconf(name: i32) -> i64; } unsafe { sysconf(name) } } lazy_static! { static ref CLOCK_TICKS: f64 = sysconf(libc::_SC_CLK_TCK) as f64; } pub struct ProcFsPidStat { pub status: u8, pub utime: u64, pub stime: u64, pub starttime: u64, pub vsize: u64, pub rss: i64, } pub fn read_proc_pid_stat(pid: libc::pid_t) -> Result { let statstr = tools::file_read_firstline(format!("/proc/{}/stat", pid))?; lazy_static! { static ref REGEX: Regex = Regex::new(concat!( r"^(?P\d+) \(.*\) (?P\S) -?\d+ -?\d+ -?\d+ -?\d+ -?\d+ \d+ \d+ \d+ \d+ \d+ ", r"(?P\d+) (?P\d+) -?\d+ -?\d+ -?\d+ -?\d+ -?\d+ 0 ", r"(?P\d+) (?P\d+) (?P-?\d+) ", r"\d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ -?\d+ -?\d+ \d+ \d+ \d+" )).unwrap(); } if let Some(cap) = REGEX.captures(&statstr) { if pid != cap["pid"].parse::().unwrap() { bail!("unable to read pid stat for process '{}' - got wrong pid", pid); } return Ok(ProcFsPidStat { status: cap["status"].as_bytes()[0], utime: cap["utime"].parse::().unwrap(), stime: cap["stime"].parse::().unwrap(), starttime: cap["starttime"].parse::().unwrap(), vsize: cap["vsize"].parse::().unwrap(), rss: cap["rss"].parse::().unwrap() * 4096, }); } bail!("unable to read pid stat for process '{}'", pid); } pub fn read_proc_starttime(pid: libc::pid_t) -> Result { let info = read_proc_pid_stat(pid)?; Ok(info.starttime) } pub fn check_process_running(pid: libc::pid_t) -> Option { if let Ok(info) = read_proc_pid_stat(pid) { if info.status != 'Z' as u8 { return Some(info); } } None } pub fn check_process_running_pstart(pid: libc::pid_t, pstart: u64) -> Option { if let Some(info) = check_process_running(pid) { if info.starttime == pstart { return Some(info); } } None } pub fn read_proc_uptime() -> Result<(f64, f64), Error> { let path = "/proc/uptime"; let line = tools::file_read_firstline(&path)?; let mut values = line.split_whitespace().map(|v| v.parse::()); match (values.next(), values.next()) { (Some(Ok(up)), Some(Ok(idle))) => return Ok((up, idle)), _ => bail!("Error while parsing '{}'", path), } } pub fn read_proc_uptime_ticks() -> Result<(u64, u64), Error> { let (mut up, mut idle) = read_proc_uptime()?; up *= *CLOCK_TICKS; idle *= *CLOCK_TICKS; Ok((up as u64, idle as u64)) } #[derive(Debug)] pub struct ProcFsMemInfo { pub memtotal: u64, pub memfree: u64, pub memused: u64, pub memshared: u64, pub swaptotal: u64, pub swapfree: u64, pub swapused: u64, } pub fn read_meminfo() -> Result { let path = "/proc/meminfo"; let file = OpenOptions::new().read(true).open(&path)?; let mut meminfo = ProcFsMemInfo { memtotal: 0, memfree: 0, memused: 0, memshared: 0, swaptotal: 0, swapfree: 0, swapused: 0, }; let (mut buffers, mut cached) = (0, 0); for line in BufReader::new(&file).lines() { let content = line?; let mut content_iter = content.split_whitespace(); if let (Some(key), Some(value)) = (content_iter.next(), content_iter.next()) { match key { "MemTotal:" => meminfo.memtotal = value.parse::()? * 1024, "MemFree:" => meminfo.memfree = value.parse::()? * 1024, "SwapTotal:" => meminfo.swaptotal = value.parse::()? * 1024, "SwapFree:" => meminfo.swapfree = value.parse::()? * 1024, "Buffers:" => buffers = value.parse::()? * 1024, "Cached:" => cached = value.parse::()? * 1024, _ => continue, } } } meminfo.memfree += buffers + cached; meminfo.memused = meminfo.memtotal - meminfo.memfree; meminfo.swapused = meminfo.swaptotal - meminfo.swapfree; let spages_line = tools::file_read_firstline("/sys/kernel/mm/ksm/pages_sharing")?; meminfo.memshared = spages_line.trim_end().parse::()? * 4096; Ok(meminfo) } #[derive(Clone, Debug)] pub struct ProcFsCPUInfo { pub user_hz: f64, pub mhz: f64, pub model: String, pub hvm: bool, pub sockets: usize, pub cpus: usize, } static CPU_INFO: Option = None; pub fn read_cpuinfo() -> Result { if let Some(cpu_info) = &CPU_INFO { return Ok(cpu_info.clone()); } let path = "/proc/cpuinfo"; let file = OpenOptions::new().read(true).open(&path)?; let mut cpuinfo = ProcFsCPUInfo { user_hz: *CLOCK_TICKS, mhz: 0.0, model: String::new(), hvm: false, sockets: 0, cpus: 0, }; let mut socket_ids = HashSet::new(); for line in BufReader::new(&file).lines() { let content = line?; if content.is_empty() { continue; } let mut content_iter = content.split(":"); match (content_iter.next(), content_iter.next()) { (Some(key), Some(value)) => { match key.trim_end() { "processor" => cpuinfo.cpus += 1, "model name" => cpuinfo.model = value.trim().to_string(), "cpu MHz" => cpuinfo.mhz = value.trim().parse::()?, "flags" => cpuinfo.hvm = value.contains(" vmx ") || value.contains(" svm "), "physical id" => { let id = value.trim().parse::()?; socket_ids.insert(id); }, _ => continue, } }, _ => bail!("Error while parsing '{}'", path), } } cpuinfo.sockets = socket_ids.len(); Ok(cpuinfo) } #[derive(Debug)] pub struct ProcFsMemUsage { pub size: u64, pub resident: u64, pub shared: u64, } pub fn read_memory_usage() -> Result { let path = format!("/proc/{}/statm", process::id()); let line = tools::file_read_firstline(&path)?; let mut values = line.split_whitespace().map(|v| v.parse::()); let ps = 4096; match (values.next(), values.next(), values.next()) { (Some(Ok(size)), Some(Ok(resident)), Some(Ok(shared))) => Ok(ProcFsMemUsage { size: size * ps, resident: resident * ps, shared: shared * ps, }), _ => bail!("Error while parsing '{}'", path), } } #[derive(Debug)] pub struct ProcFsNetDev { pub device: String, pub receive: u64, pub send: u64, } pub fn read_proc_net_dev() -> Result, Error> { let path = "/proc/net/dev"; let file = OpenOptions::new().read(true).open(&path)?; let mut result = Vec::new(); for line in BufReader::new(&file).lines().skip(2) { let content = line?; let mut iter = content.split_whitespace(); match (iter.next(), iter.next(), iter.skip(7).next()) { (Some(device), Some(receive), Some(send)) => { result.push(ProcFsNetDev { device: device[..device.len()-1].to_string(), receive: receive.parse::()?, send: send.parse::()?, }); }, _ => bail!("Error while parsing '{}'", path), } } Ok(result) } fn hex_nibble(c: u8) -> Result { Ok(match c { b'0'..=b'9' => c - b'0', b'a'..=b'f' => c - b'a' + 0xa, b'A'..=b'F' => c - b'A' + 0xa, _ => bail!("not a hex digit: {}", c as char), }) } fn hexstr_to_ipv4addr>(hex: T) -> Result { let hex = hex.as_ref(); if hex.len() != 8 { bail!("Error while converting hex string to IPv4 address: unexpected string length"); } let mut addr: [u8; 4] = unsafe { std::mem::uninitialized() }; for i in 0..4 { addr[3 - i] = (hex_nibble(hex[i * 2])? << 4) + hex_nibble(hex[i * 2 + 1])?; } Ok(Ipv4Addr::from(addr)) } #[derive(Debug)] pub struct ProcFsNetRoute { pub dest: Ipv4Addr, pub gateway: Ipv4Addr, pub mask: Ipv4Addr, pub metric: u32, pub mtu: u32, pub iface: String, } pub fn read_proc_net_route() -> Result, Error> { let path = "/proc/net/route"; let file = OpenOptions::new().read(true).open(&path)?; let mut result = Vec::new(); for line in BufReader::new(&file).lines().skip(1) { let content = line?; if content.is_empty() { continue; } let mut iter = content.split_whitespace(); let mut next = || iter.next() .ok_or(format_err!("Error while parsing '{}'", path)); let (iface, dest, gateway) = (next()?, next()?, next()?); for _ in 0..3 { next()?; } let (metric, mask, mtu) = (next()?, next()?, next()?); result.push(ProcFsNetRoute { dest: hexstr_to_ipv4addr(dest)?, gateway: hexstr_to_ipv4addr(gateway)?, mask: hexstr_to_ipv4addr(mask)?, metric: metric.parse()?, mtu: mtu.parse()?, iface: iface.to_string(), }); } Ok(result) } fn hexstr_to_ipv6addr>(hex: T) -> Result { let hex = hex.as_ref(); if hex.len() != 32 { bail!("Error while converting hex string to IPv6 address: unexpected string length"); } let mut addr: [u8; 16] = unsafe { std::mem::uninitialized() }; for i in 0..16 { addr[i] = (hex_nibble(hex[i * 2])? << 4) + hex_nibble(hex[i * 2 + 1])?; } Ok(Ipv6Addr::from(addr)) } fn hexstr_to_u8>(hex: T) -> Result { let hex = hex.as_ref(); if hex.len() != 2 { bail!("Error while converting hex string to u8: unexpected string length"); } Ok((hex_nibble(hex[0])? << 4) + hex_nibble(hex[1])?) } fn hexstr_to_u32>(hex: T) -> Result { let hex = hex.as_ref(); if hex.len() != 8 { bail!("Error while converting hex string to u32: unexpected string length"); } let mut bytes: [u8; 4] = unsafe { std::mem::uninitialized() }; for i in 0..4 { bytes[i] = (hex_nibble(hex[i * 2])? << 4) + hex_nibble(hex[i * 2 + 1])?; } Ok(u32::from_be_bytes(bytes)) } #[derive(Debug)] pub struct ProcFsNetIPv6Route { pub dest: Ipv6Addr, pub prefix: u8, pub gateway: Ipv6Addr, pub metric: u32, pub iface: String, } pub fn read_proc_net_ipv6_route() -> Result, Error> { let path = "/proc/net/ipv6_route"; let file = OpenOptions::new().read(true).open(&path)?; let mut result = Vec::new(); for line in BufReader::new(&file).lines() { let content = line?; if content.is_empty() { continue; } let mut iter = content.split_whitespace(); let mut next = || iter.next() .ok_or_else(|| format_err!("Error while parsing '{}'", path)); let (dest, prefix) = (next()?, next()?); for _ in 0..2 { next()?; } let (nexthop, metric) = (next()?, next()?); for _ in 0..3 { next()?; } let iface = next()?; result.push(ProcFsNetIPv6Route { dest: hexstr_to_ipv6addr(dest)?, prefix: hexstr_to_u8(prefix)?, gateway: hexstr_to_ipv6addr(nexthop)?, metric: hexstr_to_u32(metric)?, iface: iface.to_string(), }); } Ok(result) } #[cfg(test)] mod tests { use super::*; #[test] fn test_read_proc_net_route() { read_proc_net_route().unwrap(); } #[test] fn test_read_proc_net_ipv6_route() { read_proc_net_ipv6_route().unwrap(); } }