1#![warn(unused_extern_crates)]
8use clap::{Parser, ValueEnum};
9use std::{
10 cmp::Ordering,
11 fmt,
12 fs::File,
13 io::{BufRead, BufReader, Error},
14};
15use textwrap::{fill, Options};
16
17const MAX_WIDTH: usize = 80;
19
20const SUB_WIDTH: usize = 45;
22
23const NR_SIGS: u8 = 64;
25
26const SIGRTMIN_STR: &str = "RTMIN";
28const SIGRTMAX_STR: &str = "RTMAX";
29
30const SIGRTMIN_IDX: u8 = 0x22;
32const SIGRTMAX_IDX: u8 = 0x40;
33
34static SIG_TAB: &[&str; 32] = &[
36 "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1",
37 "SEGV", "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP",
38 "TSTP", "TTIN", "TTOU", "URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH",
39 "POLL", "IO", "PWR", "SYS",
40];
41
42static POSIX_RANGE: std::ops::Range<u8> = 0x01..0x20;
44static RTMIN_RANGE: std::ops::Range<u8> = 0x20..0x32;
45static RTMAX_RANGE: std::ops::Range<u8> = 0x32..0x41;
46
47#[derive(ValueEnum, Clone, Debug, Default)]
49pub enum BitmapType {
50 #[default]
52 SigPnd,
53
54 ShdPnd,
56
57 SigBlk,
59
60 SigIgn,
62
63 SigCgt,
65}
66
67#[derive(Parser, Debug)]
69#[command(version, about, long_about)]
70pub struct SigBitmapArgs {
71 #[arg(short, long)]
73 pub pid: u32,
74
75 #[arg(short, long, value_enum, default_value_t=BitmapType::SigPnd)]
77 pub map: BitmapType,
78}
79
80impl fmt::Display for BitmapType {
83 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84 match self {
85 BitmapType::SigPnd => write!(f, "SigPnd:"),
86 BitmapType::ShdPnd => write!(f, "ShdPnd:"),
87 BitmapType::SigBlk => write!(f, "SigBlk:"),
88 BitmapType::SigIgn => write!(f, "SigIgn:"),
89 BitmapType::SigCgt => write!(f, "SigCgt:"),
90 }
91 }
92}
93
94fn fmt_range(idx: &u8, off: &u8, tmpl: &str) -> String {
97 let diff: i8 = (*idx as i8) - (*off as i8);
98 match diff.cmp(&0) {
99 Ordering::Equal => tmpl.to_string(),
100 _ => format!("{}{:+}", tmpl, diff),
101 }
102}
103
104fn sigabbrev_np(idx: &u8) -> String {
107 if POSIX_RANGE.contains(idx) {
108 return SIG_TAB[(*idx as usize) - 1].to_string();
109 }
110
111 if RTMIN_RANGE.contains(idx) {
112 return fmt_range(idx, &SIGRTMIN_IDX, SIGRTMIN_STR);
113 }
114
115 if RTMAX_RANGE.contains(idx) {
116 return fmt_range(idx, &SIGRTMAX_IDX, SIGRTMAX_STR);
117 }
118
119 String::from("INVL")
120}
121
122pub fn interpret(map: &u64) -> Vec<String> {
139 let mut sig_idx: u8 = 0x1;
140 let mut sig_vec: Vec<String> = Vec::new();
141
142 while sig_idx < NR_SIGS {
143 if (map & (0x1_u64 << (sig_idx - 1))) != 0 {
144 sig_vec.push(sigabbrev_np(&sig_idx).to_string());
145 }
146 sig_idx += 1;
147 }
148
149 sig_vec
150}
151
152fn proc_bitmap(pid: &u32, typ: &BitmapType) -> u64 {
155 let lpfx: String = typ.to_string();
156 let file: Result<File, Error> =
157 File::open(format!("/proc/{}/status", pid).as_str());
158
159 if let Ok(fread) = file {
160 let fbuff: BufReader<File> = BufReader::new(fread);
161 for line in fbuff.lines().flatten() {
162 if line.starts_with(&lpfx) {
163 return u64::from_str_radix(
164 line.trim_start_matches(&lpfx).trim(),
165 16,
166 )
167 .unwrap();
168 }
169 }
170 }
171
172 0x0
173}
174
175pub fn sig_bitmap(args: &SigBitmapArgs) {
196 let bit_map: u64 = proc_bitmap(&args.pid, &args.map);
197 let sub_fmt: &str = &" ".repeat(SUB_WIDTH);
198 let sig_lst: Vec<String> = interpret(&bit_map);
199
200 let lst_fmt: String = match sig_lst.is_empty() {
201 true => String::from("NONE"),
202 false => sig_lst.join(", "),
203 };
204
205 let out: String = fill(
206 &format!(
207 "PID: {:<6} {} {:<2} [0x{:016x}]: {}",
208 args.pid,
209 args.map,
210 sig_lst.len(),
211 bit_map,
212 lst_fmt,
213 ),
214 Options::new(MAX_WIDTH)
215 .subsequent_indent(sub_fmt)
216 .word_splitter(textwrap::WordSplitter::NoHyphenation)
217 .break_words(false),
218 );
219
220 println!("{out}");
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_sigabbrev_np() {
229 let tests: Vec<(&str, u8)> = Vec::<(&str, u8)>::from([
230 ("KILL", 0x09),
231 ("RTMIN", 0x22),
232 ("RTMIN+2", 0x24),
233 ("RTMAX", 0x40),
234 ("RTMAX-2", 0x3e),
235 ("INVL", 0x00),
236 ]);
237
238 for test in tests {
239 assert_eq!(test.0, sigabbrev_np(&test.1));
240 }
241 }
242
243 #[test]
244 fn test_bit_map_type_str() {
245 let tests: Vec<(BitmapType, &str)> = Vec::<(BitmapType, &str)>::from([
246 (BitmapType::SigPnd, "SigPnd"),
247 (BitmapType::ShdPnd, "ShdPnd"),
248 (BitmapType::SigBlk, "SigBlk"),
249 (BitmapType::SigIgn, "SigIgn"),
250 (BitmapType::SigCgt, "SigCgt"),
251 ]);
252
253 for test in tests {
254 assert!(test.0.to_string().contains(test.1));
255 }
256 }
257 #[test]
258 fn test_interpret() {
259 let bit_map: u64 = 0xbadc0ffee;
260 let sig_chk: Vec<&str> = vec![
261 "INT", "QUIT", "ILL", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV",
262 "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "URG", "XCPU", "XFSZ",
263 "PROF", "WINCH", "IO", "RTMIN-2", "RTMIN-1", "RTMIN", "RTMIN+2",
264 ];
265 let sig_ret: Vec<String> = interpret(&bit_map);
266 assert_eq!(sig_ret, sig_chk);
267 }
268}