From 3980e7714f0c29c6aacdda9e4e2a06a4390f4554 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Wed, 12 Feb 2025 22:11:34 +0200
Subject: [PATCH 01/10] `lscpu`: Show CPU vulnerability mitigation info

---
 src/uu/lscpu/src/lscpu.rs | 11 +++++++++++
 src/uu/lscpu/src/sysfs.rs | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 44 insertions(+)
 create mode 100644 src/uu/lscpu/src/sysfs.rs

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 3150e53..4cd5647 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -15,6 +15,8 @@ mod options {
     pub const JSON: &str = "json";
 }
 
+mod sysfs;
+
 const ABOUT: &str = help_about!("lscpu.md");
 const USAGE: &str = help_usage!("lscpu.md");
 
@@ -126,6 +128,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
         cpu_infos.push(vendor_info);
     }
 
+    let vulns = sysfs::read_cpu_vulnerabilities();
+    if !vulns.is_empty() {
+        let mut vuln_info = CpuInfo::new("Vulnerabilities", "", None);
+        for vuln in vulns {
+            vuln_info.add_child(CpuInfo::new(&vuln.name, &vuln.mitigation, None));
+        }
+        cpu_infos.push(vuln_info);
+    }
+
     print_output(cpu_infos, output_opts);
 
     Ok(())
diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs
new file mode 100644
index 0000000..62adc05
--- /dev/null
+++ b/src/uu/lscpu/src/sysfs.rs
@@ -0,0 +1,33 @@
+use std::fs;
+
+pub struct CpuVulnerability {
+    pub name: String,
+    pub mitigation: String,
+}
+
+pub fn read_cpu_vulnerabilities() -> Vec<CpuVulnerability> {
+    let mut out: Vec<CpuVulnerability> = vec![];
+
+    if let Ok(dir) = fs::read_dir("/sys/devices/system/cpu/vulnerabilities") {
+        let mut files: Vec<_> = dir
+            .flatten()
+            .map(|x| x.path())
+            .filter(|x| !x.is_dir())
+            .collect();
+
+        files.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
+
+        for file in files {
+            if let Ok(content) = fs::read_to_string(&file) {
+                let name = file.file_name().unwrap().to_str().unwrap();
+
+                out.push(CpuVulnerability {
+                    name: (name[..1].to_uppercase() + &name[1..]).replace("_", " "),
+                    mitigation: content.trim().to_string(),
+                });
+            }
+        }
+    };
+
+    out
+}

From e689bee77103ade3320bb5eeea2b7994fbd84e58 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Thu, 13 Feb 2025 18:55:26 +0200
Subject: [PATCH 02/10] `lscpu`: Read CPU count from `sysfs`, add freq_boost
 status to output

---
 src/uu/lscpu/src/lscpu.rs | 26 ++++++++++------
 src/uu/lscpu/src/sysfs.rs | 65 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 81 insertions(+), 10 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 4cd5647..bc532f4 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -7,7 +7,6 @@ use clap::{crate_version, Arg, ArgAction, Command};
 use regex::RegexBuilder;
 use serde::Serialize;
 use std::{cmp, fs};
-use sysinfo::System;
 use uucore::{error::UResult, format_usage, help_about, help_usage};
 
 mod options {
@@ -72,8 +71,6 @@ struct OutputOptions {
 pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     let matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?;
 
-    let system = System::new_all();
-
     let output_opts = OutputOptions {
         _hex: matches.get_flag(options::HEX),
         json: matches.get_flag(options::JSON),
@@ -91,18 +88,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
         arch_info.add_child(CpuInfo::new("Address sizes", &addr_sizes, None))
     }
 
-    if let Ok(byte_order) = fs::read_to_string("/sys/kernel/cpu_byteorder") {
-        match byte_order.trim() {
-            "big" => arch_info.add_child(CpuInfo::new("Byte Order", "Big Endian", None)),
-            "little" => arch_info.add_child(CpuInfo::new("Byte Order", "Little Endian", None)),
-            _ => eprintln!("Unrecognised Byte Order: {}", byte_order),
-        }
+    if let Some(byte_order) = sysfs::read_cpu_byte_order() {
+        arch_info.add_child(CpuInfo::new("Byte Order", byte_order, None));
     }
 
+    let cpu_topology = sysfs::read_cpu_topology();
+
     cpu_infos.push(arch_info);
     cpu_infos.push(CpuInfo::new(
         "CPU(s)",
-        &format!("{}", system.cpus().len()),
+        &format!("{}", cpu_topology.cpus.len()),
         None,
     ));
 
@@ -123,6 +118,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
                 model_name_info.add_child(CpuInfo::new("Model", &model, None));
             }
 
+            if let Some(freq_boost_enabled) = sysfs::read_freq_boost_state() {
+                let s = if freq_boost_enabled {
+                    "enabled"
+                } else {
+                    "disabled"
+                };
+                model_name_info.add_child(CpuInfo::new("Frequency boost", s, None));
+            }
+
             vendor_info.add_child(model_name_info);
         }
         cpu_infos.push(vendor_info);
@@ -211,6 +215,8 @@ fn find_cpuinfo_value(contents: &str, key: &str) -> Option<String> {
     value
 }
 
+// TODO: This is non-exhaustive and assumes that compile-time arch is the same as runtime
+// This is not always guaranteed to be the case, ie. you can run a x86 binary on a x86_64 machine
 fn get_architecture() -> String {
     if cfg!(target_arch = "x86") {
         "x86".to_string()
diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs
index 62adc05..8536e52 100644
--- a/src/uu/lscpu/src/sysfs.rs
+++ b/src/uu/lscpu/src/sysfs.rs
@@ -5,6 +5,60 @@ pub struct CpuVulnerability {
     pub mitigation: String,
 }
 
+pub struct CpuTopology {
+    pub cpus: Vec<Cpu>,
+}
+
+pub struct Cpu {
+    _index: usize,
+    _caches: Vec<CpuCache>,
+}
+
+pub struct CpuCache {
+    _index: usize,
+    _typ: String,
+    _level: String,
+    _size: String,
+}
+
+// TODO: This should go through each CPU in sysfs and calculate things such as cache sizes and physical topology
+// For now it just returns a list of CPUs which are enabled
+pub fn read_cpu_topology() -> CpuTopology {
+    let mut out: Vec<Cpu> = vec![];
+
+    // NOTE: All examples I could find was where this file contains a CPU index range in the form of `<start>-<end>`
+    // Theoretically, there might be a situation where some cores are disabled, so that `enabled` cannot be represented
+    // as a continuous range. For now we just assume it's always `X-Y` and use those as our bounds to read CPU information
+    let enabled_cpus = match fs::read_to_string("/sys/devices/system/cpu/enabled") {
+        Ok(content) => {
+            let parts: Vec<_> = content
+                .trim()
+                .split("-")
+                .flat_map(|part| part.parse::<usize>())
+                .collect();
+            assert_eq!(parts.len(), 2);
+            (parts[0], parts[1])
+        }
+        Err(e) => panic!("Could not read sysfs: {}", e),
+    };
+
+    for cpu_index in enabled_cpus.0..(enabled_cpus.1 + 1) {
+        out.push(Cpu {
+            _index: cpu_index,
+            _caches: vec![],
+        })
+    }
+
+    CpuTopology { cpus: out }
+}
+
+pub fn read_freq_boost_state() -> Option<bool> {
+    match fs::read_to_string("/sys/devices/system/cpu/cpufreq/boost") {
+        Ok(content) => Some(content.trim() == "1"),
+        Err(_) => None,
+    }
+}
+
 pub fn read_cpu_vulnerabilities() -> Vec<CpuVulnerability> {
     let mut out: Vec<CpuVulnerability> = vec![];
 
@@ -31,3 +85,14 @@ pub fn read_cpu_vulnerabilities() -> Vec<CpuVulnerability> {
 
     out
 }
+
+pub fn read_cpu_byte_order() -> Option<&'static str> {
+    if let Ok(byte_order) = fs::read_to_string("/sys/kernel/cpu_byteorder") {
+        match byte_order.trim() {
+            "big" => return Some("Big Endian"),
+            "little" => return Some("Little Endian"),
+            _ => eprintln!("Unrecognised Byte Order: {}", byte_order),
+        }
+    }
+    None
+}

From 99c751bd1d250f5207aab1fe70d623982127ff73 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Fri, 14 Feb 2025 12:55:50 +0200
Subject: [PATCH 03/10] `lscpu`: Parse CPU cache topology from `sysfs`

---
 Cargo.lock                |  13 +++-
 Cargo.toml                |   1 +
 src/uu/lscpu/Cargo.toml   |   1 +
 src/uu/lscpu/src/lscpu.rs |  67 ++++++++++++++++++--
 src/uu/lscpu/src/sysfs.rs | 129 ++++++++++++++++++++++++++++++--------
 5 files changed, 177 insertions(+), 34 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 5dc55c1..1a26061 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -271,7 +271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
 dependencies = [
  "libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -513,6 +513,12 @@ dependencies = [
  "unicode-width 0.2.0",
 ]
 
+[[package]]
+name = "parse-size"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
+
 [[package]]
 name = "parse_datetime"
 version = "0.7.0"
@@ -783,7 +789,7 @@ dependencies = [
  "errno",
  "libc",
  "linux-raw-sys 0.4.15",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -923,7 +929,7 @@ dependencies = [
  "getrandom",
  "once_cell",
  "rustix 0.38.44",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -1113,6 +1119,7 @@ name = "uu_lscpu"
 version = "0.0.1"
 dependencies = [
  "clap",
+ "parse-size",
  "regex",
  "serde",
  "serde_json",
diff --git a/Cargo.toml b/Cargo.toml
index f2864ca..9222a85 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -46,6 +46,7 @@ dns-lookup = "2.0.4"
 libc = "0.2.152"
 linux-raw-sys = { version = "0.7.0", features = ["ioctl"] }
 nix = { version = "0.29", default-features = false }
+parse-size = "1.1.0"
 phf = "0.11.2"
 phf_codegen = "0.11.2"
 rand = { version = "0.9.0", features = ["small_rng"] }
diff --git a/src/uu/lscpu/Cargo.toml b/src/uu/lscpu/Cargo.toml
index 8d45f12..bb8c86a 100644
--- a/src/uu/lscpu/Cargo.toml
+++ b/src/uu/lscpu/Cargo.toml
@@ -17,3 +17,4 @@ uucore = { workspace = true }
 clap = { workspace = true }
 serde = { workspace = true }
 serde_json = { workspace = true }
+parse-size = { workspace = true }
diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index bc532f4..71bbe1d 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -6,7 +6,7 @@
 use clap::{crate_version, Arg, ArgAction, Command};
 use regex::RegexBuilder;
 use serde::Serialize;
-use std::{cmp, fs};
+use std::{cmp, collections::HashMap, fs};
 use uucore::{error::UResult, format_usage, help_about, help_usage};
 
 mod options {
@@ -92,15 +92,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
         arch_info.add_child(CpuInfo::new("Byte Order", byte_order, None));
     }
 
-    let cpu_topology = sysfs::read_cpu_topology();
-
     cpu_infos.push(arch_info);
-    cpu_infos.push(CpuInfo::new(
-        "CPU(s)",
-        &format!("{}", cpu_topology.cpus.len()),
+
+    let cpu_topology = sysfs::read_cpu_topology();
+    let mut cores_info = CpuInfo::new("CPU(s)", &format!("{}", cpu_topology.cpus.len()), None);
+
+    cores_info.add_child(CpuInfo::new(
+        "On-line CPU(s) list",
+        &sysfs::read_online_cpus(),
         None,
     ));
 
+    cpu_infos.push(cores_info);
+
     // TODO: This is currently quite verbose and doesn't strictly respect the hierarchy of `/proc/cpuinfo` contents
     // ie. the file might contain multiple sections, each with their own vendor_id/model name etc. but right now
     // we're just taking whatever our regex matches first and using that
@@ -132,6 +136,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
         cpu_infos.push(vendor_info);
     }
 
+    if let Some(cache_info) = calculate_cache_totals(cpu_topology.cpus) {
+        cpu_infos.push(cache_info);
+    }
+
     let vulns = sysfs::read_cpu_vulnerabilities();
     if !vulns.is_empty() {
         let mut vuln_info = CpuInfo::new("Vulnerabilities", "", None);
@@ -146,6 +154,53 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     Ok(())
 }
 
+fn calculate_cache_totals(cpus: Vec<sysfs::Cpu>) -> Option<CpuInfo> {
+    let mut by_levels: HashMap<String, Vec<&sysfs::CpuCache>> = HashMap::new();
+    let all_caches: Vec<_> = cpus.iter().flat_map(|cpu| &cpu.caches).collect();
+
+    if all_caches.is_empty() {
+        return None;
+    }
+
+    for cache in all_caches {
+        let type_suffix = match cache.typ {
+            sysfs::CacheType::Instruction => "i",
+            sysfs::CacheType::Data => "d",
+            _ => "",
+        };
+        let level_key = format!("L{}{}", cache.level, type_suffix);
+
+        if let Some(caches) = by_levels.get_mut(&level_key) {
+            caches.push(cache);
+        } else {
+            by_levels.insert(level_key, vec![cache]);
+        }
+    }
+
+    let mut cache_info = CpuInfo::new("Caches (sum of all)", "", None);
+
+    for (level, caches) in by_levels.iter_mut() {
+        // Cache instances that are shared across multiple CPUs should have the same `shared_cpu_map` value
+        // Deduplicating the list on a per-level basic using the CPU map ensures that we don't count any shared caches multiple times
+        caches.sort_by(|a, b| a.shared_cpu_map.cmp(&b.shared_cpu_map));
+        caches.dedup_by_key(|c| &c.shared_cpu_map);
+
+        let count = caches.len();
+        let size_total = caches.iter().fold(0_u64, |acc, c| acc + c.size);
+        cache_info.add_child(CpuInfo::new(
+            level,
+            // TODO: Format sizes using `KiB`, `MiB` etc.
+            &format!("{} bytes ({} instances)", size_total, count),
+            None,
+        ));
+    }
+
+    // Make sure caches get printed in alphabetical order
+    cache_info.children.sort_by(|a, b| a.field.cmp(&b.field));
+
+    Some(cache_info)
+}
+
 fn print_output(infos: CpuInfos, out_opts: OutputOptions) {
     if out_opts.json {
         println!("{}", infos.to_json());
diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs
index 8536e52..f4b1102 100644
--- a/src/uu/lscpu/src/sysfs.rs
+++ b/src/uu/lscpu/src/sysfs.rs
@@ -1,4 +1,5 @@
-use std::fs;
+use parse_size::parse_size;
+use std::{fs, path::PathBuf};
 
 pub struct CpuVulnerability {
     pub name: String,
@@ -9,49 +10,127 @@ pub struct CpuTopology {
     pub cpus: Vec<Cpu>,
 }
 
+#[derive(Debug)]
 pub struct Cpu {
     _index: usize,
-    _caches: Vec<CpuCache>,
+    pub _pkg_id: usize,
+    pub caches: Vec<CpuCache>,
 }
 
+#[derive(Debug)]
 pub struct CpuCache {
-    _index: usize,
-    _typ: String,
-    _level: String,
-    _size: String,
+    pub typ: CacheType,
+    pub level: usize,
+    pub size: u64,
+    pub shared_cpu_map: String,
+}
+
+#[derive(Debug)]
+pub enum CacheType {
+    Data,
+    Instruction,
+    Unified,
+}
+
+// TODO: respect `--hex` option and output the bitmask instead of human-readable range
+pub fn read_online_cpus() -> String {
+    fs::read_to_string("/sys/devices/system/cpu/online")
+        .expect("Could not read sysfs")
+        .trim()
+        .to_string()
+}
+
+// Takes in a human-readable list of CPUs, and returns a list of indices parsed from that list
+// These can come in the form of a plain range like `X-Y`, or a comma-separated ranges and indices ie. `1,3-4,7-8,10`
+// Kernel docs with examples: https://www.kernel.org/doc/html/latest/admin-guide/cputopology.html
+fn parse_cpu_list(list: &str) -> Vec<usize> {
+    let mut out: Vec<usize> = vec![];
+
+    for part in list.trim().split(",") {
+        if part.contains("-") {
+            let bounds: Vec<_> = part.split("-").flat_map(|x| x.parse::<usize>()).collect();
+            assert_eq!(bounds.len(), 2);
+            for idx in bounds[0]..bounds[1] + 1 {
+                out.push(idx)
+            }
+        } else {
+            let idx = part.parse::<usize>().expect("Invalid CPU index value");
+            out.push(idx);
+        }
+    }
+
+    out
 }
 
-// TODO: This should go through each CPU in sysfs and calculate things such as cache sizes and physical topology
-// For now it just returns a list of CPUs which are enabled
 pub fn read_cpu_topology() -> CpuTopology {
     let mut out: Vec<Cpu> = vec![];
 
-    // NOTE: All examples I could find was where this file contains a CPU index range in the form of `<start>-<end>`
-    // Theoretically, there might be a situation where some cores are disabled, so that `enabled` cannot be represented
-    // as a continuous range. For now we just assume it's always `X-Y` and use those as our bounds to read CPU information
-    let enabled_cpus = match fs::read_to_string("/sys/devices/system/cpu/enabled") {
-        Ok(content) => {
-            let parts: Vec<_> = content
-                .trim()
-                .split("-")
-                .flat_map(|part| part.parse::<usize>())
-                .collect();
-            assert_eq!(parts.len(), 2);
-            (parts[0], parts[1])
-        }
-        Err(e) => panic!("Could not read sysfs: {}", e),
-    };
+    let online_cpus = parse_cpu_list(&read_online_cpus());
+
+    for cpu_index in online_cpus {
+        let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/", cpu_index));
+
+        let physical_pkg_id = fs::read_to_string(cpu_dir.join("topology/physical_package_id"))
+            .unwrap()
+            .trim()
+            .parse::<usize>()
+            .unwrap();
+
+        let caches = read_cpu_caches(cpu_index);
 
-    for cpu_index in enabled_cpus.0..(enabled_cpus.1 + 1) {
         out.push(Cpu {
             _index: cpu_index,
-            _caches: vec![],
+            _pkg_id: physical_pkg_id,
+            caches,
         })
     }
 
     CpuTopology { cpus: out }
 }
 
+fn read_cpu_caches(cpu_index: usize) -> Vec<CpuCache> {
+    let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/", cpu_index));
+    let cache_dir = fs::read_dir(cpu_dir.join("cache")).unwrap();
+    let cache_paths = cache_dir
+        .flatten()
+        .filter(|x| x.path().is_dir())
+        .map(|x| x.path());
+
+    let mut caches: Vec<CpuCache> = vec![];
+
+    for cache_path in cache_paths {
+        let type_string = fs::read_to_string(cache_path.join("type")).unwrap();
+
+        let c_type = match type_string.trim() {
+            "Unified" => CacheType::Unified,
+            "Data" => CacheType::Data,
+            "Instruction" => CacheType::Instruction,
+            _ => panic!("Unrecognized cache type: {}", type_string),
+        };
+
+        let c_level = fs::read_to_string(cache_path.join("level"))
+            .map(|s| s.trim().parse::<usize>().unwrap())
+            .unwrap();
+
+        let size_string = fs::read_to_string(cache_path.join("size")).unwrap();
+        let c_size = parse_size(size_string.trim()).unwrap();
+
+        let shared_cpu_map = fs::read_to_string(cache_path.join("shared_cpu_map"))
+            .unwrap()
+            .trim()
+            .to_string();
+
+        caches.push(CpuCache {
+            level: c_level,
+            size: c_size,
+            typ: c_type,
+            shared_cpu_map,
+        });
+    }
+
+    caches
+}
+
 pub fn read_freq_boost_state() -> Option<bool> {
     match fs::read_to_string("/sys/devices/system/cpu/cpufreq/boost") {
         Ok(content) => Some(content.trim() == "1"),

From 41bff4fbd64b0340defabaa5b9844dd0251709c8 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Fri, 14 Feb 2025 14:57:43 +0200
Subject: [PATCH 04/10] `lscpu`: Show number of physical CPU sockets, add unit
 test

---
 src/uu/lscpu/src/lscpu.rs |   9 ++-
 src/uu/lscpu/src/sysfs.rs | 128 +++++++++++++++++++++++---------------
 2 files changed, 86 insertions(+), 51 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 71bbe1d..1f32a1c 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -94,7 +94,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
 
     cpu_infos.push(arch_info);
 
-    let cpu_topology = sysfs::read_cpu_topology();
+    let cpu_topology = sysfs::CpuTopology::new();
     let mut cores_info = CpuInfo::new("CPU(s)", &format!("{}", cpu_topology.cpus.len()), None);
 
     cores_info.add_child(CpuInfo::new(
@@ -131,8 +131,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
                 model_name_info.add_child(CpuInfo::new("Frequency boost", s, None));
             }
 
+            model_name_info.add_child(CpuInfo::new(
+                "Socket(s)",
+                &cpu_topology.socket_count().to_string(),
+                None,
+            ));
+
             vendor_info.add_child(model_name_info);
         }
+
         cpu_infos.push(vendor_info);
     }
 
diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs
index f4b1102..f710a65 100644
--- a/src/uu/lscpu/src/sysfs.rs
+++ b/src/uu/lscpu/src/sysfs.rs
@@ -1,5 +1,5 @@
 use parse_size::parse_size;
-use std::{fs, path::PathBuf};
+use std::{collections::HashSet, fs, path::PathBuf};
 
 pub struct CpuVulnerability {
     pub name: String,
@@ -13,7 +13,7 @@ pub struct CpuTopology {
 #[derive(Debug)]
 pub struct Cpu {
     _index: usize,
-    pub _pkg_id: usize,
+    pub pkg_id: usize,
     pub caches: Vec<CpuCache>,
 }
 
@@ -32,6 +32,41 @@ pub enum CacheType {
     Unified,
 }
 
+impl CpuTopology {
+    pub fn new() -> Self {
+        let mut out: Vec<Cpu> = vec![];
+
+        let online_cpus = parse_cpu_list(&read_online_cpus());
+
+        for cpu_index in online_cpus {
+            let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/", cpu_index));
+
+            let physical_pkg_id = fs::read_to_string(cpu_dir.join("topology/physical_package_id"))
+                .unwrap()
+                .trim()
+                .parse::<usize>()
+                .unwrap();
+
+            let caches = read_cpu_caches(cpu_index);
+
+            out.push(Cpu {
+                _index: cpu_index,
+                pkg_id: physical_pkg_id,
+                caches,
+            })
+        }
+        Self { cpus: out }
+    }
+
+    pub fn socket_count(&self) -> usize {
+        // Each physical socket is represented as its own package_id, so amount of unique pkg_ids = sockets
+        // https://www.kernel.org/doc/html/latest/admin-guide/abi-stable.html#abi-sys-devices-system-cpu-cpux-topology-physical-package-id
+        let physical_sockets: HashSet<usize> = self.cpus.iter().map(|cpu| cpu.pkg_id).collect();
+
+        physical_sockets.len()
+    }
+}
+
 // TODO: respect `--hex` option and output the bitmask instead of human-readable range
 pub fn read_online_cpus() -> String {
     fs::read_to_string("/sys/devices/system/cpu/online")
@@ -40,54 +75,6 @@ pub fn read_online_cpus() -> String {
         .to_string()
 }
 
-// Takes in a human-readable list of CPUs, and returns a list of indices parsed from that list
-// These can come in the form of a plain range like `X-Y`, or a comma-separated ranges and indices ie. `1,3-4,7-8,10`
-// Kernel docs with examples: https://www.kernel.org/doc/html/latest/admin-guide/cputopology.html
-fn parse_cpu_list(list: &str) -> Vec<usize> {
-    let mut out: Vec<usize> = vec![];
-
-    for part in list.trim().split(",") {
-        if part.contains("-") {
-            let bounds: Vec<_> = part.split("-").flat_map(|x| x.parse::<usize>()).collect();
-            assert_eq!(bounds.len(), 2);
-            for idx in bounds[0]..bounds[1] + 1 {
-                out.push(idx)
-            }
-        } else {
-            let idx = part.parse::<usize>().expect("Invalid CPU index value");
-            out.push(idx);
-        }
-    }
-
-    out
-}
-
-pub fn read_cpu_topology() -> CpuTopology {
-    let mut out: Vec<Cpu> = vec![];
-
-    let online_cpus = parse_cpu_list(&read_online_cpus());
-
-    for cpu_index in online_cpus {
-        let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/", cpu_index));
-
-        let physical_pkg_id = fs::read_to_string(cpu_dir.join("topology/physical_package_id"))
-            .unwrap()
-            .trim()
-            .parse::<usize>()
-            .unwrap();
-
-        let caches = read_cpu_caches(cpu_index);
-
-        out.push(Cpu {
-            _index: cpu_index,
-            _pkg_id: physical_pkg_id,
-            caches,
-        })
-    }
-
-    CpuTopology { cpus: out }
-}
-
 fn read_cpu_caches(cpu_index: usize) -> Vec<CpuCache> {
     let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/", cpu_index));
     let cache_dir = fs::read_dir(cpu_dir.join("cache")).unwrap();
@@ -175,3 +162,44 @@ pub fn read_cpu_byte_order() -> Option<&'static str> {
     }
     None
 }
+
+// Takes in a human-readable list of CPUs, and returns a list of indices parsed from that list
+// These can come in the form of a plain range like `X-Y`, or a comma-separated ranges and indices ie. `1,3-4,7-8,10`
+// Kernel docs with examples: https://www.kernel.org/doc/html/latest/admin-guide/cputopology.html
+fn parse_cpu_list(list: &str) -> Vec<usize> {
+    let mut out: Vec<usize> = vec![];
+
+    if list.is_empty() {
+        return out;
+    }
+
+    for part in list.trim().split(",") {
+        if part.contains("-") {
+            let bounds: Vec<_> = part.split("-").flat_map(|x| x.parse::<usize>()).collect();
+            assert_eq!(bounds.len(), 2);
+            for idx in bounds[0]..bounds[1] + 1 {
+                out.push(idx)
+            }
+        } else {
+            let idx = part.parse::<usize>().expect("Invalid CPU index value");
+            out.push(idx);
+        }
+    }
+
+    out
+}
+
+#[test]
+fn test_parse_cpu_list() {
+    assert_eq!(parse_cpu_list(""), Vec::<usize>::new());
+    assert_eq!(parse_cpu_list("1-3"), Vec::<usize>::from([1, 2, 3]));
+    assert_eq!(parse_cpu_list("1,2,3"), Vec::<usize>::from([1, 2, 3]));
+    assert_eq!(
+        parse_cpu_list("1,3-6,8"),
+        Vec::<usize>::from([1, 3, 4, 5, 6, 8])
+    );
+    assert_eq!(
+        parse_cpu_list("1-2,3-5,7"),
+        Vec::<usize>::from([1, 2, 3, 4, 5, 7])
+    );
+}

From bc44b86fe71d1d39ef3a9649a3b449a4ca394b75 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Fri, 14 Feb 2025 15:26:38 +0200
Subject: [PATCH 05/10] `lscpu`: Calculate core/socket count

---
 src/uu/lscpu/src/lscpu.rs | 15 +++++++++------
 src/uu/lscpu/src/sysfs.rs | 19 ++++++++++++++++---
 2 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 1f32a1c..19cce4e 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -122,6 +122,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
                 model_name_info.add_child(CpuInfo::new("Model", &model, None));
             }
 
+            let socket_count = &cpu_topology.socket_count();
+            let core_count = &cpu_topology.core_count();
+            model_name_info.add_child(CpuInfo::new(
+                "Core(s) per socket",
+                &(core_count / socket_count).to_string(),
+                None,
+            ));
+            model_name_info.add_child(CpuInfo::new("Socket(s)", &socket_count.to_string(), None));
+
             if let Some(freq_boost_enabled) = sysfs::read_freq_boost_state() {
                 let s = if freq_boost_enabled {
                     "enabled"
@@ -131,12 +140,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
                 model_name_info.add_child(CpuInfo::new("Frequency boost", s, None));
             }
 
-            model_name_info.add_child(CpuInfo::new(
-                "Socket(s)",
-                &cpu_topology.socket_count().to_string(),
-                None,
-            ));
-
             vendor_info.add_child(model_name_info);
         }
 
diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs
index f710a65..787e0b7 100644
--- a/src/uu/lscpu/src/sysfs.rs
+++ b/src/uu/lscpu/src/sysfs.rs
@@ -14,6 +14,7 @@ pub struct CpuTopology {
 pub struct Cpu {
     _index: usize,
     pub pkg_id: usize,
+    pub core_id: usize,
     pub caches: Vec<CpuCache>,
 }
 
@@ -41,7 +42,13 @@ impl CpuTopology {
         for cpu_index in online_cpus {
             let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/", cpu_index));
 
-            let physical_pkg_id = fs::read_to_string(cpu_dir.join("topology/physical_package_id"))
+            let pkg_id = fs::read_to_string(cpu_dir.join("topology/physical_package_id"))
+                .unwrap()
+                .trim()
+                .parse::<usize>()
+                .unwrap();
+
+            let core_id = fs::read_to_string(cpu_dir.join("topology/core_id"))
                 .unwrap()
                 .trim()
                 .parse::<usize>()
@@ -51,7 +58,8 @@ impl CpuTopology {
 
             out.push(Cpu {
                 _index: cpu_index,
-                pkg_id: physical_pkg_id,
+                pkg_id,
+                core_id,
                 caches,
             })
         }
@@ -61,10 +69,15 @@ impl CpuTopology {
     pub fn socket_count(&self) -> usize {
         // Each physical socket is represented as its own package_id, so amount of unique pkg_ids = sockets
         // https://www.kernel.org/doc/html/latest/admin-guide/abi-stable.html#abi-sys-devices-system-cpu-cpux-topology-physical-package-id
-        let physical_sockets: HashSet<usize> = self.cpus.iter().map(|cpu| cpu.pkg_id).collect();
+        let physical_sockets: HashSet<_> = self.cpus.iter().map(|cpu| cpu.pkg_id).collect();
 
         physical_sockets.len()
     }
+
+    pub fn core_count(&self) -> usize {
+        let core_ids: HashSet<_> = self.cpus.iter().map(|cpu| cpu.core_id).collect();
+        core_ids.len()
+    }
 }
 
 // TODO: respect `--hex` option and output the bitmask instead of human-readable range

From 2a381e532150b9d8e15cb5ad5aec4f71cc8a53c1 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Fri, 14 Feb 2025 17:21:30 +0200
Subject: [PATCH 06/10] `lscpu`: Use own function to parse cache sizes

---
 Cargo.lock                |  7 -------
 Cargo.toml                |  1 -
 src/uu/lscpu/Cargo.toml   |  1 -
 src/uu/lscpu/src/sysfs.rs | 39 +++++++++++++++++++++++++++++++++++++--
 4 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 1a26061..7e993e6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -513,12 +513,6 @@ dependencies = [
  "unicode-width 0.2.0",
 ]
 
-[[package]]
-name = "parse-size"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
-
 [[package]]
 name = "parse_datetime"
 version = "0.7.0"
@@ -1119,7 +1113,6 @@ name = "uu_lscpu"
 version = "0.0.1"
 dependencies = [
  "clap",
- "parse-size",
  "regex",
  "serde",
  "serde_json",
diff --git a/Cargo.toml b/Cargo.toml
index 9222a85..f2864ca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -46,7 +46,6 @@ dns-lookup = "2.0.4"
 libc = "0.2.152"
 linux-raw-sys = { version = "0.7.0", features = ["ioctl"] }
 nix = { version = "0.29", default-features = false }
-parse-size = "1.1.0"
 phf = "0.11.2"
 phf_codegen = "0.11.2"
 rand = { version = "0.9.0", features = ["small_rng"] }
diff --git a/src/uu/lscpu/Cargo.toml b/src/uu/lscpu/Cargo.toml
index bb8c86a..8d45f12 100644
--- a/src/uu/lscpu/Cargo.toml
+++ b/src/uu/lscpu/Cargo.toml
@@ -17,4 +17,3 @@ uucore = { workspace = true }
 clap = { workspace = true }
 serde = { workspace = true }
 serde_json = { workspace = true }
-parse-size = { workspace = true }
diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs
index 787e0b7..2497f23 100644
--- a/src/uu/lscpu/src/sysfs.rs
+++ b/src/uu/lscpu/src/sysfs.rs
@@ -1,4 +1,3 @@
-use parse_size::parse_size;
 use std::{collections::HashSet, fs, path::PathBuf};
 
 pub struct CpuVulnerability {
@@ -113,7 +112,7 @@ fn read_cpu_caches(cpu_index: usize) -> Vec<CpuCache> {
             .unwrap();
 
         let size_string = fs::read_to_string(cache_path.join("size")).unwrap();
-        let c_size = parse_size(size_string.trim()).unwrap();
+        let c_size = parse_cache_size(size_string.trim());
 
         let shared_cpu_map = fs::read_to_string(cache_path.join("shared_cpu_map"))
             .unwrap()
@@ -202,6 +201,42 @@ fn parse_cpu_list(list: &str) -> Vec<usize> {
     out
 }
 
+fn parse_cache_size(s: &str) -> u64 {
+    // Yes, this will break if we ever reach a point where caches exceed terabytes in size...
+    const EXPONENTS: [(char, u32); 4] = [('K', 1), ('M', 2), ('G', 3), ('T', 4)];
+
+    // If we only have numbers, treat it as a raw amount of bytes and parse as-is
+    if s.chars().all(|c| c.is_numeric()) {
+        return s.parse::<u64>().expect("Could not parse cache size");
+    };
+
+    for (suffix, exponent) in EXPONENTS {
+        if s.ends_with(suffix) {
+            let nums = s.strip_suffix(suffix).unwrap();
+            let value = nums.parse::<u64>().expect("Could not parse cache size");
+            let multiplier = 1024_u64.pow(exponent);
+
+            return value * multiplier;
+        }
+    }
+
+    panic!("No known suffix in cache size string");
+}
+
+#[test]
+fn test_parse_cache_size() {
+    assert_eq!(parse_cache_size("512"), 512);
+
+    assert_eq!(parse_cache_size("1K"), 1024);
+    assert_eq!(parse_cache_size("1M"), 1024 * 1024);
+    assert_eq!(parse_cache_size("1G"), 1024 * 1024 * 1024);
+    assert_eq!(parse_cache_size("1T"), 1024 * 1024 * 1024 * 1024);
+
+    assert_eq!(parse_cache_size("123K"), 123 * 1024);
+    assert_eq!(parse_cache_size("32M"), 32 * 1024 * 1024);
+    assert_eq!(parse_cache_size("345G"), 345 * 1024 * 1024 * 1024);
+}
+
 #[test]
 fn test_parse_cpu_list() {
     assert_eq!(parse_cpu_list(""), Vec::<usize>::new());

From c6a36590296c6dd549fd0a74085fbee01b9f181c Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Fri, 14 Feb 2025 20:38:12 +0200
Subject: [PATCH 07/10] `lscpu`: Print cache sizes in human-readable form

---
 src/uu/lscpu/src/lscpu.rs |  13 +++--
 src/uu/lscpu/src/sysfs.rs | 108 +++++++++++++++++++++++++++-----------
 2 files changed, 86 insertions(+), 35 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 19cce4e..6b43bc8 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -7,6 +7,7 @@ use clap::{crate_version, Arg, ArgAction, Command};
 use regex::RegexBuilder;
 use serde::Serialize;
 use std::{cmp, collections::HashMap, fs};
+use sysfs::CacheSize;
 use uucore::{error::UResult, format_usage, help_about, help_usage};
 
 mod options {
@@ -196,11 +197,17 @@ fn calculate_cache_totals(cpus: Vec<sysfs::Cpu>) -> Option<CpuInfo> {
         caches.dedup_by_key(|c| &c.shared_cpu_map);
 
         let count = caches.len();
-        let size_total = caches.iter().fold(0_u64, |acc, c| acc + c.size);
+        let size_total = caches
+            .iter()
+            .fold(0_u64, |acc, c| acc + c.size.size_bytes());
+
         cache_info.add_child(CpuInfo::new(
             level,
-            // TODO: Format sizes using `KiB`, `MiB` etc.
-            &format!("{} bytes ({} instances)", size_total, count),
+            &format!(
+                "{} ({} instances)",
+                CacheSize::new(size_total).human_readable(),
+                count
+            ),
             None,
         ));
     }
diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs
index 2497f23..2e1409d 100644
--- a/src/uu/lscpu/src/sysfs.rs
+++ b/src/uu/lscpu/src/sysfs.rs
@@ -21,10 +21,13 @@ pub struct Cpu {
 pub struct CpuCache {
     pub typ: CacheType,
     pub level: usize,
-    pub size: u64,
+    pub size: CacheSize,
     pub shared_cpu_map: String,
 }
 
+#[derive(Debug)]
+pub struct CacheSize(u64);
+
 #[derive(Debug)]
 pub enum CacheType {
     Data,
@@ -79,6 +82,51 @@ impl CpuTopology {
     }
 }
 
+impl CacheSize {
+    pub fn new(size: u64) -> Self {
+        Self(size)
+    }
+
+    fn parse(s: &str) -> Self {
+        // Yes, this will break if we ever reach a point where caches exceed terabytes in size...
+        const EXPONENTS: [(char, u32); 4] = [('K', 1), ('M', 2), ('G', 3), ('T', 4)];
+
+        // If we only have numbers, treat it as a raw amount of bytes and parse as-is
+        if s.chars().all(|c| c.is_numeric()) {
+            return Self(s.parse::<u64>().expect("Could not parse cache size"));
+        };
+
+        for (suffix, exponent) in EXPONENTS {
+            if s.ends_with(suffix) {
+                let nums = s.strip_suffix(suffix).unwrap();
+                let value = nums.parse::<u64>().expect("Could not parse cache size");
+                let multiplier = 1024_u64.pow(exponent);
+
+                return Self(value * multiplier);
+            }
+        }
+
+        panic!("No known suffix in cache size string");
+    }
+
+    pub fn size_bytes(&self) -> u64 {
+        self.0
+    }
+
+    pub fn human_readable(&self) -> String {
+        let (unit, denominator) = match self.0 {
+            x if x < 1024_u64.pow(1) => ("B", 1024_u64.pow(0)),
+            x if x < 1024_u64.pow(2) => ("KiB", 1024_u64.pow(1)),
+            x if x < 1024_u64.pow(3) => ("MiB", 1024_u64.pow(2)),
+            x if x < 1024_u64.pow(4) => ("GiB", 1024_u64.pow(3)),
+            x if x < 1024_u64.pow(5) => ("TiB", 1024_u64.pow(4)),
+            _ => return format!("{} bytes", self.0),
+        };
+        let scaled_size = self.0 / denominator;
+        format!("{} {}", scaled_size, unit)
+    }
+}
+
 // TODO: respect `--hex` option and output the bitmask instead of human-readable range
 pub fn read_online_cpus() -> String {
     fs::read_to_string("/sys/devices/system/cpu/online")
@@ -112,7 +160,7 @@ fn read_cpu_caches(cpu_index: usize) -> Vec<CpuCache> {
             .unwrap();
 
         let size_string = fs::read_to_string(cache_path.join("size")).unwrap();
-        let c_size = parse_cache_size(size_string.trim());
+        let c_size = CacheSize::parse(size_string.trim());
 
         let shared_cpu_map = fs::read_to_string(cache_path.join("shared_cpu_map"))
             .unwrap()
@@ -201,40 +249,36 @@ fn parse_cpu_list(list: &str) -> Vec<usize> {
     out
 }
 
-fn parse_cache_size(s: &str) -> u64 {
-    // Yes, this will break if we ever reach a point where caches exceed terabytes in size...
-    const EXPONENTS: [(char, u32); 4] = [('K', 1), ('M', 2), ('G', 3), ('T', 4)];
-
-    // If we only have numbers, treat it as a raw amount of bytes and parse as-is
-    if s.chars().all(|c| c.is_numeric()) {
-        return s.parse::<u64>().expect("Could not parse cache size");
-    };
-
-    for (suffix, exponent) in EXPONENTS {
-        if s.ends_with(suffix) {
-            let nums = s.strip_suffix(suffix).unwrap();
-            let value = nums.parse::<u64>().expect("Could not parse cache size");
-            let multiplier = 1024_u64.pow(exponent);
-
-            return value * multiplier;
-        }
-    }
-
-    panic!("No known suffix in cache size string");
+#[test]
+fn test_parse_cache_size() {
+    assert_eq!(CacheSize::parse("512").size_bytes(), 512);
+    assert_eq!(CacheSize::parse("1K").size_bytes(), 1024);
+    assert_eq!(CacheSize::parse("1M").size_bytes(), 1024 * 1024);
+    assert_eq!(CacheSize::parse("1G").size_bytes(), 1024 * 1024 * 1024);
+    assert_eq!(
+        CacheSize::parse("1T").size_bytes(),
+        1024 * 1024 * 1024 * 1024
+    );
+    assert_eq!(CacheSize::parse("123K").size_bytes(), 123 * 1024);
+    assert_eq!(CacheSize::parse("32M").size_bytes(), 32 * 1024 * 1024);
+    assert_eq!(
+        CacheSize::parse("345G").size_bytes(),
+        345 * 1024 * 1024 * 1024
+    );
 }
 
 #[test]
-fn test_parse_cache_size() {
-    assert_eq!(parse_cache_size("512"), 512);
+fn test_print_cache_size() {
+    assert_eq!(CacheSize::new(1023).human_readable(), "1023 B");
+    assert_eq!(CacheSize::new(1024).human_readable(), "1 KiB");
+    assert_eq!(CacheSize::new(1024 * 1024).human_readable(), "1 MiB");
+    assert_eq!(CacheSize::new(1024 * 1024 * 1024).human_readable(), "1 GiB");
 
-    assert_eq!(parse_cache_size("1K"), 1024);
-    assert_eq!(parse_cache_size("1M"), 1024 * 1024);
-    assert_eq!(parse_cache_size("1G"), 1024 * 1024 * 1024);
-    assert_eq!(parse_cache_size("1T"), 1024 * 1024 * 1024 * 1024);
-
-    assert_eq!(parse_cache_size("123K"), 123 * 1024);
-    assert_eq!(parse_cache_size("32M"), 32 * 1024 * 1024);
-    assert_eq!(parse_cache_size("345G"), 345 * 1024 * 1024 * 1024);
+    assert_eq!(CacheSize::new(3 * 1024).human_readable(), "3 KiB");
+    assert_eq!(
+        CacheSize::new((7.6 * 1024.0 * 1024.0) as u64).human_readable(),
+        "7 MiB"
+    );
 }
 
 #[test]

From 4b6b9f9291d5f3271cfdee15ba2dd73dd79c8ca3 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Fri, 14 Feb 2025 22:17:52 +0200
Subject: [PATCH 08/10] `lscpu`: Show "threads per core" count

---
 src/uu/lscpu/src/lscpu.rs | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 6b43bc8..df39810 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -125,6 +125,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
 
             let socket_count = &cpu_topology.socket_count();
             let core_count = &cpu_topology.core_count();
+
+            model_name_info.add_child(CpuInfo::new(
+                "Thread(s) per core",
+                &(cpu_topology.cpus.len() / core_count).to_string(),
+                None,
+            ));
+
             model_name_info.add_child(CpuInfo::new(
                 "Core(s) per socket",
                 &(core_count / socket_count).to_string(),

From 3180b895227563e3aaed33ab922d19f790efda4b Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Mon, 17 Feb 2025 16:40:08 +0200
Subject: [PATCH 09/10] `lscpu`: Fix minor issues from review

---
 src/uu/lscpu/src/sysfs.rs | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs
index 2e1409d..462e536 100644
--- a/src/uu/lscpu/src/sysfs.rs
+++ b/src/uu/lscpu/src/sysfs.rs
@@ -1,3 +1,8 @@
+// This file is part of the uutils util-linux package.
+//
+// For the full copyright and license information, please view the LICENSE
+// file that was distributed with this source code.
+
 use std::{collections::HashSet, fs, path::PathBuf};
 
 pub struct CpuVulnerability {
@@ -92,7 +97,7 @@ impl CacheSize {
         const EXPONENTS: [(char, u32); 4] = [('K', 1), ('M', 2), ('G', 3), ('T', 4)];
 
         // If we only have numbers, treat it as a raw amount of bytes and parse as-is
-        if s.chars().all(|c| c.is_numeric()) {
+        if s.chars().all(char::is_numeric) {
             return Self(s.parse::<u64>().expect("Could not parse cache size"));
         };
 

From 748bbf845ef96ae033d42a50bfebacbc5e9df0d5 Mon Sep 17 00:00:00 2001
From: Andrey Aleksandrov <44431221+alxndrv@users.noreply.github.com>
Date: Mon, 17 Feb 2025 17:28:25 +0200
Subject: [PATCH 10/10] Update src/uu/lscpu/src/sysfs.rs

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
---
 src/uu/lscpu/src/sysfs.rs | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs
index 462e536..bbbf9d7 100644
--- a/src/uu/lscpu/src/sysfs.rs
+++ b/src/uu/lscpu/src/sysfs.rs
@@ -184,10 +184,9 @@ fn read_cpu_caches(cpu_index: usize) -> Vec<CpuCache> {
 }
 
 pub fn read_freq_boost_state() -> Option<bool> {
-    match fs::read_to_string("/sys/devices/system/cpu/cpufreq/boost") {
-        Ok(content) => Some(content.trim() == "1"),
-        Err(_) => None,
-    }
+    fs::read_to_string("/sys/devices/system/cpu/cpufreq/boost")
+        .map(|content| content.trim() == "1")
+        .ok()
 }
 
 pub fn read_cpu_vulnerabilities() -> Vec<CpuVulnerability> {