diff --git a/Cargo.toml b/Cargo.toml index 0a1fb86..20af51f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" unsafe_code = "forbid" [lints.clippy] +redundant_closure_for_method_calls = "allow" similar_names = "allow" pedantic = { level = "warn", priority = -1 } diff --git a/src/main.rs b/src/main.rs index 1cb5203..767955a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ mod sysfs; use std::{env, thread::sleep}; +use sysfs::NetdevError; + use crate::{cfg::Config, measure::LinkSpeed}; fn main() -> eyre::Result<()> { @@ -19,7 +21,12 @@ fn main() -> eyre::Result<()> { (Some(_), Some(na)) => na, (Some(nc), None) => nc, (None, Some(na)) => na, - (None, None) => eyre::bail!("No network device specified"), + (None, None) => { + eyre::bail!( + "No network device specified. Please provide a network device name as an argument or in the config file.\n\n{}", + NetdevError::available_netdevs_msg() + ); + } }; let link_speed = LinkSpeed::new(&netdev_name)?; diff --git a/src/sysfs.rs b/src/sysfs.rs index 0b395ae..78a0bcf 100644 --- a/src/sysfs.rs +++ b/src/sysfs.rs @@ -3,24 +3,73 @@ use std::{ io::{Read as _, Seek as _, SeekFrom}, }; -use eyre::Context as _; - -pub(crate) struct Netdev { +pub struct Netdev { rx_bytes: File, tx_bytes: File, } +#[derive(Debug)] +pub enum NetdevError { + NotFound { name: String }, + IoError { name: String, err: std::io::Error }, +} + +impl NetdevError { + pub fn available_netdevs_msg() -> String { + let list = Netdev::list() + .unwrap_or_default() + .into_iter() + .map(|dev| format!(" {dev}")) + .collect::>() + .join("\n"); + + format!("Available network devices:\n{list}") + } +} + +impl std::fmt::Display for NetdevError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + NetdevError::NotFound { name } => write!( + f, + "Netdev '{name}' not found.\n\n{}", + Self::available_netdevs_msg() + ), + NetdevError::IoError { name, err } => { + write!(f, "I/O Error occurred for netdev '{name}': {err}") + } + } + } +} + impl Netdev { + const SYS_CLASS_NET: &'static str = "/sys/class/net"; + pub fn from_name(name: &str) -> eyre::Result { - let rx_bytes = Self::open_netdev(name, "rx_bytes")?; - let tx_bytes = Self::open_netdev(name, "tx_bytes")?; + let rx_bytes = Self::open_netdev_stat(name, "rx_bytes")?; + let tx_bytes = Self::open_netdev_stat(name, "tx_bytes")?; Ok(Self { rx_bytes, tx_bytes }) } - fn open_netdev(dev: &str, stat: &str) -> eyre::Result { - let path = format!("/sys/class/net/{dev}/statistics/{stat}"); - File::open(&path).wrap_err(format!("Failed to open netdev '{dev}'")) + fn open_netdev_stat(name: &str, stat: &str) -> eyre::Result { + let sys_class_net = Self::SYS_CLASS_NET; + let path = format!("{sys_class_net}/{name}/statistics/{stat}"); + match File::open(&path) { + Ok(file) => Ok(file), + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Err(eyre::eyre!(NetdevError::NotFound { + name: name.to_string(), + })) + } else { + Err(eyre::eyre!(NetdevError::IoError { + name: name.to_string(), + err: e, + })) + } + } + } } fn read_bytes(file: &mut File) -> eyre::Result { @@ -37,4 +86,17 @@ impl Netdev { pub fn tx_bytes(&mut self) -> eyre::Result { Self::read_bytes(&mut self.tx_bytes) } + + pub fn list() -> eyre::Result> { + let mut entries = std::fs::read_dir(Self::SYS_CLASS_NET)? + .filter_map(|entry| { + entry + .ok() + .and_then(|entry| entry.file_name().into_string().ok()) + }) + .collect::>(); + + entries.sort(); + Ok(entries) + } }