From dc06ab730d40cf262283eac33b3105fc7ac2777d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 26 Jan 2024 22:35:52 +0100 Subject: [PATCH] implement the first version of free --- Cargo.lock | 9 +++ Cargo.toml | 2 + src/uu/free/Cargo.toml | 17 +++++ src/uu/free/free.md | 7 ++ src/uu/free/src/free.rs | 151 +++++++++++++++++++++++++++++++++++++ src/uu/free/src/main.rs | 1 + tests/by-util/test_free.rs | 27 +++++++ tests/tests.rs | 4 + 8 files changed, 218 insertions(+) create mode 100644 src/uu/free/Cargo.toml create mode 100644 src/uu/free/free.md create mode 100644 src/uu/free/src/free.rs create mode 100644 src/uu/free/src/main.rs create mode 100644 tests/by-util/test_free.rs diff --git a/Cargo.lock b/Cargo.lock index ddb27e9e..e9fdd918 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,6 +351,7 @@ dependencies = [ "rlimit", "tempfile", "textwrap", + "uu_free", "uu_pwdx", "uucore", "xattr", @@ -562,6 +563,14 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uu_free" +version = "0.0.1" +dependencies = [ + "clap", + "uucore", +] + [[package]] name = "uu_pwdx" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index 51fab511..aff615bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ default = ["feat_common_core"] feat_common_core = [ "pwdx", + "free", ] [workspace.dependencies] @@ -54,6 +55,7 @@ textwrap = { workspace = true } # pwdx = { optional = true, version = "0.0.1", package = "uu_pwdx", path = "src/uu/pwdx" } +free = { optional = true, version = "0.0.1", package = "uu_free", path = "src/uu/free" } [dev-dependencies] pretty_assertions = "1" diff --git a/src/uu/free/Cargo.toml b/src/uu/free/Cargo.toml new file mode 100644 index 00000000..41dc9656 --- /dev/null +++ b/src/uu/free/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "uu_free" +version = "0.0.1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +uucore = { workspace = true } +clap = { workspace = true } + +[lib] +path = "src/free.rs" + +[[bin]] +name = "free" +path = "src/main.rs" diff --git a/src/uu/free/free.md b/src/uu/free/free.md new file mode 100644 index 00000000..f8bf67cf --- /dev/null +++ b/src/uu/free/free.md @@ -0,0 +1,7 @@ +# free + +``` +free [options] +``` + +Display amount of free and used memory in the system diff --git a/src/uu/free/src/free.rs b/src/uu/free/src/free.rs new file mode 100644 index 00000000..22acb84f --- /dev/null +++ b/src/uu/free/src/free.rs @@ -0,0 +1,151 @@ +// This file is part of the uutils procps package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use clap::Arg; +use clap::ArgAction; +use clap::{crate_version, Command}; +use std::env; +use std::fs; +use std::process; +use uucore::{error::UResult, format_usage, help_about, help_usage}; + +const ABOUT: &str = help_about!("free.md"); +const USAGE: &str = help_usage!("free.md"); + +fn parse_meminfo() -> Result<(u64, u64, u64, u64, u64, u64, u64, u64, u64, u64), std::io::Error> { + let contents = fs::read_to_string("/proc/meminfo")?; + let mut total = 0; + let mut free = 0; + let mut available = 0; + let mut shared = 0; + let mut buffers = 0; + let mut cached = 0; + let mut swap_total = 0; + let mut swap_free = 0; + let mut reclaimable = 0; + + for line in contents.lines() { + if let Some((key, value)) = line.split_once(':') { + let parsed_value = parse_meminfo_value(value)?; + match key.trim() { + "MemTotal" => total = parsed_value, + "MemFree" => free = parsed_value, + "MemAvailable" => available = parsed_value, + "Shmem" => shared = parsed_value, + "Buffers" => buffers = parsed_value, + "Cached" => cached = parsed_value, + "SwapTotal" => swap_total = parsed_value, + "SwapFree" => swap_free = parsed_value, + "SReclaimable" => reclaimable = parsed_value, + _ => {} + } + } + } + + Ok(( + total, + free, + available, + shared, + buffers, + cached, + swap_total, + swap_free, + swap_total - swap_free, + reclaimable, + )) +} + +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches = uu_app().try_get_matches_from(args)?; + let wide = matches.get_flag("wide"); + + match parse_meminfo() { + Ok(( + total, + free, + available, + shared, + buffers, + cached, + swap_total, + swap_free, + swap_used, + reclaimable, + )) => { + let buff_cache = if wide { buffers } else { buffers + cached }; + let cache = if wide { cached } else { 0 }; + let used = total - free; + + if wide { + println!(" total used free shared buffers cache available"); + println!( + "Mem: {:12} {:12} {:12} {:12} {:12} {:12} {:12}", + total, + used, + free, + shared, + buff_cache, + cache + reclaimable, + available + ); + } else { + println!(" total used free shared buff/cache available"); + println!( + "Mem: {:12} {:12} {:12} {:12} {:12} {:12}", + total, + used, + free, + shared, + buff_cache + reclaimable, + available + ); + } + println!("Swap: {:12} {:12} {:12}", swap_total, swap_used, swap_free); + } + Err(e) => { + eprintln!("free: failed to read memory info: {}", e); + process::exit(1); + } + } + + Ok(()) +} + +pub fn uu_app() -> Command { + Command::new(uucore::util_name()) + .version(crate_version!()) + .about(ABOUT) + .override_usage(format_usage(USAGE)) + .infer_long_args(true) + .arg( + Arg::new("wide") + .short('w') + .long("wide") + .help("wide output") + .action(ArgAction::SetTrue), + ) +} + +fn parse_meminfo_value(value: &str) -> Result { + value + .split_whitespace() + .next() + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid memory info format", + ) + }) + .and_then(|v| { + v.parse::().map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid memory info format", + ) + }) + }) +} diff --git a/src/uu/free/src/main.rs b/src/uu/free/src/main.rs new file mode 100644 index 00000000..9b196ed9 --- /dev/null +++ b/src/uu/free/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_free); diff --git a/tests/by-util/test_free.rs b/tests/by-util/test_free.rs new file mode 100644 index 00000000..039fc6ae --- /dev/null +++ b/tests/by-util/test_free.rs @@ -0,0 +1,27 @@ +// This file is part of the uutils procps package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore (words) symdir somefakedir + +use std::path::PathBuf; + +use crate::common::util::{TestScenario, UCommand}; + +#[test] +fn test_invalid_arg() { + new_ucmd!().arg("--definitely-invalid").fails().code_is(1); +} + +#[test] +fn test_free() { + let result = new_ucmd!().succeeds(); + assert!(result.stdout_str().contains("Mem:")) +} + +#[test] +fn test_free_wide() { + let result = new_ucmd!().arg("--wide").succeeds(); + assert!(result.stdout_str().contains("Mem:")); + assert!(!result.stdout_str().contains("buff/cache")); +} diff --git a/tests/tests.rs b/tests/tests.rs index 353662d0..33ffec08 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -8,3 +8,7 @@ mod common; #[cfg(feature = "pwdx")] #[path = "by-util/test_pwdx.rs"] mod test_pwdx; + +#[cfg(feature = "free")] +#[path = "by-util/test_free.rs"] +mod test_free;