#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(dead_code)]
use crate::ported::crt::{ColorElements as CE, ColorScheme, TreeStr};
use crate::ported::dynamiccolumn::DynamicColumn_writeField;
use crate::ported::hashtable::Hashtable_get;
use crate::ported::machine::Machine;
use crate::ported::object::{Arg, Object, ObjectClass, Object_isA};
use crate::ported::processtable::ProcessTable;
use crate::ported::richstring::{
RichString, RichString_appendAscii, RichString_appendWide, RichString_setAttrn, RichString_size,
};
use crate::ported::row::{
spaceship_number, PercentageAttr, Row, RowClass, Row_class, Row_display, Row_fieldWidths,
Row_getGroupOrParent, Row_init, Row_pidDigits, Row_printCount, Row_printKBytes,
Row_printLeftAlignedField, Row_printPercentage, Row_printTime, Row_uidDigits,
Row_updateFieldWidth,
};
use crate::ported::scheduling::Scheduling_formatPolicy;
use crate::ported::settings::{
RowField, ScreenSettings_getActiveDirection, ScreenSettings_getActiveSortKey, Settings,
Settings_isReadonly,
};
use crate::ported::table::Table;
use crate::ported::xutils::{compareRealNumbers, String_contains_i, String_startsWith};
use core::ffi::c_void;
use core::ops::Deref;
use std::sync::atomic::Ordering;
macro_rules! spaceship_nullstr {
($a:expr, $b:expr) => {{
let a: &[u8] = $a.unwrap_or(b"");
let b: &[u8] = $b.unwrap_or(b"");
match a.cmp(b) {
core::cmp::Ordering::Less => -1,
core::cmp::Ordering::Equal => 0,
core::cmp::Ordering::Greater => 1,
}
}};
}
pub(crate) use spaceship_nullstr;
macro_rules! spaceship_defaultstr {
($a:expr, $b:expr, $s:expr) => {{
let a: &[u8] = $a.unwrap_or($s);
let b: &[u8] = $b.unwrap_or($s);
match a.cmp(b) {
core::cmp::Ordering::Less => -1,
core::cmp::Ordering::Equal => 0,
core::cmp::Ordering::Greater => 1,
}
}};
}
const kthreadID: &[u8] = b"KTHREAD";
pub const PROCESS_FLAG_IO: u32 = 0x00000001;
pub const PROCESS_FLAG_CWD: u32 = 0x00000002;
pub const PROCESS_FLAG_SCHEDPOL: u32 = 0x00000004;
pub const DEFAULT_HIGHLIGHT_SECS: i32 = 5;
pub const PROCESS_NICE_UNKNOWN: i32 = -i32::MAX;
pub const CMDLINE_HIGHLIGHT_FLAG_SEPARATOR: i32 = 0x00000001;
pub const CMDLINE_HIGHLIGHT_FLAG_BASENAME: i32 = 0x00000002;
pub const CMDLINE_HIGHLIGHT_FLAG_COMM: i32 = 0x00000004;
pub const CMDLINE_HIGHLIGHT_FLAG_DELETED: i32 = 0x00000008;
pub const CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR: i32 = 0x00000010;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
#[allow(non_camel_case_types)]
pub enum ProcessState {
UNKNOWN = 1,
RUNNABLE,
RUNNING,
QUEUED,
WAITING,
UNINTERRUPTIBLE_WAIT,
BLOCKED,
PAGING,
STOPPED,
TRACED,
ZOMBIE,
DEFUNCT,
IDLE,
SLEEPING,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(i8)]
#[allow(non_camel_case_types)]
pub enum Tristate {
TRI_OFF = -1,
#[default]
TRI_INITIAL = 0,
TRI_ON = 1,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ProcessCmdlineHighlight {
pub offset: usize,
pub length: usize,
pub attr: i32,
pub flags: i32,
}
#[derive(Debug, Clone, Default)]
pub struct ProcessMergedCommand {
pub lastUpdate: u64,
pub str: Option<String>,
pub highlightCount: usize,
pub highlights: [ProcessCmdlineHighlight; 8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
#[allow(non_camel_case_types)]
pub enum ProcessField {
NULL_FIELD = 0,
PID = 1,
COMM = 2,
STATE = 3,
PPID = 4,
PGRP = 5,
SESSION = 6,
TTY = 7,
TPGID = 8,
MINFLT = 10,
MAJFLT = 12,
PRIORITY = 18,
NICE = 19,
STARTTIME = 21,
PROCESSOR = 38,
M_VIRT = 39,
M_RESIDENT = 40,
ST_UID = 46,
PERCENT_CPU = 47,
PERCENT_MEM = 48,
USER = 49,
TIME = 50,
NLWP = 51,
TGID = 52,
PERCENT_NORM_CPU = 53,
ELAPSED = 54,
SCHEDULERPOLICY = 55,
PROC_COMM = 124,
PROC_EXE = 125,
CWD = 126,
CMINFLT = 11,
CMAJFLT = 13,
UTIME = 14,
STIME = 15,
CUTIME = 16,
CSTIME = 17,
M_SHARE = 41,
M_TRS = 42,
M_DRS = 43,
M_LRS = 44,
CTID = 100,
VPID = 101,
VXID = 102,
RCHAR = 103,
WCHAR = 104,
SYSCR = 105,
SYSCW = 106,
RBYTES = 107,
WBYTES = 108,
CNCLWB = 109,
IO_READ_RATE = 110,
IO_WRITE_RATE = 111,
IO_RATE = 112,
CGROUP = 113,
OOM = 114,
IO_PRIORITY = 115,
PERCENT_CPU_DELAY = 116,
PERCENT_IO_DELAY = 117,
PERCENT_SWAP_DELAY = 118,
M_PSS = 119,
M_SWAP = 120,
M_PSSWP = 121,
CTXT = 122,
SECATTR = 123,
AUTOGROUP_ID = 127,
AUTOGROUP_NICE = 128,
CCGROUP = 129,
CONTAINER = 130,
M_PRIV = 131,
GPU_TIME = 132,
GPU_PERCENT = 133,
ISCONTAINER = 134,
}
#[derive(Debug, Clone, Copy)]
#[allow(non_snake_case)]
pub struct ProcessFieldData {
pub name: &'static str,
pub title: Option<&'static str>,
pub description: Option<&'static str>,
pub flags: u32,
pub pidColumn: bool,
pub defaultSortDesc: bool,
pub autoWidth: bool,
pub autoTitleRightAlign: bool,
}
#[derive(Debug, Clone, Default)]
pub struct Process {
pub super_: Row,
pub pgrp: i32,
pub session: i32,
pub tpgid: i32,
pub isKernelThread: bool,
pub isUserlandThread: bool,
pub isRunningInContainer: Tristate,
pub tty_nr: u64,
pub tty_name: Option<String>,
pub st_uid: u32,
pub user: Option<String>,
pub elevated_priv: Tristate,
pub time: u64,
pub cmdline: Option<String>,
pub cmdlineBasenameEnd: usize,
pub cmdlineBasenameStart: usize,
pub procComm: Option<String>,
pub procExe: Option<String>,
pub procCwd: Option<String>,
pub procExeBasenameOffset: usize,
pub procExeDeleted: bool,
pub usesDeletedLib: bool,
pub processor: i32,
pub percent_cpu: f32,
pub percent_mem: f32,
pub priority: i64,
pub nice: i32,
pub nlwp: i64,
pub starttime_ctime: i64,
pub starttime_show: [u8; 8],
pub m_virt: i64,
pub m_resident: i64,
pub minflt: u64,
pub majflt: u64,
pub state: ProcessState,
pub scheduling_policy: i32,
pub mergedCommand: ProcessMergedCommand,
}
impl Default for ProcessState {
fn default() -> Self {
ProcessState::UNKNOWN
}
}
pub type Process_CompareByKey = fn(&dyn Object, &dyn Object, RowField) -> i32;
pub struct ProcessClass {
pub super_: RowClass,
pub compareByKey: Option<Process_CompareByKey>,
}
impl Deref for ProcessClass {
type Target = ObjectClass;
fn deref(&self) -> &ObjectClass {
&self.super_.super_
}
}
pub static Process_class: ProcessClass = ProcessClass {
super_: RowClass {
super_: ObjectClass {
extends: Some(&Row_class.super_),
},
isHighlighted: Some(Process_rowIsHighlighted),
isVisible: Some(Process_rowIsVisible),
writeField: Some(Process_rowWriteField),
matchesFilter: Some(Process_rowMatchesFilter),
sortKeyString: Some(Process_rowGetSortKey),
compareByParent: Some(Process_compareByParent),
},
compareByKey: None,
};
impl Object for Process {
fn klass(&self) -> &'static ObjectClass {
&Process_class.super_.super_
}
fn row_class(&self) -> Option<&'static RowClass> {
Some(&Process_class.super_)
}
fn as_row(&self) -> Option<&Row> {
Some(&self.super_)
}
fn as_process(&self) -> Option<&Process> {
Some(self)
}
fn as_row_mut(&mut self) -> Option<&mut Row> {
Some(&mut self.super_)
}
fn as_process_mut(&mut self) -> Option<&mut Process> {
Some(self)
}
fn process_class(&self) -> Option<&'static ProcessClass> {
Some(&Process_class)
}
fn display(&self, out: &mut RichString) {
Row_display(self, out)
}
fn compare(&self, other: &dyn Object) -> i32 {
Process_compare(self, other)
}
}
const TASK_COMM_LEN: usize = 16;
pub fn findCommInCmdline(
comm: &[u8],
cmdline: &[u8],
cmdlineBasenameStart: usize,
) -> Option<(usize, usize)> {
let commLen = comm.len();
let mut token = cmdlineBasenameStart;
while token < cmdline.len() {
let mut tokenBase = token;
while token < cmdline.len() && cmdline[token] != b'\n' {
if cmdline[token] == b'/' {
tokenBase = token + 1;
}
token += 1;
}
let tokenLen = token - tokenBase;
if (tokenLen == commLen || (tokenLen > commLen && commLen == TASK_COMM_LEN - 1))
&& cmdline[tokenBase..tokenBase + commLen] == comm[..commLen]
{
return Some((tokenBase, tokenLen));
}
if token < cmdline.len() {
loop {
token += 1;
if !(token < cmdline.len() && cmdline[token] == b'\n') {
break;
}
}
}
}
None
}
pub fn matchCmdlinePrefixWithExeSuffix(
cmdline: &[u8],
cmdlineBasenameStart: usize,
exe: &[u8],
exeBaseOffset: usize,
exeBaseLen: usize,
) -> (usize, usize) {
let at = |s: &[u8], i: usize| -> u8 {
if i < s.len() {
s[i]
} else {
0
}
};
let strncmp_eq = |a: &[u8], ao: usize, b: &[u8], bo: usize, n: usize| -> bool {
for k in 0..n {
let ca = if ao + k < a.len() { a[ao + k] } else { 0 };
let cb = if bo + k < b.len() { b[bo + k] } else { 0 };
if ca != cb {
return false;
}
if ca == 0 {
break;
}
}
true
};
if at(cmdline, 0) == b'/' {
let matchLen = exeBaseLen + exeBaseOffset;
if strncmp_eq(cmdline, 0, exe, 0, matchLen) {
let delim = at(cmdline, matchLen);
if delim == 0 || delim == b'\n' || delim == b' ' {
return (matchLen, cmdlineBasenameStart);
}
}
return (0, cmdlineBasenameStart);
}
let mut cmdlineBaseOffset = cmdlineBasenameStart;
let mut delimFound;
loop {
let matchLen = exeBaseLen + cmdlineBaseOffset;
if cmdlineBaseOffset < exeBaseOffset
&& strncmp_eq(cmdline, cmdlineBaseOffset, exe, exeBaseOffset, exeBaseLen)
{
let delim = at(cmdline, matchLen);
if delim == 0 || delim == b'\n' || delim == b' ' {
let mut i = cmdlineBaseOffset;
let mut j = exeBaseOffset;
while i >= 1 && j >= 1 && at(cmdline, i - 1) == at(exe, j - 1) {
i -= 1;
j -= 1;
}
if i < 1 && j >= 1 && at(exe, j - 1) == b'/' {
return (matchLen, cmdlineBaseOffset);
}
}
}
delimFound = false;
if cmdlineBaseOffset <= 2 {
return (0, cmdlineBasenameStart);
}
cmdlineBaseOffset -= 2;
while cmdlineBaseOffset > 0 {
if delimFound {
if at(cmdline, cmdlineBaseOffset - 1) == b'/' {
break;
}
} else if at(cmdline, cmdlineBaseOffset) == b' '
|| at(cmdline, cmdlineBaseOffset) == b'\n'
{
delimFound = true;
}
cmdlineBaseOffset -= 1;
}
if !delimFound {
return (0, cmdlineBasenameStart);
}
}
}
pub fn Process_fillStarttimeBuffer(this: &mut Process) {
let host = this.super_.host as *const Machine;
let now = unsafe { ((*host).realtimeMs / 1000) as i64 };
let ctime = this.starttime_ctime as libc::time_t;
let mut date: libc::tm = unsafe { core::mem::zeroed() };
unsafe {
libc::localtime_r(&ctime, &mut date);
}
let fmt: &[u8] = if this.starttime_ctime > now - 86400 {
b"%R \0"
} else if this.starttime_ctime > now - 364 * 86400 {
b"%b%d \0"
} else {
b" %Y \0"
};
unsafe {
libc::strftime(
this.starttime_show.as_mut_ptr() as *mut libc::c_char,
this.starttime_show.len() - 1, fmt.as_ptr() as *const libc::c_char,
&date,
);
}
}
pub fn stpcpyWithNewlineConversion(dst: &mut Vec<u8>, src: &[u8]) {
for &c in src {
dst.push(if c == b'\n' { b' ' } else { c });
}
}
pub fn Process_makeCommandStr(this: &mut Process, settings: &Settings) {
let show_merged_command = settings.showMergedCommand;
let show_program_path = settings.showProgramPath;
let search_comm_in_cmdline = settings.findCommInCmdline;
let strip_exe_from_cmdline = settings.stripExeFromCmdline;
let show_thread_names = settings.showThreadNames;
let shadow_dist_path_prefix = settings.shadowDistPathPrefix;
let settings_stamp = settings.lastUpdate;
if Process_isKernelThread(this) {
return;
}
if this.state == ProcessState::ZOMBIE && this.mergedCommand.str.is_none() {
return;
}
if this.mergedCommand.lastUpdate >= settings_stamp {
return;
}
this.mergedCommand.lastUpdate = settings_stamp;
let is_thread = Process_isThread(this);
let is_userland_thread = Process_isUserlandThread(this);
let scheme = ColorScheme::active();
let base_attr = if is_thread {
CE::PROCESS_THREAD_BASENAME
} else {
CE::PROCESS_BASENAME
}
.packed(scheme);
let comm_attr = if is_thread {
CE::PROCESS_THREAD_COMM
} else {
CE::PROCESS_COMM
}
.packed(scheme);
let del_exe_attr = CE::FAILED_READ.packed(scheme);
let del_lib_attr = CE::PROCESS_TAG.packed(scheme);
let shadow_attr = CE::PROCESS_SHADOW.packed(scheme);
let sep_attr = CE::FAILED_READ.packed(scheme);
let proc_exe_deleted = this.procExeDeleted;
let uses_deleted_lib = this.usesDeletedLib;
let proc_exe_basename_offset = this.procExeBasenameOffset;
let cmdline_basename_end = this.cmdlineBasenameEnd;
let cmdline_owned: Option<Vec<u8>> = this.cmdline.as_ref().map(|s| s.as_bytes().to_vec());
let proc_comm: Option<&[u8]> = this.procComm.as_deref().map(str::as_bytes);
let proc_exe: Option<&[u8]> = this.procExe.as_deref().map(str::as_bytes);
let proc_comm: Option<Vec<u8>> = proc_comm.map(|s| s.to_vec());
let proc_exe: Option<Vec<u8>> = proc_exe.map(|s| s.to_vec());
let separator = TreeStr::TREE_STR_VERT.glyph();
let separator_len = separator.len();
let mut buf: Vec<u8> = Vec::new();
let mut highlights: Vec<ProcessCmdlineHighlight> = Vec::new();
let mut mb_mismatch: usize = 0;
fn commit(this: &mut Process, buf: &[u8], highlights: &[ProcessCmdlineHighlight]) {
let mc = &mut this.mergedCommand;
for h in mc.highlights.iter_mut() {
*h = ProcessCmdlineHighlight::default();
}
mc.highlightCount = highlights.len();
for (i, h) in highlights.iter().enumerate() {
mc.highlights[i] = *h;
}
mc.str = Some(String::from_utf8_lossy(buf).into_owned());
}
macro_rules! write_highlight {
($offset:expr, $length:expr, $attr:expr, $flags:expr) => {{
if highlights.len() < 8 {
highlights.push(ProcessCmdlineHighlight {
offset: buf.len() + $offset - mb_mismatch,
length: $length,
attr: $attr,
flags: $flags,
});
}
}};
}
macro_rules! write_separator {
() => {{
write_highlight!(0, 1, sep_attr, CMDLINE_HIGHLIGHT_FLAG_SEPARATOR);
mb_mismatch += separator_len - 1;
buf.extend_from_slice(separator.as_bytes());
}};
}
let dist_prefix_len = |s: &[u8]| -> Option<usize> {
const PREFIXES: &[&[u8]] = &[
b"/bin/",
b"/lib/",
b"/lib32/",
b"/lib64/",
b"/libx32/",
b"/sbin/",
b"/usr/bin/",
b"/usr/libexec/",
b"/usr/lib/",
b"/usr/lib32/",
b"/usr/lib64/",
b"/usr/libx32/",
b"/usr/local/bin/",
b"/usr/local/lib/",
b"/usr/local/sbin/",
b"/usr/sbin/",
b"/nix/store/",
b"/run/current-system/",
];
PREFIXES.iter().find(|p| s.starts_with(p)).map(|p| p.len())
};
let cmdline_present = cmdline_owned.is_some();
let mut cmdline: &[u8] = match &cmdline_owned {
Some(c) => c,
None => b"(zombie)",
};
let proc_comm_s: Option<&[u8]> = proc_comm.as_deref();
let proc_exe_s: Option<&[u8]> = proc_exe.as_deref();
let mut cmdline_basename_start = if cmdline_present {
this.cmdlineBasenameStart
} else {
0
};
let mut cmdline_basename_len =
if cmdline_present && cmdline_basename_end > cmdline_basename_start {
cmdline_basename_end - cmdline_basename_start
} else {
0
};
let mut match_len = 0usize;
let mut exe_basename_offset = 0usize;
let mut exe_basename_len = 0usize;
if let Some(pe) = proc_exe_s {
exe_basename_offset = proc_exe_basename_offset;
exe_basename_len = pe.len() - exe_basename_offset;
if cmdline_present {
let (ml, new_start) = matchCmdlinePrefixWithExeSuffix(
cmdline,
cmdline_basename_start,
pe,
exe_basename_offset,
exe_basename_len,
);
match_len = ml;
cmdline_basename_start = new_start;
}
if match_len != 0 {
cmdline_basename_len = exe_basename_len;
} else if cmdline_present {
const SELF: &[u8] = b"/proc/self/exe";
const THREAD: &[u8] = b"/proc/thread-self/exe";
let sep_self = cmdline.get(SELF.len()).copied().unwrap_or(0);
let sep_thread = cmdline.get(THREAD.len()).copied().unwrap_or(0);
if cmdline.starts_with(SELF) && (sep_self == 0 || sep_self == b' ' || sep_self == b'\n')
{
match_len = SELF.len();
} else if cmdline.starts_with(THREAD)
&& (sep_thread == 0 || sep_thread == b' ' || sep_thread == b'\n')
{
match_len = THREAD.len();
}
}
}
if !show_merged_command || proc_exe_s.is_none() || proc_comm_s.is_none() {
if (show_merged_command || (is_userland_thread && show_thread_names))
&& proc_comm_s.is_some_and(|c| !c.is_empty())
{
let pc = proc_comm_s.unwrap();
let n = (TASK_COMM_LEN - 1).min(pc.len());
let from = &cmdline[cmdline_basename_start.min(cmdline.len())..];
if from.len() < n || from[..n] != pc[..n] {
write_highlight!(0, pc.len(), comm_attr, CMDLINE_HIGHLIGHT_FLAG_COMM);
buf.extend_from_slice(pc);
if !show_merged_command {
commit(this, &buf, &highlights);
return;
}
write_separator!();
}
}
if shadow_dist_path_prefix && show_program_path {
if let Some(plen) = dist_prefix_len(cmdline) {
write_highlight!(0, plen, shadow_attr, CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR);
}
}
if cmdline_basename_len > 0 {
let off = if show_program_path {
cmdline_basename_start
} else {
0
};
write_highlight!(
off,
cmdline_basename_len,
base_attr,
CMDLINE_HIGHLIGHT_FLAG_BASENAME
);
if proc_exe_deleted {
write_highlight!(
off,
cmdline_basename_len,
del_exe_attr,
CMDLINE_HIGHLIGHT_FLAG_DELETED
);
} else if uses_deleted_lib {
write_highlight!(
off,
cmdline_basename_len,
del_lib_attr,
CMDLINE_HIGHLIGHT_FLAG_DELETED
);
}
}
let tail = if show_program_path {
cmdline
} else {
&cmdline[cmdline_basename_start.min(cmdline.len())..]
};
stpcpyWithNewlineConversion(&mut buf, tail);
commit(this, &buf, &highlights);
return;
}
let proc_exe_s = proc_exe_s.unwrap();
let proc_comm_s = proc_comm_s.unwrap();
let mut comm_len = 0usize;
let mut have_comm_in_exe = false;
if !is_userland_thread || show_thread_names {
let n = (TASK_COMM_LEN - 1).min(proc_comm_s.len());
let exe_tail = &proc_exe_s[exe_basename_offset.min(proc_exe_s.len())..];
have_comm_in_exe = exe_tail.len() >= n && exe_tail[..n] == proc_comm_s[..n];
}
if have_comm_in_exe {
comm_len = exe_basename_len;
}
let mut have_comm_in_cmdline = false;
let mut comm_start = 0usize;
if !have_comm_in_exe
&& cmdline_present
&& search_comm_in_cmdline
&& (!is_userland_thread || show_thread_names)
{
if let Some((cs, cl)) = findCommInCmdline(proc_comm_s, cmdline, cmdline_basename_start) {
have_comm_in_cmdline = true;
comm_start = cs;
comm_len = cl;
}
}
if !strip_exe_from_cmdline {
match_len = 0;
}
if match_len != 0 {
cmdline = &cmdline[match_len.min(cmdline.len())..];
if have_comm_in_cmdline {
if comm_start == cmdline_basename_start {
have_comm_in_exe = true;
have_comm_in_cmdline = false;
comm_start = 0;
} else {
comm_start -= match_len;
}
}
}
if show_program_path {
if shadow_dist_path_prefix {
if let Some(plen) = dist_prefix_len(proc_exe_s) {
write_highlight!(0, plen, shadow_attr, CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR);
}
}
if have_comm_in_exe {
write_highlight!(
exe_basename_offset,
comm_len,
comm_attr,
CMDLINE_HIGHLIGHT_FLAG_COMM
);
}
write_highlight!(
exe_basename_offset,
exe_basename_len,
base_attr,
CMDLINE_HIGHLIGHT_FLAG_BASENAME
);
if proc_exe_deleted {
write_highlight!(
exe_basename_offset,
exe_basename_len,
del_exe_attr,
CMDLINE_HIGHLIGHT_FLAG_DELETED
);
} else if uses_deleted_lib {
write_highlight!(
exe_basename_offset,
exe_basename_len,
del_lib_attr,
CMDLINE_HIGHLIGHT_FLAG_DELETED
);
}
buf.extend_from_slice(proc_exe_s);
} else {
if have_comm_in_exe {
write_highlight!(0, comm_len, comm_attr, CMDLINE_HIGHLIGHT_FLAG_COMM);
}
write_highlight!(
0,
exe_basename_len,
base_attr,
CMDLINE_HIGHLIGHT_FLAG_BASENAME
);
if proc_exe_deleted {
write_highlight!(
0,
exe_basename_len,
del_exe_attr,
CMDLINE_HIGHLIGHT_FLAG_DELETED
);
} else if uses_deleted_lib {
write_highlight!(
0,
exe_basename_len,
del_lib_attr,
CMDLINE_HIGHLIGHT_FLAG_DELETED
);
}
buf.extend_from_slice(&proc_exe_s[exe_basename_offset.min(proc_exe_s.len())..]);
}
let mut have_comm_field = false;
if !have_comm_in_exe && !have_comm_in_cmdline && (!is_userland_thread || show_thread_names) {
write_separator!();
write_highlight!(0, proc_comm_s.len(), comm_attr, CMDLINE_HIGHLIGHT_FLAG_COMM);
buf.extend_from_slice(proc_comm_s);
have_comm_field = true;
}
if match_len == 0 || (have_comm_field && !cmdline.is_empty()) {
write_separator!();
}
if shadow_dist_path_prefix {
if let Some(plen) = dist_prefix_len(cmdline) {
write_highlight!(0, plen, shadow_attr, CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR);
}
}
if !have_comm_in_exe
&& have_comm_in_cmdline
&& !have_comm_field
&& (!is_userland_thread || show_thread_names)
{
write_highlight!(comm_start, comm_len, comm_attr, CMDLINE_HIGHLIGHT_FLAG_COMM);
}
if !cmdline.is_empty() {
stpcpyWithNewlineConversion(&mut buf, cmdline);
}
commit(this, &buf, &highlights);
}
pub fn Process_writeCommand(this: &Process, attr: i32, baseAttr: i32, str: &mut RichString) {
let mc = &this.mergedCommand;
let str_start = RichString_size(str) as usize;
let host = unsafe { &*(this.super_.host as *const Machine) };
let settings = host
.settings
.as_ref()
.expect("Process_writeCommand: host->settings is NULL");
let highlight_base_name = settings.highlightBaseName;
let highlight_separator = true;
let highlight_deleted = settings.highlightDeletedExe;
let merged_command = match &mc.str {
None => {
let cmdline_full = this.cmdline.as_deref().unwrap_or("");
let cmdline_bytes = cmdline_full.as_bytes();
let mut len: usize = 0;
let mut basename: usize = 0;
let mut cmd_offset: usize = 0; let mut str_start = str_start;
if highlight_base_name || !settings.showProgramPath {
for i in 0..this.cmdlineBasenameEnd.min(cmdline_bytes.len()) {
if cmdline_bytes[i] == b'/' {
basename = i + 1;
} else if cmdline_bytes[i] == b':' {
len = i + 1;
break;
}
}
if len == 0 {
if settings.showProgramPath {
str_start += basename;
} else {
cmd_offset = basename;
}
len = this.cmdlineBasenameEnd - basename;
}
}
RichString_appendWide(
str,
attr,
&cmdline_bytes[cmd_offset.min(cmdline_bytes.len())..],
);
if settings.highlightBaseName {
RichString_setAttrn(str, baseAttr, str_start, len);
}
return;
}
Some(s) => s,
};
RichString_appendWide(str, attr, merged_command.as_bytes());
let hl_count = mc.highlightCount.min(mc.highlights.len());
for hl in &mc.highlights[..hl_count] {
if hl.length == 0 {
continue;
}
if hl.flags & CMDLINE_HIGHLIGHT_FLAG_SEPARATOR != 0 && !highlight_separator {
continue;
}
if hl.flags & CMDLINE_HIGHLIGHT_FLAG_BASENAME != 0 && !highlight_base_name {
continue;
}
if hl.flags & CMDLINE_HIGHLIGHT_FLAG_DELETED != 0 && !highlight_deleted {
continue;
}
if hl.flags & CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR != 0 && !highlight_deleted {
continue;
}
RichString_setAttrn(str, hl.attr, str_start + hl.offset, hl.length);
}
}
pub fn processStateChar(state: ProcessState) -> char {
match state {
ProcessState::UNKNOWN => '?',
ProcessState::RUNNABLE => 'U',
ProcessState::RUNNING => 'R',
ProcessState::QUEUED => 'Q',
ProcessState::WAITING => 'W',
ProcessState::UNINTERRUPTIBLE_WAIT => 'D',
ProcessState::BLOCKED => 'B',
ProcessState::PAGING => 'P',
ProcessState::STOPPED => 'T',
ProcessState::TRACED => 't',
ProcessState::ZOMBIE => 'Z',
ProcessState::DEFUNCT => 'X',
ProcessState::IDLE => 'I',
ProcessState::SLEEPING => 'S',
}
}
pub fn Process_rowWriteField(super_: &dyn Object, str: &mut RichString, field: RowField) {
debug_assert!(Object_isA(Some(super_), &Process_class));
let this = super_
.as_process()
.expect("Process_rowWriteField: row is not a Process");
Process_writeField(this, str, field);
}
pub fn Process_writeField(this: &Process, str: &mut RichString, field: RowField) {
use ProcessField as PF;
use ProcessState::*;
let host = unsafe { &*(this.super_.host as *const Machine) };
let settings = host
.settings
.as_ref()
.expect("Process_writeField: host->settings is NULL");
let coloring = settings.highlightMegabytes;
let scheme = ColorScheme::active();
let n = 255usize; let mut attr = CE::DEFAULT_COLOR.packed(scheme);
let buffer: String;
match field {
f if f == PF::COMM as RowField => {
let mut baseattr = CE::PROCESS_BASENAME.packed(scheme);
if settings.highlightThreads && Process_isThread(this) {
attr = CE::PROCESS_THREAD.packed(scheme);
baseattr = CE::PROCESS_THREAD_BASENAME.packed(scheme);
}
let ss = &settings.screens[settings.ssIndex as usize];
let indent = this.super_.indent;
if !ss.treeView || indent == 0 {
Process_writeCommand(this, attr, baseattr, str);
return;
}
let last_item = indent < 0;
let mut tree = String::new();
let mut ind: u32 = if indent < 0 {
(-indent) as u32
} else {
indent as u32
};
while ind > 1 {
if ind & 1 != 0 {
tree.push_str(TreeStr::TREE_STR_VERT.glyph());
tree.push_str(" ");
} else {
tree.push_str(" ");
}
ind >>= 1;
}
let draw = if last_item {
TreeStr::TREE_STR_BEND
} else {
TreeStr::TREE_STR_RTEE
};
let openshut = if this.super_.showChildren {
TreeStr::TREE_STR_SHUT
} else {
TreeStr::TREE_STR_OPEN
};
tree.push_str(draw.glyph());
tree.push_str(openshut.glyph());
tree.push(' ');
RichString_appendWide(str, CE::PROCESS_TREE.packed(scheme), tree.as_bytes());
Process_writeCommand(this, attr, baseattr, str);
return;
}
f if f == PF::PROC_COMM as RowField => {
let (a, content): (i32, &[u8]) = match &this.procComm {
Some(pc) => {
let a = if Process_isUserlandThread(this) {
CE::PROCESS_THREAD_COMM.packed(scheme)
} else {
CE::PROCESS_COMM.packed(scheme)
};
(a, pc.as_bytes())
}
None => {
let c: &[u8] = if Process_isKernelThread(this) {
kthreadID
} else {
b"N/A"
};
(CE::PROCESS_SHADOW.packed(scheme), c)
}
};
Row_printLeftAlignedField(str, a, content, (TASK_COMM_LEN - 1) as u32);
return;
}
f if f == PF::PROC_EXE as RowField => {
let (a, content): (i32, &[u8]) = match &this.procExe {
Some(pe) => {
let mut a = if Process_isUserlandThread(this) {
CE::PROCESS_THREAD_BASENAME.packed(scheme)
} else {
CE::PROCESS_BASENAME.packed(scheme)
};
if settings.highlightDeletedExe {
if this.procExeDeleted {
a = CE::FAILED_READ.packed(scheme);
} else if this.usesDeletedLib {
a = CE::PROCESS_TAG.packed(scheme);
}
}
(a, &pe.as_bytes()[this.procExeBasenameOffset..])
}
None => {
let c: &[u8] = if Process_isKernelThread(this) {
kthreadID
} else {
b"N/A"
};
(CE::PROCESS_SHADOW.packed(scheme), c)
}
};
Row_printLeftAlignedField(str, a, content, (TASK_COMM_LEN - 1) as u32);
return;
}
f if f == PF::CWD as RowField => {
let (a, content): (i32, &[u8]) = match &this.procCwd {
None => (CE::PROCESS_SHADOW.packed(scheme), b"N/A".as_slice()),
Some(c) if String_startsWith(c, "/proc/") && c.contains(" (deleted)") => (
CE::PROCESS_SHADOW.packed(scheme),
b"main thread terminated".as_slice(),
),
Some(c) => (attr, c.as_bytes()),
};
Row_printLeftAlignedField(str, a, content, 25);
return;
}
f if f == PF::ELAPSED as RowField => {
let rt = host.realtimeMs;
let st = (this.starttime_ctime as u64).wrapping_mul(1000);
let dt = if rt < st { 0 } else { rt - st };
Row_printTime(str, dt / 10, coloring);
return;
}
f if f == PF::MAJFLT as RowField => {
Row_printCount(str, this.majflt, coloring);
return;
}
f if f == PF::MINFLT as RowField => {
Row_printCount(str, this.minflt, coloring);
return;
}
f if f == PF::M_RESIDENT as RowField => {
Row_printKBytes(str, this.m_resident as u64, coloring);
return;
}
f if f == PF::M_VIRT as RowField => {
Row_printKBytes(str, this.m_virt as u64, coloring);
return;
}
f if f == PF::NICE as RowField => {
if this.nice == PROCESS_NICE_UNKNOWN {
buffer = "N/A ".to_string();
attr = CE::PROCESS_SHADOW.packed(scheme);
} else {
buffer = format!("{:>3} ", this.nice);
attr = if this.nice < 0 {
CE::PROCESS_HIGH_PRIORITY.packed(scheme)
} else if this.nice > 0 {
CE::PROCESS_LOW_PRIORITY.packed(scheme)
} else {
CE::PROCESS_SHADOW.packed(scheme)
};
}
}
f if f == PF::NLWP as RowField => {
if this.nlwp == 1 {
attr = CE::PROCESS_SHADOW.packed(scheme);
}
buffer = format!("{:>4} ", this.nlwp);
}
f if f == PF::PERCENT_CPU as RowField => {
let mut pa = PercentageAttr::Unchanged;
let w = Row_fieldWidths[PF::PERCENT_CPU as usize].load(Ordering::Relaxed);
buffer = Row_printPercentage(this.percent_cpu, n, w, &mut pa);
match pa {
PercentageAttr::Shadow => attr = CE::PROCESS_SHADOW.packed(scheme),
PercentageAttr::Megabytes => attr = CE::PROCESS_MEGABYTES.packed(scheme),
PercentageAttr::Unchanged => {}
}
}
f if f == PF::PERCENT_NORM_CPU as RowField => {
let mut pa = PercentageAttr::Unchanged;
let w = Row_fieldWidths[PF::PERCENT_CPU as usize].load(Ordering::Relaxed);
let cpu_pct = this.percent_cpu / host.activeCPUs as f32;
buffer = Row_printPercentage(cpu_pct, n, w, &mut pa);
match pa {
PercentageAttr::Shadow => attr = CE::PROCESS_SHADOW.packed(scheme),
PercentageAttr::Megabytes => attr = CE::PROCESS_MEGABYTES.packed(scheme),
PercentageAttr::Unchanged => {}
}
}
f if f == PF::PERCENT_MEM as RowField => {
let mut pa = PercentageAttr::Unchanged;
buffer = Row_printPercentage(this.percent_mem, n, 4, &mut pa);
match pa {
PercentageAttr::Shadow => attr = CE::PROCESS_SHADOW.packed(scheme),
PercentageAttr::Megabytes => attr = CE::PROCESS_MEGABYTES.packed(scheme),
PercentageAttr::Unchanged => {}
}
}
f if f == PF::PGRP as RowField => {
let w = Row_pidDigits.load(Ordering::Relaxed) as usize;
buffer = format!("{:>w$} ", this.pgrp);
}
f if f == PF::PID as RowField => {
let w = Row_pidDigits.load(Ordering::Relaxed) as usize;
buffer = format!("{:>w$} ", Process_getPid(this));
}
f if f == PF::PPID as RowField => {
let w = Row_pidDigits.load(Ordering::Relaxed) as usize;
buffer = format!("{:>w$} ", Process_getParent(this));
}
f if f == PF::PRIORITY as RowField => {
buffer = if this.priority <= -100 {
" RT ".to_string()
} else {
format!("{:>3} ", this.priority)
};
}
f if f == PF::PROCESSOR as RowField => {
let cpu_id = if settings.countCPUsFromOne {
this.processor + 1
} else {
this.processor
};
buffer = format!("{:>3} ", cpu_id);
}
f if f == PF::SCHEDULERPOLICY as RowField => {
let s = if this.scheduling_policy >= 0 {
Scheduling_formatPolicy(this.scheduling_policy)
} else {
"N/A"
};
buffer = format!("{s:<5} ");
}
f if f == PF::SESSION as RowField => {
let w = Row_pidDigits.load(Ordering::Relaxed) as usize;
buffer = format!("{:>w$} ", this.session);
}
f if f == PF::STARTTIME as RowField => {
let end = this
.starttime_show
.iter()
.position(|&b| b == 0)
.unwrap_or(this.starttime_show.len());
buffer = String::from_utf8_lossy(&this.starttime_show[..end]).into_owned();
}
f if f == PF::STATE as RowField => {
buffer = format!("{} ", processStateChar(this.state));
attr = match this.state {
RUNNABLE | RUNNING | TRACED => CE::PROCESS_RUN_STATE.packed(scheme),
BLOCKED | DEFUNCT | STOPPED | UNINTERRUPTIBLE_WAIT | ZOMBIE => {
CE::PROCESS_D_STATE.packed(scheme)
}
QUEUED | WAITING | IDLE | SLEEPING => CE::PROCESS_SHADOW.packed(scheme),
UNKNOWN | PAGING => attr,
};
}
f if f == PF::ST_UID as RowField => {
let w = Row_uidDigits.load(Ordering::Relaxed) as usize;
buffer = format!("{:>w$} ", this.st_uid);
}
f if f == PF::TIME as RowField => {
Row_printTime(str, this.time, coloring);
return;
}
f if f == PF::TGID as RowField => {
if Process_getThreadGroup(this) == Process_getPid(this) {
attr = CE::PROCESS_SHADOW.packed(scheme);
}
let w = Row_pidDigits.load(Ordering::Relaxed) as usize;
buffer = format!("{:>w$} ", Process_getThreadGroup(this));
}
f if f == PF::TPGID as RowField => {
let w = Row_pidDigits.load(Ordering::Relaxed) as usize;
buffer = format!("{:>w$} ", this.tpgid);
}
f if f == PF::TTY as RowField => match &this.tty_name {
None => {
attr = CE::PROCESS_SHADOW.packed(scheme);
buffer = "(no tty) ".to_string();
}
Some(t) => {
let name = if String_startsWith(t, "/dev/") {
&t[5..]
} else {
t.as_str()
};
buffer = format!("{name:<8} ");
}
},
f if f == PF::USER as RowField => {
if this.elevated_priv == Tristate::TRI_ON {
attr = CE::PROCESS_PRIV.packed(scheme);
} else if host.htopUserId != this.st_uid {
attr = CE::PROCESS_SHADOW.packed(scheme);
}
if let Some(u) = &this.user {
Row_printLeftAlignedField(str, attr, u.as_bytes(), 10);
return;
}
buffer = format!("{:<10} ", this.st_uid);
}
_ => {
if DynamicColumn_writeField(this, str, field as u32) {
return;
}
debug_assert!(false, "Process_writeField: default key reached");
buffer = "- ".to_string();
}
}
RichString_appendAscii(str, attr, buffer.as_bytes());
}
pub fn Process_done(this: Process) {
let _ = this;
}
pub fn Process_getCommand(this: &Process) -> Option<&[u8]> {
let host = unsafe { &*(this.super_.host as *const Machine) };
let settings = host
.settings
.as_ref()
.expect("Process_getCommand: host->settings is NULL");
if (Process_isUserlandThread(this) && settings.showThreadNames)
|| this.mergedCommand.str.is_none()
{
return this.cmdline.as_deref().map(str::as_bytes);
}
this.mergedCommand.str.as_deref().map(str::as_bytes)
}
pub fn Process_getSortKey(this: &Process) -> Option<&[u8]> {
Process_getCommand(this)
}
pub fn Process_rowGetSortKey(super_: &dyn Object) -> Option<&[u8]> {
debug_assert!(Object_isA(Some(super_), &Process_class));
let this = super_
.as_process()
.expect("Process_rowGetSortKey: row is not a Process");
Process_getSortKey(this)
}
pub fn Process_isHighlighted(this: &Process) -> bool {
let host = unsafe { &*(this.super_.host as *const Machine) };
let settings = host
.settings
.as_ref()
.expect("Process_isHighlighted: host->settings is NULL");
settings.shadowOtherUsers && this.st_uid != host.htopUserId
}
pub fn Process_rowIsHighlighted(super_: &dyn Object) -> bool {
debug_assert!(Object_isA(Some(super_), &Process_class));
let this = super_
.as_process()
.expect("Process_rowIsHighlighted: row is not a Process");
Process_isHighlighted(this)
}
pub fn Process_isVisible(p: &Process, settings: &Settings) -> bool {
if settings.hideUserlandThreads {
return !Process_isThread(p);
}
true
}
pub fn Process_rowIsVisible(super_: &dyn Object, table: &Table) -> bool {
debug_assert!(Object_isA(Some(super_), &Process_class));
let this = super_
.as_process()
.expect("Process_rowIsVisible: row is not a Process");
let host = unsafe { &*table.host };
let settings = host
.settings
.as_ref()
.expect("Process_rowIsVisible: table->host->settings is NULL");
Process_isVisible(this, settings)
}
pub fn Process_matchesFilter(this: &Process, table: &Table) -> bool {
let host = unsafe { &*table.host };
if host.userId != u32::MAX && this.st_uid != host.userId {
return true;
}
if let Some(incFilter) = table.incFilter.as_deref() {
let cmd_bytes = Process_getCommand(this).unwrap_or(b"");
let cmd = core::str::from_utf8(cmd_bytes).unwrap_or("");
if !String_contains_i(cmd, incFilter, true) {
return true;
}
}
let pt = unsafe {
let t = host
.activeTable
.expect("Process_matchesFilter: host->activeTable is NULL");
&*(t as *const ProcessTable)
};
if let Some(pml) = pt.pidMatchList {
let list = unsafe { &*pml };
if Hashtable_get(list, Process_getThreadGroup(this) as u32).is_none() {
return true;
}
}
false
}
pub fn Process_rowMatchesFilter(super_: &dyn Object, table: &Table) -> bool {
debug_assert!(Object_isA(Some(super_), &Process_class));
let this = super_
.as_process()
.expect("Process_rowMatchesFilter: row is not a Process");
Process_matchesFilter(this, table)
}
pub fn Process_init(this: &mut Process, host: *const c_void) {
Row_init(&mut this.super_, host);
this.cmdlineBasenameEnd = 0;
this.st_uid = u32::MAX; }
pub fn Process_setPriority(this: &mut Process, priority: i32) -> bool {
if Settings_isReadonly() {
return false;
}
let who = Process_getPid(this) as libc::id_t;
let old_prio = unsafe { libc::getpriority(libc::PRIO_PROCESS, who as _) };
let err = unsafe { libc::setpriority(libc::PRIO_PROCESS, who as _, priority) };
if err == 0 && old_prio != unsafe { libc::getpriority(libc::PRIO_PROCESS, who as _) } {
this.nice = priority;
}
err == 0
}
pub fn Process_rowChangePriorityBy(super_: &mut dyn Object, delta: Arg) -> bool {
debug_assert!(Object_isA(Some(super_ as &dyn Object), &Process_class));
let this = super_
.as_process_mut()
.expect("Process_rowChangePriorityBy: row is not a Process");
let delta_i = match delta {
Arg::I(i) => i,
Arg::V(_) => panic!("Process_rowChangePriorityBy: Arg must carry the delta in arg.i"),
};
let priority = this.nice + delta_i;
Process_setPriority(this, priority)
}
pub fn Process_sendSignal(this: &Process, sgn: Arg) -> bool {
let signum = match sgn {
Arg::I(i) => i,
Arg::V(_) => panic!("Process_sendSignal: Arg must carry the signal in arg.i"),
};
unsafe { libc::kill(Process_getPid(this), signum) == 0 }
}
pub fn Process_rowSendSignal(super_: &mut dyn Object, sgn: Arg) -> bool {
debug_assert!(Object_isA(Some(super_ as &dyn Object), &Process_class));
let this = super_
.as_process()
.expect("Process_rowSendSignal: row is not a Process");
Process_sendSignal(this, sgn)
}
pub fn Process_compare(v1: &dyn Object, v2: &dyn Object) -> i32 {
let p1 = v1
.as_process()
.expect("Process_compare: v1 is not a Process");
let p2 = v2
.as_process()
.expect("Process_compare: v2 is not a Process");
let host = unsafe { &*(p1.super_.host as *const Machine) };
let settings = host
.settings
.as_ref()
.expect("Process_compare: host->settings is NULL");
let ss = &settings.screens[settings.ssIndex as usize];
let key = ScreenSettings_getActiveSortKey(ss);
let result = match v1.process_class().and_then(|pc| pc.compareByKey) {
Some(f) => f(v1, v2, key),
None => Process_compareByKey_Base(p1, p2, key),
};
if result == 0 {
return spaceship_number!(Process_getPid(p1), Process_getPid(p2));
}
if ScreenSettings_getActiveDirection(ss) == 1 {
result
} else {
-result
}
}
pub fn Process_compareByParent(r1: &dyn Object, r2: &dyn Object) -> i32 {
debug_assert!(Object_isA(Some(r1), &Process_class));
debug_assert!(Object_isA(Some(r2), &Process_class));
let p1 = r1
.as_process()
.expect("Process_compareByParent: row is not a Process");
let p2 = r2
.as_process()
.expect("Process_compareByParent: row is not a Process");
let result = spaceship_number!(
if p1.super_.isRoot {
0
} else {
Row_getGroupOrParent(&p1.super_)
},
if p2.super_.isRoot {
0
} else {
Row_getGroupOrParent(&p2.super_)
}
);
if result != 0 {
return result;
}
Process_compare(p1, p2)
}
pub fn Process_compareByKey_Base(p1: &Process, p2: &Process, key: RowField) -> i32 {
use ProcessField as PF;
match key {
k if k == PF::PERCENT_CPU as RowField || k == PF::PERCENT_NORM_CPU as RowField => {
compareRealNumbers(p1.percent_cpu as f64, p2.percent_cpu as f64)
}
k if k == PF::PERCENT_MEM as RowField => spaceship_number!(p1.m_resident, p2.m_resident),
k if k == PF::COMM as RowField => {
spaceship_nullstr!(Process_getCommand(p1), Process_getCommand(p2))
}
k if k == PF::PROC_COMM as RowField => {
let comm1: &[u8] = match &p1.procComm {
Some(c) => c.as_bytes(),
None => {
if Process_isKernelThread(p1) {
kthreadID
} else {
b""
}
}
};
let comm2: &[u8] = match &p2.procComm {
Some(c) => c.as_bytes(),
None => {
if Process_isKernelThread(p2) {
kthreadID
} else {
b""
}
}
};
spaceship_nullstr!(Some(comm1), Some(comm2))
}
k if k == PF::PROC_EXE as RowField => {
let exe1: &[u8] = match &p1.procExe {
Some(e) => &e.as_bytes()[p1.procExeBasenameOffset..],
None => {
if Process_isKernelThread(p1) {
kthreadID
} else {
b""
}
}
};
let exe2: &[u8] = match &p2.procExe {
Some(e) => &e.as_bytes()[p2.procExeBasenameOffset..],
None => {
if Process_isKernelThread(p2) {
kthreadID
} else {
b""
}
}
};
spaceship_nullstr!(Some(exe1), Some(exe2))
}
k if k == PF::CWD as RowField => spaceship_nullstr!(
p1.procCwd.as_deref().map(str::as_bytes),
p2.procCwd.as_deref().map(str::as_bytes)
),
k if k == PF::ELAPSED as RowField => {
let r = -spaceship_number!(p1.starttime_ctime, p2.starttime_ctime);
if r != 0 {
r
} else {
spaceship_number!(Process_getPid(p1), Process_getPid(p2))
}
}
k if k == PF::MAJFLT as RowField => spaceship_number!(p1.majflt, p2.majflt),
k if k == PF::MINFLT as RowField => spaceship_number!(p1.minflt, p2.minflt),
k if k == PF::M_RESIDENT as RowField => spaceship_number!(p1.m_resident, p2.m_resident),
k if k == PF::M_VIRT as RowField => spaceship_number!(p1.m_virt, p2.m_virt),
k if k == PF::NICE as RowField => spaceship_number!(p1.nice, p2.nice),
k if k == PF::NLWP as RowField => spaceship_number!(p1.nlwp, p2.nlwp),
k if k == PF::PGRP as RowField => spaceship_number!(p1.pgrp, p2.pgrp),
k if k == PF::PID as RowField => spaceship_number!(Process_getPid(p1), Process_getPid(p2)),
k if k == PF::PPID as RowField => {
spaceship_number!(Process_getParent(p1), Process_getParent(p2))
}
k if k == PF::PRIORITY as RowField => spaceship_number!(p1.priority, p2.priority),
k if k == PF::PROCESSOR as RowField => spaceship_number!(p1.processor, p2.processor),
k if k == PF::SCHEDULERPOLICY as RowField => {
spaceship_number!(p1.scheduling_policy, p2.scheduling_policy)
}
k if k == PF::SESSION as RowField => spaceship_number!(p1.session, p2.session),
k if k == PF::STARTTIME as RowField => {
let r = spaceship_number!(p1.starttime_ctime, p2.starttime_ctime);
if r != 0 {
r
} else {
spaceship_number!(Process_getPid(p1), Process_getPid(p2))
}
}
k if k == PF::STATE as RowField => spaceship_number!(p1.state as i32, p2.state as i32),
k if k == PF::ST_UID as RowField => spaceship_number!(p1.st_uid, p2.st_uid),
k if k == PF::TIME as RowField => spaceship_number!(p1.time, p2.time),
k if k == PF::TGID as RowField => {
spaceship_number!(Process_getThreadGroup(p1), Process_getThreadGroup(p2))
}
k if k == PF::TPGID as RowField => spaceship_number!(p1.tpgid, p2.tpgid),
k if k == PF::TTY as RowField => {
spaceship_defaultstr!(
p1.tty_name.as_deref().map(str::as_bytes),
p2.tty_name.as_deref().map(str::as_bytes),
b"\x7f"
)
}
k if k == PF::USER as RowField => spaceship_nullstr!(
p1.user.as_deref().map(str::as_bytes),
p2.user.as_deref().map(str::as_bytes)
),
_ => spaceship_number!(Process_getPid(p1), Process_getPid(p2)),
}
}
pub fn Process_getPid(this: &Process) -> i32 {
this.super_.id
}
pub fn Process_getParent(this: &Process) -> i32 {
this.super_.parent
}
pub fn Process_getThreadGroup(this: &Process) -> i32 {
this.super_.group
}
pub fn Process_setPid(this: &mut Process, pid: i32) {
this.super_.id = pid;
}
pub fn Process_setThreadGroup(this: &mut Process, pid: i32) {
this.super_.group = pid;
}
pub fn Process_setParent(this: &mut Process, pid: i32) {
this.super_.parent = pid;
}
pub fn Process_isKernelThread(this: &Process) -> bool {
this.isKernelThread
}
pub fn Process_isUserlandThread(this: &Process) -> bool {
this.isUserlandThread
}
pub fn Process_isThread(this: &Process) -> bool {
Process_isUserlandThread(this) || Process_isKernelThread(this)
}
pub fn Process_updateComm(this: &mut Process, comm: Option<&str>) {
if this.procComm.is_none() && comm.is_none() {
return;
}
if let (Some(cur), Some(new)) = (&this.procComm, comm) {
if cur == new {
return;
}
}
this.procComm = comm.map(|s| s.to_string());
this.mergedCommand.lastUpdate = 0;
}
pub fn skipPotentialPath(cmdline: &[u8], end: usize) -> usize {
let at = |i: usize| -> u8 {
if i < cmdline.len() {
cmdline[i]
} else {
0
}
};
if at(0) != b'/' {
return 0;
}
let mut slash = 0;
let mut i = 1;
while i < end {
if at(i) == b'/' && at(i + 1) != 0 {
slash = i + 1;
i += 1;
continue;
}
if at(i) == b' ' && at(i - 1) != b'\\' {
return slash;
}
if at(i) == b':' && at(i + 1) == b' ' {
return slash;
}
i += 1;
}
slash
}
pub fn Process_updateCmdline(
this: &mut Process,
cmdline: Option<&str>,
basenameStart: usize,
basenameEnd: usize,
) {
debug_assert!(cmdline.map_or(basenameStart == 0, |c| basenameStart < c.len()));
debug_assert!(basenameEnd > basenameStart || (basenameEnd == 0 && basenameStart == 0));
debug_assert!(cmdline.map_or(basenameEnd == 0, |c| basenameEnd <= c.len()));
if this.cmdline.is_none() && cmdline.is_none() {
return;
}
if let (Some(cur), Some(new)) = (&this.cmdline, cmdline) {
if cur == new {
return;
}
}
if Process_isKernelThread(this) {
this.cmdlineBasenameStart = 0;
this.cmdlineBasenameEnd = 0;
} else {
this.cmdlineBasenameStart = match cmdline {
Some(c) if basenameStart == 0 => skipPotentialPath(c.as_bytes(), basenameEnd),
_ => basenameStart,
};
this.cmdlineBasenameEnd = basenameEnd;
}
this.cmdline = cmdline.map(|s| s.to_string());
this.mergedCommand.lastUpdate = 0;
}
pub fn Process_updateExe(this: &mut Process, exe: Option<&str>) {
if this.procExe.is_none() && exe.is_none() {
return;
}
if let (Some(cur), Some(new)) = (&this.procExe, exe) {
if cur == new {
return;
}
}
match exe {
Some(exe) => {
let last_slash = exe.rfind('/');
this.procExeBasenameOffset = match last_slash {
Some(pos) if pos + 1 < exe.len() && pos != 0 => pos + 1,
_ => 0,
};
this.procExe = Some(exe.to_string());
}
None => {
this.procExe = None;
this.procExeBasenameOffset = 0;
}
}
this.mergedCommand.lastUpdate = 0;
}
pub fn Process_updateCPUFieldWidths(percentage: f32) {
if !(percentage >= 99.9_f32) {
Row_updateFieldWidth(ProcessField::PERCENT_CPU as RowField, 4);
Row_updateFieldWidth(ProcessField::PERCENT_NORM_CPU as RowField, 4);
return;
}
let width = ((percentage + 0.1).log10().ceil() + 2.0) as u8 as usize;
Row_updateFieldWidth(ProcessField::PERCENT_CPU as RowField, width);
Row_updateFieldWidth(ProcessField::PERCENT_NORM_CPU as RowField, width);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn process_state_char_maps_every_state() {
assert_eq!(processStateChar(ProcessState::UNKNOWN), '?');
assert_eq!(processStateChar(ProcessState::RUNNABLE), 'U');
assert_eq!(processStateChar(ProcessState::RUNNING), 'R');
assert_eq!(processStateChar(ProcessState::QUEUED), 'Q');
assert_eq!(processStateChar(ProcessState::WAITING), 'W');
assert_eq!(processStateChar(ProcessState::UNINTERRUPTIBLE_WAIT), 'D');
assert_eq!(processStateChar(ProcessState::BLOCKED), 'B');
assert_eq!(processStateChar(ProcessState::PAGING), 'P');
assert_eq!(processStateChar(ProcessState::STOPPED), 'T');
assert_eq!(processStateChar(ProcessState::TRACED), 't');
assert_eq!(processStateChar(ProcessState::ZOMBIE), 'Z');
assert_eq!(processStateChar(ProcessState::DEFUNCT), 'X');
assert_eq!(processStateChar(ProcessState::IDLE), 'I');
assert_eq!(processStateChar(ProcessState::SLEEPING), 'S');
}
#[test]
fn process_state_discriminants_match_c() {
assert_eq!(ProcessState::UNKNOWN as u8, 1);
assert_eq!(ProcessState::SLEEPING as u8, 14);
}
#[test]
fn find_comm_exact_token_match() {
let cmdline = b"/usr/bin/bash\n--login";
assert_eq!(findCommInCmdline(b"bash", cmdline, 9), Some((9, 4)));
}
#[test]
fn find_comm_resets_basename_after_slash() {
let cmdline = b"/usr/bin/bash";
assert_eq!(findCommInCmdline(b"bash", cmdline, 0), Some((9, 4)));
}
#[test]
fn find_comm_no_match_returns_none() {
let cmdline = b"/usr/bin/zsh";
assert_eq!(findCommInCmdline(b"bash", cmdline, 0), None);
assert_eq!(findCommInCmdline(b"bash", b"", 0), None);
}
#[test]
fn find_comm_truncated_comm_allows_longer_token() {
let comm = b"012345678901234"; assert_eq!(comm.len(), TASK_COMM_LEN - 1);
let cmdline = b"0123456789012345678"; assert_eq!(findCommInCmdline(comm, cmdline, 0), Some((0, 19)));
assert_eq!(findCommInCmdline(b"0123", b"01234567", 0), None);
}
#[test]
fn find_comm_skips_consecutive_newlines() {
let cmdline = b"foo\n\n\nbar";
assert_eq!(findCommInCmdline(b"bar", cmdline, 0), Some((6, 3)));
}
#[test]
fn skip_potential_path_non_absolute_returns_zero() {
assert_eq!(skipPotentialPath(b"bash --login", 12), 0);
assert_eq!(skipPotentialPath(b"", 0), 0);
}
#[test]
fn skip_potential_path_returns_after_last_slash() {
let c = b"/usr/bin/bash";
assert_eq!(skipPotentialPath(c, c.len()), 9);
}
#[test]
fn skip_potential_path_stops_at_unescaped_space() {
let c = b"/usr/bin/bash --login";
assert_eq!(skipPotentialPath(c, c.len()), 9);
}
#[test]
fn skip_potential_path_escaped_space_does_not_stop() {
let c = b"/a/b\\ c/d";
assert_eq!(skipPotentialPath(c, c.len()), 8);
}
#[test]
fn skip_potential_path_stops_at_colon_space() {
let c = b"/usr/bin/foo: bar";
assert_eq!(skipPotentialPath(c, c.len()), 9);
}
#[test]
fn skip_potential_path_trailing_slash_not_counted() {
let c = b"/usr/bin/";
assert_eq!(skipPotentialPath(c, c.len()), 5);
}
#[test]
fn match_exe_absolute_path_full_match() {
let exe = b"/usr/bin/bash";
let cmdline = b"/usr/bin/bash --login";
let (matchLen, base) = matchCmdlinePrefixWithExeSuffix(cmdline, 9, exe, 9, 4);
assert_eq!(matchLen, 13); assert_eq!(base, 9); }
#[test]
fn match_exe_absolute_path_no_match() {
let exe = b"/usr/bin/bash";
let cmdline = b"/usr/bin/zsh";
let (matchLen, base) = matchCmdlinePrefixWithExeSuffix(cmdline, 9, exe, 9, 3);
assert_eq!(matchLen, 0);
assert_eq!(base, 9);
}
#[test]
fn match_exe_absolute_bad_delimiter() {
let exe = b"/usr/bin/bash";
let cmdline = b"/usr/bin/bashx";
let (matchLen, _) = matchCmdlinePrefixWithExeSuffix(cmdline, 9, exe, 9, 4);
assert_eq!(matchLen, 0);
}
#[test]
fn match_exe_relative_path_reverse_match() {
let exe = b"/usr/bin/bash";
let cmdline = b"bin/bash";
let (matchLen, base) = matchCmdlinePrefixWithExeSuffix(cmdline, 4, exe, 9, 4);
assert_eq!(matchLen, 8); assert_eq!(base, 4);
}
#[test]
fn match_exe_relative_no_match() {
let exe = b"/usr/bin/bash";
let cmdline = b"bin/zsh";
let (matchLen, base) = matchCmdlinePrefixWithExeSuffix(cmdline, 4, exe, 9, 3);
assert_eq!(matchLen, 0);
assert_eq!(base, 4); }
#[test]
fn tristate_discriminants_match_c() {
assert_eq!(Tristate::TRI_OFF as i8, -1);
assert_eq!(Tristate::TRI_INITIAL as i8, 0);
assert_eq!(Tristate::TRI_ON as i8, 1);
assert_eq!(Tristate::default(), Tristate::TRI_INITIAL);
}
#[test]
fn process_field_discriminants_match_c() {
assert_eq!(ProcessField::PID as i32, 1);
assert_eq!(ProcessField::COMM as i32, 2);
assert_eq!(ProcessField::ST_UID as i32, 46);
assert_eq!(ProcessField::PROC_COMM as i32, 124);
assert_eq!(ProcessField::CWD as i32, 126);
}
#[test]
fn process_default_state_is_unknown() {
let p = Process::default();
assert_eq!(p.state, ProcessState::UNKNOWN);
}
#[test]
fn process_init_runs_row_init_and_sets_defaults() {
let mut p = Process::default();
let host = 0xBEEF_usize as *const c_void;
Process_init(&mut p, host);
assert_eq!(p.st_uid, u32::MAX); assert_eq!(p.cmdlineBasenameEnd, 0);
assert_eq!(p.super_.host, host);
assert!(p.super_.show);
assert!(p.super_.showChildren);
}
#[test]
fn pid_parent_tgid_getters_and_setters() {
let mut p = Process::default();
Process_setPid(&mut p, 4321);
Process_setParent(&mut p, 1);
Process_setThreadGroup(&mut p, 4000);
assert_eq!(Process_getPid(&p), 4321);
assert_eq!(Process_getParent(&p), 1);
assert_eq!(Process_getThreadGroup(&p), 4000);
assert_eq!(p.super_.id, 4321);
assert_eq!(p.super_.parent, 1);
assert_eq!(p.super_.group, 4000);
}
#[test]
fn thread_predicates() {
let mut p = Process::default();
assert!(!Process_isKernelThread(&p));
assert!(!Process_isUserlandThread(&p));
assert!(!Process_isThread(&p));
p.isKernelThread = true;
assert!(Process_isKernelThread(&p));
assert!(Process_isThread(&p));
p.isKernelThread = false;
p.isUserlandThread = true;
assert!(Process_isUserlandThread(&p));
assert!(Process_isThread(&p)); }
fn proc() -> Process {
Process::default()
}
#[test]
fn compare_pid_numeric() {
let mut a = proc();
let mut b = proc();
Process_setPid(&mut a, 100);
Process_setPid(&mut b, 200);
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::PID as RowField),
-1
);
assert_eq!(
Process_compareByKey_Base(&b, &a, ProcessField::PID as RowField),
1
);
assert_eq!(
Process_compareByKey_Base(&a, &a, ProcessField::PID as RowField),
0
);
}
#[test]
fn compare_percent_cpu_float_and_nan() {
let mut a = proc();
let mut b = proc();
a.percent_cpu = 1.0;
b.percent_cpu = 2.0;
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::PERCENT_CPU as RowField),
-1
);
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::PERCENT_NORM_CPU as RowField),
-1
);
a.percent_cpu = f32::NAN;
b.percent_cpu = 1.0;
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::PERCENT_CPU as RowField),
-1
);
}
#[test]
fn compare_percent_mem_uses_resident_set() {
let mut a = proc();
let mut b = proc();
a.m_resident = 500;
b.m_resident = 1000;
a.percent_mem = 99.0; b.percent_mem = 1.0;
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::PERCENT_MEM as RowField),
-1
);
}
#[test]
fn compare_state_by_enum_order() {
let mut a = proc();
let mut b = proc();
a.state = ProcessState::RUNNING; b.state = ProcessState::SLEEPING; assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::STATE as RowField),
-1
);
assert_eq!(
Process_compareByKey_Base(&a, &a, ProcessField::STATE as RowField),
0
);
}
#[test]
fn compare_proc_comm_string() {
let mut a = proc();
let mut b = proc();
a.procComm = Some("alpha".to_string());
b.procComm = Some("beta".to_string());
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::PROC_COMM as RowField),
-1
);
}
#[test]
fn compare_proc_comm_kthread_fallback() {
let mut a = proc();
a.isKernelThread = true; let mut b = proc();
b.procComm = Some("aaa".to_string());
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::PROC_COMM as RowField),
-1
);
let c = proc(); assert_eq!(
Process_compareByKey_Base(&c, &b, ProcessField::PROC_COMM as RowField),
-1
);
}
#[test]
fn compare_starttime_tie_breaks_on_pid() {
let mut a = proc();
let mut b = proc();
a.starttime_ctime = 100;
b.starttime_ctime = 100;
Process_setPid(&mut a, 5);
Process_setPid(&mut b, 9);
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::STARTTIME as RowField),
-1
);
b.starttime_ctime = 200;
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::STARTTIME as RowField),
-1
);
}
#[test]
fn compare_elapsed_negates_starttime() {
let mut a = proc();
let mut b = proc();
a.starttime_ctime = 200; b.starttime_ctime = 100;
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::ELAPSED as RowField),
-1
);
b.starttime_ctime = 200;
Process_setPid(&mut a, 3);
Process_setPid(&mut b, 8);
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::ELAPSED as RowField),
-1
);
}
#[test]
fn compare_tty_orders_no_tty_last() {
let mut a = proc();
a.tty_name = Some("tty1".to_string());
let b = proc(); assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::TTY as RowField),
-1
);
let c = proc();
assert_eq!(
Process_compareByKey_Base(&b, &c, ProcessField::TTY as RowField),
0
);
}
#[test]
fn compare_user_nullstr() {
let mut a = proc();
let mut b = proc();
a.user = Some("alice".to_string());
b.user = Some("bob".to_string());
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::USER as RowField),
-1
);
let c = proc();
assert_eq!(
Process_compareByKey_Base(&c, &a, ProcessField::USER as RowField),
-1
);
}
#[test]
fn compare_default_arm_falls_back_to_pid() {
let mut a = proc();
let mut b = proc();
Process_setPid(&mut a, 1);
Process_setPid(&mut b, 2);
assert_eq!(
Process_compareByKey_Base(&a, &b, ProcessField::NULL_FIELD as RowField),
-1
);
}
#[test]
fn process_klass_chain_extends_row() {
let p = proc();
assert!(core::ptr::eq(p.klass(), &Process_class.super_.super_));
assert!(crate::ported::object::Object_isA(
Some(&p as &dyn Object),
&Process_class
));
assert!(crate::ported::object::Object_isA(
Some(&p as &dyn Object),
&Row_class
));
}
#[test]
fn update_exe_both_none_is_noop() {
let mut p = proc();
p.mergedCommand.lastUpdate = 7; Process_updateExe(&mut p, None);
assert_eq!(p.procExe, None);
assert_eq!(p.mergedCommand.lastUpdate, 7);
}
#[test]
fn update_exe_equal_string_is_noop() {
let mut p = proc();
p.procExe = Some("/usr/bin/htop".to_string());
p.procExeBasenameOffset = 999; p.mergedCommand.lastUpdate = 7;
Process_updateExe(&mut p, Some("/usr/bin/htop"));
assert_eq!(p.procExe.as_deref(), Some("/usr/bin/htop"));
assert_eq!(p.procExeBasenameOffset, 999);
assert_eq!(p.mergedCommand.lastUpdate, 7);
}
#[test]
fn update_exe_sets_basename_offset_past_last_slash() {
let mut p = proc();
p.mergedCommand.lastUpdate = 7;
Process_updateExe(&mut p, Some("/usr/bin/htop"));
assert_eq!(p.procExe.as_deref(), Some("/usr/bin/htop"));
assert_eq!(p.procExeBasenameOffset, 9);
assert_eq!(&"/usr/bin/htop"[p.procExeBasenameOffset..], "htop");
assert_eq!(p.mergedCommand.lastUpdate, 0); }
#[test]
fn update_exe_trailing_slash_yields_zero_offset() {
let mut p = proc();
Process_updateExe(&mut p, Some("/usr/bin/"));
assert_eq!(p.procExeBasenameOffset, 0);
}
#[test]
fn update_exe_leading_slash_only_yields_zero_offset() {
let mut p = proc();
Process_updateExe(&mut p, Some("/init"));
assert_eq!(p.procExeBasenameOffset, 0);
}
#[test]
fn update_exe_no_slash_yields_zero_offset() {
let mut p = proc();
Process_updateExe(&mut p, Some("bash"));
assert_eq!(p.procExe.as_deref(), Some("bash"));
assert_eq!(p.procExeBasenameOffset, 0);
}
#[test]
fn update_exe_clears_when_new_is_none() {
let mut p = proc();
p.procExe = Some("/usr/bin/htop".to_string());
p.procExeBasenameOffset = 9;
p.mergedCommand.lastUpdate = 7;
Process_updateExe(&mut p, None);
assert_eq!(p.procExe, None);
assert_eq!(p.procExeBasenameOffset, 0);
assert_eq!(p.mergedCommand.lastUpdate, 0);
}
#[test]
fn update_cmdline_both_none_is_noop() {
let mut p = proc();
p.mergedCommand.lastUpdate = 7;
Process_updateCmdline(&mut p, None, 0, 0);
assert_eq!(p.cmdline, None);
assert_eq!(p.mergedCommand.lastUpdate, 7);
}
#[test]
fn update_cmdline_equal_string_is_noop() {
let mut p = proc();
p.cmdline = Some("/bin/sh -c foo".to_string());
p.cmdlineBasenameStart = 5;
p.mergedCommand.lastUpdate = 7;
Process_updateCmdline(&mut p, Some("/bin/sh -c foo"), 5, 7);
assert_eq!(p.cmdlineBasenameStart, 5); assert_eq!(p.mergedCommand.lastUpdate, 7);
}
#[test]
fn update_cmdline_honours_explicit_basename_start() {
let mut p = proc();
Process_updateCmdline(&mut p, Some("/usr/bin/htop --tree"), 9, 13);
assert_eq!(p.cmdline.as_deref(), Some("/usr/bin/htop --tree"));
assert_eq!(p.cmdlineBasenameStart, 9);
assert_eq!(p.cmdlineBasenameEnd, 13);
assert_eq!(p.mergedCommand.lastUpdate, 0);
}
#[test]
fn update_cmdline_derives_basename_via_skip_potential_path() {
let mut p = proc();
Process_updateCmdline(&mut p, Some("/usr/bin/htop"), 0, 13);
assert_eq!(p.cmdlineBasenameStart, 9);
assert_eq!(p.cmdlineBasenameEnd, 13);
}
#[test]
fn update_cmdline_kernel_thread_has_no_basename() {
let mut p = proc();
p.isKernelThread = true;
Process_updateCmdline(&mut p, Some("[kworker/0:0]"), 1, 8);
assert_eq!(p.cmdlineBasenameStart, 0);
assert_eq!(p.cmdlineBasenameEnd, 0);
assert_eq!(p.cmdline.as_deref(), Some("[kworker/0:0]"));
}
#[test]
fn send_signal_zero_to_self_succeeds() {
let mut p = proc();
Process_setPid(&mut p, unsafe { libc::getpid() });
assert!(Process_sendSignal(&p, Arg::I(0)));
}
#[test]
#[should_panic]
fn send_signal_rejects_arg_v() {
let p = proc();
let _ = Process_sendSignal(&p, Arg::V(core::ptr::null_mut()));
}
#[test]
fn row_send_signal_delegates_through_object_isa() {
let mut p = proc();
Process_setPid(&mut p, unsafe { libc::getpid() });
assert!(Process_rowSendSignal(&mut p as &mut dyn Object, Arg::I(0)));
}
#[test]
#[should_panic]
fn row_send_signal_rejects_non_process() {
let mut row = crate::ported::row::Row::default();
let _ = Process_rowSendSignal(&mut row as &mut dyn Object, Arg::I(0));
}
#[test]
#[should_panic]
fn row_change_priority_rejects_non_process() {
let mut row = crate::ported::row::Row::default();
let _ = Process_rowChangePriorityBy(&mut row as &mut dyn Object, Arg::I(1));
}
#[test]
fn fill_starttime_buffer_formats_recent_start_as_hh_mm() {
let mut machine = Machine::default();
machine.realtimeMs = 1_700_000_000_000; let now_secs = (machine.realtimeMs / 1000) as i64;
let mut p = proc();
p.super_.host = &machine as *const Machine as *const c_void;
p.starttime_ctime = now_secs - 60;
Process_fillStarttimeBuffer(&mut p);
let end = p
.starttime_show
.iter()
.position(|&b| b == 0)
.unwrap_or(p.starttime_show.len());
let shown = std::str::from_utf8(&p.starttime_show[..end]).unwrap();
assert!(!shown.is_empty());
assert!(shown.contains(':'));
}
#[test]
fn fill_starttime_buffer_formats_old_start_as_year() {
let mut machine = Machine::default();
machine.realtimeMs = 1_700_000_000_000;
let now_secs = (machine.realtimeMs / 1000) as i64;
let mut p = proc();
p.super_.host = &machine as *const Machine as *const c_void;
p.starttime_ctime = now_secs - 2 * 365 * 86400;
Process_fillStarttimeBuffer(&mut p);
let end = p
.starttime_show
.iter()
.position(|&b| b == 0)
.unwrap_or(p.starttime_show.len());
let shown = std::str::from_utf8(&p.starttime_show[..end])
.unwrap()
.trim();
assert_eq!(shown.len(), 4);
assert!(shown.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn update_cmdline_clears_when_new_is_none() {
let mut p = proc();
p.cmdline = Some("/bin/sh".to_string());
p.mergedCommand.lastUpdate = 7;
Process_updateCmdline(&mut p, None, 0, 0);
assert_eq!(p.cmdline, None);
assert_eq!(p.cmdlineBasenameStart, 0);
assert_eq!(p.cmdlineBasenameEnd, 0);
assert_eq!(p.mergedCommand.lastUpdate, 0);
}
#[test]
fn write_command_merged_and_cmdline_basename() {
use crate::ported::settings::Settings;
let mut machine = Machine::default();
machine.settings = Some(Settings::default());
let mut p = Process::default();
p.super_.host = &machine as *const Machine as *const c_void;
p.mergedCommand.str = Some("foo bar".to_string());
p.mergedCommand.highlightCount = 0;
let mut rs = RichString::default();
Process_writeCommand(&p, 0, 0, &mut rs);
assert_eq!(RichString_size(&rs), 7);
let mut settings = Settings::default();
settings.highlightBaseName = true;
settings.showProgramPath = false;
machine.settings = Some(settings);
let mut p = Process::default();
p.super_.host = &machine as *const Machine as *const c_void;
p.cmdline = Some("/bin/ls".to_string());
p.cmdlineBasenameEnd = 7;
let mut rs = RichString::default();
Process_writeCommand(&p, 0, 0, &mut rs);
assert_eq!(RichString_size(&rs), 2);
}
#[test]
fn write_field_renders_representative_fields() {
use crate::ported::settings::Settings;
let mut machine = Machine::default();
machine.settings = Some(Settings::default());
let mut p = Process::default();
p.super_.host = &machine as *const Machine as *const c_void;
let render = |p: &Process, field: RowField| -> i32 {
let mut rs = RichString::default();
Process_writeField(p, &mut rs, field);
RichString_size(&rs)
};
p.nice = 0;
assert_eq!(render(&p, ProcessField::NICE as RowField), 4);
p.nice = PROCESS_NICE_UNKNOWN;
assert_eq!(render(&p, ProcessField::NICE as RowField), 4);
p.priority = -100;
assert_eq!(render(&p, ProcessField::PRIORITY as RowField), 4);
p.state = ProcessState::RUNNING;
assert_eq!(render(&p, ProcessField::STATE as RowField), 2);
}
#[test]
fn is_visible_gates_userland_threads() {
use crate::ported::settings::Settings;
let mut p = Process::default();
p.isUserlandThread = true;
let mut s = Settings::default();
s.hideUserlandThreads = false;
assert!(Process_isVisible(&p, &s));
s.hideUserlandThreads = true;
assert!(!Process_isVisible(&p, &s)); p.isUserlandThread = false;
assert!(Process_isVisible(&p, &s)); }
#[test]
fn get_command_picks_cmdline_or_merged() {
use crate::ported::settings::Settings;
let mut machine = Machine::default();
machine.settings = Some(Settings::default());
let mut p = Process::default();
p.super_.host = &machine as *const Machine as *const c_void;
p.cmdline = Some("cmd".to_string());
p.mergedCommand.str = Some("merged".to_string());
assert_eq!(Process_getCommand(&p), Some(b"merged".as_slice()));
p.mergedCommand.str = None;
assert_eq!(Process_getCommand(&p), Some(b"cmd".as_slice()));
let mut s = Settings::default();
s.showThreadNames = true;
machine.settings = Some(s);
p.mergedCommand.str = Some("merged".to_string());
p.isUserlandThread = true;
assert_eq!(Process_getCommand(&p), Some(b"cmd".as_slice()));
}
#[test]
fn make_command_str_fallback_cmdline() {
use crate::ported::settings::Settings;
let mk = |show_program_path: bool| -> Process {
let mut s = Settings::default();
s.showProgramPath = show_program_path;
s.lastUpdate = 1;
let mut p = Process::default();
p.state = ProcessState::RUNNING;
p.cmdline = Some("/usr/bin/foo bar".to_string());
p.cmdlineBasenameStart = 9;
p.cmdlineBasenameEnd = 12;
Process_makeCommandStr(&mut p, &s);
p
};
let p = mk(true);
assert_eq!(p.mergedCommand.str.as_deref(), Some("/usr/bin/foo bar"));
assert!(p.mergedCommand.highlightCount >= 1);
let hl = &p.mergedCommand.highlights[0];
assert_eq!(
(hl.offset, hl.length, hl.flags),
(9, 3, CMDLINE_HIGHLIGHT_FLAG_BASENAME)
);
let p = mk(false);
assert_eq!(p.mergedCommand.str.as_deref(), Some("foo bar"));
let hl = &p.mergedCommand.highlights[0];
assert_eq!(
(hl.offset, hl.length, hl.flags),
(0, 3, CMDLINE_HIGHLIGHT_FLAG_BASENAME)
);
}
#[test]
fn update_cpu_field_widths_floor_and_growth() {
Process_updateCPUFieldWidths(50.0);
let w = Row_fieldWidths[ProcessField::PERCENT_CPU as usize].load(Ordering::Relaxed);
assert!(w >= 4);
Process_updateCPUFieldWidths(999.9);
let w2 = Row_fieldWidths[ProcessField::PERCENT_CPU as usize].load(Ordering::Relaxed);
assert!(w2 >= 5);
}
}