use std::process::Child;
use std::time::{Duration, Instant};
pub mod stat {
pub const STOPPED: u32 = 1 << 0; pub const DONE: u32 = 1 << 1; pub const SUBJOB: u32 = 1 << 2; pub const CURSH: u32 = 1 << 3; pub const SUPERJOB: u32 = 1 << 4; pub const WASSUPER: u32 = 1 << 5; pub const INUSE: u32 = 1 << 6; pub const BUILTIN: u32 = 1 << 7; pub const DISOWN: u32 = 1 << 8; pub const NOTIFY: u32 = 1 << 9; pub const ATTACH: u32 = 1 << 10; }
pub const SP_RUNNING: i32 = -1;
pub const MAX_PIPESTATS: usize = 256;
#[derive(Clone, Debug, Default)]
pub struct TimeInfo {
pub user_time: Duration,
pub sys_time: Duration,
}
#[derive(Clone, Debug)]
pub struct Process {
pub pid: i32,
pub status: i32,
pub start_time: Option<Instant>,
pub end_time: Option<Instant>,
pub ti: TimeInfo,
pub text: String,
}
impl Process {
pub fn new(pid: i32) -> Self {
Process {
pid,
status: SP_RUNNING,
start_time: Some(Instant::now()),
end_time: None,
ti: TimeInfo::default(),
text: String::new(),
}
}
pub fn is_running(&self) -> bool {
self.status == SP_RUNNING
}
pub fn is_stopped(&self) -> bool {
self.status & 0xff == 0x7f
}
pub fn is_signaled(&self) -> bool {
(self.status & 0x7f) > 0 && (self.status & 0x7f) < 0x7f
}
pub fn exit_status(&self) -> i32 {
(self.status >> 8) & 0xff
}
pub fn term_sig(&self) -> i32 {
self.status & 0x7f
}
pub fn stop_sig(&self) -> i32 {
(self.status >> 8) & 0xff
}
}
#[derive(Clone, Debug)]
pub struct Job {
pub stat: u32,
pub gleader: i32, pub procs: Vec<Process>, pub auxprocs: Vec<Process>, pub other: usize, pub filelist: Vec<String>, pub text: String, }
impl Job {
pub fn new() -> Self {
Job {
stat: 0,
gleader: 0,
procs: Vec::new(),
auxprocs: Vec::new(),
other: 0,
filelist: Vec::new(),
text: String::new(),
}
}
pub fn is_done(&self) -> bool {
(self.stat & stat::DONE) != 0
}
pub fn is_stopped(&self) -> bool {
(self.stat & stat::STOPPED) != 0
}
pub fn is_superjob(&self) -> bool {
(self.stat & stat::SUPERJOB) != 0
}
pub fn is_subjob(&self) -> bool {
(self.stat & stat::SUBJOB) != 0
}
pub fn is_inuse(&self) -> bool {
(self.stat & stat::INUSE) != 0
}
pub fn has_procs(&self) -> bool {
!self.procs.is_empty() || !self.auxprocs.is_empty()
}
pub fn make_running(&mut self) {
self.stat &= !stat::STOPPED;
for proc in &mut self.procs {
if proc.is_stopped() {
proc.status = SP_RUNNING;
}
}
}
}
impl Default for Job {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct JobInfo {
pub id: usize,
pub pid: i32,
pub child: Option<Child>,
pub command: String,
pub state: JobState,
pub is_current: bool,
}
pub struct JobTable {
jobs: Vec<Option<JobInfo>>,
current_id: Option<usize>,
next_id: usize,
}
impl Default for JobTable {
fn default() -> Self {
Self::new()
}
}
impl JobTable {
pub fn new() -> Self {
JobTable {
jobs: Vec::with_capacity(16),
current_id: None,
next_id: 1,
}
}
pub fn add_job(&mut self, child: Child, command: String, state: JobState) -> usize {
let id = self.next_id;
self.next_id += 1;
let pid = child.id() as i32;
let job = JobInfo {
id,
pid,
child: Some(child),
command,
state,
is_current: true,
};
if let Some(cur_id) = self.current_id {
if let Some(j) = self.get_mut_internal(cur_id) {
j.is_current = false;
}
}
let slot = self.get_free_slot();
if slot >= self.jobs.len() {
self.jobs.resize_with(slot + 1, || None);
}
self.jobs[slot] = Some(job);
self.current_id = Some(id);
id
}
fn get_free_slot(&self) -> usize {
for (i, slot) in self.jobs.iter().enumerate() {
if slot.is_none() {
return i;
}
}
self.jobs.len()
}
fn get_mut_internal(&mut self, id: usize) -> Option<&mut JobInfo> {
for job in self.jobs.iter_mut().flatten() {
if job.id == id {
return Some(job);
}
}
None
}
pub fn get(&self, id: usize) -> Option<&JobInfo> {
for job in self.jobs.iter().flatten() {
if job.id == id {
return Some(job);
}
}
None
}
pub fn get_mut(&mut self, id: usize) -> Option<&mut JobInfo> {
self.get_mut_internal(id)
}
pub fn remove(&mut self, id: usize) -> Option<JobInfo> {
for slot in self.jobs.iter_mut() {
if slot.as_ref().map(|j| j.id == id).unwrap_or(false) {
let job = slot.take();
if self.current_id == Some(id) {
self.current_id = None;
}
return job;
}
}
None
}
pub fn list(&self) -> Vec<&JobInfo> {
self.jobs.iter().filter_map(|j| j.as_ref()).collect()
}
pub fn iter(&self) -> impl Iterator<Item = (usize, &JobInfo)> {
self.jobs
.iter()
.filter_map(|j| j.as_ref().map(|job| (job.id, job)))
}
pub fn count(&self) -> usize {
self.jobs.iter().filter(|j| j.is_some()).count()
}
pub fn is_empty(&self) -> bool {
self.count() == 0
}
pub fn current(&self) -> Option<&JobInfo> {
self.current_id.and_then(|id| self.get(id))
}
pub fn reap_finished(&mut self) -> Vec<JobInfo> {
let mut finished = Vec::new();
for slot in self.jobs.iter_mut() {
if let Some(job) = slot {
if let Some(ref mut child) = job.child {
match child.try_wait() {
Ok(Some(_status)) => {
job.state = JobState::Done;
}
Ok(None) => {
}
Err(_) => {
job.state = JobState::Done;
}
}
}
}
}
for slot in self.jobs.iter_mut() {
if slot
.as_ref()
.map(|j| j.state == JobState::Done)
.unwrap_or(false)
{
if let Some(job) = slot.take() {
finished.push(job);
}
}
}
finished
}
}
pub fn format_job(
job: &Job,
job_num: usize,
cur_job: Option<usize>,
prev_job: Option<usize>,
) -> String {
let marker = if Some(job_num) == cur_job {
'+'
} else if Some(job_num) == prev_job {
'-'
} else {
' '
};
let status = if job.is_done() {
"done"
} else if job.is_stopped() {
"suspended"
} else {
"running"
};
format!("[{}]{} {:10} {}", job_num, marker, status, job.text)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process_new() {
let proc = Process::new(1234);
assert_eq!(proc.pid, 1234);
assert!(proc.is_running());
}
#[test]
fn test_job_new() {
let job = Job::new();
assert_eq!(job.stat, 0);
assert!(!job.is_done());
assert!(!job.is_stopped());
}
#[test]
fn test_job_table_new() {
let table = JobTable::new();
assert!(table.is_empty());
}
#[test]
fn test_job_table_remove() {
}
#[test]
fn test_job_make_running() {
let mut job = Job::new();
job.stat |= stat::STOPPED;
job.procs.push(Process {
status: 0x007f,
..Process::new(1234)
});
job.make_running();
assert!(!job.is_stopped());
assert!(job.procs[0].is_running());
}
#[test]
fn test_format_job() {
let mut job = Job::new();
job.text = "vim file.txt".to_string();
job.stat |= stat::STOPPED;
let formatted = format_job(&job, 1, Some(1), None);
assert!(formatted.contains("[1]+"));
assert!(formatted.contains("suspended"));
assert!(formatted.contains("vim file.txt"));
}
#[test]
fn test_job_state_enum() {
let state = JobState::Running;
assert_eq!(state, JobState::Running);
assert_ne!(state, JobState::Stopped);
assert_ne!(state, JobState::Done);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum JobState {
Running,
Stopped,
Done,
}
#[derive(Debug)]
pub struct JobEntry {
pub pid: i32,
pub child: Option<Child>,
pub command: String,
pub state: JobState,
pub is_current: bool,
}
#[cfg(unix)]
pub fn send_signal(pid: i32, sig: nix::sys::signal::Signal) -> Result<(), String> {
use nix::sys::signal::kill;
use nix::unistd::Pid;
kill(Pid::from_raw(pid), sig).map_err(|e| e.to_string())
}
#[cfg(not(unix))]
pub fn send_signal(_pid: i32, _sig: i32) -> Result<(), String> {
Err("Signal sending not supported on this platform".to_string())
}
#[cfg(unix)]
pub fn continue_job(pid: i32) -> Result<(), String> {
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
kill(Pid::from_raw(pid), Signal::SIGCONT).map_err(|e| e.to_string())
}
#[cfg(not(unix))]
pub fn continue_job(_pid: i32) -> Result<(), String> {
Err("Job control not supported on this platform".to_string())
}
#[cfg(unix)]
pub fn wait_for_job(pid: i32) -> Result<i32, String> {
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::Pid;
loop {
match waitpid(Pid::from_raw(pid), None) {
Ok(WaitStatus::Exited(_, code)) => return Ok(code),
Ok(WaitStatus::Signaled(_, sig, _)) => return Ok(128 + sig as i32),
Ok(WaitStatus::Stopped(_, _)) => return Ok(128),
Ok(_) => continue,
Err(nix::errno::Errno::ECHILD) => return Ok(0),
Err(e) => return Err(e.to_string()),
}
}
}
#[cfg(not(unix))]
pub fn wait_for_job(_pid: i32) -> Result<i32, String> {
Err("Job waiting not supported on this platform".to_string())
}
pub fn wait_for_child(child: &mut Child) -> Result<i32, String> {
match child.wait() {
Ok(status) => Ok(status.code().unwrap_or(0)),
Err(e) => Err(e.to_string()),
}
}
pub fn get_clktck() -> i64 {
#[cfg(unix)]
{
use std::sync::OnceLock;
static CLKTCK: OnceLock<i64> = OnceLock::new();
*CLKTCK.get_or_init(|| unsafe { libc::sysconf(libc::_SC_CLK_TCK) as i64 })
}
#[cfg(not(unix))]
{
100 }
}
pub fn format_hhmmss(secs: f64) -> String {
let mins = (secs / 60.0) as i32;
let hours = mins / 60;
let secs = secs - (mins * 60) as f64;
let mins = mins - (hours * 60);
if hours > 0 {
format!("{}:{:02}:{:05.2}", hours, mins, secs)
} else if mins > 0 {
format!("{}:{:05.2}", mins, secs)
} else {
format!("{:.3}", secs)
}
}
pub fn format_time(
elapsed_secs: f64,
user_secs: f64,
system_secs: f64,
format: &str,
job_name: &str,
) -> String {
let mut result = String::new();
let total_time = user_secs + system_secs;
let percent = if elapsed_secs > 0.0 {
(100.0 * total_time / elapsed_secs) as i32
} else {
0
};
let mut chars = format.chars().peekable();
while let Some(c) = chars.next() {
if c == '%' {
match chars.next() {
Some('E') => result.push_str(&format!("{:.2}s", elapsed_secs)),
Some('U') => result.push_str(&format!("{:.2}s", user_secs)),
Some('S') => result.push_str(&format!("{:.2}s", system_secs)),
Some('P') => result.push_str(&format!("{}%", percent)),
Some('J') => result.push_str(job_name),
Some('m') => match chars.next() {
Some('E') => result.push_str(&format!("{:.0}ms", elapsed_secs * 1000.0)),
Some('U') => result.push_str(&format!("{:.0}ms", user_secs * 1000.0)),
Some('S') => result.push_str(&format!("{:.0}ms", system_secs * 1000.0)),
_ => result.push_str("%m"),
},
Some('u') => match chars.next() {
Some('E') => result.push_str(&format!("{:.0}us", elapsed_secs * 1_000_000.0)),
Some('U') => result.push_str(&format!("{:.0}us", user_secs * 1_000_000.0)),
Some('S') => result.push_str(&format!("{:.0}us", system_secs * 1_000_000.0)),
_ => result.push_str("%u"),
},
Some('n') => match chars.next() {
Some('E') => {
result.push_str(&format!("{:.0}ns", elapsed_secs * 1_000_000_000.0))
}
Some('U') => result.push_str(&format!("{:.0}ns", user_secs * 1_000_000_000.0)),
Some('S') => {
result.push_str(&format!("{:.0}ns", system_secs * 1_000_000_000.0))
}
_ => result.push_str("%n"),
},
Some('*') => match chars.next() {
Some('E') => result.push_str(&format_hhmmss(elapsed_secs)),
Some('U') => result.push_str(&format_hhmmss(user_secs)),
Some('S') => result.push_str(&format_hhmmss(system_secs)),
_ => result.push_str("%*"),
},
Some('%') => result.push('%'),
Some(other) => {
result.push('%');
result.push(other);
}
None => result.push('%'),
}
} else {
result.push(c);
}
}
result
}
pub const DEFAULT_TIMEFMT: &str = "%J %U user %S system %P cpu %*E total";
pub struct CommandTimer {
start: std::time::Instant,
job_name: String,
}
impl CommandTimer {
pub fn new(job_name: &str) -> Self {
CommandTimer {
start: std::time::Instant::now(),
job_name: job_name.to_string(),
}
}
pub fn elapsed(&self) -> Duration {
self.start.elapsed()
}
pub fn format(
&self,
user_time: Duration,
sys_time: Duration,
format_str: Option<&str>,
) -> String {
let elapsed = self.start.elapsed().as_secs_f64();
let user = user_time.as_secs_f64();
let sys = sys_time.as_secs_f64();
format_time(
elapsed,
user,
sys,
format_str.unwrap_or(DEFAULT_TIMEFMT),
&self.job_name,
)
}
}
pub struct PipeStats {
stats: Vec<i32>,
}
impl Default for PipeStats {
fn default() -> Self {
Self::new()
}
}
impl PipeStats {
pub fn new() -> Self {
PipeStats { stats: Vec::new() }
}
pub fn clear(&mut self) {
self.stats.clear();
}
pub fn add(&mut self, status: i32) {
if self.stats.len() < MAX_PIPESTATS {
self.stats.push(status);
}
}
pub fn get(&self) -> &[i32] {
&self.stats
}
pub fn len(&self) -> usize {
self.stats.len()
}
pub fn is_empty(&self) -> bool {
self.stats.is_empty()
}
pub fn pipefail_status(&self) -> i32 {
*self.stats.iter().rev().find(|&&s| s != 0).unwrap_or(&0)
}
}
pub fn sigmsg(sig: i32) -> &'static str {
match sig {
libc::SIGHUP => "hangup",
libc::SIGINT => "interrupt",
libc::SIGQUIT => "quit",
libc::SIGILL => "illegal instruction",
libc::SIGTRAP => "trace trap",
libc::SIGABRT => "abort",
libc::SIGBUS => "bus error",
libc::SIGFPE => "floating point exception",
libc::SIGKILL => "killed",
libc::SIGUSR1 => "user-defined signal 1",
libc::SIGSEGV => "segmentation fault",
libc::SIGUSR2 => "user-defined signal 2",
libc::SIGPIPE => "broken pipe",
libc::SIGALRM => "alarm",
libc::SIGTERM => "terminated",
libc::SIGCHLD => "child exited",
libc::SIGCONT => "continued",
libc::SIGSTOP => "stopped (signal)",
libc::SIGTSTP => "stopped",
libc::SIGTTIN => "stopped (tty input)",
libc::SIGTTOU => "stopped (tty output)",
libc::SIGURG => "urgent I/O condition",
libc::SIGXCPU => "CPU time exceeded",
libc::SIGXFSZ => "file size exceeded",
libc::SIGVTALRM => "virtual timer expired",
libc::SIGPROF => "profiling timer expired",
libc::SIGWINCH => "window changed",
libc::SIGIO => "I/O ready",
libc::SIGSYS => "bad system call",
_ => "unknown signal",
}
}
pub fn format_process_status(status: i32) -> String {
if status == SP_RUNNING {
"running".to_string()
} else if (status & 0x7f) == 0 {
let code = (status >> 8) & 0xff;
if code == 0 {
"done".to_string()
} else {
format!("exit {}", code)
}
} else if (status & 0xff) == 0x7f {
let sig = (status >> 8) & 0xff;
format!("suspended ({})", sigmsg(sig))
} else {
let sig = status & 0x7f;
let core = (status >> 7) & 1;
if core != 0 {
format!("{} (core dumped)", sigmsg(sig))
} else {
sigmsg(sig).to_string()
}
}
}
pub fn format_job_long(
job_num: usize,
current: bool,
pid: i32,
status: &str,
text: &str,
) -> String {
let marker = if current { '+' } else { '-' };
format!("[{}] {} {:>5} {} {}", job_num, marker, pid, status, text)
}
pub fn format_job_short(job_num: usize, current: bool, status: &str, text: &str) -> String {
let marker = if current { '+' } else { '-' };
format!("[{}] {} {} {}", job_num, marker, status, text)
}
pub struct BgStatus {
statuses: std::collections::HashMap<i32, i32>,
}
impl Default for BgStatus {
fn default() -> Self {
Self::new()
}
}
impl BgStatus {
pub fn new() -> Self {
BgStatus {
statuses: std::collections::HashMap::new(),
}
}
pub fn add(&mut self, pid: i32, status: i32) {
self.statuses.insert(pid, status);
}
pub fn get(&self, pid: i32) -> Option<i32> {
self.statuses.get(&pid).copied()
}
pub fn remove(&mut self, pid: i32) -> Option<i32> {
self.statuses.remove(&pid)
}
pub fn clear(&mut self) {
self.statuses.clear();
}
}
pub fn waitforpid(pid: i32) -> Option<i32> {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
loop {
let mut status: i32 = 0;
let result = unsafe { libc::waitpid(pid, &mut status, 0) };
if result == pid {
if libc::WIFEXITED(status) {
return Some(libc::WEXITSTATUS(status));
} else if libc::WIFSIGNALED(status) {
return Some(128 + libc::WTERMSIG(status));
} else if libc::WIFSTOPPED(status) {
return None;
}
} else if result == -1 {
return None;
}
}
}
#[cfg(not(unix))]
{
let _ = pid;
None
}
}
pub fn waitjob(job: &mut Job) -> Option<i32> {
if job.procs.is_empty() {
return Some(0);
}
let mut last_status = 0;
for proc in &mut job.procs {
if proc.is_running() {
if let Some(status) = waitforpid(proc.pid) {
proc.status = make_status(status);
last_status = status;
}
} else {
last_status = proc.exit_status();
}
}
job.stat |= stat::DONE;
Some(last_status)
}
pub fn make_status(code: i32) -> i32 {
code << 8
}
pub fn make_signal_status(sig: i32) -> i32 {
sig
}
pub fn havefiles(job: &Job) -> bool {
!job.filelist.is_empty()
}
pub fn deletejob(job: &mut Job, disowning: bool) {
if !disowning {
job.filelist.clear();
}
job.procs.clear();
job.auxprocs.clear();
job.stat = 0;
}
pub fn freejob(job: &mut Job, notify: bool) {
let _ = notify;
job.procs.clear();
job.auxprocs.clear();
job.filelist.clear();
job.stat = 0;
job.gleader = 0;
job.text.clear();
}
pub fn addproc(job: &mut Job, pid: i32, text: &str, aux: bool) {
let proc = Process::new(pid);
let proc = Process {
pid,
status: SP_RUNNING,
text: text.to_string(),
..proc
};
if aux {
job.auxprocs.push(proc);
} else {
if job.gleader == 0 {
job.gleader = pid;
}
job.procs.push(proc);
}
job.stat &= !stat::DONE;
}
pub fn killjob(job: &Job, sig: i32) -> bool {
#[cfg(unix)]
{
if job.gleader > 0 {
let result = unsafe { libc::killpg(job.gleader, sig) };
return result == 0;
}
let mut success = true;
for proc in &job.procs {
if proc.is_running() {
let result = unsafe { libc::kill(proc.pid, sig) };
if result != 0 {
success = false;
}
}
}
success
}
#[cfg(not(unix))]
{
let _ = (job, sig);
false
}
}
pub fn fg_job(job: &mut Job) -> Option<i32> {
#[cfg(unix)]
{
if (job.stat & stat::STOPPED) != 0 {
if job.gleader > 0 {
unsafe { libc::killpg(job.gleader, libc::SIGCONT) };
} else {
for proc in &job.procs {
unsafe { libc::kill(proc.pid, libc::SIGCONT) };
}
}
job.stat &= !stat::STOPPED;
}
waitjob(job)
}
#[cfg(not(unix))]
{
let _ = job;
None
}
}
pub fn bg_job(job: &mut Job) -> bool {
#[cfg(unix)]
{
if (job.stat & stat::STOPPED) != 0 {
if job.gleader > 0 {
unsafe { libc::killpg(job.gleader, libc::SIGCONT) };
} else {
for proc in &job.procs {
unsafe { libc::kill(proc.pid, libc::SIGCONT) };
}
}
job.stat &= !stat::STOPPED;
return true;
}
false
}
#[cfg(not(unix))]
{
let _ = job;
false
}
}
pub fn disown_job(job: &mut Job) {
job.stat |= stat::DISOWN;
}
pub fn job_is_done(job: &Job) -> bool {
(job.stat & stat::DONE) != 0 || job.procs.iter().all(|p| !p.is_running())
}
pub fn job_is_stopped(job: &Job) -> bool {
(job.stat & stat::STOPPED) != 0 || job.procs.iter().any(|p| p.is_stopped())
}
pub fn get_job_text(job: &Job) -> String {
if !job.text.is_empty() {
return job.text.clone();
}
job.procs
.iter()
.map(|p| p.text.as_str())
.collect::<Vec<_>>()
.join(" | ")
}
pub fn super_job(jobtab: &[Job], job_idx: usize) -> Option<usize> {
for (i, job) in jobtab.iter().enumerate() {
if (job.stat & stat::SUPERJOB) != 0 && job.other == job_idx {
return Some(i);
}
}
None
}
pub struct JobPointers {
pub cur_job: Option<usize>,
pub prev_job: Option<usize>,
}
impl JobPointers {
pub fn new() -> Self {
JobPointers {
cur_job: None,
prev_job: None,
}
}
pub fn set_current(&mut self, job: usize) {
if Some(job) != self.cur_job {
self.prev_job = self.cur_job;
self.cur_job = Some(job);
}
}
pub fn clear(&mut self, job: usize) {
if self.cur_job == Some(job) {
self.cur_job = self.prev_job;
self.prev_job = None;
} else if self.prev_job == Some(job) {
self.prev_job = None;
}
}
}
impl Default for JobPointers {
fn default() -> Self {
Self::new()
}
}
pub fn getjob(spec: &str, table: &JobTable, ptrs: &JobPointers) -> Option<usize> {
if spec.is_empty() {
return ptrs.cur_job;
}
let spec = if spec.starts_with('%') {
&spec[1..]
} else {
spec
};
match spec {
"+" | "%" | "" => ptrs.cur_job,
"-" => ptrs.prev_job,
_ => {
if let Ok(n) = spec.parse::<usize>() {
if table.get(n).is_some() {
return Some(n);
}
return None;
}
if let Some(substr) = spec.strip_prefix('?') {
for (id, job) in table.iter() {
if job.command.contains(substr) {
return Some(id);
}
}
return None;
}
for (id, job) in table.iter() {
if job.command.starts_with(spec) {
return Some(id);
}
}
None
}
}
}
pub fn findjobnam(name: &str, table: &JobTable) -> Option<usize> {
for (id, job) in table.iter() {
if job.command == name {
return Some(id);
}
}
None
}
pub fn isanum(s: &str) -> bool {
!s.is_empty() && s.chars().all(|c| c.is_ascii_digit())
}
pub fn init_jobs() -> (JobTable, JobPointers) {
(JobTable::new(), JobPointers::new())
}
#[cfg(unix)]
pub fn acquire_pgrp() -> bool {
unsafe {
let mypgrp = libc::getpgrp();
let tpgrp = libc::tcgetpgrp(0);
if tpgrp == -1 || tpgrp == mypgrp {
return true;
}
if libc::setpgid(0, 0) == 0 {
libc::tcsetpgrp(0, libc::getpgrp());
return true;
}
false
}
}
pub fn storepipestats(job: &Job) -> Vec<i32> {
job.procs.iter().map(|p| p.status).collect()
}
pub fn clearjobtab(table: &mut JobTable, ptrs: &mut JobPointers) {
table.jobs.clear();
table.next_id = 1;
ptrs.cur_job = None;
ptrs.prev_job = None;
}
pub fn scanjobs(table: &JobTable) -> Vec<String> {
let mut output = Vec::new();
for (id, job) in table.iter() {
let state_str = match job.state {
JobState::Running => "running",
JobState::Done => "done",
JobState::Stopped => "stopped",
_ => "unknown",
};
output.push(format!("[{}] {} {}", id, state_str, job.command));
}
output
}
#[derive(Debug, Clone, Default)]
pub struct ChildTimes {
pub user_sec: f64,
pub sys_sec: f64,
}
pub fn shelltime() -> ChildTimes {
#[cfg(unix)]
{
let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
if unsafe { libc::getrusage(libc::RUSAGE_SELF, &mut usage) } == 0 {
return ChildTimes {
user_sec: usage.ru_utime.tv_sec as f64
+ usage.ru_utime.tv_usec as f64 / 1_000_000.0,
sys_sec: usage.ru_stime.tv_sec as f64 + usage.ru_stime.tv_usec as f64 / 1_000_000.0,
};
}
}
ChildTimes::default()
}
pub fn childtime() -> ChildTimes {
#[cfg(unix)]
{
let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
if unsafe { libc::getrusage(libc::RUSAGE_CHILDREN, &mut usage) } == 0 {
return ChildTimes {
user_sec: usage.ru_utime.tv_sec as f64
+ usage.ru_utime.tv_usec as f64 / 1_000_000.0,
sys_sec: usage.ru_stime.tv_sec as f64 + usage.ru_stime.tv_usec as f64 / 1_000_000.0,
};
}
}
ChildTimes::default()
}
pub fn update_process(proc: &mut Process, status: i32) {
proc.end_time = Some(Instant::now());
proc.status = status;
}
pub fn findproc(jobtab: &[Job], pid: i32) -> Option<(usize, usize, bool)> {
for (ji, job) in jobtab.iter().enumerate() {
for (pi, proc) in job.procs.iter().enumerate() {
if proc.pid == pid {
return Some((ji, pi, false));
}
}
for (pi, proc) in job.auxprocs.iter().enumerate() {
if proc.pid == pid {
return Some((ji, pi, true));
}
}
}
None
}
pub fn update_job(job: &mut Job) -> bool {
for proc in &job.auxprocs {
if proc.is_running() {
return false;
}
}
let mut all_done = true;
let mut some_stopped = false;
let mut last_status = 0;
for proc in &job.procs {
if proc.is_running() {
return false; }
if proc.is_stopped() {
some_stopped = true;
}
}
if let Some(last) = job.procs.last() {
if last.is_signaled() {
last_status = 0x80 | last.term_sig();
} else if last.is_stopped() {
last_status = 0x80 | last.stop_sig();
} else {
last_status = last.exit_status();
}
}
if some_stopped {
job.stat |= stat::STOPPED;
job.stat &= !stat::DONE;
} else {
job.stat |= stat::DONE;
job.stat &= !stat::STOPPED;
}
true
}
pub fn update_bg_job(jobtab: &mut [Job], pid: i32, status: i32) -> bool {
if let Some((ji, pi, is_aux)) = findproc(jobtab, pid) {
if is_aux {
jobtab[ji].auxprocs[pi].status = status;
jobtab[ji].auxprocs[pi].end_time = Some(Instant::now());
} else {
jobtab[ji].procs[pi].status = status;
jobtab[ji].procs[pi].end_time = Some(Instant::now());
}
update_job(&mut jobtab[ji]);
return true;
}
false
}
pub fn handle_sub(jobtab: &mut [Job], super_idx: usize, fg: bool) {
let sub_idx = jobtab[super_idx].other;
if sub_idx >= jobtab.len() {
return;
}
if jobtab[sub_idx].is_done() {
if fg {
}
jobtab[super_idx].stat &= !stat::SUPERJOB;
jobtab[super_idx].stat |= stat::WASSUPER;
}
}
pub fn setprevjob(ptrs: &mut JobPointers, jobtab: &[Job], maxjob: usize) {
let mut best = None;
for i in (1..=maxjob).rev() {
if i >= jobtab.len() {
continue;
}
let job = &jobtab[i];
if (job.stat & stat::INUSE) != 0 && Some(i) != ptrs.cur_job {
if (job.stat & stat::STOPPED) != 0 {
best = Some(i);
break;
}
if best.is_none() {
best = Some(i);
}
}
}
ptrs.prev_job = best;
}
pub fn setcurjob(ptrs: &mut JobPointers, jobtab: &[Job], maxjob: usize) {
ptrs.cur_job = None;
for i in (1..=maxjob).rev() {
if i >= jobtab.len() {
continue;
}
if (jobtab[i].stat & (stat::INUSE | stat::STOPPED)) == (stat::INUSE | stat::STOPPED) {
ptrs.cur_job = Some(i);
break;
}
}
if ptrs.cur_job.is_none() {
for i in (1..=maxjob).rev() {
if i >= jobtab.len() {
continue;
}
if (jobtab[i].stat & stat::INUSE) != 0 {
ptrs.cur_job = Some(i);
break;
}
}
}
setprevjob(ptrs, jobtab, maxjob);
}
pub fn should_report_time(job: &Job, reporttime: f64) -> bool {
if reporttime < 0.0 {
return false;
}
if let Some(first) = job.procs.first() {
if let (Some(start), Some(end)) =
(first.start_time, job.procs.last().and_then(|p| p.end_time))
{
let elapsed = end.duration_since(start).as_secs_f64();
return elapsed >= reporttime;
}
}
false
}
pub fn dumptime(job: &Job, format: &str) -> Option<String> {
let first_start = job.procs.first()?.start_time?;
let last_end = job.procs.last()?.end_time?;
let elapsed = last_end.duration_since(first_start).as_secs_f64();
let mut total_user = 0.0;
let mut total_sys = 0.0;
for proc in &job.procs {
total_user += proc.ti.user_time.as_secs_f64();
total_sys += proc.ti.sys_time.as_secs_f64();
}
Some(format_time(
elapsed,
total_user,
total_sys,
format,
&get_job_text(job),
))
}
pub fn waitjobs(jobtab: &mut Vec<Job>, thisjob: usize) {
if thisjob < jobtab.len() {
while !jobtab[thisjob].is_done() && !jobtab[thisjob].is_stopped() {
#[cfg(unix)]
{
let mut status: i32 = 0;
let pid = unsafe { libc::waitpid(-1, &mut status, libc::WUNTRACED) };
if pid > 0 {
update_bg_job(jobtab, pid, status);
} else {
break;
}
}
#[cfg(not(unix))]
{
break;
}
}
}
}
pub fn waitonejob(job: &mut Job) {
for proc in &mut job.procs {
if proc.is_running() {
if let Some(_status) = waitforpid(proc.pid) {
}
}
}
}
pub fn initjob(jobtab: &mut Vec<Job>) -> usize {
for (i, job) in jobtab.iter().enumerate() {
if (job.stat & stat::INUSE) == 0 {
jobtab[i] = Job::new();
jobtab[i].stat = stat::INUSE;
return i;
}
}
let idx = jobtab.len();
let mut job = Job::new();
job.stat = stat::INUSE;
jobtab.push(job);
idx
}
pub fn setjobpwd(job: &mut Job) {
if let Ok(cwd) = std::env::current_dir() {
let _ = cwd;
}
}
pub fn spawnjob(job: &mut Job, fg: bool) {
job.stat |= stat::INUSE;
if !fg {
job.stat &= !stat::CURSH;
}
}
pub fn selectjobtab(jobtab: &[Job]) -> usize {
let mut max = 0;
for (i, job) in jobtab.iter().enumerate() {
if (job.stat & stat::INUSE) != 0 {
max = i;
}
}
max
}
pub fn expandjobtab(jobtab: &mut Vec<Job>, needed: usize) {
while jobtab.len() <= needed {
jobtab.push(Job::new());
}
}
pub fn maybeshrinkjobtab(jobtab: &mut Vec<Job>) {
while jobtab
.last()
.map(|j| (j.stat & stat::INUSE) == 0)
.unwrap_or(false)
{
jobtab.pop();
}
}
pub fn addfilelist(job: &mut Job, filename: &str) {
job.filelist.push(filename.to_string());
}
pub fn pipecleanfilelist(job: &mut Job, proc_subst_only: bool) {
if proc_subst_only {
job.filelist
.retain(|f| !f.starts_with("/dev/fd/") && !f.starts_with("/proc/"));
} else {
for file in &job.filelist {
let _ = std::fs::remove_file(file);
}
job.filelist.clear();
}
}
pub fn deletefilelist(job: &mut Job, disowning: bool) {
if !disowning {
for file in &job.filelist {
let _ = std::fs::remove_file(file);
}
}
job.filelist.clear();
}
pub fn printjob(
job: &Job,
job_num: usize,
long_format: bool,
cur_job: Option<usize>,
prev_job: Option<usize>,
) -> String {
let marker = if Some(job_num) == cur_job {
'+'
} else if Some(job_num) == prev_job {
'-'
} else {
' '
};
let status_str = if job.is_done() {
if let Some(last) = job.procs.last() {
format_process_status(last.status)
} else {
"done".to_string()
}
} else if job.is_stopped() {
"suspended".to_string()
} else {
"running".to_string()
};
if long_format {
let mut lines = Vec::new();
for (i, proc) in job.procs.iter().enumerate() {
let pstatus = format_process_status(proc.status);
if i == 0 {
lines.push(format!(
"[{}] {} {:>5} {:16} {}",
job_num, marker, proc.pid, pstatus, proc.text
));
} else {
lines.push(format!(
" {:>5} {:16} | {}",
proc.pid, pstatus, proc.text
));
}
}
lines.join("\n")
} else {
format!(
"[{}] {} {:16} {}",
job_num,
marker,
status_str,
get_job_text(job)
)
}
}
pub fn getsigname(sig: i32) -> String {
match sig {
0 => "EXIT".to_string(),
libc::SIGHUP => "HUP".to_string(),
libc::SIGINT => "INT".to_string(),
libc::SIGQUIT => "QUIT".to_string(),
libc::SIGILL => "ILL".to_string(),
libc::SIGTRAP => "TRAP".to_string(),
libc::SIGABRT => "ABRT".to_string(),
libc::SIGBUS => "BUS".to_string(),
libc::SIGFPE => "FPE".to_string(),
libc::SIGKILL => "KILL".to_string(),
libc::SIGUSR1 => "USR1".to_string(),
libc::SIGSEGV => "SEGV".to_string(),
libc::SIGUSR2 => "USR2".to_string(),
libc::SIGPIPE => "PIPE".to_string(),
libc::SIGALRM => "ALRM".to_string(),
libc::SIGTERM => "TERM".to_string(),
libc::SIGCHLD => "CHLD".to_string(),
libc::SIGCONT => "CONT".to_string(),
libc::SIGSTOP => "STOP".to_string(),
libc::SIGTSTP => "TSTP".to_string(),
libc::SIGTTIN => "TTIN".to_string(),
libc::SIGTTOU => "TTOU".to_string(),
libc::SIGURG => "URG".to_string(),
libc::SIGXCPU => "XCPU".to_string(),
libc::SIGXFSZ => "XFSZ".to_string(),
libc::SIGVTALRM => "VTALRM".to_string(),
libc::SIGPROF => "PROF".to_string(),
libc::SIGWINCH => "WINCH".to_string(),
libc::SIGIO => "IO".to_string(),
libc::SIGSYS => "SYS".to_string(),
_ => format!("SIG{}", sig),
}
}
pub fn dtime_tv(dt: &mut Duration, t1: &Duration, t2: &Duration) -> Duration {
if *t2 > *t1 {
*dt = *t2 - *t1;
} else {
*dt = Duration::ZERO;
}
*dt
}
pub fn dtime_ts(t1: &Instant, t2: &Instant) -> Duration {
if *t2 > *t1 {
t2.duration_since(*t1)
} else {
Duration::ZERO
}
}
pub fn makerunning(job: &mut Job) {
job.make_running();
}
pub fn hasprocs(job: &Job) -> bool {
job.has_procs()
}
pub fn get_usage() -> ChildTimes {
childtime()
}
#[cfg(unix)]
pub fn check_cursh_sig(jobtab: &[Job], sig: i32) {
for job in jobtab {
if (job.stat & stat::CURSH) != 0 && !job.is_done() {
for proc in &job.procs {
if proc.is_running() {
unsafe {
libc::kill(proc.pid, sig);
}
}
}
}
}
}
pub fn cleanfilelists(jobtab: &mut [Job]) {
for job in jobtab.iter_mut() {
deletefilelist(job, false);
}
}
pub fn clearoldjobtab(jobtab: &mut Vec<Job>) {
jobtab.retain(|j| (j.stat & stat::INUSE) != 0);
}
pub fn addbgstatus(bg: &mut BgStatus, pid: i32, status_val: i32) {
bg.add(pid, status_val);
}
pub fn getbgstatus(bg: &mut BgStatus, pid: i32) -> Option<i32> {
bg.remove(pid)
}
pub fn gettrapnode(sig: i32) -> Option<String> {
let _ = sig;
None
}
pub fn removetrapnode(sig: i32) {
let _ = sig;
}
#[cfg(unix)]
pub fn release_pgrp() {
}
pub fn getsigidx(name: &str) -> Option<i32> {
let name = name.strip_prefix("SIG").unwrap_or(name);
match name.to_uppercase().as_str() {
"EXIT" => Some(0),
"HUP" => Some(libc::SIGHUP),
"INT" => Some(libc::SIGINT),
"QUIT" => Some(libc::SIGQUIT),
"ILL" => Some(libc::SIGILL),
"TRAP" => Some(libc::SIGTRAP),
"ABRT" | "IOT" => Some(libc::SIGABRT),
"BUS" => Some(libc::SIGBUS),
"FPE" => Some(libc::SIGFPE),
"KILL" => Some(libc::SIGKILL),
"USR1" => Some(libc::SIGUSR1),
"SEGV" => Some(libc::SIGSEGV),
"USR2" => Some(libc::SIGUSR2),
"PIPE" => Some(libc::SIGPIPE),
"ALRM" => Some(libc::SIGALRM),
"TERM" => Some(libc::SIGTERM),
"CHLD" | "CLD" => Some(libc::SIGCHLD),
"CONT" => Some(libc::SIGCONT),
"STOP" => Some(libc::SIGSTOP),
"TSTP" => Some(libc::SIGTSTP),
"TTIN" => Some(libc::SIGTTIN),
"TTOU" => Some(libc::SIGTTOU),
"URG" => Some(libc::SIGURG),
"XCPU" => Some(libc::SIGXCPU),
"XFSZ" => Some(libc::SIGXFSZ),
"VTALRM" => Some(libc::SIGVTALRM),
"PROF" => Some(libc::SIGPROF),
"WINCH" => Some(libc::SIGWINCH),
"IO" | "POLL" => Some(libc::SIGIO),
"SYS" => Some(libc::SIGSYS),
_ => name.parse().ok(),
}
}