#![allow(non_snake_case)]
#![allow(dead_code)]
use core::sync::atomic::{AtomicBool, Ordering};
use std::ffi::{CStr, CString};
use std::sync::Mutex;
use crate::ported::batterymeter::ACPresence;
use crate::ported::batterymeter::BatteryMeter_class;
use crate::ported::cpumeter::{
AllCPUs2Meter_class, AllCPUs4Meter_class, AllCPUs8Meter_class, AllCPUsMeter_class,
CPUMeter_class, LeftCPUs2Meter_class, LeftCPUs4Meter_class, LeftCPUs8Meter_class,
LeftCPUsMeter_class, RightCPUs2Meter_class, RightCPUs4Meter_class, RightCPUs8Meter_class,
RightCPUsMeter_class,
};
use crate::ported::crt::ColorElements;
use crate::ported::datetimemeter::{ClockMeter_class, DateMeter_class, DateTimeMeter_class};
use crate::ported::hostnamemeter::HostnameMeter_class;
use crate::ported::linux::compat::{Compat_openatArgClose, Compat_readfile, Compat_readfileat};
use crate::ported::linux::linuxmachine::LinuxMachine;
use crate::ported::loadaveragemeter::{LoadAverageMeter_class, LoadMeter_class};
use crate::ported::memorymeter::MemoryMeter_class;
use crate::ported::meter::{BlankMeter_class, Meter, MeterClass};
use crate::ported::swapmeter::SwapMeter_class;
use crate::ported::sysarchmeter::SysArchMeter_class;
use crate::ported::tasksmeter::TasksMeter_class;
use crate::ported::uptimemeter::{SecondsUptimeMeter_class, UptimeMeter_class};
use crate::ported::xutils::{
saturatingSub, sumPositiveValues, String_contains_i, String_eq, String_startsWith,
};
pub struct MemoryClass {
pub label: &'static str,
pub countsAsUsed: bool,
pub countsAsCache: bool,
pub color: ColorElements,
}
pub const MEMORY_CLASS_USED: usize = 0;
pub const MEMORY_CLASS_SHARED: usize = 1;
pub const MEMORY_CLASS_COMPRESSED: usize = 2;
pub const MEMORY_CLASS_BUFFERS: usize = 3;
pub const MEMORY_CLASS_CACHE: usize = 4;
pub const MEMORY_CLASS_AVAILABLE: usize = 5;
#[allow(non_upper_case_globals)] pub static Platform_meterTypes: &[&MeterClass] = &[
&CPUMeter_class,
&ClockMeter_class,
&DateMeter_class,
&DateTimeMeter_class,
&LoadAverageMeter_class,
&LoadMeter_class,
&MemoryMeter_class,
&SwapMeter_class,
&TasksMeter_class,
&BatteryMeter_class,
&HostnameMeter_class,
&SysArchMeter_class,
&UptimeMeter_class,
&SecondsUptimeMeter_class,
&AllCPUsMeter_class,
&AllCPUs2Meter_class,
&AllCPUs4Meter_class,
&AllCPUs8Meter_class,
&LeftCPUsMeter_class,
&RightCPUsMeter_class,
&LeftCPUs2Meter_class,
&RightCPUs2Meter_class,
&LeftCPUs4Meter_class,
&RightCPUs4Meter_class,
&LeftCPUs8Meter_class,
&RightCPUs8Meter_class,
&BlankMeter_class,
];
#[allow(non_upper_case_globals)] pub static Platform_memoryClasses: [MemoryClass; 6] = [
MemoryClass {
label: "used",
countsAsUsed: true,
countsAsCache: false,
color: ColorElements::MEMORY_1,
},
MemoryClass {
label: "shared",
countsAsUsed: true,
countsAsCache: false,
color: ColorElements::MEMORY_2,
},
MemoryClass {
label: "compressed",
countsAsUsed: true,
countsAsCache: false,
color: ColorElements::MEMORY_3,
},
MemoryClass {
label: "buffers",
countsAsUsed: false,
countsAsCache: true,
color: ColorElements::MEMORY_4,
},
MemoryClass {
label: "cache",
countsAsUsed: false,
countsAsCache: true,
color: ColorElements::MEMORY_5,
},
MemoryClass {
label: "available",
countsAsUsed: false,
countsAsCache: false,
color: ColorElements::MEMORY_6,
},
];
#[allow(non_upper_case_globals)] pub const Platform_numberOfMemoryClasses: usize = Platform_memoryClasses.len();
const PROCDIR: &str = "/proc";
const PROC_BATTERY_DIR: &str = "/proc/acpi/battery";
const PROC_POWERSUPPLY_ACSTATE_FILE: &str = "/proc/acpi/ac_adapter/AC/state";
const SYS_POWERSUPPLY_DIR: &str = "/sys/class/power_supply";
const O_PATH: libc::c_int = 0o10000000;
#[allow(non_upper_case_globals)] pub static Running_containerized: AtomicBool = AtomicBool::new(false);
pub fn Platform_actionSetIOPriority() {
todo!("port of Platform.c:172")
}
pub fn Platform_changeAutogroupPriority() {
todo!("port of Platform.c:194")
}
pub fn Platform_actionHigherAutogroupPriority() {
todo!("port of Platform.c:206")
}
pub fn Platform_actionLowerAutogroupPriority() {
todo!("port of Platform.c:214")
}
pub fn Platform_setBindings() {
todo!("port of Platform.c:222")
}
pub fn Platform_getUptime() -> i32 {
let mut uptimedata = [0u8; 64];
let path = CString::new(format!("{}/uptime", PROCDIR)).unwrap();
let uptimeread = Compat_readfile(&path, &mut uptimedata);
if uptimeread < 1 {
return 0;
}
let text = String::from_utf8_lossy(&uptimedata[..uptimeread as usize]);
let mut tokens = text.split_whitespace();
let uptime: Option<f64> = tokens.next().and_then(|t| t.parse().ok());
let idle: Option<f64> = tokens.next().and_then(|t| t.parse().ok());
match (uptime, idle) {
(Some(uptime), Some(_idle)) => uptime.floor() as i32,
_ => 0,
}
}
pub fn Platform_getLoadAverage(one: &mut f64, five: &mut f64, fifteen: &mut f64) {
*one = f64::NAN;
*five = f64::NAN;
*fifteen = f64::NAN;
let mut loaddata = [0u8; 128];
let path = CString::new(format!("{}/loadavg", PROCDIR)).unwrap();
let loadread = Compat_readfile(&path, &mut loaddata);
if loadread < 1 {
return;
}
let text = String::from_utf8_lossy(&loaddata[..loadread as usize]);
let mut tokens = text.split_whitespace();
let scan_one: Option<f64> = tokens.next().and_then(|t| t.parse().ok());
let scan_five: Option<f64> = tokens.next().and_then(|t| t.parse().ok());
let scan_fifteen: Option<f64> = tokens.next().and_then(|t| t.parse().ok());
if let (Some(a), Some(b), Some(c)) = (scan_one, scan_five, scan_fifteen) {
*one = a;
*five = b;
*fifteen = c;
}
}
pub fn Platform_getMaxPid() -> libc::pid_t {
let mut piddata = [0u8; 32];
let path = CString::new(format!("{}/sys/kernel/pid_max", PROCDIR)).unwrap();
let pidread = Compat_readfile(&path, &mut piddata);
if pidread < 1 {
return 0x3FFFFF; }
let text = String::from_utf8_lossy(&piddata[..pidread as usize]);
match text
.split_whitespace()
.next()
.and_then(|t| t.parse::<i32>().ok())
{
Some(pidmax) => pidmax as libc::pid_t,
None => 0x3FFFFF, }
}
pub fn Platform_setGPUValues(
this: &mut Meter,
total_usage: &mut f64,
total_gpu_time_diff: &mut u64,
) {
use crate::ported::gpumeter::GPUMeter_engineData;
static RESIDUE: Mutex<(u64, f64, u64)> = Mutex::new((0, 0.0, 0));
const RESIDUE_INDEX: usize = 4;
let h = unsafe { &*(this.host as *const LinuxMachine) };
let mut r = RESIDUE.lock().unwrap();
if h.super_.monotonicMs > r.0 {
let monotonic_delta = (h.super_.monotonicMs - r.0) as f64;
let mut cur_residue_time = h.curGpuTime;
{
let mut ed = GPUMeter_engineData.lock().unwrap();
let mut node = h.gpuEngineData.as_deref();
let mut i = 0;
while let Some(g) = node {
if i >= RESIDUE_INDEX {
break;
}
ed[i].key = g.key.clone();
ed[i].timeDiff = saturatingSub(g.curTime, g.prevTime);
ed[i].percentage =
100.0 * ed[i].timeDiff as f64 / (1000.0 * 1000.0) / monotonic_delta;
cur_residue_time = saturatingSub(cur_residue_time, g.curTime);
node = g.next.as_deref();
i += 1;
}
}
r.1 = 100.0 * saturatingSub(cur_residue_time, r.2) as f64
/ (1000.0 * 1000.0)
/ monotonic_delta;
*total_gpu_time_diff = saturatingSub(h.curGpuTime, h.prevGpuTime);
*total_usage = 100.0 * *total_gpu_time_diff as f64 / (1000.0 * 1000.0) / monotonic_delta;
r.2 = cur_residue_time;
r.0 = h.super_.monotonicMs;
}
this.curItems = (RESIDUE_INDEX + 1) as u8;
let ed = GPUMeter_engineData.lock().unwrap();
for i in 0..RESIDUE_INDEX {
this.values[i] = ed[i].percentage;
}
this.values[RESIDUE_INDEX] = r.1;
}
pub fn Generic_hostname() -> String {
let mut buf = [0u8; 256];
unsafe {
libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf.len() - 1);
}
let nul = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
String::from_utf8_lossy(&buf[..nul]).into_owned()
}
pub fn Platform_getHostname() -> String {
Generic_hostname()
}
pub fn parseOSRelease() -> String {
for path in ["/etc/os-release", "/usr/lib/os-release"] {
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(_) => continue,
};
let (mut name, mut version) = (String::new(), String::new());
for line in content.lines() {
if let Some(rest) = line.strip_prefix("PRETTY_NAME=\"") {
if let Some(end) = rest.rfind('"') {
if end > 0 {
return rest[..end].to_string();
}
}
} else if let Some(rest) = line.strip_prefix("NAME=\"") {
if let Some(end) = rest.rfind('"') {
if end > 0 {
name = rest[..end].to_string();
}
}
} else if let Some(rest) = line.strip_prefix("VERSION=\"") {
if let Some(end) = rest.rfind('"') {
if end > 0 {
version = rest[..end].to_string();
}
}
}
}
let sep = if !name.is_empty() && !version.is_empty() {
" "
} else {
""
};
return format!("{name}{sep}{version}");
}
String::new()
}
pub fn Generic_uname() -> &'static str {
static SAVED: std::sync::OnceLock<String> = std::sync::OnceLock::new();
SAVED.get_or_init(|| {
let mut info: libc::utsname = unsafe { std::mem::zeroed() };
let result = unsafe { libc::uname(&mut info) };
let distro = parseOSRelease();
let field = |arr: &[libc::c_char]| -> String {
unsafe { CStr::from_ptr(arr.as_ptr()) }
.to_string_lossy()
.into_owned()
};
if result == 0 {
let mut s = format!(
"{} {} [{}]",
field(&info.sysname),
field(&info.release),
field(&info.machine)
);
if !distro.is_empty() && !String_contains_i(&s, &distro, false) {
s.push_str(&format!(" @ {distro}"));
}
s
} else if !distro.is_empty() {
distro
} else {
"No information".to_string()
}
})
}
pub fn Platform_getRelease() -> &'static str {
Generic_uname()
}
pub fn Platform_getDiskIO(data: &mut crate::ported::diskiometer::DiskIOData) -> bool {
let content = match std::fs::read_to_string(format!("{PROCDIR}/diskstats")) {
Ok(c) => c,
Err(_) => return false,
};
let mut last_top_disk = String::new();
let (mut read_sum, mut write_sum, mut time_spend_sum, mut num_disks) = (0u64, 0u64, 0u64, 0u64);
for line in content.lines() {
let f: Vec<&str> = line.split_whitespace().collect();
if f.len() < 13 {
continue;
}
let diskname = f[2];
let (read_tmp, write_tmp, time_spend_tmp) = match (
f[5].parse::<u64>(),
f[9].parse::<u64>(),
f[12].parse::<u64>(),
) {
(Ok(r), Ok(w), Ok(t)) => (r, w, t),
_ => continue,
};
if String_startsWith(diskname, "dm-")
|| String_startsWith(diskname, "loop")
|| String_startsWith(diskname, "md")
|| String_startsWith(diskname, "zram")
{
continue;
}
if !last_top_disk.is_empty() && String_startsWith(diskname, &last_top_disk) {
continue;
}
last_top_disk = diskname.to_string();
read_sum += read_tmp;
write_sum += write_tmp;
time_spend_sum += time_spend_tmp;
num_disks += 1;
}
data.totalBytesRead = 512 * read_sum;
data.totalBytesWritten = 512 * write_sum;
data.totalMsTimeSpend = time_spend_sum;
data.numDisks = num_disks;
true
}
pub fn Platform_getNetworkIO(data: &mut crate::ported::networkiometer::NetworkIOData) -> bool {
let content = match std::fs::read_to_string(format!("{PROCDIR}/net/dev")) {
Ok(c) => c,
Err(_) => return false,
};
for line in content.lines() {
let f: Vec<&str> = line.split_whitespace().collect();
if f.len() < 11 {
continue;
}
let interface_name = f[0];
let (rx_bytes, rx_packets, tx_bytes, tx_packets) = match (
f[1].parse::<u64>(),
f[2].parse::<u64>(),
f[9].parse::<u64>(),
f[10].parse::<u64>(),
) {
(Ok(rb), Ok(rp), Ok(tb), Ok(tp)) => (rb, rp, tb, tp),
_ => continue,
};
if String_eq(interface_name, "lo:") {
continue;
}
data.bytesReceived += rx_bytes;
data.packetsReceived += rx_packets;
data.bytesTransmitted += tx_bytes;
data.packetsTransmitted += tx_packets;
}
true
}
const CPU_METER_NICE: usize = 0;
const CPU_METER_NORMAL: usize = 1;
const CPU_METER_KERNEL: usize = 2;
const CPU_METER_IRQ: usize = 3;
const CPU_METER_SOFTIRQ: usize = 4;
const CPU_METER_STEAL: usize = 5;
const CPU_METER_GUEST: usize = 6;
const CPU_METER_IOWAIT: usize = 7;
const CPU_METER_FREQUENCY: usize = 8;
const CPU_METER_TEMPERATURE: usize = 9;
pub fn Platform_setCPUValues(this: &mut Meter, cpu: u32) -> f64 {
let h = unsafe { &*(this.host as *const LinuxMachine) };
let cpu_data = &h.cpuData[cpu as usize];
let total = if cpu_data.totalPeriod == 0 {
1.0
} else {
cpu_data.totalPeriod as f64
};
if !cpu_data.online {
this.curItems = 0;
return f64::NAN;
}
let settings = h
.super_
.settings
.as_ref()
.expect("Platform_setCPUValues: host->settings");
let detailed = settings.detailedCPUTime;
let account_guest = settings.accountGuestInCPUMeter;
this.values[CPU_METER_NICE] = cpu_data.nicePeriod as f64 / total * 100.0;
this.values[CPU_METER_NORMAL] = cpu_data.userPeriod as f64 / total * 100.0;
if detailed {
this.values[CPU_METER_KERNEL] = cpu_data.systemPeriod as f64 / total * 100.0;
this.values[CPU_METER_IRQ] = cpu_data.irqPeriod as f64 / total * 100.0;
this.values[CPU_METER_SOFTIRQ] = cpu_data.softIrqPeriod as f64 / total * 100.0;
this.curItems = 5;
this.values[CPU_METER_STEAL] = cpu_data.stealPeriod as f64 / total * 100.0;
this.values[CPU_METER_GUEST] = cpu_data.guestPeriod as f64 / total * 100.0;
if account_guest {
this.curItems = 7;
}
this.values[CPU_METER_IOWAIT] = cpu_data.ioWaitPeriod as f64 / total * 100.0;
} else {
this.values[CPU_METER_KERNEL] = cpu_data.systemAllPeriod as f64 / total * 100.0;
this.values[CPU_METER_IRQ] =
(cpu_data.stealPeriod + cpu_data.guestPeriod) as f64 / total * 100.0;
this.curItems = 4;
}
let percent = sumPositiveValues(&this.values[..this.curItems as usize]).min(100.0);
if detailed {
this.curItems = 8;
}
this.values[CPU_METER_FREQUENCY] = cpu_data.frequency;
this.values[CPU_METER_TEMPERATURE] = f64::NAN;
percent
}
pub fn Platform_setMemoryValues(this: &mut Meter) {
let h = unsafe { &*(this.host as *const LinuxMachine) };
this.total = h.super_.totalMem as f64;
this.values[MEMORY_CLASS_USED] = h.usedMem as f64;
this.values[MEMORY_CLASS_SHARED] = h.sharedMem as f64;
this.values[MEMORY_CLASS_COMPRESSED] = 0.0;
this.values[MEMORY_CLASS_BUFFERS] = h.buffersMem as f64;
this.values[MEMORY_CLASS_CACHE] = h.cachedMem as f64;
this.values[MEMORY_CLASS_AVAILABLE] = h.availableMem as f64;
if h.zfs.enabled != 0 && !Running_containerized.load(Ordering::Relaxed) {
let mut shrinkable_size: u64 = 0;
if h.zfs.size > h.zfs.min {
shrinkable_size = h.zfs.size - h.zfs.min;
}
this.values[MEMORY_CLASS_USED] -= shrinkable_size as f64;
this.values[MEMORY_CLASS_CACHE] += shrinkable_size as f64;
this.values[MEMORY_CLASS_AVAILABLE] += shrinkable_size as f64;
}
if h.zswap.usedZswapOrig > 0 || h.zswap.usedZswapComp > 0 {
this.values[MEMORY_CLASS_USED] -= h.zswap.usedZswapComp as f64;
this.values[MEMORY_CLASS_COMPRESSED] += h.zswap.usedZswapComp as f64;
}
}
pub fn Platform_setSwapValues(this: &mut Meter) {
let h = unsafe { &*(this.host as *const LinuxMachine) };
this.total = h.super_.totalSwap as f64;
this.values[0] = h.super_.usedSwap as f64;
this.values[1] = h.super_.cachedSwap as f64;
this.values[2] = 0.0;
if h.zswap.usedZswapOrig > 0 || h.zswap.usedZswapComp > 0 {
this.values[0] -= h.zswap.usedZswapOrig as f64;
if this.values[0] < 0.0 {
this.values[1] += this.values[0];
this.values[0] = 0.0;
}
this.values[2] += h.zswap.usedZswapOrig as f64;
}
}
pub fn Platform_setZramValues(this: &mut Meter) {
let h = unsafe { &*(this.host as *const LinuxMachine) };
this.total = h.zram.totalZram as f64;
this.values[0] = h.zram.usedZramComp as f64;
this.values[1] = h.zram.usedZramOrig.wrapping_sub(h.zram.usedZramComp) as f64;
}
pub fn Platform_setZfsArcValues() {
todo!("port of Platform.c:507")
}
pub fn Platform_setZfsCompressedArcValues() {
todo!("port of Platform.c:513")
}
pub fn Platform_getProcessEnv(pid: libc::pid_t) -> Option<String> {
let procname = format!("{}/{}/environ", PROCDIR, pid);
let mut env = std::fs::read(&procname).ok()?;
env.push(b'\0');
env.push(b'\0');
Some(String::from_utf8_lossy(&env).into_owned())
}
pub fn Platform_getProcessLocks() {
todo!("port of Platform.c:555")
}
pub fn Platform_getPressureStall(
file: &str,
some: bool,
ten: &mut f64,
sixty: &mut f64,
threehundred: &mut f64,
) {
*ten = 0.0;
*sixty = 0.0;
*threehundred = 0.0;
let procname = format!("{}/pressure/{}", PROCDIR, file);
let content = match std::fs::read_to_string(&procname) {
Ok(c) => c,
Err(_) => {
*ten = f64::NAN;
*sixty = f64::NAN;
*threehundred = f64::NAN;
return;
}
};
let parse_line = |line: &str| -> Option<(f64, f64, f64)> {
let mut a10: Option<f64> = None;
let mut a60: Option<f64> = None;
let mut a300: Option<f64> = None;
for tok in line.split_whitespace() {
if let Some(v) = tok.strip_prefix("avg10=") {
a10 = v.parse().ok();
} else if let Some(v) = tok.strip_prefix("avg60=") {
a60 = v.parse().ok();
} else if let Some(v) = tok.strip_prefix("avg300=") {
a300 = v.parse().ok();
}
}
match (a10, a60, a300) {
(Some(x), Some(y), Some(z)) => Some((x, y, z)),
_ => None,
}
};
let mut total = 0;
if let Some((x, y, z)) = content
.lines()
.find(|l| l.starts_with("some"))
.and_then(parse_line)
{
*ten = x;
*sixty = y;
*threehundred = z;
total = 3;
}
if !some {
total = 0;
if let Some((x, y, z)) = content
.lines()
.find(|l| l.starts_with("full"))
.and_then(parse_line)
{
*ten = x;
*sixty = y;
*threehundred = z;
total = 3;
}
}
debug_assert!(total == 3);
}
pub fn Platform_getFileDescriptors(used: &mut f64, max: &mut f64) {
*used = f64::NAN;
*max = 65536.0;
let mut buffer = [0u8; 128];
let path = CString::new(format!("{}/sys/fs/file-nr", PROCDIR)).unwrap();
let fdread = Compat_readfile(&path, &mut buffer);
if fdread < 1 {
return;
}
let text = String::from_utf8_lossy(&buffer[..fdread as usize]);
let mut tokens = text.split_whitespace();
let v1: Option<u64> = tokens.next().and_then(|t| t.parse().ok());
let v2: Option<u64> = tokens.next().and_then(|t| t.parse().ok());
let v3: Option<u64> = tokens.next().and_then(|t| t.parse().ok());
if let (Some(v1), Some(_v2), Some(v3)) = (v1, v2, v3) {
*used = v1 as f64;
*max = v3 as f64;
}
}
pub fn Platform_Battery_getProcBatInfo() -> f64 {
let batdir = CString::new(PROC_BATTERY_DIR).unwrap();
let battery_dir = unsafe { libc::opendir(batdir.as_ptr()) };
if battery_dir.is_null() {
return f64::NAN;
}
let mut total_full: u64 = 0;
let mut total_remain: u64 = 0;
let scan_c_int = |s: &str| -> Option<i32> {
let s = s.trim_start();
let bytes = s.as_bytes();
let mut end = 0;
if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {
end += 1;
}
let digits_start = end;
while end < bytes.len() && bytes[end].is_ascii_digit() {
end += 1;
}
if end == digits_start {
return None;
}
s[..end].parse::<i32>().ok()
};
let scan_field_colon_int = |line: &str| -> Option<(String, i32)> {
let colon = line.find(':')?;
if colon == 0 {
return None; }
let field = &line[..colon.min(99)];
let val = scan_c_int(&line[colon + 1..])?;
Some((field.to_string(), val))
};
loop {
let dir_entry = unsafe { libc::readdir(battery_dir) };
if dir_entry.is_null() {
break;
}
let entry_name = unsafe { CStr::from_ptr((*dir_entry).d_name.as_ptr()) }.to_string_lossy();
if !String_startsWith(&entry_name, "BAT") {
continue;
}
let mut buf_info = [0u8; 1024];
let info_path = CString::new(format!("{}/{}/info", PROC_BATTERY_DIR, entry_name)).unwrap();
let r = Compat_readfile(&info_path, &mut buf_info);
if r < 0 {
continue;
}
let info = String::from_utf8_lossy(&buf_info[..r as usize]).into_owned();
let mut buf_state = [0u8; 1024];
let state_path =
CString::new(format!("{}/{}/state", PROC_BATTERY_DIR, entry_name)).unwrap();
let r = Compat_readfile(&state_path, &mut buf_state);
if r < 0 {
continue;
}
let state = String::from_utf8_lossy(&buf_state[..r as usize]).into_owned();
for line in info.split('\n') {
if let Some((field, val)) = scan_field_colon_int(line) {
if String_eq(&field, "last full capacity") {
total_full += val as u64;
break;
}
}
}
for line in state.split('\n') {
if let Some((field, val)) = scan_field_colon_int(line) {
if String_eq(&field, "remaining capacity") {
total_remain += val as u64;
break;
}
}
}
}
unsafe {
libc::closedir(battery_dir);
}
if total_full > 0 {
(total_remain as f64 * 100.0) / total_full as f64
} else {
f64::NAN
}
}
pub fn procAcpiCheck() -> ACPresence {
let mut buffer = [0u8; 1024];
let path = CString::new(PROC_POWERSUPPLY_ACSTATE_FILE).unwrap();
let r = Compat_readfile(&path, &mut buffer);
if r < 1 {
return ACPresence::AC_ERROR;
}
let content = String::from_utf8_lossy(&buffer[..r as usize]);
if String_eq(&content, "on-line") {
ACPresence::AC_PRESENT
} else {
ACPresence::AC_ABSENT
}
}
pub fn Platform_Battery_getProcData(percent: &mut f64, isOnAC: &mut ACPresence) {
*isOnAC = procAcpiCheck();
*percent = if *isOnAC != ACPresence::AC_ERROR {
Platform_Battery_getProcBatInfo()
} else {
f64::NAN
};
}
pub fn Platform_Battery_getSysData(percent: &mut f64, isOnAC: &mut ACPresence) {
*percent = f64::NAN;
*isOnAC = ACPresence::AC_ERROR;
let sysdir = CString::new(SYS_POWERSUPPLY_DIR).unwrap();
let dir = unsafe { libc::opendir(sysdir.as_ptr()) };
if dir.is_null() {
return;
}
let mut total_full: u64 = 0;
let mut total_remain: u64 = 0;
let scan_c_int = |s: &str| -> Option<i32> {
let s = s.trim_start();
let bytes = s.as_bytes();
let mut end = 0;
if end < bytes.len() && (bytes[end] == b'+' || bytes[end] == b'-') {
end += 1;
}
let digits_start = end;
while end < bytes.len() && bytes[end].is_ascii_digit() {
end += 1;
}
if end == digits_start {
return None;
}
s[..end].parse::<i32>().ok()
};
#[allow(clippy::upper_case_acronyms)]
#[derive(PartialEq)]
enum EntryType {
AC,
BAT,
}
loop {
let dir_entry = unsafe { libc::readdir(dir) };
if dir_entry.is_null() {
break;
}
let entry_name_c = unsafe { CStr::from_ptr((*dir_entry).d_name.as_ptr()) };
let entry_fd = unsafe {
libc::openat(
libc::dirfd(dir),
entry_name_c.as_ptr(),
libc::O_DIRECTORY | O_PATH,
)
};
if entry_fd < 0 {
continue;
}
'next: {
let entry_name = entry_name_c.to_string_lossy();
let etype = if String_startsWith(&entry_name, "BAT") {
EntryType::BAT
} else if String_startsWith(&entry_name, "AC") {
EntryType::AC
} else {
let mut buffer = [0u8; 32];
let ret = Compat_readfileat(entry_fd, c"type", &mut buffer);
if ret <= 0 {
break 'next;
}
let typestr = String::from_utf8_lossy(&buffer[..ret as usize]);
let typestr = typestr.trim_end_matches('\n');
if String_eq(typestr, "Battery") {
EntryType::BAT
} else if String_eq(typestr, "Mains") {
EntryType::AC
} else {
break 'next;
}
};
if etype == EntryType::BAT {
let mut buffer = [0u8; 1024];
let r = Compat_readfileat(entry_fd, c"uevent", &mut buffer);
if r < 0 {
break 'next;
}
let mut full = false;
let mut now = false;
let mut full_charge: f64 = 0.0;
let mut capacity_level: f64 = f64::NAN;
let content = String::from_utf8_lossy(&buffer[..r as usize]).into_owned();
for line in content.split('\n') {
let rest = match line.strip_prefix("POWER_SUPPLY_") {
Some(r) => r,
None => continue,
};
let eq = match rest.find('=') {
Some(e) if e > 0 => e,
_ => continue, };
let field = &rest[..eq.min(99)];
let val = match scan_c_int(&rest[eq + 1..]) {
Some(v) => v,
None => continue,
};
if String_eq(field, "CAPACITY") {
capacity_level = val as f64 / 100.0;
continue;
}
if String_eq(field, "ENERGY_FULL") || String_eq(field, "CHARGE_FULL") {
full_charge = val as f64;
total_full += full_charge as u64;
full = true;
if now {
break;
}
continue;
}
if String_eq(field, "ENERGY_NOW") || String_eq(field, "CHARGE_NOW") {
total_remain += val as u64;
now = true;
if full {
break;
}
continue;
}
}
if !now && full && capacity_level >= 0.0 {
total_remain += (capacity_level * full_charge) as u64;
}
} else {
if *isOnAC != ACPresence::AC_ERROR {
break 'next;
}
let mut buffer = [0u8; 2];
let r = Compat_readfileat(entry_fd, c"online", &mut buffer);
if r < 1 {
*isOnAC = ACPresence::AC_ERROR;
break 'next;
}
if buffer[0] == b'0' {
*isOnAC = ACPresence::AC_ABSENT;
} else if buffer[0] == b'1' {
*isOnAC = ACPresence::AC_PRESENT;
}
}
}
Compat_openatArgClose(entry_fd);
}
unsafe {
libc::closedir(dir);
}
*percent = if total_full > 0 {
(total_remain as f64 * 100.0) / total_full as f64
} else {
f64::NAN
};
}
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
enum Platform_Battery_method_t {
BAT_PROC,
BAT_SYS,
BAT_ERR,
}
#[allow(non_camel_case_types)]
struct Platform_Battery_cache_t {
method: Platform_Battery_method_t,
cacheTime: libc::time_t,
cachePercent: f64,
cacheIsOnAC: ACPresence,
}
static PLATFORM_BATTERY: Mutex<Platform_Battery_cache_t> = Mutex::new(Platform_Battery_cache_t {
method: Platform_Battery_method_t::BAT_PROC, cacheTime: 0,
cachePercent: f64::NAN, cacheIsOnAC: ACPresence::AC_ABSENT, });
pub fn Platform_getBattery(percent: &mut f64, isOnAC: &mut ACPresence) {
let now = unsafe { libc::time(std::ptr::null_mut()) };
let mut cache = PLATFORM_BATTERY.lock().unwrap();
if now < cache.cacheTime + 10 {
*percent = cache.cachePercent;
*isOnAC = cache.cacheIsOnAC;
return;
}
if matches!(cache.method, Platform_Battery_method_t::BAT_PROC) {
Platform_Battery_getProcData(percent, isOnAC);
if !(*percent >= 0.0) {
cache.method = Platform_Battery_method_t::BAT_SYS;
}
}
if matches!(cache.method, Platform_Battery_method_t::BAT_SYS) {
Platform_Battery_getSysData(percent, isOnAC);
if !(*percent >= 0.0) {
cache.method = Platform_Battery_method_t::BAT_ERR;
}
}
if matches!(cache.method, Platform_Battery_method_t::BAT_ERR) {
*percent = f64::NAN;
*isOnAC = ACPresence::AC_ERROR;
} else {
*percent = percent.clamp(0.0, 100.0);
}
cache.cachePercent = *percent;
cache.cacheIsOnAC = *isOnAC;
cache.cacheTime = now;
}
pub fn Platform_longOptionsUsage(_name: &str) {}
pub fn Platform_getLongOption() {
todo!("port of Platform.c:1008")
}
pub fn dropCapabilities() {
todo!("port of Platform.c:1044")
}
pub fn Platform_init() -> bool {
let procdir = std::ffi::CString::new(PROCDIR).unwrap();
if unsafe { libc::access(procdir.as_ptr(), libc::R_OK) } != 0 {
eprintln!(
"Error: could not read procfs (compiled to look in {}).",
PROCDIR
);
return false;
}
let nspath = std::ffi::CString::new(format!("{}/self/ns/pid", PROCDIR)).unwrap();
let mut target = [0u8; 4096];
let ret = unsafe {
libc::readlink(
nspath.as_ptr(),
target.as_mut_ptr() as *mut libc::c_char,
target.len() - 1,
)
};
if ret > 0 {
let link = String::from_utf8_lossy(&target[..ret as usize]);
if !String_eq("pid:[4026531836]", &link) {
Running_containerized.store(true, Ordering::Relaxed);
return true; }
}
if let Ok(mounts) = std::fs::read_to_string(format!("{}/1/mounts", PROCDIR)) {
for lineBuffer in mounts.lines() {
if String_startsWith(lineBuffer, "lxcfs /proc")
|| String_startsWith(lineBuffer, "overlay / overlay")
{
Running_containerized.store(true, Ordering::Relaxed);
break;
}
}
}
true
}
pub fn Platform_done() {}
pub fn Platform_dynamicColumns() -> Option<crate::ported::hashtable::Hashtable> {
None
}
pub fn Platform_dynamicColumnsDone(_table: &crate::ported::hashtable::Hashtable) {}
pub fn Platform_dynamicColumnName(_key: u32) -> Option<&'static str> {
None
}
pub fn Platform_dynamicColumnWriteField(
_proc: &crate::ported::process::Process,
_str: &mut crate::ported::richstring::RichString,
_key: u32,
) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn getprocessenv_missing_pid_is_none() {
assert!(Platform_getProcessEnv(2147483646).is_none());
}
#[cfg(target_os = "linux")]
#[test]
fn getprocessenv_self_has_double_nul_terminator() {
let env = Platform_getProcessEnv(std::process::id() as libc::pid_t)
.expect("self environ must be readable on Linux");
assert!(env.ends_with("\0\0"));
}
#[test]
fn getpressurestall_missing_file_is_nan() {
let (mut ten, mut sixty, mut threehundred) = (0.0, 0.0, 0.0);
Platform_getPressureStall(
"zzz_nonexistent_pressure_file_zzz",
true,
&mut ten,
&mut sixty,
&mut threehundred,
);
assert!(ten.is_nan() && sixty.is_nan() && threehundred.is_nan());
}
#[test]
fn noop_ports_do_not_panic() {
Platform_longOptionsUsage("htop");
Platform_done();
}
#[test]
fn getmaxpid_is_positive() {
assert!(Platform_getMaxPid() > 0);
}
#[test]
fn getuptime_nonnegative() {
assert!(Platform_getUptime() >= 0);
}
#[test]
fn getfiledescriptors_sets_max() {
let (mut used, mut max) = (0.0, 0.0);
Platform_getFileDescriptors(&mut used, &mut max);
assert!(max > 0.0);
let _ = used;
}
#[test]
fn getbattery_does_not_panic() {
let mut percent = 0.0;
let mut is_on_ac = ACPresence::AC_ABSENT;
Platform_getBattery(&mut percent, &mut is_on_ac);
assert!(percent.is_nan() || (0.0..=100.0).contains(&percent));
}
}