use crate::{
error::{Error, ErrorExt, ErrorImpl},
flags::OpenFlags,
procfs::{ProcfsBase, ProcfsHandle},
};
use std::{
io::{BufRead, BufReader},
path::PathBuf,
str::FromStr,
};
pub(crate) fn sysctl_read_parse<T>(procfs: &ProcfsHandle, sysctl: &str) -> Result<T, Error>
where
T: FromStr,
T::Err: Into<ErrorImpl> + Into<Error>,
{
let mut sysctl_path = PathBuf::from("sys");
sysctl_path.push(sysctl.replace(".", "/"));
let sysctl_file = procfs.open(ProcfsBase::ProcRoot, sysctl_path, OpenFlags::O_RDONLY)?;
let mut reader = BufReader::new(sysctl_file);
let mut line = String::new();
reader
.read_line(&mut line)
.map_err(|err| ErrorImpl::OsError {
operation: format!("read first line of {sysctl:?} sysctl").into(),
source: err,
})?;
line.trim_end_matches("\n")
.parse()
.map_err(Error::from)
.with_wrap(|| {
format!(
"could not parse sysctl {sysctl:?} as {:?}",
std::any::type_name::<T>()
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
error::{Error, ErrorKind},
procfs::ProcfsHandle,
};
use once_cell::sync::Lazy;
use pretty_assertions::assert_eq;
static TEST_PROCFS_HANDLE: Lazy<ProcfsHandle> =
Lazy::new(|| ProcfsHandle::new().expect("should be able to get some /proc handle"));
#[test]
fn bad_sysctl_file_noexist() {
assert_eq!(
sysctl_read_parse::<String>(&TEST_PROCFS_HANDLE, "nonexistent.dummy.sysctl.path")
.as_ref()
.map_err(Error::kind),
Err(ErrorKind::OsError(Some(libc::ENOENT))),
"reading line from non-existent sysctl",
);
assert_eq!(
sysctl_read_parse::<u32>(&TEST_PROCFS_HANDLE, "nonexistent.sysctl.path")
.as_ref()
.map_err(Error::kind),
Err(ErrorKind::OsError(Some(libc::ENOENT))),
"parsing line from non-existent sysctl",
);
}
#[test]
fn bad_sysctl_file_noread() {
assert_eq!(
sysctl_read_parse::<String>(&TEST_PROCFS_HANDLE, "vm.drop_caches")
.as_ref()
.map_err(Error::kind),
Err(ErrorKind::OsError(Some(libc::EACCES))),
"reading line from non-readable sysctl",
);
assert_eq!(
sysctl_read_parse::<u32>(&TEST_PROCFS_HANDLE, "vm.drop_caches")
.as_ref()
.map_err(Error::kind),
Err(ErrorKind::OsError(Some(libc::EACCES))),
"parse line from non-readable sysctl",
);
}
#[test]
fn bad_sysctl_parse_invalid_multinumber() {
assert!(sysctl_read_parse::<String>(&TEST_PROCFS_HANDLE, "kernel.printk").is_ok());
assert_eq!(
sysctl_read_parse::<u32>(&TEST_PROCFS_HANDLE, "kernel.printk")
.as_ref()
.map_err(Error::kind),
Err(ErrorKind::InternalError),
"parsing line from multi-number sysctl",
);
}
#[test]
fn bad_sysctl_parse_invalid_nonnumber() {
assert!(sysctl_read_parse::<String>(&TEST_PROCFS_HANDLE, "kernel.random.uuid").is_ok());
assert_eq!(
sysctl_read_parse::<u32>(&TEST_PROCFS_HANDLE, "kernel.random.uuid")
.as_ref()
.map_err(Error::kind),
Err(ErrorKind::InternalError),
"parsing line from non-number sysctl",
);
}
#[test]
fn sysctl_parse_int() {
assert!(sysctl_read_parse::<String>(&TEST_PROCFS_HANDLE, "kernel.pid_max").is_ok());
assert!(sysctl_read_parse::<u64>(&TEST_PROCFS_HANDLE, "kernel.pid_max").is_ok());
}
}