#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use core::ffi::c_int;
use std::fs::File;
use std::io::Read;
use std::os::unix::io::{AsRawFd, FromRawFd};
use crate::ported::crt::{CRT_disableDelay, CRT_enableDelay, KEY_F};
use crate::ported::functionbar::{FunctionBar_new, FunctionBar_setLabel, Ncurses};
use crate::ported::incset::IncSet_new;
use crate::ported::infoscreen::{
InfoScreen, InfoScreenClass, InfoScreen_addLine, InfoScreen_appendLine, InfoScreen_drawTitled,
InfoScreen_init,
};
use crate::ported::listitem::ListItem_new;
use crate::ported::object::{Object, ObjectClass};
use crate::ported::panel::{Panel_new, Panel_setSelected, Panel_size};
use crate::ported::process::{Process, Process_getCommand, Process_getPid};
use crate::ported::vector::Vector_new;
const VECTOR_DEFAULT_SIZE: c_int = 10;
const TraceScreenFunctions: [&str; 5] = [
"Search ",
"Filter ",
"AutoScroll ",
"Stop Tracing ",
"Done ",
];
const TraceScreenKeys: [&str; 5] = ["F3", "F4", "F8", "F9", "Esc"];
const TraceScreenEvents: [c_int; 5] = [KEY_F(3), KEY_F(4), KEY_F(8), KEY_F(9), 27];
pub struct TraceScreen {
pub super_: InfoScreen,
pub strace: Option<File>,
pub child: libc::pid_t,
pub tracing: bool,
pub contLine: bool,
pub follow: bool,
pub strace_alive: bool,
}
impl InfoScreenClass for TraceScreen {
fn super_InfoScreen(&mut self) -> &mut InfoScreen {
&mut self.super_
}
fn draw(&mut self) {
TraceScreen_draw(&mut self.super_);
}
fn onErr(&mut self) {
TraceScreen_updateTrace(self);
}
fn onKey(&mut self, ch: c_int) -> bool {
TraceScreen_onKey(self, ch)
}
fn has_onErr(&self) -> bool {
true
}
fn has_onKey(&self) -> bool {
true
}
}
pub fn TraceScreen_new(process: &Process) -> TraceScreen {
let list_item_class: &'static ObjectClass = ListItem_new("", 0).klass();
let mut this = TraceScreen {
super_: InfoScreen {
process: core::ptr::null(),
display: Panel_new(0, 0, 0, 0, None),
inc: IncSet_new(None),
lines: Vector_new(list_item_class, true, VECTOR_DEFAULT_SIZE),
},
strace: None,
child: 0,
tracing: true,
contLine: false,
follow: false,
strace_alive: false,
};
let fuBar = FunctionBar_new(
Some(&TraceScreenFunctions[..]),
Some(&TraceScreenKeys[..]),
Some(&TraceScreenEvents[..]),
);
CRT_disableDelay();
InfoScreen_init(
&mut this.super_,
process as *const Process,
Some(fuBar),
Ncurses::lines() - 2,
" ",
);
this
}
pub fn TraceScreen_delete(this: &mut TraceScreen) {
if this.child > 0 {
unsafe {
libc::kill(this.child, libc::SIGTERM);
let mut status: c_int = 0;
loop {
let ret = libc::waitpid(this.child, &mut status, 0);
if ret != -1 || std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR)
{
break;
}
}
}
}
this.strace = None;
CRT_enableDelay();
}
pub fn TraceScreen_draw(this: &mut InfoScreen) {
let pid = Process_getPid(unsafe { &*this.process });
let cmd = match Process_getCommand(unsafe { &*this.process }) {
Some(b) => String::from_utf8_lossy(b).into_owned(),
None => String::new(),
};
let title = format!("Trace of process {} - {}", pid, cmd);
InfoScreen_drawTitled(this, &title);
}
pub fn TraceScreen_forkTracer(this: &mut TraceScreen) -> bool {
let mut fdpair: [c_int; 2] = [-1, -1];
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "solaris"
))]
let argv_store: Vec<std::ffi::CString> = {
let pid = unsafe { crate::ported::process::Process_getPid(&*this.super_.process) };
let pid_c = std::ffi::CString::new(pid.to_string()).expect("pid string has no NUL");
#[cfg(target_os = "linux")]
{
vec![
std::ffi::CString::new("strace").unwrap(),
std::ffi::CString::new("-T").unwrap(),
std::ffi::CString::new("-tt").unwrap(),
std::ffi::CString::new("-s").unwrap(),
std::ffi::CString::new("512").unwrap(),
std::ffi::CString::new("-p").unwrap(),
pid_c,
]
}
#[cfg(not(target_os = "linux"))]
{
vec![
std::ffi::CString::new("truss").unwrap(),
std::ffi::CString::new("-s").unwrap(),
std::ffi::CString::new("512").unwrap(),
std::ffi::CString::new("-p").unwrap(),
pid_c,
]
}
};
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "solaris"
))]
let argv: Vec<*const core::ffi::c_char> = {
let mut v: Vec<*const core::ffi::c_char> = argv_store.iter().map(|c| c.as_ptr()).collect();
v.push(core::ptr::null());
v
};
unsafe {
if libc::pipe(fdpair.as_mut_ptr()) < 0 {
return false;
}
if libc::fcntl(fdpair[0], libc::F_SETFL, libc::O_NONBLOCK) < 0 {
libc::close(fdpair[1]);
libc::close(fdpair[0]);
return false;
}
if libc::fcntl(fdpair[1], libc::F_SETFL, libc::O_NONBLOCK) < 0 {
libc::close(fdpair[1]);
libc::close(fdpair[0]);
return false;
}
let child = libc::fork();
if child < 0 {
libc::close(fdpair[1]);
libc::close(fdpair[0]);
return false;
}
if child == 0 {
libc::close(fdpair[0]);
libc::dup2(fdpair[1], libc::STDOUT_FILENO);
libc::dup2(fdpair[1], libc::STDERR_FILENO);
libc::close(fdpair[1]);
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "solaris"
))]
{
libc::execvp(argv[0], argv.as_ptr());
let message: &[u8] =
b"Could not execute 'truss'. Please make sure it is available in your $PATH.";
let _ = libc::write(libc::STDERR_FILENO, message.as_ptr().cast(), message.len());
}
#[cfg(target_os = "linux")]
{
libc::execvp(argv[0], argv.as_ptr());
let message: &[u8] =
b"Could not execute 'strace'. Please make sure it is available in your $PATH.";
let _ = libc::write(libc::STDERR_FILENO, message.as_ptr().cast(), message.len());
}
#[cfg(not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "solaris"
)))]
{
let message: &[u8] = b"Tracing unavailable on not supported system.";
let _ = libc::write(libc::STDERR_FILENO, message.as_ptr().cast(), message.len());
}
libc::_exit(127);
}
this.child = child;
let fp = File::from_raw_fd(fdpair[0]);
libc::close(fdpair[1]);
this.strace = Some(fp);
this.strace_alive = true;
true
}
}
pub fn TraceScreen_updateTrace(this: &mut TraceScreen) {
let fd_strace = this.strace.as_ref().map_or(-1, |f| f.as_raw_fd());
let mut fds: libc::fd_set = unsafe { core::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fds);
libc::FD_SET(libc::STDIN_FILENO, &mut fds);
}
if this.strace_alive {
debug_assert!(fd_strace != -1);
unsafe {
libc::FD_SET(fd_strace, &mut fds);
}
}
let mut tv = libc::timeval {
tv_sec: 0,
tv_usec: 500,
};
let nfds = core::cmp::max(libc::STDIN_FILENO, fd_strace) + 1;
let ready = unsafe {
libc::select(
nfds,
&mut fds,
core::ptr::null_mut(),
core::ptr::null_mut(),
&mut tv,
)
};
let mut buffer = [0u8; 1025];
let mut nread: usize = 0;
if ready > 0 && unsafe { libc::FD_ISSET(fd_strace, &fds) } {
if let Some(f) = this.strace.as_mut() {
nread = f.read(&mut buffer[..1024]).unwrap_or(0);
}
}
if nread != 0 && this.tracing {
let mut line_start: usize = 0;
let mut i = 0usize;
while i < nread {
if buffer[i] == b'\n' {
let s = String::from_utf8_lossy(&buffer[line_start..i]).into_owned();
if this.contLine {
InfoScreen_appendLine(&mut this.super_, &s);
this.contLine = false;
} else {
InfoScreen_addLine(&mut this.super_, &s);
}
line_start = i + 1;
}
i += 1;
}
if line_start < nread {
let s = String::from_utf8_lossy(&buffer[line_start..nread]).into_owned();
InfoScreen_addLine(&mut this.super_, &s);
this.contLine = true;
}
if this.follow {
let sz = Panel_size(&this.super_.display);
Panel_setSelected(&mut this.super_.display, sz - 1);
}
} else {
if this.strace_alive {
let mut status: c_int = 0;
let ret = loop {
let r = unsafe { libc::waitpid(this.child, &mut status, libc::WNOHANG) };
if r != -1 || std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR) {
break r;
}
};
if ret != 0 {
this.strace_alive = false;
}
}
}
}
pub fn TraceScreen_onKey(this: &mut TraceScreen, ch: c_int) -> bool {
if ch == 'f' as c_int || ch == KEY_F(8) {
this.follow = !this.follow;
if this.follow {
let sz = Panel_size(&this.super_.display);
Panel_setSelected(&mut this.super_.display, sz - 1);
}
return true;
}
if ch == 't' as c_int || ch == KEY_F(9) {
this.tracing = !this.tracing;
let label = if this.tracing {
"Stop Tracing "
} else {
"Resume Tracing "
};
if let Some(bar) = this.super_.display.defaultBar.as_mut() {
FunctionBar_setLabel(bar, KEY_F(9), label);
}
TraceScreen_draw(&mut this.super_);
return true;
}
this.follow = false;
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::functionbar::Ncurses;
use crate::ported::incset::IncSet_filter;
use crate::ported::panel::{Panel_headerHeight, Panel_size};
use crate::ported::process::{Process, Process_setPid};
use crate::ported::vector::Vector_size;
#[test]
fn new_sets_tracer_defaults() {
let mut p = Process::default();
Process_setPid(&mut p, 1234);
let ts = TraceScreen_new(&p);
assert!(ts.tracing);
assert!(!ts.strace_alive);
assert!(!ts.contLine);
assert!(!ts.follow);
assert_eq!(ts.child, 0);
assert!(ts.strace.is_none());
}
#[test]
fn new_initializes_the_embedded_infoscreen() {
let mut p = Process::default();
Process_setPid(&mut p, 7);
let ts = TraceScreen_new(&p);
assert_eq!(ts.super_.process, &p as *const Process);
assert_eq!(Vector_size(&ts.super_.lines), 0);
assert_eq!(Panel_size(&ts.super_.display), 0);
assert_eq!(ts.super_.display.x, 0);
assert_eq!(ts.super_.display.y, 1);
assert_eq!(ts.super_.display.w, Ncurses::cols());
assert_eq!(ts.super_.display.h, Ncurses::lines() - 2);
assert_eq!(Panel_headerHeight(&ts.super_.display), 1);
assert!(IncSet_filter(&ts.super_.inc).is_none());
}
#[test]
fn new_builds_the_tracescreen_function_bar() {
let mut p = Process::default();
Process_setPid(&mut p, 9);
let ts = TraceScreen_new(&p);
let bar = ts
.super_
.display
.defaultBar
.as_ref()
.expect("default bar built");
assert_eq!(bar.functions, TraceScreenFunctions.to_vec());
assert_eq!(bar.keys, TraceScreenKeys.to_vec());
assert_eq!(bar.events, TraceScreenEvents.to_vec());
}
#[test]
fn tracescreen_events_match_keys() {
assert_eq!(
TraceScreenEvents,
[KEY_F(3), KEY_F(4), KEY_F(8), KEY_F(9), 27]
);
}
#[test]
fn delete_without_child_does_not_kill_or_panic() {
let mut p = Process::default();
Process_setPid(&mut p, 55);
let mut ts = TraceScreen_new(&p);
TraceScreen_delete(&mut ts);
assert_eq!(ts.child, 0);
assert!(ts.strace.is_none());
}
#[test]
fn onkey_f8_toggles_follow_and_consumes_key() {
let mut p = Process::default();
Process_setPid(&mut p, 101);
let mut ts = TraceScreen_new(&p);
assert!(!ts.follow);
assert!(TraceScreen_onKey(&mut ts, KEY_F(8)));
assert!(ts.follow);
assert!(TraceScreen_onKey(&mut ts, 'f' as c_int));
assert!(!ts.follow);
}
#[test]
fn onkey_unhandled_clears_follow_and_returns_false() {
let mut p = Process::default();
Process_setPid(&mut p, 202);
let mut ts = TraceScreen_new(&p);
assert!(TraceScreen_onKey(&mut ts, KEY_F(8)));
assert!(ts.follow);
assert!(!TraceScreen_onKey(&mut ts, 'z' as c_int));
assert!(!ts.follow);
}
#[test]
fn delete_closes_the_strace_handle() {
let mut p = Process::default();
Process_setPid(&mut p, 77);
let mut ts = TraceScreen_new(&p);
ts.strace = Some(File::open("/dev/null").expect("/dev/null opens"));
ts.child = 0; TraceScreen_delete(&mut ts);
assert!(ts.strace.is_none());
}
}