#![allow(non_snake_case)]
#![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::meter::Meter;
use crate::ported::richstring::{RichString, RichString_appendAscii, RichString_writeAscii};
use crate::ported::settings::Settings_isReadonly;
use crate::ported::xutils::String_eq;
const INVALID_VALUE: usize = usize::MAX;
pub struct OpenRCMeterContext {
runlevel: Option<String>,
services_stopped: usize,
services_started: usize,
}
static CTX_SYSTEM: Mutex<OpenRCMeterContext> = Mutex::new(OpenRCMeterContext {
runlevel: None,
services_stopped: 0,
services_started: 0,
});
static CTX_USER: Mutex<OpenRCMeterContext> = Mutex::new(OpenRCMeterContext {
runlevel: None,
services_stopped: 0,
services_started: 0,
});
pub fn OpenRCMeter_done(this: &Meter) {
let ctx_mutex = if String_eq(this.name, "OpenRCUser") {
&CTX_USER
} else {
&CTX_SYSTEM
};
ctx_mutex.lock().unwrap().runlevel = None;
}
pub fn updateViaExec(user: bool) {
let ctx_mutex = if user { &CTX_USER } else { &CTX_SYSTEM };
let mut ctx = ctx_mutex.lock().unwrap();
ctx.services_started = INVALID_VALUE;
ctx.services_stopped = INVALID_VALUE;
if Settings_isReadonly() {
return;
}
let exec_rc_status = |user: bool, full: bool| -> (c_int, libc::pid_t) {
if user {
match std::env::var_os("XDG_RUNTIME_DIR") {
Some(v) if !v.is_empty() => {}
_ => return (-1, -1),
}
}
let mut fdpair: [c_int; 2] = [-1, -1];
if unsafe { libc::pipe(fdpair.as_mut_ptr()) } < 0 {
return (-1, -1);
}
let args: &[&str] = match (user, full) {
(true, true) => &["rc-status", "-C", "--user", "-f", "ini", "-a"],
(true, false) => &["rc-status", "-C", "--user", "-r"],
(false, true) => &["rc-status", "-C", "-f", "ini", "-a"],
(false, false) => &["rc-status", "-C", "-r"],
};
let cargs: Vec<CString> = args
.iter()
.map(|s| CString::new(*s).expect("no interior NUL"))
.collect();
let mut argv: Vec<*const c_char> = cargs.iter().map(|c| c.as_ptr()).collect();
argv.push(core::ptr::null());
let child = unsafe { libc::fork() };
if child < 0 {
unsafe {
libc::close(fdpair[1]);
libc::close(fdpair[0]);
}
return (-1, -1);
}
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(cargs[0].as_ptr(), argv.as_ptr());
libc::_exit(127);
}
}
unsafe {
libc::close(fdpair[1]);
}
(fdpair[0], child)
};
let xwaitpid = |child: libc::pid_t| -> Option<c_int> {
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 {
None
} else {
Some(wstatus)
}
};
let (fd, child) = exec_rc_status(user, false);
if fd < 0 {
return;
}
{
let file = unsafe { File::from_raw_fd(fd) };
let mut reader = BufReader::new(file);
let mut line = String::new();
if reader.read_line(&mut line).unwrap_or(0) > 0 {
if line.ends_with('\n') {
line.pop();
}
ctx.runlevel = Some(line);
}
}
match xwaitpid(child) {
Some(ws) if libc::WIFEXITED(ws) && libc::WEXITSTATUS(ws) == 0 => {}
_ => return,
}
let (fd, child) = exec_rc_status(user, true);
if fd < 0 {
return;
}
ctx.services_started = 0;
ctx.services_stopped = 0;
{
let file = unsafe { File::from_raw_fd(fd) };
let mut reader = BufReader::new(file);
let mut line = String::new();
loop {
line.clear();
match reader.read_line(&mut line) {
Ok(0) | Err(_) => break,
Ok(_) => {}
}
let eq = match line.find('=') {
Some(i) => i,
None => continue,
};
let mut status = &line[eq + 1..];
status = status.trim_start_matches([' ', '\t']);
let status = match status.find('\n') {
Some(i) => &status[..i],
None => status,
};
if status.contains("started") {
ctx.services_started += 1;
} else if status.contains("stopped") {
ctx.services_stopped += 1;
}
}
}
match xwaitpid(child) {
Some(ws) if libc::WIFEXITED(ws) && libc::WEXITSTATUS(ws) == 0 => {}
_ => {
ctx.services_started = INVALID_VALUE;
ctx.services_stopped = INVALID_VALUE;
}
}
}
pub fn OpenRCMeter_updateValues(this: &mut Meter) {
let user = String_eq(this.name, "OpenRCUser");
let ctx_mutex = if user { &CTX_USER } else { &CTX_SYSTEM };
{
let mut ctx = ctx_mutex.lock().unwrap();
ctx.runlevel = None;
ctx.services_stopped = INVALID_VALUE;
ctx.services_started = INVALID_VALUE;
}
updateViaExec(user);
let ctx = ctx_mutex.lock().unwrap();
this.txtBuffer = ctx.runlevel.clone().unwrap_or_else(|| "???".to_string());
}
pub fn OpenRCMeter_display(out: &mut RichString, ctx: &OpenRCMeterContext) {
let scheme = ColorScheme::active();
RichString_writeAscii(out, ColorElements::METER_TEXT.packed(scheme), b"Runlevel: ");
RichString_appendAscii(
out,
ColorElements::METER_VALUE.packed(scheme),
ctx.runlevel.as_deref().unwrap_or("N/A").as_bytes(),
);
if ctx.services_started == INVALID_VALUE && ctx.services_stopped == INVALID_VALUE {
return;
}
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b" (");
let buffer = if ctx.services_started == INVALID_VALUE {
"?".to_string()
} else {
ctx.services_started.to_string()
};
RichString_appendAscii(
out,
ColorElements::METER_VALUE_OK.packed(scheme),
buffer.as_bytes(),
);
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b" started, ");
let buffer = if ctx.services_stopped == INVALID_VALUE {
"?".to_string()
} else {
ctx.services_stopped.to_string()
};
RichString_appendAscii(
out,
ColorElements::METER_VALUE_ERROR.packed(scheme),
buffer.as_bytes(),
);
RichString_appendAscii(out, ColorElements::METER_TEXT.packed(scheme), b" stopped)");
}
pub fn OpenRCMeter_display_system(out: &mut RichString) {
let ctx = CTX_SYSTEM.lock().unwrap();
OpenRCMeter_display(out, &ctx);
}
pub fn OpenRCMeter_display_user(out: &mut RichString) {
let ctx = CTX_USER.lock().unwrap();
OpenRCMeter_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()
}
fn ctx(runlevel: Option<&str>, started: usize, stopped: usize) -> OpenRCMeterContext {
OpenRCMeterContext {
runlevel: runlevel.map(|s| s.to_string()),
services_started: started,
services_stopped: stopped,
}
}
#[test]
fn display_runlevel_only_when_counts_invalid() {
let c = ctx(Some("default"), INVALID_VALUE, INVALID_VALUE);
let mut out = RichString::new();
OpenRCMeter_display(&mut out, &c);
assert_eq!(text(&out), "Runlevel: default");
}
#[test]
fn display_na_when_no_runlevel() {
let c = ctx(None, INVALID_VALUE, INVALID_VALUE);
let mut out = RichString::new();
OpenRCMeter_display(&mut out, &c);
assert_eq!(text(&out), "Runlevel: N/A");
}
#[test]
fn display_with_counts() {
let c = ctx(Some("default"), 3, 1);
let mut out = RichString::new();
OpenRCMeter_display(&mut out, &c);
assert_eq!(text(&out), "Runlevel: default (3 started, 1 stopped)");
}
#[test]
fn display_question_mark_for_single_invalid_count() {
let c = ctx(Some("default"), INVALID_VALUE, 2);
let mut out = RichString::new();
OpenRCMeter_display(&mut out, &c);
assert_eq!(text(&out), "Runlevel: default (? started, 2 stopped)");
}
}