#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use core::ffi::{c_char, c_int};
use std::ffi::CString;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::os::unix::io::FromRawFd;
use std::sync::Mutex;
use crate::ported::crt::{ColorElements, ColorScheme};
use crate::ported::richstring::{
RichString, RichString_appendAscii, RichString_appendnAscii, RichString_writeAscii,
};
use crate::ported::settings::Settings_isReadonly;
use crate::ported::xutils::{String_eq, String_startsWith};
const INVALID_VALUE: u32 = u32::MAX;
struct SystemdMeterContext_t {
systemState: Option<String>,
nFailedUnits: u32,
nInstalledJobs: u32,
nNames: u32,
nJobs: u32,
}
impl SystemdMeterContext_t {
const fn new() -> Self {
SystemdMeterContext_t {
systemState: None,
nFailedUnits: 0,
nInstalledJobs: 0,
nNames: 0,
nJobs: 0,
}
}
}
static ctx_system: Mutex<SystemdMeterContext_t> = Mutex::new(SystemdMeterContext_t::new());
static ctx_user: Mutex<SystemdMeterContext_t> = Mutex::new(SystemdMeterContext_t::new());
pub fn SystemdMeter_done() {
todo!("port of SystemdMeter.c:71: needs Meter.name (Meter_name) + sd_bus/dlopen teardown")
}
pub fn updateViaLib() {
todo!("port of SystemdMeter.c:98: needs libsystemd sd-bus FFI (dlopen/dlsym)")
}
pub fn updateViaExec(user: bool) {
let ctx_mutex = if user { &ctx_user } else { &ctx_system };
if Settings_isReadonly() {
return;
}
let mut fdpair: [c_int; 2] = [-1, -1];
if unsafe { libc::pipe(fdpair.as_mut_ptr()) } < 0 {
return;
}
let c_systemctl = CString::new("systemctl").expect("no interior NUL");
let c_show = CString::new("show").expect("no interior NUL");
let c_scope = CString::new(if user { "--user" } else { "--system" }).expect("no interior NUL");
let c_p_state = CString::new("--property=SystemState").expect("no interior NUL");
let c_p_failed = CString::new("--property=NFailedUnits").expect("no interior NUL");
let c_p_names = CString::new("--property=NNames").expect("no interior NUL");
let c_p_jobs = CString::new("--property=NJobs").expect("no interior NUL");
let c_p_installed = CString::new("--property=NInstalledJobs").expect("no interior NUL");
let argv: [*const c_char; 9] = [
c_systemctl.as_ptr(),
c_show.as_ptr(),
c_scope.as_ptr(),
c_p_state.as_ptr(),
c_p_failed.as_ptr(),
c_p_names.as_ptr(),
c_p_jobs.as_ptr(),
c_p_installed.as_ptr(),
core::ptr::null(),
];
let child = unsafe { libc::fork() };
if child < 0 {
unsafe {
libc::close(fdpair[1]);
libc::close(fdpair[0]);
}
return;
}
if child == 0 {
unsafe {
libc::close(fdpair[0]);
libc::dup2(fdpair[1], libc::STDOUT_FILENO);
libc::close(fdpair[1]);
let fdnull = libc::open(c"/dev/null".as_ptr(), libc::O_WRONLY);
if fdnull < 0 {
libc::_exit(1);
}
libc::dup2(fdnull, libc::STDERR_FILENO);
libc::close(fdnull);
libc::execvp(c_systemctl.as_ptr(), argv.as_ptr());
libc::_exit(127);
}
}
unsafe {
libc::close(fdpair[1]);
}
let mut wstatus: c_int = 0;
let ret = loop {
let r = unsafe { libc::waitpid(child, &mut wstatus, 0) };
if r == -1 && std::io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
continue;
}
break r;
};
if ret < 0 || !libc::WIFEXITED(wstatus) || libc::WEXITSTATUS(wstatus) != 0 {
unsafe {
libc::close(fdpair[0]);
}
return;
}
let file = unsafe { File::from_raw_fd(fdpair[0]) };
let mut reader = BufReader::new(file);
let strtoul = |s: &str| -> (u64, u8) {
let bytes = s.as_bytes();
let mut i = 0usize;
while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r') {
i += 1;
}
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
let mut val: u64 = 0;
let mut any = false;
while i < bytes.len() && bytes[i].is_ascii_digit() {
any = true;
val = val
.saturating_mul(10)
.saturating_add((bytes[i] - b'0') as u64);
i += 1;
}
let endbyte = if any {
bytes.get(i).copied().unwrap_or(0)
} else {
bytes.first().copied().unwrap_or(0)
};
(val, endbyte)
};
let mut ctx = ctx_mutex.lock().unwrap();
let mut lineBuffer = String::new();
loop {
lineBuffer.clear();
match reader.read_line(&mut lineBuffer) {
Ok(0) => break,
Ok(_) => {}
Err(_) => break,
}
if String_startsWith(&lineBuffer, "SystemState=") {
let rest = &lineBuffer["SystemState=".len()..];
let rest = match rest.find('\n') {
Some(p) => &rest[..p],
None => rest,
};
ctx.systemState = Some(rest.to_string());
} else if String_startsWith(&lineBuffer, "NFailedUnits=") {
let (value, endbyte) = strtoul(&lineBuffer["NFailedUnits=".len()..]);
if value <= u32::MAX as u64 && endbyte == 0 {
ctx.nFailedUnits = value as u32;
}
} else if String_startsWith(&lineBuffer, "NNames=") {
let (value, endbyte) = strtoul(&lineBuffer["NNames=".len()..]);
if value <= u32::MAX as u64 && endbyte == 0 {
ctx.nNames = value as u32;
}
} else if String_startsWith(&lineBuffer, "NJobs=") {
let (value, endbyte) = strtoul(&lineBuffer["NJobs=".len()..]);
if value <= u32::MAX as u64 && endbyte == 0 {
ctx.nJobs = value as u32;
}
} else if String_startsWith(&lineBuffer, "NInstalledJobs=") {
let (value, endbyte) = strtoul(&lineBuffer["NInstalledJobs=".len()..]);
if value <= u32::MAX as u64 && endbyte == 0 {
ctx.nInstalledJobs = value as u32;
}
}
}
}
pub fn SystemdMeter_updateValues() {
todo!("port of SystemdMeter.c:300: needs Meter.name (Meter_name) + updateViaLib")
}
pub fn zeroDigitColor(value: u32) -> i32 {
let scheme = ColorScheme::active();
match value {
0 => ColorElements::METER_VALUE.packed(scheme),
INVALID_VALUE => ColorElements::METER_VALUE_ERROR.packed(scheme),
_ => ColorElements::METER_VALUE_NOTICE.packed(scheme),
}
}
pub fn valueDigitColor(value: u32) -> i32 {
let scheme = ColorScheme::active();
match value {
0 => ColorElements::METER_VALUE_NOTICE.packed(scheme),
INVALID_VALUE => ColorElements::METER_VALUE_ERROR.packed(scheme),
_ => ColorElements::METER_VALUE.packed(scheme),
}
}
fn SystemdMeter_display(out: &mut RichString, ctx: &SystemdMeterContext_t) {
let scheme = ColorScheme::active();
let color = if let Some(state) = ctx.systemState.as_deref() {
if String_eq(state, "running") {
ColorElements::METER_VALUE_OK
} else if String_eq(state, "degraded") {
ColorElements::METER_VALUE_ERROR
} else {
ColorElements::METER_VALUE_WARN
}
} else {
ColorElements::METER_VALUE_ERROR
};
RichString_writeAscii(
out,
color.packed(scheme),
ctx.systemState.as_deref().unwrap_or("N/A").as_bytes(),
);
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b" (");
let buffer = if ctx.nFailedUnits == INVALID_VALUE {
"?".to_string()
} else {
format!("{}", ctx.nFailedUnits)
};
RichString_appendnAscii(
out,
zeroDigitColor(ctx.nFailedUnits),
buffer.as_bytes(),
buffer.len(),
);
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b"/");
let buffer = if ctx.nNames == INVALID_VALUE {
"?".to_string()
} else {
format!("{}", ctx.nNames)
};
RichString_appendnAscii(
out,
valueDigitColor(ctx.nNames),
buffer.as_bytes(),
buffer.len(),
);
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b" failed) (");
let buffer = if ctx.nJobs == INVALID_VALUE {
"?".to_string()
} else {
format!("{}", ctx.nJobs)
};
RichString_appendnAscii(
out,
zeroDigitColor(ctx.nJobs),
buffer.as_bytes(),
buffer.len(),
);
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b"/");
let buffer = if ctx.nInstalledJobs == INVALID_VALUE {
"?".to_string()
} else {
format!("{}", ctx.nInstalledJobs)
};
RichString_appendnAscii(
out,
valueDigitColor(ctx.nInstalledJobs),
buffer.as_bytes(),
buffer.len(),
);
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b" jobs)");
}
pub fn SystemdMeter_display_system(out: &mut RichString) {
let ctx = ctx_system.lock().unwrap();
SystemdMeter_display(out, &ctx);
}
pub fn SystemdMeter_display_user(out: &mut RichString) {
let ctx = ctx_user.lock().unwrap();
SystemdMeter_display(out, &ctx);
}
#[cfg(test)]
mod tests {
use super::*;
fn text(r: &RichString) -> String {
(0..r.chlen as usize).map(|i| r.chptr[i].chars).collect()
}
#[test]
fn display_running_all_known() {
let ctx = SystemdMeterContext_t {
systemState: Some("running".to_string()),
nFailedUnits: 0,
nInstalledJobs: 5,
nNames: 42,
nJobs: 0,
};
let mut out = RichString::new();
SystemdMeter_display(&mut out, &ctx);
assert_eq!(text(&out), "running (0/42 failed) (0/5 jobs)");
}
#[test]
fn display_no_state_all_invalid() {
let ctx = SystemdMeterContext_t {
systemState: None,
nFailedUnits: INVALID_VALUE,
nInstalledJobs: INVALID_VALUE,
nNames: INVALID_VALUE,
nJobs: INVALID_VALUE,
};
let mut out = RichString::new();
SystemdMeter_display(&mut out, &ctx);
assert_eq!(text(&out), "N/A (?/? failed) (?/? jobs)");
}
#[test]
fn digit_colors_map_each_branch() {
let scheme = ColorScheme::active();
assert_eq!(zeroDigitColor(0), ColorElements::METER_VALUE.packed(scheme));
assert_eq!(
zeroDigitColor(INVALID_VALUE),
ColorElements::METER_VALUE_ERROR.packed(scheme)
);
assert_eq!(
zeroDigitColor(3),
ColorElements::METER_VALUE_NOTICE.packed(scheme)
);
assert_eq!(
valueDigitColor(0),
ColorElements::METER_VALUE_NOTICE.packed(scheme)
);
assert_eq!(
valueDigitColor(INVALID_VALUE),
ColorElements::METER_VALUE_ERROR.packed(scheme)
);
assert_eq!(
valueDigitColor(3),
ColorElements::METER_VALUE.packed(scheme)
);
}
}