refactor(sysctl): split sysctl implementation into modules

This commit is contained in:
Orhun Parmaksız 2021-12-04 23:47:36 +03:00
parent 41ca625e99
commit 9c1cdaa188
No known key found for this signature in database
GPG key ID: F83424824B3E4B90
11 changed files with 338 additions and 321 deletions

View file

@ -1,5 +1,5 @@
use crate::display::DisplayType;
use crate::sysctl::Section;
use crate::sysctl::display::DisplayType;
use crate::sysctl::section::Section;
use colored::Color;
use std::collections::HashMap;

View file

@ -8,7 +8,7 @@ extern crate lazy_static;
/// Export regex crate.
pub use systeroid_parser::regex;
/// Kernel parameter handler.
/// Sysctl implementation.
pub mod sysctl;
/// Error implementation.
@ -20,8 +20,5 @@ pub mod parsers;
/// Configuration.
pub mod config;
/// Display options.
pub mod display;
/// Cache manager.
pub mod cache;

View file

@ -1,312 +0,0 @@
use crate::config::{AppConfig, SysctlConfig};
use crate::display::DisplayType;
use crate::error::Result;
use crate::parsers::parse_kernel_docs;
use colored::*;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt::{self, Display, Formatter};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::result::Result as StdResult;
use sysctl::{Ctl, CtlFlags, CtlIter, Sysctl as SysctlImpl};
/// Sections of the sysctl documentation.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum Section {
/// Documentation for `/proc/sys/abi/*`
Abi,
/// Documentation for `/proc/sys/fs/*`
Fs,
/// Documentation for `/proc/sys/kernel/*`
Kernel,
/// Documentation for `/proc/sys/net/*`
Net,
/// Documentation for `/proc/sys/sunrpc/*`
Sunrpc,
/// Documentation for `/proc/sys/user/*`
User,
/// Documentation for `/proc/sys/vm/*`
Vm,
/// Unknown.
Unknown,
}
impl From<String> for Section {
fn from(value: String) -> Self {
for section in Self::variants() {
if value.starts_with(&format!("{}.", section)) {
return *section;
}
}
Self::Unknown
}
}
impl<'a> From<&'a Path> for Section {
fn from(value: &'a Path) -> Self {
for section in Self::variants() {
if value.file_stem().map(|v| v.to_str()).flatten() == Some(&section.to_string()) {
return *section;
}
}
Self::Net
}
}
impl Display for Section {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", format!("{:?}", self).to_lowercase())
}
}
impl Section {
/// Returns the variants.
pub fn variants() -> &'static [Section] {
&[
Self::Abi,
Self::Fs,
Self::Kernel,
Self::Net,
Self::Sunrpc,
Self::User,
Self::Vm,
]
}
}
/// Representation of a kernel parameter.
#[derive(Serialize, Deserialize, Debug)]
pub struct Parameter {
/// Name of the kernel parameter.
pub name: String,
/// Value of the kernel parameter.
#[serde(skip)]
pub value: String,
/// Description of the kernel parameter
pub description: Option<String>,
/// Section of the kernel parameter.
pub section: Section,
/// Documentation path.
pub docs_path: PathBuf,
/// Title of the kernel parameter taken from the documentation.
pub docs_title: String,
}
impl Parameter {
/// Returns the parameter name with corresponding section colors.
pub fn colored_name(&self, config: &AppConfig) -> String {
let fields = self.name.split('.').collect::<Vec<&str>>();
fields
.iter()
.enumerate()
.fold(String::new(), |mut result, (i, v)| {
if i != fields.len() - 1 {
let section_color = *(config
.section_colors
.get(&self.section)
.unwrap_or(&config.default_color));
result += &format!(
"{}{}",
v.color(section_color),
".".color(config.default_color)
);
} else {
result += v;
}
result
})
}
/// Prints the kernel parameter to given output.
pub fn display_value<W: Write>(&self, config: &AppConfig, output: &mut W) -> Result<()> {
if !config.no_color {
match config.display_type {
DisplayType::Name => {
writeln!(output, "{}", self.colored_name(config))?;
}
DisplayType::Value => {
writeln!(output, "{}", self.value.bold())?;
}
DisplayType::Binary => {
write!(output, "{}", self.value.bold())?;
}
DisplayType::Default => {
writeln!(
output,
"{} {} {}",
self.colored_name(config),
"=".color(config.default_color),
self.value.bold(),
)?;
}
}
} else {
match config.display_type {
DisplayType::Name => {
writeln!(output, "{}", self.name)?;
}
DisplayType::Value => {
writeln!(output, "{}", self.value)?;
}
DisplayType::Binary => {
write!(output, "{}", self.value)?;
}
DisplayType::Default => {
writeln!(output, "{} = {}", self.name, self.value)?;
}
}
}
Ok(())
}
/// Returns the parameter documentation if it exists.
pub fn get_documentation(&self) -> Option<String> {
self.description.as_ref().map(|description| {
format!(
"{}\n{}\n{}\n-\nParameter: {}\nReference: {}",
self.docs_title,
"=".repeat(self.docs_title.len()),
description,
self.name,
self.docs_path.to_string_lossy()
)
})
}
/// Prints the description of the kernel parameter to the given output.
pub fn display_documentation<W: Write>(&self, output: &mut W) -> Result<()> {
if let Some(documentation) = self.get_documentation() {
writeln!(output, "{}", documentation)?;
} else {
writeln!(output, "No documentation available")?;
}
Ok(())
}
/// Sets a new value for the kernel parameter.
pub fn update_value<W: Write>(
&mut self,
new_value: &str,
config: &AppConfig,
output: &mut W,
) -> Result<()> {
let ctl = Ctl::new(&self.name)?;
let new_value = ctl.set_value_string(new_value)?;
self.value = new_value;
self.display_value(config, output)
}
}
impl<'a> TryFrom<&'a Ctl> for Parameter {
type Error = crate::error::Error;
fn try_from(ctl: &'a Ctl) -> Result<Self> {
Ok(Parameter {
name: ctl.name()?,
value: ctl.value_string()?,
description: ctl
.description()
.ok()
.and_then(|v| (v == "[N/A]").then(|| None)?),
section: Section::from(ctl.name()?),
docs_path: PathBuf::new(),
docs_title: String::new(),
})
}
}
/// Sysctl wrapper for managing the kernel parameters.
#[derive(Debug)]
pub struct Sysctl {
/// Available kernel parameters.
pub parameters: Vec<Parameter>,
/// Configuration.
pub config: SysctlConfig,
}
impl Sysctl {
/// Constructs a new instance by fetching the available kernel parameters.
pub fn init(config: SysctlConfig) -> Result<Self> {
let mut parameters = Vec::new();
for ctl in CtlIter::root().filter_map(StdResult::ok).filter(|ctl| {
ctl.flags()
.map(|flags| !flags.contains(CtlFlags::SKIP))
.unwrap_or(false)
}) {
match Parameter::try_from(&ctl) {
Ok(parameter) => {
parameters.push(parameter);
}
Err(e) => {
eprintln!("error: `{} ({})`", e, ctl.name()?);
}
}
}
Ok(Self { parameters, config })
}
/// Searches and returns the parameter if it exists.
pub fn get_parameter(&mut self, param_name: &str) -> Option<&mut Parameter> {
let parameter = self
.parameters
.iter_mut()
.find(|param| param.name == *param_name);
if parameter.is_none() && !self.config.ignore_errors {
eprintln!(
"{}: cannot stat /proc/{}: No such file or directory",
env!("CARGO_PKG_NAME").split('-').collect::<Vec<_>>()[0],
param_name.replace(".", "/")
)
}
parameter
}
/// Updates the parameters using the given list.
///
/// Keeps the original values.
pub fn update_params(&mut self, mut parameters: Vec<Parameter>) {
parameters.par_iter_mut().for_each(|parameter| {
if let Some(param) = self
.parameters
.par_iter()
.find_any(|param| param.name == parameter.name)
{
parameter.value = param.value.to_string();
}
});
self.parameters = parameters;
}
/// Updates the descriptions of the kernel parameters.
pub fn update_docs(&mut self, kernel_docs: &Path) -> Result<()> {
let documents = parse_kernel_docs(kernel_docs)?;
self.parameters
.par_iter_mut()
.filter(|p| p.description.is_none())
.for_each(|param| {
for document in documents
.iter()
.filter(|document| Section::from(document.path.as_path()) == param.section)
{
if let Some(paragraph) =
document.paragraphs.par_iter().find_first(|paragraph| {
match param.name.split('.').collect::<Vec<&str>>().last() {
Some(absolute_name) => {
absolute_name.len() > 2
&& paragraph.title.contains(absolute_name)
}
_ => false,
}
})
{
param.description = Some(paragraph.contents.to_owned());
param.docs_title = paragraph.title.to_owned();
param.docs_path = document.path.clone();
continue;
}
}
});
Ok(())
}
}

View file

@ -0,0 +1,105 @@
use crate::config::SysctlConfig;
use crate::error::Result;
use crate::parsers::parse_kernel_docs;
use crate::sysctl::parameter::Parameter;
use crate::sysctl::section::Section;
use rayon::prelude::*;
use std::convert::TryFrom;
use std::path::Path;
use std::result::Result as StdResult;
use sysctl::{CtlFlags, CtlIter, Sysctl as SysctlImpl};
/// Sysctl wrapper for managing the kernel parameters.
#[derive(Debug)]
pub struct Sysctl {
/// Available kernel parameters.
pub parameters: Vec<Parameter>,
/// Configuration.
pub config: SysctlConfig,
}
impl Sysctl {
/// Constructs a new instance by fetching the available kernel parameters.
pub fn init(config: SysctlConfig) -> Result<Self> {
let mut parameters = Vec::new();
for ctl in CtlIter::root().filter_map(StdResult::ok).filter(|ctl| {
ctl.flags()
.map(|flags| !flags.contains(CtlFlags::SKIP))
.unwrap_or(false)
}) {
match Parameter::try_from(&ctl) {
Ok(parameter) => {
parameters.push(parameter);
}
Err(e) => {
eprintln!("error: `{} ({})`", e, ctl.name()?);
}
}
}
Ok(Self { parameters, config })
}
/// Searches and returns the parameter if it exists.
pub fn get_parameter(&mut self, param_name: &str) -> Option<&mut Parameter> {
let parameter = self
.parameters
.iter_mut()
.find(|param| param.name == *param_name);
if parameter.is_none() && !self.config.ignore_errors {
eprintln!(
"{}: cannot stat /proc/{}: No such file or directory",
env!("CARGO_PKG_NAME").split('-').collect::<Vec<_>>()[0],
param_name.replace(".", "/")
)
}
parameter
}
/// Updates the parameters using the given list.
///
/// Keeps the original values.
pub fn update_params(&mut self, mut parameters: Vec<Parameter>) {
parameters.par_iter_mut().for_each(|parameter| {
if let Some(param) = self
.parameters
.par_iter()
.find_any(|param| param.name == parameter.name)
{
parameter.value = param.value.to_string();
}
});
self.parameters = parameters;
}
/// Updates the descriptions of the kernel parameters.
pub fn update_docs(&mut self, kernel_docs: &Path) -> Result<()> {
let documents = parse_kernel_docs(kernel_docs)?;
self.parameters
.par_iter_mut()
.filter(|p| p.description.is_none())
.for_each(|param| {
for document in documents
.iter()
.filter(|document| Section::from(document.path.as_path()) == param.section)
{
if let Some(paragraph) =
document.paragraphs.par_iter().find_first(|paragraph| {
match param.name.split('.').collect::<Vec<&str>>().last() {
Some(absolute_name) => {
absolute_name.len() > 2
&& paragraph.title.contains(absolute_name)
}
_ => false,
}
})
{
param.description = Some(paragraph.contents.to_owned());
param.docs_title = paragraph.title.to_owned();
param.docs_path = document.path.clone();
continue;
}
}
});
Ok(())
}
}

View file

@ -0,0 +1,11 @@
/// Sysctl wrapper.
pub mod controller;
/// Sysctl section.
pub mod section;
/// Sysctl display options.
pub mod display;
/// Kernel parameter.
pub mod parameter;

View file

@ -0,0 +1,149 @@
use crate::config::AppConfig;
use crate::error::Result;
use crate::sysctl::display::DisplayType;
use crate::sysctl::section::Section;
use colored::*;
use serde::{Deserialize, Serialize};
use std::io::Write;
use std::path::PathBuf;
use sysctl::{Ctl, Sysctl as SysctlImpl};
/// Representation of a kernel parameter.
#[derive(Serialize, Deserialize, Debug)]
pub struct Parameter {
/// Name of the kernel parameter.
pub name: String,
/// Value of the kernel parameter.
#[serde(skip)]
pub value: String,
/// Description of the kernel parameter
pub description: Option<String>,
/// Section of the kernel parameter.
pub section: Section,
/// Documentation path.
pub docs_path: PathBuf,
/// Title of the kernel parameter taken from the documentation.
pub docs_title: String,
}
impl<'a> TryFrom<&'a Ctl> for Parameter {
type Error = crate::error::Error;
fn try_from(ctl: &'a Ctl) -> Result<Self> {
Ok(Parameter {
name: ctl.name()?,
value: ctl.value_string()?,
description: ctl
.description()
.ok()
.and_then(|v| (v == "[N/A]").then(|| None)?),
section: Section::from(ctl.name()?),
docs_path: PathBuf::new(),
docs_title: String::new(),
})
}
}
impl Parameter {
/// Returns the parameter name with corresponding section colors.
pub fn colored_name(&self, config: &AppConfig) -> String {
let fields = self.name.split('.').collect::<Vec<&str>>();
fields
.iter()
.enumerate()
.fold(String::new(), |mut result, (i, v)| {
if i != fields.len() - 1 {
let section_color = *(config
.section_colors
.get(&self.section)
.unwrap_or(&config.default_color));
result += &format!(
"{}{}",
v.color(section_color),
".".color(config.default_color)
);
} else {
result += v;
}
result
})
}
/// Prints the kernel parameter to given output.
pub fn display_value<W: Write>(&self, config: &AppConfig, output: &mut W) -> Result<()> {
if !config.no_color {
match config.display_type {
DisplayType::Name => {
writeln!(output, "{}", self.colored_name(config))?;
}
DisplayType::Value => {
writeln!(output, "{}", self.value.bold())?;
}
DisplayType::Binary => {
write!(output, "{}", self.value.bold())?;
}
DisplayType::Default => {
writeln!(
output,
"{} {} {}",
self.colored_name(config),
"=".color(config.default_color),
self.value.bold(),
)?;
}
}
} else {
match config.display_type {
DisplayType::Name => {
writeln!(output, "{}", self.name)?;
}
DisplayType::Value => {
writeln!(output, "{}", self.value)?;
}
DisplayType::Binary => {
write!(output, "{}", self.value)?;
}
DisplayType::Default => {
writeln!(output, "{} = {}", self.name, self.value)?;
}
}
}
Ok(())
}
/// Returns the parameter documentation if it exists.
pub fn get_documentation(&self) -> Option<String> {
self.description.as_ref().map(|description| {
format!(
"{}\n{}\n{}\n-\nParameter: {}\nReference: {}",
self.docs_title,
"=".repeat(self.docs_title.len()),
description,
self.name,
self.docs_path.to_string_lossy()
)
})
}
/// Prints the description of the kernel parameter to the given output.
pub fn display_documentation<W: Write>(&self, output: &mut W) -> Result<()> {
if let Some(documentation) = self.get_documentation() {
writeln!(output, "{}", documentation)?;
} else {
writeln!(output, "No documentation available")?;
}
Ok(())
}
/// Sets a new value for the kernel parameter.
pub fn update_value<W: Write>(
&mut self,
new_value: &str,
config: &AppConfig,
output: &mut W,
) -> Result<()> {
let ctl = Ctl::new(&self.name)?;
let new_value = ctl.set_value_string(new_value)?;
self.value = new_value;
self.display_value(config, output)
}
}

View file

@ -0,0 +1,67 @@
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use std::path::Path;
/// Sections of the sysctl documentation.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum Section {
/// Documentation for `/proc/sys/abi/*`
Abi,
/// Documentation for `/proc/sys/fs/*`
Fs,
/// Documentation for `/proc/sys/kernel/*`
Kernel,
/// Documentation for `/proc/sys/net/*`
Net,
/// Documentation for `/proc/sys/sunrpc/*`
Sunrpc,
/// Documentation for `/proc/sys/user/*`
User,
/// Documentation for `/proc/sys/vm/*`
Vm,
/// Unknown.
Unknown,
}
impl From<String> for Section {
fn from(value: String) -> Self {
for section in Self::variants() {
if value.starts_with(&format!("{}.", section)) {
return *section;
}
}
Self::Unknown
}
}
impl<'a> From<&'a Path> for Section {
fn from(value: &'a Path) -> Self {
for section in Self::variants() {
if value.file_stem().map(|v| v.to_str()).flatten() == Some(&section.to_string()) {
return *section;
}
}
Self::Net
}
}
impl Display for Section {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", format!("{:?}", self).to_lowercase())
}
}
impl Section {
/// Returns the variants.
pub fn variants() -> &'static [Section] {
&[
Self::Abi,
Self::Fs,
Self::Kernel,
Self::Net,
Self::Sunrpc,
Self::User,
Self::Vm,
]
}
}

View file

@ -7,7 +7,7 @@ use systeroid_core::config::AppConfig;
use systeroid_core::error::Result;
use systeroid_core::parsers::KERNEL_DOCS_PATH;
use systeroid_core::regex::Regex;
use systeroid_core::sysctl::Sysctl;
use systeroid_core::sysctl::controller::Sysctl;
/// Label for caching the kernel parameters.
const PARAMETERS_CACHE_LABEL: &str = "parameters";

View file

@ -1,8 +1,8 @@
use getopts::Options;
use std::env;
use std::path::PathBuf;
use systeroid_core::display::DisplayType;
use systeroid_core::regex::Regex;
use systeroid_core::sysctl::display::DisplayType;
/// Help message for the arguments.
const HELP_MESSAGE: &str = r#"

View file

@ -12,7 +12,7 @@ use crate::args::Args;
use std::env;
use systeroid_core::config::Config;
use systeroid_core::error::Result;
use systeroid_core::sysctl::Sysctl;
use systeroid_core::sysctl::controller::Sysctl;
/// Runs `systeroid`.
pub fn run(args: Args) -> Result<()> {