#[cfg(feature = "v2")]
use std::collections::HashSet;
use std::{fs, path::Path};
use anyhow::Result;
use clap::Parser;
use libcontainer::rootless;
use procfs::{CpuInfo, Meminfo};
#[cfg(feature = "v2")]
use libcgroups::{common::CgroupSetup, v2::controller_type::ControllerType};
#[derive(Parser, Debug)]
pub struct Info {}
pub fn info(_: Info) -> Result<()> {
print_youki();
print_kernel();
print_os();
print_hardware();
print_cgroups();
print_namespaces();
print_capabilities();
Ok(())
}
pub fn print_youki() {
println!("{:<18}{}", "Version", env!("CARGO_PKG_VERSION"));
println!("{:<18}{}", "Commit", env!("VERGEN_GIT_SHA_SHORT"));
}
pub fn print_kernel() {
let uname = nix::sys::utsname::uname().unwrap();
println!(
"{:<18}{}",
"Kernel-Release",
uname.release().to_string_lossy()
);
println!(
"{:<18}{}",
"Kernel-Version",
uname.version().to_string_lossy()
);
println!(
"{:<18}{}",
"Architecture",
uname.machine().to_string_lossy()
);
}
pub fn print_os() {
if let Some(os) = try_read_os_from("/etc/os-release") {
println!("{:<18}{}", "Operating System", os);
} else if let Some(os) = try_read_os_from("/usr/lib/os-release") {
println!("{:<18}{}", "Operating System", os);
}
}
fn try_read_os_from<P: AsRef<Path>>(path: P) -> Option<String> {
let os_release = path.as_ref();
if !os_release.exists() {
return None;
}
if let Ok(release_content) = fs::read_to_string(path) {
let pretty = find_parameter(&release_content, "PRETTY_NAME");
if let Some(pretty) = pretty {
return Some(pretty.trim_matches('"').to_owned());
}
let name = find_parameter(&release_content, "NAME");
let version = find_parameter(&release_content, "VERSION");
if let Some((name, version)) = name.zip(version) {
return Some(format!(
"{} {}",
name.trim_matches('"'),
version.trim_matches('"')
));
}
}
None
}
fn find_parameter<'a>(content: &'a str, param_name: &str) -> Option<&'a str> {
content
.lines()
.find(|l| l.starts_with(param_name))
.and_then(|l| l.split_terminator('=').last())
}
pub fn print_hardware() {
if let Ok(cpu_info) = CpuInfo::new() {
println!("{:<18}{}", "Cores", cpu_info.num_cores());
}
if let Ok(mem_info) = Meminfo::new() {
println!(
"{:<18}{}",
"Total Memory",
mem_info.mem_total / u64::pow(1024, 2)
);
}
}
pub fn print_cgroups() {
print_cgroups_setup();
print_cgroup_mounts();
#[cfg(feature = "v2")]
print_cgroup_v2_controllers();
}
pub fn print_cgroups_setup() {
let cgroup_setup = libcgroups::common::get_cgroup_setup();
if let Ok(cgroup_setup) = &cgroup_setup {
println!("{:<18}{}", "Cgroup setup", cgroup_setup);
}
}
pub fn print_cgroup_mounts() {
println!("Cgroup mounts");
#[cfg(feature = "v1")]
if let Ok(v1_mounts) = libcgroups::v1::util::list_supported_mount_points() {
let mut v1_mounts: Vec<String> = v1_mounts
.iter()
.map(|kv| format!(" {:<16}{}", kv.0.to_string(), kv.1.display()))
.collect();
v1_mounts.sort();
for cgroup_mount in v1_mounts {
println!("{cgroup_mount}");
}
}
#[cfg(feature = "v2")]
if let Ok(mount_point) = libcgroups::v2::util::get_unified_mount_point() {
println!(" {:<16}{}", "unified", mount_point.display());
}
}
#[cfg(feature = "v2")]
pub fn print_cgroup_v2_controllers() {
let cgroup_setup = libcgroups::common::get_cgroup_setup();
let unified = libcgroups::v2::util::get_unified_mount_point();
if let Ok(cgroup_setup) = cgroup_setup {
if let Ok(unified) = &unified {
if matches!(cgroup_setup, CgroupSetup::Hybrid | CgroupSetup::Unified) {
if let Ok(controllers) = libcgroups::v2::util::get_available_controllers(unified) {
println!("CGroup v2 controllers");
let active_controllers: HashSet<ControllerType> =
controllers.into_iter().collect();
for controller in libcgroups::v2::controller_type::CONTROLLER_TYPES {
let status = if active_controllers.contains(controller) {
"attached"
} else {
"detached"
};
println!(" {:<16}{}", controller.to_string(), status);
}
}
if let Some(config) = read_kernel_config() {
let display = FeatureDisplay::with_status("device", "attached", "detached");
print_feature_status(&config, "CONFIG_CGROUP_BPF", display);
}
}
}
}
}
fn read_kernel_config() -> Option<String> {
let uname = nix::sys::utsname::uname();
let kernel_config = Path::new("/boot").join(format!(
"config-{}",
uname.unwrap().release().to_string_lossy()
));
if !kernel_config.exists() {
return None;
}
fs::read_to_string(kernel_config).ok()
}
pub fn print_namespaces() {
if let Some(content) = read_kernel_config() {
if let Some(ns_enabled) = find_parameter(&content, "CONFIG_NAMESPACES") {
if ns_enabled == "y" {
println!("{:<18}enabled", "Namespaces");
} else {
println!("{:<18}disabled", "Namespaces");
return;
}
}
println!(" {:<16}enabled", "mount");
print_feature_status(&content, "CONFIG_UTS_NS", FeatureDisplay::new("uts"));
print_feature_status(&content, "CONFIG_IPC_NS", FeatureDisplay::new("ipc"));
let user_display = match rootless::unprivileged_user_ns_enabled() {
Ok(false) => FeatureDisplay::with_status("user", "enabled (root only)", "disabled"),
_ => FeatureDisplay::new("user"),
};
print_feature_status(&content, "CONFIG_USER_NS", user_display);
print_feature_status(&content, "CONFIG_PID_NS", FeatureDisplay::new("pid"));
print_feature_status(&content, "CONFIG_NET_NS", FeatureDisplay::new("network"));
print_feature_status(&content, "CONFIG_CGROUPS", FeatureDisplay::new("cgroup"))
}
}
#[inline]
fn is_cap_available(caps: &caps::CapsHashSet, cap: caps::Capability) -> &'static str {
if caps.contains(&cap) {
"available"
} else {
"unavailable"
}
}
pub fn print_capabilities() {
println!("Capabilities");
if let Ok(current) = caps::read(None, caps::CapSet::Bounding) {
println!(
"{:<17} {}",
"CAP_BPF",
is_cap_available(¤t, caps::Capability::CAP_BPF)
);
println!(
"{:<17} {}",
"CAP_PERFMON",
is_cap_available(¤t, caps::Capability::CAP_PERFMON)
);
println!(
"{:<17} {}",
"CAP_CHECKPOINT_RESTORE",
is_cap_available(¤t, caps::Capability::CAP_CHECKPOINT_RESTORE)
);
} else {
println!("<cannot find cap info>");
}
}
fn print_feature_status(config: &str, feature: &str, display: FeatureDisplay) {
if let Some(status_flag) = find_parameter(config, feature) {
let status = if status_flag == "y" {
display.enabled
} else {
display.disabled
};
println!(" {:<16}{}", display.name, status);
} else {
println!(" {:<16}{}", display.name, display.disabled);
}
}
struct FeatureDisplay<'a> {
name: &'a str,
enabled: &'a str,
disabled: &'a str,
}
impl<'a> FeatureDisplay<'a> {
fn new(name: &'a str) -> Self {
Self {
name,
enabled: "enabled",
disabled: "disabled",
}
}
fn with_status(name: &'a str, enabled: &'a str, disabled: &'a str) -> Self {
Self {
name,
enabled,
disabled,
}
}
}