~deadjakk/RiceStats

5662400811409a9cfbca757332d45aee3efe2f03 — deadjakk 2 years ago
init
8 files changed, 516 insertions(+), 0 deletions(-)

A .gitignore
A Cargo.lock
A Cargo.toml
A README.md
A src/lib.rs
A src/main.rs
A src/network.rs
A src/updates_utils.rs
A  => .gitignore +2 -0
@@ 1,2 @@
/target
**/*.swp

A  => Cargo.lock +233 -0
@@ 1,233 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "aho-corasick"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
 "memchr",
]

[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"

[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
 "bitflags",
 "textwrap",
 "unicode-width",
]

[[package]]
name = "heck"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
dependencies = [
 "unicode-segmentation",
]

[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"

[[package]]
name = "libc"
version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"

[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
 "cfg-if",
]

[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"

[[package]]
name = "once_cell"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"

[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
 "proc-macro-error-attr",
 "proc-macro2",
 "quote",
 "syn",
 "version_check",
]

[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
 "proc-macro2",
 "quote",
 "version_check",
]

[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
 "unicode-xid",
]

[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "regex"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-syntax",
 "thread_local",
]

[[package]]
name = "regex-syntax"
version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"

[[package]]
name = "rice_stats"
version = "0.1.0"
dependencies = [
 "regex",
 "structopt",
 "sudo",
]

[[package]]
name = "structopt"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
dependencies = [
 "clap",
 "lazy_static",
 "structopt-derive",
]

[[package]]
name = "structopt-derive"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
dependencies = [
 "heck",
 "proc-macro-error",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "sudo"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88bd84d4c082e18e37fef52c0088e4407dabcef19d23a607fb4b5ee03b7d5b83"
dependencies = [
 "libc",
 "log",
]

[[package]]
name = "syn"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-xid",
]

[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
 "unicode-width",
]

[[package]]
name = "thread_local"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
 "once_cell",
]

[[package]]
name = "unicode-segmentation"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"

[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"

[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"

[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"

A  => Cargo.toml +12 -0
@@ 1,12 @@
[package]
name = "rice_stats"
version = "0.1.0"
authors = ["deadjakk <jack72jill@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
structopt = { version = "0.3", default-features = false }
sudo = {version = "0.6.0" }
regex = "1"

A  => README.md +46 -0
@@ 1,46 @@
# Rice Status
## Description  
The goal of rice_stats is to have one cross-compilable binary that can be
used to retrieve output for 'custom/script' polybar modules to streamline
polybar configurations across installations. 
Each command will append to the last, so that it can all in one polybar module. 
Alternatively the binary can be run separately for each polybar module, however
one might prefer...  

## Installation  
`sudo cargo install --git https://git.sr.ht/~deadjakk/RiceStats` 
If you plan on using the `-a` (update) function of the binary, it will need
root privileges to obtain the number of available updates, this is most easily
achieved by setting the SUID bit on the binary. 
```
sudo chown root `which rice_stats` && sudo chmod u+s `which rice_stats`
``` 

## Help  
```
rice_stats -h
rice_stats 0.1.0

USAGE:
    rice_stats [FLAGS] [OPTIONS]

FLAGS:
    -a, --apt        This flag will cause the program to run `apt update -y && apt upgrade` updating the apt repository
                     but NOT the packages and then output the parsed number of available updates REQUIRES SUID bit to be
                     set on this binary via: sudo chown <this binary> && sudo chmod u+s <this binary> or alternatively
                     this binary can be run as an elevated user
    -h, --help       Prints help information
    -u, --usage      Return the current network usage for the primary interface
    -V, --version    Prints version information

OPTIONS:
    -i, --interface <interface>    Override the interface that is used for several commands. If this is not provided an
                                   attempt will be made to find the primary interface based on the presence of an ip and
                                   then traffic rate
``` 

## Usage  
```
~$ rice_stats -u -a -i wlp2s0
0 Updates Available rx-425, tx-7903
```

A  => src/lib.rs +49 -0
@@ 1,49 @@
pub use std::{fmt,error::Error,process::Command};

#[derive(Debug)]
pub enum RiceError {
    SUIDFailed,
    CommandFailed,
    InterfaceNotFound,
}

impl Error for RiceError{}
impl fmt::Display for RiceError{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            RiceError::SUIDFailed => {
                write!(f,"SUID could not be obtained \
                         chown root <this binary> && \
                         sudo chmod u+s this binary and \
                         try again") }
            RiceError::CommandFailed => {
                write!(f,"command must have failed")
            }
            RiceError::InterfaceNotFound => {
                write!(f,"interface provided may be invalid")
            }
        }
    }
}

pub fn run_command(command : &str, output_line: &mut String) -> Result<(),RiceError>{
    let output = Command::new("sh")
                .arg("-c")
                .arg(&command)
                .output()
                .expect("failed to execute process");

    match output.status.success() {
        true => {
            output_line.push_str(std::str::from_utf8(&output.stdout)
                 .unwrap_or("failed to parse output"));
            return Ok(());
        }
        false => {
            let err_out = std::str::from_utf8(&output.stderr)
                .unwrap_or("failed to parse stderr");
            output_line.push_str(err_out);
            return Err(RiceError::CommandFailed);
        }
    }
}

A  => src/main.rs +50 -0
@@ 1,50 @@
mod updates_utils;
mod network;
use updates_utils::apt_updates;
use network::usage;
use structopt::StructOpt;

#[derive(Debug,StructOpt)]
struct UserOptions {

    #[structopt(short,long)]
    /// Override the interface that is used for
    /// several commands.
    /// If this is not provided an attempt
    /// will be made to find the primary interface based
    /// on the presence of an ip and then traffic rate.
    interface: Option<String>,

    #[structopt(short,long)]
    /// This flag will cause the program to run `apt update -y && apt upgrade` 
    /// updating the apt repository but NOT the packages
    /// and then output the parsed number of available updates
    /// REQUIRES SUID bit to be set on this binary via:
    /// sudo chown <this binary> && sudo chmod u+s <this binary>
    /// or alternatively this binary can be run as an elevated user
    apt: bool,

    #[structopt(short,long)]
    /// Return the current network usage for the primary interface
    usage: bool,

}

fn main() {
    let args = UserOptions::from_args();
    let mut output_line = String::new();

    if args.usage == true {
        if let Err(e) = usage(&mut output_line, args.interface){
            eprintln!("Error getting network usage: {}",e);
        }
    }

    if args.apt == true {
        if let Err(e) = apt_updates(&mut output_line){
            eprintln!("Error received attempting show_updates: {}",e);
        }
    }

    print!("{}",output_line);
}

A  => src/network.rs +85 -0
@@ 1,85 @@
use rice_stats::{RiceError};
use std::fs::{read_dir,read};
use std::error::Error;
use std::str::{from_utf8};
use std::path::PathBuf;
use std::ffi::{OsString};
use std::time::Duration;
use std::thread::sleep;

pub fn usage(output_line: &mut String, provided_interface: Option<String>) -> Result<(),Box<dyn Error>> {
    let interface = match provided_interface {
        Some(v) => {
            // Validate that the provided interface exists
            let int = OsString::from(&v);
            check_interface(&int)?;
            int
        }
        None => {
            // No interface was provided, so we need to make 
            // assumptions based on usage
            let int = find_interface()?;
            int
        }
    };
    let int_path = PathBuf::from(&interface);
    let first =  get_traffic(&int_path,true)?;
    sleep(Duration::from_millis(1000));
    let second = get_traffic(&int_path,true)?;
    let tx = second-first;
    let first =  get_traffic(&int_path,false)?;
    sleep(Duration::from_millis(1000));
    let second = get_traffic(&int_path,false)?;
    let rx = second-first;
    output_line.push_str(&format!("rx-{} tx-{} ",rx,tx));
    return Ok(());
}

fn get_traffic(path: &PathBuf,tx: bool) -> Result<usize,Box<dyn Error>> {
    let mut f_path = PathBuf::new();
    f_path.push("/sys/class/net/");
    f_path.push(path);
    f_path.push(r"statistics");
    if tx == true {
        f_path.push(r"tx_bytes");
    } else {
        f_path.push(r"rx_bytes");
    }

    let contents = read(f_path)?;
    let mut contents = from_utf8(&contents)?.to_string();
    contents.truncate(contents.len()-1);
    let contents_parsed = contents.parse::<usize>()?;
    Ok(contents_parsed)
}

fn check_interface(interface: &OsString) -> Result<(),RiceError> {
    let mut int_path = PathBuf::from("/sys/class/net/");
    int_path.push(interface);
    if let Err(e) = read_dir(int_path){
        eprintln!("Error reading interface: {}",e.to_string());
        return Err(RiceError::InterfaceNotFound);
    }

    Ok(())
}

fn find_interface() -> Result<OsString,Box<dyn Error>> {
    let int_path = "/sys/class/net/";
    let ints = read_dir(int_path)?
        .map(|res| res.map(|e| e.path()))
        .collect::<Result<Vec<_>, std::io::Error>>()?;

    let mut max = 0;
    let mut primary = OsString::from("_");
    for int in ints {
        let name = int.file_name().unwrap();
        let traffic = get_traffic(&int,true)?;
        if traffic > max {
            max = traffic;
            primary = OsString::from(name);
        }
    }

    Ok(primary)
}

A  => src/updates_utils.rs +39 -0
@@ 1,39 @@
use rice_stats::{RiceError,run_command};
use sudo::{check,RunningAs,escalate_if_needed};
use regex::Regex;

pub fn apt_updates(output_line: &mut String) -> Result<(),RiceError> {
    match check() {
        RunningAs::User => {
            return Err(RiceError::SUIDFailed);
        }
    _ => (),
    }

    if let Err(e) = escalate_if_needed(){
        eprintln!("{}",e.to_string());
        return Err(RiceError::SUIDFailed);
    }

    let mut command_to_parse = String::new();
    if let Err(_) = run_command("apt update -y", &mut command_to_parse){
        eprintln!("erroneous error output: {}",command_to_parse);
    }
    
    let pattern = Regex::new("\\d+? packages").unwrap();
    let p_match = pattern.captures(&command_to_parse);
    match p_match { 
        Some(v) => {
            let num_packages: Vec<_> = v[0].split(" ").collect();
            output_line.push_str(num_packages[0]);
            output_line.push_str(" Updates Available ");
            return Ok(());
        },
        None => {
            // Updates were not found
            output_line.push_str("0 Updates Available ");
            return Ok(());
        }
    }
}