use crate::app::ScrollDirection;
use crate::kernel::cmd::{Command, ModuleCommand};
use crate::style::{Style, StyledText, Symbol};
use crate::util;
use bytesize::ByteSize;
use clap::ArgMatches;
use ratatui::text::{Line, Span, Text};
use std::error::Error;
use std::slice::Iter;
#[derive(Clone, Copy, Debug)]
enum SortType {
None,
Size,
Name,
Dependent,
}
impl SortType {
#[allow(dead_code)]
pub fn iter() -> Iter<'static, SortType> {
[
SortType::None,
SortType::Size,
SortType::Name,
SortType::Dependent,
]
.iter()
}
}
pub struct ListArgs {
sort: SortType,
reverse: bool,
regex: bool,
}
impl ListArgs {
pub fn new(args: &ArgMatches) -> Self {
let mut sort_type = SortType::None;
if let Some(("sort", matches)) = args.subcommand() {
if matches.get_flag("size") {
sort_type = SortType::Size;
} else if matches.get_flag("dependent") {
sort_type = SortType::Dependent;
} else {
sort_type = SortType::Name;
}
}
Self {
sort: sort_type,
reverse: args.try_get_one::<bool>("reverse").ok().flatten()
== Some(&true),
regex: args.try_get_one::<bool>("regex").ok().flatten() == Some(&true),
}
}
pub fn regex(&self) -> bool {
self.regex
}
}
pub struct KernelModules<'a> {
pub default_list: Vec<Vec<String>>,
pub list: Vec<Vec<String>>,
pub current_name: String,
pub current_info: StyledText<'a>,
pub command: ModuleCommand,
pub index: usize,
pub info_scroll_offset: usize,
pub style: Style,
pub args: ListArgs,
}
impl KernelModules<'_> {
pub fn new(args: ListArgs, style: Style) -> Self {
let mut kernel_modules = Self {
default_list: Vec::new(),
list: Vec::new(),
current_name: String::new(),
current_info: StyledText::default(),
command: ModuleCommand::None,
index: 0,
info_scroll_offset: 0,
args,
style,
};
if let Err(e) = kernel_modules.refresh() {
eprintln!("{e}");
}
kernel_modules
}
pub fn refresh(&mut self) -> Result<(), Box<dyn Error>> {
let mut module_list: Vec<Vec<String>> = Vec::new();
let mut module_read_cmd = String::from("cat /proc/modules");
match self.args.sort {
SortType::Size => module_read_cmd += " | sort -n -r -t ' ' -k2",
SortType::Name => module_read_cmd += " | sort -t ' ' -k1",
SortType::Dependent => module_read_cmd += " | sort -n -r -t ' ' -k3",
_ => {}
}
let modules_content = util::exec_cmd("sh", &["-c", &module_read_cmd])?;
for line in modules_content.lines() {
let columns: Vec<&str> = line.split_whitespace().collect();
let mut module_name = format!(" {}", columns[0]);
if columns.len() >= 7 {
module_name.push(' ');
module_name.push_str(columns[6]);
}
let mut used_modules = format!("{} {}", columns[2], columns[3]);
if used_modules.ends_with(',') {
used_modules.pop();
}
let module_size =
ByteSize::b(columns[1].parse().unwrap_or(0)).to_string_as(true);
module_list.push(vec![module_name, module_size, used_modules]);
}
if self.args.reverse {
module_list.reverse();
}
self.default_list.clone_from(&module_list);
self.list = module_list;
self.scroll_list(ScrollDirection::Top);
Ok(())
}
pub fn get_current_command(&self) -> Command {
self.command.get(&self.current_name)
}
pub fn set_current_command(
&mut self,
module_command: ModuleCommand,
command_name: String,
) {
if !command_name.contains(' ') && !self.current_name.starts_with('!') {
if !command_name.is_empty() {
self.current_name = command_name;
}
self.command = module_command;
self.current_info.set(
Text::from({
let mut spans = vec![
Line::from(Span::styled(
"Execute the following command? [y/N]:",
self.style.colored,
)),
Line::from(Span::styled(
self.get_current_command().cmd,
self.style.default,
)),
Line::default(),
];
spans.append(
&mut Text::styled(
self.get_current_command().desc,
self.style.colored,
)
.lines,
);
spans
}),
self.get_current_command().cmd,
);
self.info_scroll_offset = 0;
}
}
pub fn execute_command(&mut self) -> bool {
let mut command_executed = false;
if !self.command.is_none() {
match util::exec_cmd("sh", &["-c", &self.get_current_command().cmd]) {
Ok(_) => command_executed = true,
Err(e) => {
self.current_info.set(
Text::from({
let mut spans = vec![
Line::from(Span::styled(
"Failed to execute command:",
self.style.colored,
)),
Line::from(Span::styled(
format!("'{}'", self.get_current_command().cmd),
self.style.default,
)),
Line::default(),
];
spans.append(
&mut Text::styled(e.to_string(), self.style.default)
.lines,
);
spans
}),
format!(
"Execution Error\n'{}'\n{}",
self.get_current_command().cmd,
e
),
);
self.current_name =
format!("!Error{}", self.style.unicode.get(Symbol::NoEntry));
}
}
self.command = ModuleCommand::None;
}
command_executed
}
pub fn cancel_execution(&mut self) -> bool {
if !self.command.is_none() {
self.command = ModuleCommand::None;
if self.index != 0 {
self.index -= 1;
self.scroll_list(ScrollDirection::Down);
} else {
self.index += 1;
self.scroll_list(ScrollDirection::Up);
};
true
} else {
false
}
}
pub fn show_used_module(&mut self, mod_index: usize) {
if let Some(used_module) = self.list[self.index][2]
.split(' ')
.collect::<Vec<&str>>()
.get(1)
.unwrap_or(&"")
.split(',')
.collect::<Vec<&str>>()
.get(mod_index)
{
if let Some(v) = self
.list
.iter()
.position(|module| module[0] == format!(" {used_module}"))
{
match v {
0 => {
self.index = v + 1;
self.scroll_list(ScrollDirection::Up);
}
v if v > 0 => {
self.index = v - 1;
self.scroll_list(ScrollDirection::Down);
}
_ => {}
}
}
}
}
pub fn scroll_list(&mut self, direction: ScrollDirection) {
self.info_scroll_offset = 0;
if self.list.is_empty() {
self.index = 0;
} else {
match direction {
ScrollDirection::Up => self.previous_module(),
ScrollDirection::Down => self.next_module(),
ScrollDirection::Top => self.index = 0,
ScrollDirection::Bottom => self.index = self.list.len() - 1,
_ => {}
}
self.current_name = self.list[self.index][0]
.split_whitespace()
.next()
.unwrap_or("?")
.trim()
.to_string();
self.current_info.stylize_data(
Box::leak(
util::exec_cmd("modinfo", &[&self.current_name])
.unwrap_or_else(|_| {
String::from("module information not available")
})
.replace("signature: ", "signature: \n")
.into_boxed_str(),
),
":",
self.style.clone(),
);
if !self.command.is_none() {
self.command = ModuleCommand::None;
}
}
}
pub fn next_module(&mut self) {
self.index += 1;
if self.index > self.list.len() - 1 {
self.index = 0;
}
}
pub fn previous_module(&mut self) {
if self.index > 0 {
self.index -= 1;
} else {
self.index = self.list.len() - 1;
}
}
pub fn scroll_mod_info(
&mut self,
direction: ScrollDirection,
smooth_scroll: bool,
) {
let scroll_amount = if smooth_scroll { 1 } else { 2 };
match direction {
ScrollDirection::Up => {
if self.info_scroll_offset > scroll_amount - 1 {
self.info_scroll_offset -= scroll_amount;
}
}
ScrollDirection::Down => {
if self.current_info.lines() > 0 {
self.info_scroll_offset += scroll_amount;
self.info_scroll_offset %= self.current_info.lines() * 2;
}
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kernel_modules() {
let args = ArgMatches::default();
let mut list_args = ListArgs::new(&args);
list_args.sort = SortType::Size;
list_args.reverse = true;
let mut kernel_modules = KernelModules::new(list_args, Style::new(&args));
for sort_type in SortType::iter().rev().chain(SortType::iter()) {
kernel_modules.args.sort = *sort_type;
kernel_modules.refresh();
}
for direction in ScrollDirection::iter().rev().chain(ScrollDirection::iter())
{
kernel_modules.show_used_module(0);
kernel_modules.scroll_list(*direction);
kernel_modules
.scroll_mod_info(*direction, *direction == ScrollDirection::Up);
}
kernel_modules.scroll_list(ScrollDirection::Down);
assert_eq!(0, kernel_modules.index);
kernel_modules.scroll_list(ScrollDirection::Up);
assert_eq!(kernel_modules.default_list.len() - 1, kernel_modules.index);
assert_ne!(0, kernel_modules.default_list.len());
assert_ne!(0, kernel_modules.current_name.len());
assert_ne!(0, kernel_modules.current_info.lines());
kernel_modules
.set_current_command(ModuleCommand::Load, String::from("test"));
assert_eq!("test", kernel_modules.current_name);
assert!(!kernel_modules.execute_command());
kernel_modules.set_current_command(ModuleCommand::Load, String::new());
kernel_modules.scroll_list(ScrollDirection::Top);
for command in [
ModuleCommand::Unload,
ModuleCommand::Blacklist,
ModuleCommand::None,
] {
kernel_modules.set_current_command(command, String::new());
assert_eq!(!command.is_none(), kernel_modules.cancel_execution());
}
}
}