Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ tempfile = "3.9.0"
textwrap = { version = "0.16.0", features = ["terminal_size"] }
thiserror = "2.0"
uucore = "0.2.2"
uulinux = { version = "0.0.1", path = "src/uulinux" }
uuid = { version = "1.16.0", features = ["rng-rand"] }
uutests = "0.7.0"
windows = { version = "0.62.2" }
Expand Down
1 change: 1 addition & 0 deletions src/uu/lscpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ path = "src/main.rs"
regex = { workspace = true }
sysinfo = { workspace = true }
uucore = { workspace = true, features = ["parser"] }
uulinux = { workspace = true }
clap = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
31 changes: 24 additions & 7 deletions src/uu/lscpu/src/lscpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
use clap::{crate_version, Arg, ArgAction, Command};
use regex::RegexBuilder;
use serde::Serialize;
use std::{cmp, collections::HashMap, fs};
use std::{cmp, collections::HashMap, fs, path::Path};
use sysfs::CacheSize;
use uucore::{error::UResult, format_usage, help_about, help_usage};
use uulinux::join_under_root;

mod options {
pub const BYTES: &str = "bytes";
pub const HEX: &str = "hex";
pub const JSON: &str = "json";
pub const SYSROOT: &str = "sysroot";
}

mod sysfs;
Expand Down Expand Up @@ -80,30 +82,37 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
json: matches.get_flag(options::JSON),
};

let sysroot = matches
.get_one::<String>(options::SYSROOT)
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::path::PathBuf::from("/"));
let root = sysroot.as_path();

let mut cpu_infos = CpuInfos::new();

let mut arch_info = CpuInfo::new("Architecture", &get_architecture());

// TODO: We just silently ignore failures to read `/proc/cpuinfo` currently and treat it as empty
// Perhaps a better solution should be put in place, but what?
let contents = fs::read_to_string("/proc/cpuinfo").unwrap_or_default();
let proc_cpuinfo = join_under_root(root, Path::new("/proc/cpuinfo"));
let contents = fs::read_to_string(proc_cpuinfo).unwrap_or_default();

if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") {
arch_info.add_child(CpuInfo::new("Address sizes", &addr_sizes))
}

if let Some(byte_order) = sysfs::read_cpu_byte_order() {
if let Some(byte_order) = sysfs::read_cpu_byte_order(root) {
arch_info.add_child(CpuInfo::new("Byte Order", byte_order));
}

cpu_infos.push(arch_info);

let cpu_topology = sysfs::CpuTopology::new();
let cpu_topology = sysfs::CpuTopology::new(root);
let mut cores_info = CpuInfo::new("CPU(s)", &format!("{}", cpu_topology.cpus.len()));

cores_info.add_child(CpuInfo::new(
"On-line CPU(s) list",
&sysfs::read_online_cpus(),
&sysfs::read_online_cpus(root),
));

cpu_infos.push(cores_info);
Expand Down Expand Up @@ -139,7 +148,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
));
model_name_info.add_child(CpuInfo::new("Socket(s)", &socket_count.to_string()));

if let Some(freq_boost_enabled) = sysfs::read_freq_boost_state() {
if let Some(freq_boost_enabled) = sysfs::read_freq_boost_state(root) {
let s = if freq_boost_enabled {
"enabled"
} else {
Expand All @@ -158,7 +167,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
cpu_infos.push(cache_info);
}

let vulns = sysfs::read_cpu_vulnerabilities();
let vulns = sysfs::read_cpu_vulnerabilities(root);
if !vulns.is_empty() {
let mut vuln_info = CpuInfo::new("Vulnerabilities", "");
for vuln in vulns {
Expand Down Expand Up @@ -350,4 +359,12 @@ pub fn uu_app() -> Command {
Setting this flag instead prints the decimal amount of bytes with no suffix.",
),
)
.arg(
Arg::new(options::SYSROOT)
.short('s')
.long("sysroot")
.action(ArgAction::Set)
.value_name("dir")
.help("Gather CPU data from the specified directory as the system root."),
)
}
45 changes: 30 additions & 15 deletions src/uu/lscpu/src/sysfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
// 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};
use std::{
collections::HashSet,
fs,
path::{Path, PathBuf},
};
use uucore::parser::parse_size;
use uulinux::join_under_root;

pub struct CpuVulnerability {
pub name: String,
Expand Down Expand Up @@ -42,13 +47,16 @@ pub enum CacheType {
}

impl CpuTopology {
pub fn new() -> Self {
pub fn new(root: &Path) -> Self {
let mut out: Vec<Cpu> = vec![];

let online_cpus = parse_cpu_list(&read_online_cpus());
let online_cpus = parse_cpu_list(&read_online_cpus(root));

for cpu_index in online_cpus {
let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{cpu_index}/"));
let cpu_dir = join_under_root(
root,
&PathBuf::from(format!("/sys/devices/system/cpu/cpu{cpu_index}/")),
);

let pkg_id = fs::read_to_string(cpu_dir.join("topology/physical_package_id"))
.unwrap()
Expand All @@ -62,7 +70,7 @@ impl CpuTopology {
.parse::<usize>()
.unwrap();

let caches = read_cpu_caches(cpu_index);
let caches = read_cpu_caches(root, cpu_index);

out.push(Cpu {
_index: cpu_index,
Expand Down Expand Up @@ -120,15 +128,19 @@ impl CacheSize {
}

// 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")
pub fn read_online_cpus(root: &Path) -> String {
let path = join_under_root(root, Path::new("/sys/devices/system/cpu/online"));
fs::read_to_string(path)
.expect("Could not read sysfs")
.trim()
.to_string()
}

fn read_cpu_caches(cpu_index: usize) -> Vec<CpuCache> {
let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{cpu_index}/"));
fn read_cpu_caches(root: &Path, cpu_index: usize) -> Vec<CpuCache> {
let cpu_dir = join_under_root(
root,
&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()
Expand Down Expand Up @@ -170,16 +182,18 @@ fn read_cpu_caches(cpu_index: usize) -> Vec<CpuCache> {
caches
}

pub fn read_freq_boost_state() -> Option<bool> {
fs::read_to_string("/sys/devices/system/cpu/cpufreq/boost")
pub fn read_freq_boost_state(root: &Path) -> Option<bool> {
let path = join_under_root(root, Path::new("/sys/devices/system/cpu/cpufreq/boost"));
fs::read_to_string(path)
.map(|content| content.trim() == "1")
.ok()
}

pub fn read_cpu_vulnerabilities() -> Vec<CpuVulnerability> {
pub fn read_cpu_vulnerabilities(root: &Path) -> Vec<CpuVulnerability> {
let mut out: Vec<CpuVulnerability> = vec![];

if let Ok(dir) = fs::read_dir("/sys/devices/system/cpu/vulnerabilities") {
let path = join_under_root(root, Path::new("/sys/devices/system/cpu/vulnerabilities"));
if let Ok(dir) = fs::read_dir(path) {
let mut files: Vec<_> = dir
.flatten()
.map(|x| x.path())
Expand All @@ -203,8 +217,9 @@ 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") {
pub fn read_cpu_byte_order(root: &Path) -> Option<&'static str> {
let path = join_under_root(root, Path::new("/sys/kernel/cpu_byteorder"));
if let Ok(byte_order) = fs::read_to_string(path) {
match byte_order.trim() {
"big" => return Some("Big Endian"),
"little" => return Some("Little Endian"),
Expand Down
1 change: 1 addition & 0 deletions src/uu/lsmem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ path = "src/main.rs"

[dependencies]
uucore = { workspace = true }
uulinux = { workspace = true }
clap = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
12 changes: 5 additions & 7 deletions src/uu/lsmem/src/lsmem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::fs;
use std::io::{self, BufRead, BufReader};
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use uucore::{error::UResult, format_usage, help_about, help_usage};
use uulinux::join_under_root;

const ABOUT: &str = help_about!("lsmem.md");
const USAGE: &str = help_usage!("lsmem.md");
Expand Down Expand Up @@ -783,12 +784,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}

if let Some(sysroot) = matches.get_one::<String>(options::SYSROOT) {
opts.sysmem = format!(
"{}{}{}",
sysroot.trim_end_matches(MAIN_SEPARATOR),
MAIN_SEPARATOR,
opts.sysmem.trim_start_matches(MAIN_SEPARATOR)
);
opts.sysmem = join_under_root(Path::new(sysroot), Path::new(&opts.sysmem))
.display()
.to_string();
}

read_info(&mut lsmem, &mut opts);
Expand Down
6 changes: 6 additions & 0 deletions src/uulinux/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "uulinux"
version = "0.0.1"
edition = "2021"

[dependencies]
55 changes: 55 additions & 0 deletions src/uulinux/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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::path::{Path, PathBuf};

/// Join `path` under `root`, ignoring any leading `/` in `path`.
///
/// Unlike [`Path::join`], this never discards `root` when `path` is absolute.
/// Useful for prepending a sysroot to a system path like `/sys/devices/...`.
pub fn join_under_root(root: &Path, path: &Path) -> PathBuf {
let relative = path.strip_prefix("/").unwrap_or(path);
root.join(relative)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn absolute_path_is_joined_under_root() {
assert_eq!(
join_under_root(
Path::new("/sysroot"),
Path::new("/sys/devices/system/memory")
),
PathBuf::from("/sysroot/sys/devices/system/memory"),
);
}

#[test]
fn relative_path_is_joined_normally() {
assert_eq!(
join_under_root(Path::new("/sysroot"), Path::new("sys/devices")),
PathBuf::from("/sysroot/sys/devices"),
);
}

#[test]
fn root_slash_alone_gives_root() {
assert_eq!(
join_under_root(Path::new("/sysroot"), Path::new("/")),
PathBuf::from("/sysroot"),
);
}

#[test]
fn trailing_slash_on_root_is_handled() {
assert_eq!(
join_under_root(Path::new("/sysroot/"), Path::new("/sys/devices")),
PathBuf::from("/sysroot/sys/devices"),
);
}
}
Loading