use std::collections::{BTreeMap, HashMap};
use std::fmt::{Debug, Write};
use crate::consts::{TIME_NEXT_ELAPSE_USEC_MONOTONIC, TIME_NEXT_ELAPSE_USEC_REALTIME, U64MAX};
use crate::systemd::{
self,
data::{UnitInfo, UnitProcess},
enums::{LoadState, Preset},
};
use crate::utils::writer::{
HyperLinkType, SPECIAL_GLYPH_TREE_BRANCH, SPECIAL_GLYPH_TREE_RIGHT, SPECIAL_GLYPH_TREE_SPACE,
SPECIAL_GLYPH_TREE_VERTICAL, UnitInfoWriter,
};
use crate::widget::{
preferences::data::PREFERENCES, unit_info::construct_info::systemd::timestamp_is_set,
};
use base::enums::UnitDBusLevel;
use systemd::time_handling::calc_next_elapse;
use systemd::{
enums::ActiveState,
swrite,
time_handling::{self, TimestampStyle},
};
use tracing::{debug, warn};
use zvariant::{DynamicType, OwnedValue, Str, Value};
pub(crate) fn fill_all_info(
unit: &UnitInfo,
unit_writer: &mut UnitInfoWriter,
) -> HashMap<String, OwnedValue> {
fill_name_description(unit_writer, unit);
let map = systemd::fetch_system_unit_info_native_map(unit).unwrap_or_else(|e| {
warn!("Fails to retrieve Unit info for {:?} {e:?}", unit.primary());
HashMap::new()
});
let timestamp_style = PREFERENCES.timestamp_style();
let level = unit.dbus_level();
fill_description(unit_writer, &map, unit);
fill_follows(unit_writer, &map);
fill_load_state(unit_writer, &map, unit);
fill_transient(unit_writer, &map);
fill_dropin(unit_writer, &map);
fill_active_state(unit_writer, &map, unit, timestamp_style);
fill_invocation(unit_writer, &map);
fill_triggered_by(unit_writer, &map, level);
fill_device(unit_writer, &map);
fill_trigger(unit_writer, &map, unit, timestamp_style);
fill_triggers(unit_writer, &map, level);
fill_where(unit_writer, &map);
fill_what(unit_writer, &map);
fill_docs(unit_writer, &map);
fill_main_pid(unit_writer, &map, unit);
fill_status(unit_writer, &map);
fill_error(unit_writer, &map);
fill_ip(unit_writer, &map);
fill_io(unit_writer, &map);
fill_tasks(unit_writer, &map);
fill_fd_store(unit_writer, &map);
fill_memory(unit_writer, &map);
fill_listen(unit_writer, &map);
fill_accept(unit_writer, &map);
fill_cpu(unit_writer, &map);
fill_control_group(unit_writer, &map, unit);
map
}
fn fill_name_description(unit_writer: &mut UnitInfoWriter, unit: &UnitInfo) {
fill_row(unit_writer, "Name:", &unit.primary())
}
const KEY_WIDTH: usize = 15;
macro_rules! get_value {
($map:expr, $key:expr) => {
get_value!($map, $key, ())
};
($map:expr, $key:expr, $dft:expr) => {{
let Some(value) = $map.get($key) else {
debug!("Key doesn't exists: {:?}", $key);
return $dft;
};
value
}};
}
macro_rules! clean_message {
($result:expr) => {
clean_message!($result, "", ())
};
($result:expr, $log_prefix:expr) => {
clean_message!($result, $log_prefix, ())
};
($result:expr, $log_prefix:expr, $default_return:expr) => {{
match $result {
Ok(ok) => ok,
Err(e) => {
tracing::warn!("{} {:?}", $log_prefix, e);
return $default_return;
}
}
}};
}
fn write_key(unit_writer: &mut UnitInfoWriter, key_label: &str) {
let s = format!("{key_label:>KEY_WIDTH$} ");
unit_writer.insert(&s);
}
fn fill_row(unit_writer: &mut UnitInfoWriter, key_label: &str, value: &str) {
let s = format!("{key_label:>KEY_WIDTH$} {value}\n");
unit_writer.insert(&s);
}
fn fill_dropin(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let value = get_value!(map, "DropInPaths");
let drop_in_paths = get_array_str(value);
if drop_in_paths.is_empty() {
return;
}
let mut map = BTreeMap::new();
for file_name in drop_in_paths {
let (first, last) = match file_name.rsplit_once('/') {
Some((first, last)) => (first, last),
None => (file_name, ""),
};
let suffixes = map.entry(first).or_insert(Vec::new());
suffixes.push((last, file_name));
}
let mut is_first1 = true;
for (prefix, suffixes) in map {
let key_label = if is_first1 {
is_first1 = false;
"Drop in:"
} else {
""
};
let s = format!("{key_label:>KEY_WIDTH$} {prefix}\n");
unit_writer.insert(&s);
let mut is_first = true;
for (d, link) in suffixes.iter() {
if is_first {
unit_writer.insert(&format!("{:KEY_WIDTH$} {}", "", SPECIAL_GLYPH_TREE_RIGHT));
is_first = false;
} else {
unit_writer.insert(", ");
}
unit_writer.hyperlink(d, link, HyperLinkType::File);
}
unit_writer.newline();
}
}
fn fill_active_state(
unit_writer: &mut UnitInfoWriter,
map: &HashMap<String, OwnedValue>,
unit: &UnitInfo,
timestamp_style: TimestampStyle,
) {
let value = get_value!(map, "ActiveState");
let state: &str = value_to(value);
let state: ActiveState = state.into();
unit.set_active_state(state);
write_key(unit_writer, "Active:");
let mut state_text = String::from(state.as_str());
if let Some(substate) = get_substate(map, unit) {
state_text.push_str(" (");
state_text.push_str(substate);
state_text.push(')');
}
if state.is_active() {
unit_writer.insert_active(&state_text);
} else {
unit_writer.insert(&state_text);
};
if let Some(since) = add_since(map, state.as_str(), timestamp_style) {
unit_writer.insert(&format!(" since {}; {}\n", since.0, since.1));
fill_duration(unit_writer, map, unit);
} else {
unit_writer.newline();
}
}
fn fill_duration(
unit_writer: &mut UnitInfoWriter,
map: &HashMap<String, OwnedValue>,
unit: &UnitInfo,
) {
if !systemd::enums::UnitType::Target.eq(&unit.unit_type()) {
return;
}
let active_enter_timestamp = value_to_def(map.get("ActiveEnterTimestamp"), U64MAX);
let active_exit_timestamp = value_to_def(map.get("ActiveExitTimestamp"), U64MAX);
if timestamp_is_set!(active_enter_timestamp)
&& timestamp_is_set!(active_exit_timestamp)
&& active_exit_timestamp >= active_enter_timestamp
{
let duration = active_exit_timestamp - active_enter_timestamp;
let timespan = time_handling::format_timespan(duration, time_handling::MSEC_PER_SEC);
fill_row(unit_writer, "Duration:", ×pan);
}
}
fn get_substate<'a>(map: &'a HashMap<String, OwnedValue>, unit: &UnitInfo) -> Option<&'a str> {
let value = get_value!(map, "SubState", None);
let value_str = value_to(value);
unit.set_sub_state(value_str);
Some(value_str)
}
fn add_since(
map: &HashMap<String, OwnedValue>,
state: &str,
timestamp_style: TimestampStyle,
) -> Option<(String, String)> {
let key = match state {
"active" | "reloading" | "refreshing" => "ActiveEnterTimestamp",
"inactive" | "failed" => "InactiveEnterTimestamp",
"activating" => "InactiveExitTimestamp",
_ => "ActiveExitTimestamp",
};
let duration = value_some(map.get(key));
if duration != 0 {
let since = time_handling::get_since_and_passed_time(duration, timestamp_style);
Some(since)
} else {
None
}
}
fn fill_description(
unit_writer: &mut UnitInfoWriter,
map: &HashMap<String, OwnedValue>,
unit: &UnitInfo,
) {
let value = get_value!(map, "Description");
let description = value_to(value);
fill_row(unit_writer, "Description:", description);
unit.set_description(description);
}
fn fill_load_state(
unit_writer: &mut UnitInfoWriter,
map: &HashMap<String, OwnedValue>,
unit: &UnitInfo,
) {
let load_state: LoadState = map.get("LoadState").into();
unit.set_load_state(load_state);
write_key(unit_writer, "Loaded:");
unit_writer.insert(load_state.as_str());
let three_param = [
map.get("FragmentPath"),
map.get("UnitFileState"),
map.get("UnitFilePreset"),
];
let mut all_none = true;
for p in three_param {
let Some(value) = p else {
continue;
};
if let Value::Str(inner_str) = value as &Value
&& !inner_str.is_empty()
{
all_none = false;
break;
}
}
if !all_none {
unit_writer.insert(" (");
let [file_path, unit_file_state_op, unit_file_preset_op] = three_param;
let mut pad_left = false;
if let Some(file_path) = file_path {
let file_path_str = value_to(file_path);
unit.set_file_path(Some(file_path_str)); unit_writer.hyperlink(file_path_str, file_path_str, HyperLinkType::File);
pad_left = true;
}
if let Some(unit_file_state) = unit_file_state_op {
if pad_left {
unit_writer.insert("; ");
}
let unit_file_state = value_to(unit_file_state);
write_enabled_state(unit_writer, unit_file_state);
pad_left = true;
}
if let Some(unit_file_preset) = unit_file_preset_op {
if pad_left {
unit_writer.insert("; ");
}
unit_writer.insert("preset: ");
let unit_file_preset: &str = value_to(unit_file_preset);
let preset: Preset = unit_file_preset.into();
unit.set_preset(preset);
write_enabled_state(unit_writer, unit_file_preset);
}
unit_writer.insert(")");
}
unit_writer.newline();
}
fn write_enabled_state(unit_writer: &mut UnitInfoWriter, state: &str) {
match state {
"enabled" => unit_writer.insert_active(state),
"disabled" => unit_writer.insert_disable(state),
_ => unit_writer.insert(state),
};
}
fn fill_follows(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let value = get_value!(map, "Following");
let value: &str = value_to(value);
if value.is_empty() {
return;
}
let s = format!("unit currently follows state of {value}");
fill_row(unit_writer, "Follows:", &s);
}
fn fill_transient(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let value = get_value!(map, "Transient");
let transient = clean_message!(bool::try_from(value), "Wrong zvalue conversion");
if transient {
let value = if transient { "yes" } else { "no" };
fill_row(unit_writer, "Transient:", value);
}
}
fn fill_status(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let value = get_value!(map, "StatusText");
let value: &str = value_to(value);
if !value.is_empty() {
let s = format!("{:>KEY_WIDTH$} ", "Status:");
unit_writer.insert(&s);
unit_writer.insert_status(value);
unit_writer.newline();
}
}
fn fill_error(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let status_errno: i32 = value_some(map.get("StatusErrno"));
let status_bus_error: &str = value_some(map.get("StatusBusError"));
let status_varlink_error: &str = value_some(map.get("StatusVarlinkError"));
if status_errno == 0 && status_bus_error.is_empty() && status_varlink_error.is_empty() {
return;
}
write_key(unit_writer, "Error:");
let mut prefix = "";
if status_errno > 0 {
let mut text = format!("{prefix} {status_errno}");
if let Some(strerror) = strerror(status_errno) {
swrite!(text, " ({strerror})");
}
unit_writer.insert(&text);
prefix = "; ";
}
if !status_bus_error.is_empty() {
let text = format!("{prefix} D-Bus: {status_bus_error}");
unit_writer.insert(&text);
prefix = "; ";
}
if !status_varlink_error.is_empty() {
let text = format!("{prefix} Varlink: {status_varlink_error}");
unit_writer.insert(&text);
}
unit_writer.newline();
}
fn strerror(err_no: i32) -> Option<String> {
const ERRNO_BUF_LEN: usize = 1024;
let mut str_error: Vec<u8> = vec![0; ERRNO_BUF_LEN];
unsafe {
let str_error_raw_ptr = str_error.as_mut_ptr() as *mut libc::c_char;
libc::strerror_r(err_no, str_error_raw_ptr, ERRNO_BUF_LEN);
let nul_range_end = str_error
.iter()
.position(|&c| c == b'\0')
.unwrap_or(ERRNO_BUF_LEN);
str_error.truncate(nul_range_end);
String::from_utf8(str_error).ok()
}
}
fn fill_device(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
fill_what_string(unit_writer, map, "SysFSPath", "Device:")
}
fn fill_where(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
fill_what_string(unit_writer, map, "Where", "Where:")
}
fn fill_what(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
fill_what_string(unit_writer, map, "What", "What:")
}
fn fill_what_string(
unit_writer: &mut UnitInfoWriter,
map: &HashMap<String, OwnedValue>,
key: &str,
key_label: &str,
) {
let value: &str = value_some(map.get(key));
if !value.is_empty() {
fill_row(unit_writer, key_label, value);
}
}
fn fill_docs(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let value = get_value!(map, "Documentation");
let Value::Array(array) = value as &Value else {
return;
};
for (idx, value) in array.iter().enumerate() {
let key = match idx {
0 if array.len() == 1 => "Doc:",
0 => "Docs:",
_ => "",
};
write_key(unit_writer, key);
let doc = value_to(value);
insert_doc(unit_writer, doc);
unit_writer.newline();
}
}
fn insert_doc(unit_writer: &mut UnitInfoWriter, doc: &str) {
if doc.starts_with("man:") {
unit_writer.hyperlink(doc, doc, HyperLinkType::Man);
} else if doc.starts_with("http") {
unit_writer.hyperlink(doc, doc, HyperLinkType::Http);
} else {
unit_writer.insert(doc);
}
}
fn get_array_str<'a>(value: &'a Value<'a>) -> Vec<&'a str> {
match value as &Value {
Value::Array(a) => {
let mut vec = Vec::with_capacity(a.len());
for mi in a.iter() {
vec.push(value_to(mi));
}
vec
}
_ => {
warn!("Wrong zvalue conversion: {:?}", value.signature());
Vec::new()
}
}
}
fn fill_fd_store(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let n_fd_store: u32 = value_some(map.get("NFileDescriptorStore"));
let fd_store_max: u32 = value_some(map.get("FileDescriptorStoreMax"));
if n_fd_store == 0 && fd_store_max == 0 {
return;
}
write_key(unit_writer, "FD Store:");
unit_writer.insert(&n_fd_store.to_string());
unit_writer.insert_grey(&format!(" (limit: {fd_store_max})\n"));
}
fn fill_memory(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let memory_current = value_to_def(map.get("MemoryCurrent"), U64MAX);
if memory_current == U64MAX {
return;
}
write_key(unit_writer, "Memory:");
let value_str = human_bytes(memory_current);
unit_writer.insert(&value_str);
type MemoryManaging = (&'static str, u64, &'static str, fn(u64) -> bool);
let mut memories: [MemoryManaging; 17] = [
("MemoryMin", U64MAX, "min: ", is_zero),
("MemoryLow", U64MAX, "low: ", is_zero),
("StartupMemoryLow", U64MAX, "low (startup): ", is_zero),
("MemoryHigh", U64MAX, "high: ", is_limit_max), (
"StartupMemoryHigh",
U64MAX,
"high (startup): ",
is_limit_max,
),
("MemoryMax", U64MAX, "max: ", is_limit_max), ("StartupMemoryMax", U64MAX, "max (startup): ", is_limit_max),
("MemorySwapMax", U64MAX, "swap max: ", is_limit_max),
(
"StartupMemorySwapMax",
U64MAX,
"swap max (startup): ",
is_limit_max,
),
("MemoryZSwapMax", U64MAX, "zswap max: ", is_limit_max),
(
"StartupMemoryZSwapMax",
U64MAX,
"zswap max (startup): ",
is_limit_max,
),
("MemoryLimit", U64MAX, "limit: ", is_limit_max),
("MemoryAvailable", U64MAX, "available: ", is_limit_max),
("MemoryPeak", U64MAX, "peak: ", is_limit_max),
("MemorySwapCurrent", U64MAX, "swap: ", is_limit_max_or_zero),
(
"MemorySwapPeak",
U64MAX,
"swap peak: ",
is_limit_max_or_zero,
),
(
"MemoryZSwapCurrent",
U64MAX,
"zswap: ",
is_limit_max_or_zero,
),
];
for (key, value, _, _) in memories.iter_mut() {
if let Some(bus_value) = map.get(key as &str)
&& let Value::U64(converted) = bus_value as &Value
{
*value = *converted;
}
}
let mut is_first = true;
let mut out = String::with_capacity(100);
for (key, value, label, not_valid) in memories {
if not_valid(value)
|| (
key == "MemoryAvailable"
&& is_limit_max(memories[3].1) && is_limit_max(memories[5].1)
)
{
continue;
}
if is_first {
out.push_str(" (");
is_first = false;
} else {
out.push_str(", ");
}
out.push_str(label);
let mem_human = human_bytes(value);
out.push_str(&mem_human);
}
if !is_first {
out.push(')');
unit_writer.insert(&out);
}
unit_writer.newline();
}
fn is_limit_max_or_zero(value: u64) -> bool {
value == 0 || value == U64MAX
}
fn is_zero(value: u64) -> bool {
value == 0
}
fn is_limit_max(value: u64) -> bool {
value == U64MAX
}
fn fill_main_pid(
unit_writer: &mut UnitInfoWriter,
map: &HashMap<String, OwnedValue>,
unit: &UnitInfo,
) {
let main_pid = get_main_pid(map);
if 0 == main_pid {
return;
}
let exec_val = if let Some(exec) = get_exec(map) {
exec
} else {
unit.display_name()
};
let v = &format!("{main_pid} ({exec_val})");
fill_row(unit_writer, "Main PID:", v)
}
fn get_main_pid(map: &HashMap<String, OwnedValue>) -> u32 {
let value = get_value!(map, "MainPID", 0);
if let Value::U32(main_pid) = value as &Value {
return *main_pid;
}
0
}
#[derive(Clone, Value, Debug, OwnedValue)]
struct ExecStart<'a> {
path: Str<'a>,
argv: Vec<Str<'a>>,
ignore_errors: bool,
start_time: u64,
stop_time: u64,
field6: u64,
field7: u64,
field8: u32,
code: i32,
status: i32,
}
fn get_exec_full(map: &HashMap<String, OwnedValue>) -> Option<ExecStart<'_>> {
let value = get_value!(map, "ExecStart", None);
debug!(
"ExecStart Signature {:?} Value: {:?}",
value.value_signature(),
value
);
let Value::Array(array) = value as &Value else {
return None;
};
for idx in 0..array.len() {
let Ok(Some(val)) = array.get::<Value>(idx) else {
warn!("Can't get value from array");
continue;
};
let exec_start = clean_message!(ExecStart::try_from(val), "ExecStart", None);
return Some(exec_start);
}
None
}
fn get_exec(map: &HashMap<String, OwnedValue>) -> Option<String> {
if let Some(exec_full) = get_exec_full(map)
&& let Some((_pre, last)) = exec_full.path.rsplit_once('/')
{
return Some(last.to_string());
}
None
}
fn fill_cpu(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let cpu_usage_nsec = value_to_def(map.get("CPUUsageNSec"), U64MAX);
if cpu_usage_nsec == U64MAX {
return;
}
let value_str = time_handling::format_timespan(
cpu_usage_nsec / time_handling::NSEC_PER_USEC,
time_handling::USEC_PER_MSEC,
);
fill_row(unit_writer, "CPU:", &value_str)
}
fn fill_ip(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let ip_ingress_bytes = value_to_def(map.get("IPIngressBytes"), U64MAX);
let ip_egress_bytes = value_to_def(map.get("IPEgressBytes"), U64MAX);
if ip_ingress_bytes == U64MAX || ip_egress_bytes == U64MAX {
return;
}
fill_row(
unit_writer,
"IP:",
&format!(
"{} in, {} out",
human_bytes(ip_ingress_bytes),
human_bytes(ip_egress_bytes)
),
);
}
fn fill_io(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let io_read_bytes = value_to_def(map.get("IOReadBytes"), U64MAX);
let io_write_bytes = value_to_def(map.get("IOWriteBytes"), U64MAX);
if io_read_bytes == U64MAX || io_write_bytes == U64MAX {
return;
}
fill_row(
unit_writer,
"IP:",
&format!(
"{} read, {} written",
human_bytes(io_read_bytes),
human_bytes(io_write_bytes)
),
);
}
fn fill_tasks(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let tasks_current = value_to_def(map.get("TasksCurrent"), U64MAX);
if tasks_current == U64MAX {
return;
}
write_key(unit_writer, "Tasks:");
let tasks_info = tasks_current.to_string();
unit_writer.insert(&tasks_info);
let tasks_max = value_to_def(map.get("TasksMax"), U64MAX);
if tasks_max != U64MAX {
unit_writer.insert_grey(&format!(" (limit: {tasks_max})"));
}
unit_writer.newline();
}
fn fill_invocation(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let value = get_value!(map, "InvocationID");
let Value::Array(array) = value as &Value else {
return;
};
if array.is_empty() {
return;
};
let mut invocation = String::with_capacity(32);
for idx in 0..array.len() {
let Ok(Some(val)) = array.get::<Value>(idx) else {
warn!("Can't get value from array at index {idx}");
continue;
};
let Value::U8(converted) = val else {
warn!("Can't convert value to u8");
continue;
};
let hexa = format!("{converted:x}");
invocation.push_str(&hexa);
}
fill_row(unit_writer, "Invocation:", &invocation)
}
#[derive(Clone, Value, OwnedValue)]
struct TimersCalendar<'a> {
timer_base: Str<'a>,
calendar_specification: Str<'a>,
elapsation_point: u64,
}
fn fill_trigger(
unit_writer: &mut UnitInfoWriter,
map: &HashMap<String, OwnedValue>,
unit: &UnitInfo,
timestamp_style: TimestampStyle,
) {
if !systemd::enums::UnitType::Timer.eq(&unit.unit_type()) {
return;
}
let next_elapse_realtime = value_some(map.get(TIME_NEXT_ELAPSE_USEC_REALTIME));
let next_elapse_monotonic = value_some(map.get(TIME_NEXT_ELAPSE_USEC_MONOTONIC));
let next_elapse = calc_next_elapse(next_elapse_realtime, next_elapse_monotonic);
let trigger_msg = if timestamp_is_set!(next_elapse) {
let (first, second) =
time_handling::get_since_and_passed_time(next_elapse, timestamp_style);
format!("{first}; {second}")
} else {
"n/a".to_owned()
};
fill_row(unit_writer, "Trigger:", &trigger_msg);
}
#[derive(Clone, Value, OwnedValue)]
struct TimersMonotonic<'a> {
timer_base: Str<'a>,
usec_offset: u64,
elapsation_point: u64,
}
fn fill_triggers(
unit_writer: &mut UnitInfoWriter,
map: &HashMap<String, OwnedValue>,
level: UnitDBusLevel,
) {
let value = get_value!(map, "Triggers");
let triggers = get_array_str(value);
if triggers.is_empty() {
return;
}
let mut is_first = true;
for trigger_unit in triggers {
let key_label = if is_first {
is_first = false;
"Triggers:"
} else {
""
};
write_key(unit_writer, key_label);
match systemd::get_unit_active_state(level, trigger_unit) {
Ok(state) => {
unit_writer.insert_state(state);
}
Err(e) => {
warn!("Can't find state of {trigger_unit}, {e:?}");
unit_writer.insert(" ");
}
};
unit_writer.insert(" ");
unit_writer.hyperlink(trigger_unit, trigger_unit, HyperLinkType::Unit(level));
unit_writer.newline();
}
}
fn fill_triggered_by(
unit_writer: &mut UnitInfoWriter,
map: &HashMap<String, OwnedValue>,
level: UnitDBusLevel,
) {
let value = get_value!(map, "TriggeredBy");
let triggers = get_array_str(value);
let mut is_first = true;
for trigger_unit in triggers {
let key_label = if is_first {
is_first = false;
"TriggeredBy:"
} else {
""
};
write_key(unit_writer, key_label);
match systemd::get_unit_active_state(level, trigger_unit) {
Ok(state) => {
unit_writer.insert_state(state);
}
Err(e) => {
warn!("Can't find state of {trigger_unit}, {e:?}");
unit_writer.insert(" ");
}
};
unit_writer.insert(" ");
unit_writer.hyperlink(trigger_unit, trigger_unit, HyperLinkType::Unit(level));
unit_writer.newline();
}
}
#[derive(Value, OwnedValue)]
struct ListenStruct<'a> {
listen_type: Str<'a>,
path: Str<'a>,
}
fn fill_listen(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let value = get_value!(map, "Listen");
let Value::Array(array) = value as &Value else {
return;
};
for i in 0..array.len() {
let Ok(Some(val_listen_stc)) = array.get::<Value>(i) else {
continue;
};
let listen_struct = clean_message!(ListenStruct::try_from(val_listen_stc), "Listen info");
let key = if i == 0 { "Listen:" } else { "" };
write_key(unit_writer, key);
let listen = format!("{} ({})\n", listen_struct.path, listen_struct.listen_type);
unit_writer.insert(&listen);
}
}
fn fill_accept(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
let accept = value_some(map.get("Accept"));
if accept {
let n_accepted: u32 = value_some(map.get("NAccepted"));
let n_connections: u32 = value_some(map.get("NConnections"));
let line = format!("{n_accepted}; Connected: {n_connections};");
fill_row(unit_writer, "Accepted:", &line);
let n_refused: u32 = value_some(map.get("NRefused"));
if n_refused != 0 {
fill_row(unit_writer, "Refused:", &n_refused.to_string());
}
}
}
fn fill_control_group(
unit_writer: &mut UnitInfoWriter,
map: &HashMap<String, OwnedValue>,
unit: &UnitInfo,
) {
let value = get_value!(map, "ControlGroup");
let c_group: &str = value_to(value);
if c_group.is_empty() {
return;
}
fill_row(unit_writer, "CGroup:", c_group);
let mut unit_processes =
clean_message!(systemd::retreive_unit_processes(unit), "Get processes");
let main_unit_name = unit.primary();
if let Some(unit_process_set) = unit_processes.remove(&main_unit_name) {
for (sub_idx, unit_process) in unit_process_set.iter().enumerate() {
let is_last = sub_idx == unit_process_set.len() - 1;
print_process(unit_writer, "", unit_process, is_last);
}
}
for (idx, (_, unit_process_set)) in unit_processes.iter().enumerate() {
let is_last = idx == unit_processes.len() - 1;
let mut padding = "";
for (sub_idx, unit_process) in unit_process_set.iter().enumerate() {
let is_first_sub = sub_idx == 0;
let is_last_sub = sub_idx == unit_process_set.len() - 1;
if is_first_sub {
let glyph = if is_last {
SPECIAL_GLYPH_TREE_RIGHT
} else {
SPECIAL_GLYPH_TREE_BRANCH
};
unit_writer.insert(&format!("{:KEY_WIDTH$} {}", " ", glyph));
unit_writer.insert(unit_process.unit_name());
unit_writer.newline();
padding = if !is_last {
SPECIAL_GLYPH_TREE_VERTICAL
} else {
SPECIAL_GLYPH_TREE_SPACE
};
}
print_process(unit_writer, padding, unit_process, is_last_sub);
}
}
}
fn print_process(
unit_writer: &mut UnitInfoWriter,
padding: &str,
unit_process: &UnitProcess,
is_last_sub: bool,
) {
let glyph = if !is_last_sub {
SPECIAL_GLYPH_TREE_BRANCH
} else {
SPECIAL_GLYPH_TREE_RIGHT
};
unit_writer.insert(&format!("{:KEY_WIDTH$} {}{}", " ", padding, glyph));
let process_info = format!("{} {}", unit_process.pid, unit_process.name);
unit_writer.insert_grey(&process_info);
unit_writer.newline();
}
const SUFFIX: [&str; 9] = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"];
const UNIT: u64 = 1024;
fn value_to<'a, 'b, T>(value: &'a Value<'b>) -> T
where
T: std::default::Default + std::convert::TryFrom<&'a zvariant::Value<'b>>,
<T as TryFrom<&'a Value<'b>>>::Error: std::fmt::Debug,
{
value
.try_into()
.inspect_err(|e| warn!("Convetion Error: {e:?}"))
.unwrap_or_default()
}
fn value_some<'a, T>(value: Option<&'a OwnedValue>) -> T
where
T: std::default::Default + std::convert::TryFrom<&'a zvariant::OwnedValue>,
<T as TryFrom<&'a OwnedValue>>::Error: std::fmt::Debug,
{
value
.and_then(|v| {
v.try_into()
.inspect_err(|e| warn!("Convetion Error: {e:?}"))
.ok()
})
.unwrap_or_default()
}
fn value_to_def<'a, T>(value: Option<&'a OwnedValue>, def: T) -> T
where
T: std::default::Default + std::convert::TryFrom<&'a zvariant::OwnedValue>,
<T as TryFrom<&'a OwnedValue>>::Error: std::fmt::Debug,
{
value
.and_then(|v| {
v.try_into()
.inspect_err(|e| warn!("Convertion Error: {e:?}"))
.ok()
})
.unwrap_or(def)
}
fn human_bytes(bytes: u64) -> String {
let mut base: usize = 0;
let mut byte_new = bytes;
loop {
if UNIT > byte_new {
break;
}
base += 1;
byte_new >>= 10;
}
let pbase = UNIT.pow(base as u32);
let value = bytes as f64 / pbase as f64;
let mut human_str = if base == 0 {
bytes.to_string()
} else {
format!("{value:.1}")
};
if let Some(suffix) = SUFFIX.get(base) {
human_str.push_str(suffix);
}
human_str
}
#[cfg(test)]
mod tests {
use chrono::Local;
use super::*;
#[test]
fn test1() {
println!("{}", human_bytes(0));
println!("{}", human_bytes(3));
println!("{}", human_bytes(18446744073709551615));
println!("{}", human_bytes(1024));
println!("{}", human_bytes(1024));
println!("{}", human_bytes(2048));
println!("{}", human_bytes(2000));
println!("{}", human_bytes(1950));
println!("{}", human_bytes(2_048_000));
}
#[test]
fn test_timer_mono() {
let local_result = chrono::TimeZone::timestamp_millis_opt(&Local, 86400000000);
let fmt = "%b %d %T %Y";
let date = match local_result {
chrono::offset::LocalResult::Single(l) => l.format(fmt).to_string(),
chrono::offset::LocalResult::Ambiguous(a, _b) => a.format(fmt).to_string(),
chrono::offset::LocalResult::None => "NONE".to_owned(),
};
println!("date {date}");
let local_result = chrono::TimeZone::timestamp_millis_opt(&Local, 173787328907);
let fmt = "%b %d %T %Y";
let date = match local_result {
chrono::offset::LocalResult::Single(l) => l.format(fmt).to_string(),
chrono::offset::LocalResult::Ambiguous(a, _b) => a.format(fmt).to_string(),
chrono::offset::LocalResult::None => "NONE".to_owned(),
};
println!("date {date}");
}
#[test]
fn test_invocation() {
let _a = [
23, 184, 156, 61, 114, 189, 74, 235, 186, 102, 85, 32, 183, 33, 38, 165,
];
}
#[test]
fn test_strerror() {
for i in 0..35 {
let out = strerror(i);
println!("Error {i} {out:?}");
}
}
}