#[cfg(unix)]
extern crate libc;
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate lazy_static;
extern crate byteorder;
extern crate chrono;
extern crate hex;
extern crate libflate;
#[cfg(unix)]
mod platform_specific_items {
pub use libc::pid_t;
pub use libc::sysconf;
pub use libc::{_SC_CLK_TCK, _SC_PAGESIZE};
}
#[cfg(windows)]
mod platform_specific_items {
pub type pid_t = i32;
pub fn sysconf(_: i32) -> i64 {
panic!()
}
pub const _SC_CLK_TCK: i32 = 2;
pub const _SC_PAGESIZE: i32 = 30;
}
use platform_specific_items::*;
use std::collections::HashMap;
use std::ffi::CStr;
use std::fmt;
use std::fs::File;
use std::io::{self, Read, Write};
use std::mem;
use std::os::raw::c_char;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use chrono::{DateTime, Local};
const PROC_CONFIG_GZ: &str = "/proc/config.gz";
const BOOT_CONFIG: &str = "/boot/config";
trait IntoOption<T> {
fn into_option(t: Self) -> Option<T>;
}
impl<T> IntoOption<T> for Option<T> {
fn into_option(t: Option<T>) -> Option<T> {
t
}
}
impl<T, R> IntoOption<T> for Result<T, R> {
fn into_option(t: Result<T, R>) -> Option<T> {
t.ok()
}
}
#[macro_use]
macro_rules! expect {
($e:expr) => {
::IntoOption::into_option($e).unwrap_or_else(|| {
panic!(
"Failed to unwrap {}. Please report this as a procfs bug.",
stringify!($e)
)
})
};
($e:expr, $msg:expr) => {
::IntoOption::into_option($e).unwrap_or_else(|| {
panic!(
"Failed to unwrap {} ({}). Please report this as a procfs bug.",
stringify!($e),
$msg
)
})
};
}
#[macro_use]
macro_rules! from_str {
($t:tt, $e:expr) => {{
let e = $e;
$t::from_str_radix(e, 10).unwrap_or_else(|_| {
panic!(
"Failed to parse {} ({:?}) as a {}. Please report this as a procfs bug.",
stringify!($e),
e,
stringify!($t),
)
})
}};
($t:tt, $e:expr, $radix:expr) => {{
let e = $e;
$t::from_str_radix(e, $radix).unwrap_or_else(|_| {
panic!(
"Failed to parse {} ({:?}) as a {}. Please report this as a procfs bug.",
stringify!($e),
e,
stringify!($t)
)
})
}};
($t:tt, $e:expr, $radix:expr, pid:$pid:expr) => {{
let e = $e;
$t::from_str_radix(e, $radix).unwrap_or_else(|_| {
panic!(
"Failed to parse {} ({:?}) as a {} (pid {}). Please report this as a procfs bug.",
stringify!($e),
e,
stringify!($t),
$pid
)
})
}};
}
pub(crate) fn read_file<P: AsRef<Path>>(path: P) -> ProcResult<String> {
let mut f = FileWrapper::open(path)?;
let mut buf = String::new();
f.read_to_string(&mut buf)?;
Ok(buf)
}
pub(crate) fn write_file<P: AsRef<Path>, T: AsRef<[u8]>>(path: P, buf: T) -> ProcResult<()> {
let mut f = File::open(path)?;
f.write_all(buf.as_ref())?;
Ok(())
}
pub(crate) fn read_value<P: AsRef<Path>, T: FromStr<Err = E>, E: fmt::Debug>(
path: P,
) -> ProcResult<T> {
read_file(path).map(|buf| buf.trim().parse().unwrap())
}
pub(crate) fn write_value<P: AsRef<Path>, T: fmt::Display>(path: P, value: T) -> ProcResult<()> {
write_file(path, value.to_string().as_bytes())
}
mod process;
pub use process::*;
mod meminfo;
pub use meminfo::*;
mod net;
pub use net::*;
mod cpuinfo;
pub use cpuinfo::*;
mod cgroups;
pub use cgroups::*;
pub mod sys;
pub use sys::kernel::Version as KernelVersion;
lazy_static! {
static ref BOOTTIME: DateTime<Local> = {
boot_time().unwrap()
};
static ref TICKS_PER_SECOND: i64 = {
ticks_per_second().unwrap()
};
static ref KERNEL: KernelVersion = {
KernelVersion::current().unwrap()
};
static ref PAGESIZE: i64 = {
page_size().unwrap()
};
}
fn convert_to_bytes(num: u64, unit: &str) -> u64 {
match unit {
"B" => num,
"KiB" | "kiB" => num * 1024,
"kB" | "KB" => num * 1000,
"MiB" | "miB" => num * 1024 * 1024,
"MB" | "mB" => num * 1000 * 1000,
"GiB" | "giB" => num * 1024 * 1024 * 1024,
"GB" | "gB" => num * 1000 * 1000 * 1000,
unknown => panic!("Unknown unit type {}", unknown),
}
}
fn convert_to_kibibytes(num: u64, unit: &str) -> u64 {
match unit {
"B" => num,
"KiB" | "kiB" | "kB" | "KB" => num * 1024,
"MiB" | "miB" | "MB" | "mB" => num * 1024 * 1024,
"GiB" | "giB" | "GB" | "gB" => num * 1024 * 1024 * 1024,
unknown => panic!("Unknown unit type {}", unknown),
}
}
trait FromStrRadix: Sized {
fn from_str_radix(t: &str, radix: u32) -> Result<Self, std::num::ParseIntError>;
}
impl FromStrRadix for u64 {
fn from_str_radix(s: &str, radix: u32) -> Result<u64, std::num::ParseIntError> {
u64::from_str_radix(s, radix)
}
}
impl FromStrRadix for i32 {
fn from_str_radix(s: &str, radix: u32) -> Result<i32, std::num::ParseIntError> {
i32::from_str_radix(s, radix)
}
}
fn split_into_num<T: FromStrRadix>(s: &str, sep: char, radix: u32) -> (T, T) {
let mut s = s.split(sep);
let a = match FromStrRadix::from_str_radix(s.next().unwrap(), radix) {
Ok(v) => v,
_ => panic!(),
};
let b = match FromStrRadix::from_str_radix(s.next().unwrap(), radix) {
Ok(v) => v,
_ => panic!(),
};
(a, b)
}
#[derive(Debug)]
struct IoErrorWrapper {
path: PathBuf,
inner: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl std::error::Error for IoErrorWrapper {}
impl fmt::Display for IoErrorWrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if let Some(inner) = &self.inner {
write!(f, "IO Error({}): {}", self.path.display(), inner)
} else {
write!(f, "IO Error({})", self.path.display())
}
}
}
struct FileWrapper {
inner: File,
path: PathBuf,
}
impl FileWrapper {
fn open<P: AsRef<Path>>(path: P) -> Result<FileWrapper, io::Error> {
let p = path.as_ref();
match File::open(&p) {
Ok(f) => Ok(FileWrapper {
inner: f,
path: p.to_owned(),
}),
Err(e) => {
let kind = e.kind();
Err(io::Error::new(
kind,
IoErrorWrapper {
path: p.to_owned(),
inner: e.into_inner(),
},
))
}
}
}
}
macro_rules! wrap_io_error {
($path:expr, $expr:expr) => {
match $expr {
Ok(v) => Ok(v),
Err(e) => {
let kind = e.kind();
Err(io::Error::new(
kind,
IoErrorWrapper {
path: $path.clone(),
inner: e.into_inner(),
},
))
}
}
};
}
impl Read for FileWrapper {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
wrap_io_error!(self.path, self.inner.read(buf))
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
wrap_io_error!(self.path, self.inner.read_to_end(buf))
}
fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
wrap_io_error!(self.path, self.inner.read_to_string(buf))
}
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
wrap_io_error!(self.path, self.inner.read_exact(buf))
}
}
pub type ProcResult<T> = Result<T, ProcError>;
#[derive(Debug)]
pub enum ProcError {
PermissionDenied(Option<PathBuf>),
NotFound(Option<PathBuf>),
Incomplete(Option<PathBuf>),
Io(std::io::Error, Option<PathBuf>),
Other(String),
}
impl From<std::io::Error> for ProcError {
fn from(io: std::io::Error) -> Self {
use std::io::ErrorKind;
let kind = io.kind();
let path: Option<PathBuf> = io.get_ref().and_then(|inner| {
if let Some(ref inner) = inner.downcast_ref::<IoErrorWrapper>() {
Some(inner.path.clone())
} else {
None
}
});
match kind {
ErrorKind::PermissionDenied => ProcError::PermissionDenied(path),
ErrorKind::NotFound => ProcError::NotFound(path),
_other => ProcError::Io(io, path),
}
}
}
impl std::fmt::Display for ProcError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
ProcError::PermissionDenied(Some(p)) => write!(f, "Permission Denied: {}", p.display()),
ProcError::NotFound(Some(p)) => write!(f, "File not found: {}", p.display()),
ProcError::Incomplete(Some(p)) => write!(f, "Data incomplete: {}", p.display()),
ProcError::Io(inner, Some(p)) => {
write!(f, "Unexpected IO error({}): {}", p.display(), inner)
}
ProcError::PermissionDenied(None) => write!(f, "Permission Denied"),
ProcError::NotFound(None) => write!(f, "File not found"),
ProcError::Incomplete(None) => write!(f, "Data incomplete"),
ProcError::Io(inner, None) => write!(f, "Unexpected IO error: {}", inner),
ProcError::Other(s) => write!(f, "Uknown error {}", s),
}
}
}
impl std::error::Error for ProcError {}
#[derive(Debug)]
pub struct LoadAverage {
pub one: f32,
pub five: f32,
pub fifteen: f32,
pub cur: u32,
pub max: u32,
pub latest_pid: u32,
}
impl LoadAverage {
pub fn new() -> ProcResult<LoadAverage> {
let mut f = FileWrapper::open("/proc/loadavg")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
let mut s = s.split_whitespace();
let one = f32::from_str(s.next().unwrap()).unwrap();
let five = f32::from_str(s.next().unwrap()).unwrap();
let fifteen = f32::from_str(s.next().unwrap()).unwrap();
let curmax = s.next().unwrap();
let latest_pid = u32::from_str(s.next().unwrap()).unwrap();
let mut s = curmax.split('/');
let cur = u32::from_str(s.next().unwrap()).unwrap();
let max = u32::from_str(s.next().unwrap()).unwrap();
Ok(LoadAverage {
one,
five,
fifteen,
cur,
max,
latest_pid,
})
}
}
pub fn ticks_per_second() -> std::io::Result<i64> {
if cfg!(unix) {
match unsafe { sysconf(_SC_CLK_TCK) } {
-1 => Err(std::io::Error::last_os_error()),
x => Ok(x),
}
} else {
panic!("Not supported on non-unix platforms")
}
}
pub fn boot_time() -> ProcResult<DateTime<Local>> {
let now = Local::now();
let mut f = FileWrapper::open("/proc/uptime")?;
let mut buf = String::new();
f.read_to_string(&mut buf)?;
let uptime_seconds = f32::from_str(buf.split_whitespace().next().unwrap()).unwrap();
Ok(now - chrono::Duration::milliseconds((uptime_seconds * 1000.0) as i64))
}
pub fn page_size() -> std::io::Result<i64> {
if cfg!(unix) {
match unsafe { sysconf(_SC_PAGESIZE) } {
-1 => Err(std::io::Error::last_os_error()),
x => Ok(x),
}
} else {
panic!("Not supported on non-unix platforms")
}
}
#[derive(Debug, PartialEq)]
pub enum ConfigSetting {
Yes,
Module,
Value(String),
}
pub fn kernel_config() -> ProcResult<HashMap<String, ConfigSetting>> {
use libflate::gzip::Decoder;
use std::io::{BufRead, BufReader};
let reader: Box<BufRead> = if Path::new(PROC_CONFIG_GZ).exists() {
let file = FileWrapper::open(PROC_CONFIG_GZ)?;
let decoder = Decoder::new(file)?;
Box::new(BufReader::new(decoder))
} else {
let mut kernel: libc::utsname = unsafe { mem::zeroed() };
if unsafe { libc::uname(&mut kernel) != 0 } {
return Err(ProcError::Other("Failed to call uname()".to_string()));
}
let filename = format!(
"{}-{}",
BOOT_CONFIG,
unsafe { CStr::from_ptr(kernel.release.as_ptr() as *const c_char) }.to_string_lossy()
);
if Path::new(&filename).exists() {
let file = FileWrapper::open(filename)?;
Box::new(BufReader::new(file))
} else {
let file = FileWrapper::open(BOOT_CONFIG)?;
Box::new(BufReader::new(file))
}
};
let mut map = HashMap::new();
for line in reader.lines() {
let line = line?;
if line.starts_with('#') {
continue;
}
if line.contains('=') {
let mut s = line.splitn(2, '=');
let name = expect!(s.next()).to_owned();
let value = match expect!(s.next()) {
"y" => ConfigSetting::Yes,
"m" => ConfigSetting::Module,
s => ConfigSetting::Value(s.to_owned()),
};
map.insert(name, value);
}
}
Ok(map)
}
pub fn meminfo() -> ProcResult<Meminfo> {
Meminfo::new()
}
#[cfg(test)]
mod tests {
extern crate failure;
use super::*;
#[test]
fn test_kernel_const() {
println!("{:?}", *KERNEL);
}
#[test]
fn test_kernel_from_str() {
let k = KernelVersion::from_str("1.2.3").unwrap();
assert_eq!(k.major, 1);
assert_eq!(k.minor, 2);
assert_eq!(k.patch, 3);
let k = KernelVersion::from_str("4.9.16-gentoo").unwrap();
assert_eq!(k.major, 4);
assert_eq!(k.minor, 9);
assert_eq!(k.patch, 16);
}
#[test]
fn test_kernel_cmp() {
let a = KernelVersion::from_str("1.2.3").unwrap();
let b = KernelVersion::from_str("1.2.3").unwrap();
let c = KernelVersion::from_str("1.2.4").unwrap();
let d = KernelVersion::from_str("1.5.4").unwrap();
let e = KernelVersion::from_str("2.5.4").unwrap();
assert_eq!(a, b);
assert!(a < c);
assert!(a < d);
assert!(a < e);
assert!(e > d);
assert!(e > c);
assert!(e > b);
}
#[test]
fn test_loadavg() {
let load = LoadAverage::new().unwrap();
println!("{:?}", load);
}
#[test]
fn test_from_str() {
assert_eq!(from_str!(u8, "12"), 12);
assert_eq!(from_str!(u8, "A", 16), 10);
}
#[test]
#[should_panic]
fn test_from_str_panic() {
let s = "four";
from_str!(u8, s);
}
#[test]
fn test_kernel_config() {
match std::env::var("TRAVIS") {
Ok(ref s) if s == "true" => return,
_ => {}
}
let config = kernel_config().unwrap();
println!("{:#?}", config);
}
#[test]
fn test_file_io_errors() {
fn inner<P: AsRef<Path>>(p: P) -> Result<(), ProcError> {
let mut file = FileWrapper::open(p)?;
let mut buf = [0; 128];
file.read_exact(&mut buf[0..128])?;
Ok(())
}
let err = inner("/this_should_not_exist").unwrap_err();
println!("{}", err);
match err {
ProcError::NotFound(Some(p)) => {
assert_eq!(p, Path::new("/this_should_not_exist"));
}
x => panic!("Unexpected return value: {:?}", x),
}
match inner("/proc/loadavg") {
Err(ProcError::Io(_, Some(p))) => {
assert_eq!(p, Path::new("/proc/loadavg"));
}
x => panic!("Unexpected return value: {:?}", x),
}
}
#[test]
fn test_failure() {
fn inner() -> Result<(), failure::Error> {
let _load = LoadAverage::new()?;
Ok(())
}
let _ = inner();
fn inner2() -> Result<(), failure::Error> {
let proc = Process::new(1)?;
let _io = proc.maps()?;
Ok(())
}
let _ = inner2();
}
}