#![allow(non_snake_case)]
#![allow(dead_code)]
use core::sync::atomic::{AtomicBool, Ordering};
use std::ffi::{CStr, CString};
use std::sync::Mutex;
use core::any::Any;
use crate::ported::action::{
Action_pickFromVector, Htop_Action, Htop_Reaction, State, HTOP_OK, HTOP_REDRAW_BAR,
HTOP_REFRESH, HTOP_UPDATE_PANELHDR,
};
use crate::ported::batterymeter::ACPresence;
use crate::ported::batterymeter::BatteryMeter_class;
use crate::ported::commandline::CommandLineStatus;
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::crt::KEY_F;
use crate::ported::datetimemeter::{ClockMeter_class, DateMeter_class, DateTimeMeter_class};
use crate::ported::functionbar::Ncurses;
use crate::ported::hostnamemeter::HostnameMeter_class;
use crate::ported::linux::compat::{Compat_openatArgClose, Compat_readfile, Compat_readfileat};
use crate::ported::linux::ioprioritypanel::IOPriorityPanel_new;
use crate::ported::linux::linuxmachine::LinuxMachine;
use crate::ported::linux::linuxprocess::{
IOPriority, LinuxProcess, LinuxProcess_isAutogroupEnabled,
LinuxProcess_rowChangeAutogroupPriorityBy, LinuxProcess_rowSetIOPriority,
};
use crate::ported::listitem::ListItem;
use crate::ported::loadaveragemeter::{LoadAverageMeter_class, LoadMeter_class};
use crate::ported::mainpanel::{MainPanel, MainPanel_foreachRow};
use crate::ported::memorymeter::MemoryMeter_class;
use crate::ported::meter::{BlankMeter_class, Meter, MeterClass};
use crate::ported::object::{Arg, Object};
use crate::ported::panel::Panel_getSelected;
use crate::ported::processlocksscreen::{
FileLocks_Data, FileLocks_LockData, FileLocks_ProcessData,
};
use crate::ported::settings::Settings_isReadonly;
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(st: &mut State) -> Htop_Reaction {
if Settings_isReadonly() {
return HTOP_OK;
}
let ioprio1: IOPriority = match Panel_getSelected(unsafe { &(*st.mainPanel).super_ }) {
Some(obj) => {
let any: &dyn Any = obj;
any.downcast_ref::<LinuxProcess>()
.expect("Platform_actionSetIOPriority: selected row is not a LinuxProcess")
.ioPriority
}
None => return HTOP_OK,
};
let ioprio_panel = IOPriorityPanel_new(ioprio1);
let set = Action_pickFromVector(st, Box::new(ioprio_panel), 20, true);
if let Some(obj) = set {
let any: &dyn Any = obj.as_ref();
let ioprio2: IOPriority = any
.downcast_ref::<ListItem>()
.expect("Platform_actionSetIOPriority: picked item is not a ListItem")
.key;
let ok = MainPanel_foreachRow(
unsafe { &mut *st.mainPanel },
LinuxProcess_rowSetIOPriority,
Arg::I(ioprio2),
None,
);
if !ok {
let mut out = std::io::stdout().lock();
Ncurses::beep(&mut out);
}
}
HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR
}
fn Platform_changeAutogroupPriority(panel: &mut MainPanel, delta: i32) -> bool {
if !LinuxProcess_isAutogroupEnabled() {
let mut out = std::io::stdout().lock();
Ncurses::beep(&mut out);
return false;
}
fn apply(row: &mut dyn Object, delta: Arg) -> bool {
LinuxProcess_rowChangeAutogroupPriorityBy(row, delta)
}
let mut anyTagged = false;
let ok = MainPanel_foreachRow(panel, apply, Arg::I(delta), Some(&mut anyTagged));
if !ok {
let mut out = std::io::stdout().lock();
Ncurses::beep(&mut out);
}
anyTagged
}
pub fn Platform_actionHigherAutogroupPriority(st: &mut State) -> Htop_Reaction {
if Settings_isReadonly() {
return HTOP_OK;
}
let changed = Platform_changeAutogroupPriority(unsafe { &mut *st.mainPanel }, -1);
if changed {
HTOP_REFRESH
} else {
HTOP_OK
}
}
pub fn Platform_actionLowerAutogroupPriority(st: &mut State) -> Htop_Reaction {
if Settings_isReadonly() {
return HTOP_OK;
}
let changed = Platform_changeAutogroupPriority(unsafe { &mut *st.mainPanel }, 1);
if changed {
HTOP_REFRESH
} else {
HTOP_OK
}
}
pub fn Platform_setBindings(keys: &mut [Option<Htop_Action>]) {
keys[b'i' as usize] = Some(Platform_actionSetIOPriority);
keys[b'{' as usize] = Some(Platform_actionLowerAutogroupPriority);
keys[b'}' as usize] = Some(Platform_actionHigherAutogroupPriority);
keys[KEY_F(19) as usize] = Some(Platform_actionLowerAutogroupPriority);
keys[KEY_F(20) as usize] = Some(Platform_actionHigherAutogroupPriority);
}
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(this: &mut Meter) {
let lhost = unsafe { &*(this.host as *const LinuxMachine) };
crate::ported::zfsarcmeter::ZfsArcMeter_readStats(this, &lhost.zfs);
}
pub fn Platform_setZfsCompressedArcValues(this: &mut Meter) {
let lhost = unsafe { &*(this.host as *const LinuxMachine) };
crate::ported::zfscompressedarcmeter::ZfsCompressedArcMeter_readStats(this, &lhost.zfs);
}
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())
}
#[allow(unused_unsafe)]
pub fn Platform_getProcessLocks(pid: libc::pid_t) -> Option<FileLocks_ProcessData> {
use std::io::Read;
use std::os::unix::io::FromRawFd;
let mut pdata = FileLocks_ProcessData {
error: false,
locks: None,
};
macro_rules! err_out {
() => {{
pdata.error = true;
return Some(pdata);
}};
}
let path = format!("{}/{}/fdinfo/", PROCDIR, pid);
if path.len() >= (libc::PATH_MAX as usize).saturating_sub(2) {
err_out!();
}
let cpath = match CString::new(path) {
Ok(c) => c,
Err(_) => err_out!(),
};
let dirp = unsafe { libc::opendir(cpath.as_ptr()) };
if dirp.is_null() {
err_out!();
}
let dfd = unsafe { libc::dirfd(dirp) };
if dfd == -1 {
unsafe { libc::closedir(dirp) };
err_out!();
}
type LockScan = (String, String, String, u32, u32, u64, u64, String);
let scan_lock = |rest: &str| -> Option<LockScan> {
let mut it = rest.split_whitespace();
let idx = it.next()?;
idx.strip_suffix(':')?.parse::<i32>().ok()?;
let take31 = |s: &str| -> String { s.chars().take(31).collect() };
let locktype = take31(it.next()?);
let exclusive = take31(it.next()?);
let readwrite = take31(it.next()?);
it.next()?.parse::<i32>().ok()?;
let devinode = it.next()?;
let mut dp = devinode.split(':');
let maj = u32::from_str_radix(dp.next()?, 16).ok()?;
let min = u32::from_str_radix(dp.next()?, 16).ok()?;
let inode = dp.next()?.parse::<u64>().ok()?;
if dp.next().is_some() {
return None;
}
let start = it.next()?.parse::<u64>().ok()?;
let lock_end: String = it.next()?.chars().take(24).collect();
Some((
locktype, exclusive, readwrite, maj, min, inode, start, lock_end,
))
};
let mut collected: Vec<FileLocks_Data> = Vec::new();
loop {
let de = unsafe { libc::readdir(dirp) };
if de.is_null() {
break;
}
let dname_c = unsafe { CStr::from_ptr((*de).d_name.as_ptr()) };
let dname = dname_c.to_string_lossy();
if dname == "." || dname == ".." {
continue;
}
let file: i32 = match dname.parse::<u64>() {
Ok(v) if v < i32::MAX as u64 => v as i32,
_ => continue,
};
let fd =
unsafe { libc::openat(dfd, (*de).d_name.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
if fd == -1 {
continue;
}
let mut fp = unsafe { std::fs::File::from_raw_fd(fd) };
let mut content = String::new();
if fp.read_to_string(&mut content).is_err() {
continue;
}
for raw in content.split_inclusive('\n') {
if !raw.ends_with('\n') {
continue;
}
let rest = match raw.strip_prefix("lock:\t") {
Some(r) => r,
None => continue,
};
let (locktype, exclusive, readwrite, maj, min, inode, start, lock_end) =
match scan_lock(rest) {
Some(t) => t,
None => continue,
};
let mut data = FileLocks_Data {
fd: file,
locktype,
exclusive,
readwrite,
dev: unsafe { libc::makedev(maj as _, min as _) } as u64,
inode,
start,
end: if lock_end == "EOF" {
u64::MAX
} else {
lock_end.parse::<u64>().unwrap_or(0)
},
filename: None,
};
let fdpath = format!("{}/{}/fd/{}", PROCDIR, pid, dname);
if fdpath.len() < (libc::PATH_MAX as usize).saturating_sub(2) {
if let Ok(cfd) = CString::new(fdpath) {
let mut link = [0u8; libc::PATH_MAX as usize];
let link_len = unsafe {
libc::readlink(
cfd.as_ptr(),
link.as_mut_ptr() as *mut libc::c_char,
link.len(),
)
};
if link_len != -1 {
data.filename =
Some(String::from_utf8_lossy(&link[..link_len as usize]).into_owned());
}
}
}
collected.push(data);
}
}
unsafe { libc::closedir(dirp) };
let mut head: Option<Box<FileLocks_LockData>> = None;
for data in collected.into_iter().rev() {
head = Some(Box::new(FileLocks_LockData { data, next: head }));
}
pdata.locks = head;
Some(pdata)
}
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(opt: i32, _argc: i32, _argv: &[String]) -> CommandLineStatus {
let _ = opt;
CommandLineStatus::ErrorExit
}
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
}
pub fn Platform_dynamicMeters() -> *mut crate::ported::hashtable::Hashtable {
std::ptr::null_mut()
}
pub fn Platform_dynamicMetersDone(_table: *mut crate::ported::hashtable::Hashtable) {}
pub fn Platform_dynamicMeterInit(_meter: &mut crate::ported::meter::Meter) {}
pub fn Platform_dynamicMeterUpdateValues(_meter: &mut crate::ported::meter::Meter) {}
pub fn Platform_dynamicMeterDisplay(
_meter: &crate::ported::meter::Meter,
_out: &mut crate::ported::richstring::RichString,
) {
}
pub fn Platform_dynamicScreens() -> *mut crate::ported::hashtable::Hashtable {
std::ptr::null_mut()
}
pub fn Platform_dynamicScreensDone(_screens: *mut crate::ported::hashtable::Hashtable) {}
pub fn Platform_addDynamicScreenAvailableColumns(
_availableColumns: &mut crate::ported::panel::Panel,
_screen: &str,
) {
}
#[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));
}
}