sig_bitmap/
lib.rs

1//! # Signal Bitmap Interpreter
2//
3//! A simple library to interpret signal bitmaps for a process, read
4//! from `/proc/<pid>/status`. Supported signal bitmaps include pending
5//! signals (`SigPnd`), shared pending signals (`ShdPnd`), blocked signals
6//! (`SigBlk`), ignored signals (`SigIgn`), and caught signals (`SigCgt`).
7#![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
17// Maximum dosplay column width.
18const MAX_WIDTH: usize = 80;
19
20// Subsequent column width (after header).
21const SUB_WIDTH: usize = 45;
22
23// Total number of signals.
24const NR_SIGS: u8 = 64;
25
26// Realtime signals (min and max).
27const SIGRTMIN_STR: &str = "RTMIN";
28const SIGRTMAX_STR: &str = "RTMAX";
29
30// Index of RT{MIN,MAX} signals (relative to the table).
31const SIGRTMIN_IDX: u8 = 0x22;
32const SIGRTMAX_IDX: u8 = 0x40;
33
34// A table of string representation of signals.
35static 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
42// Range values for signals.
43static 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/// The type of signal bitmap.
48#[derive(ValueEnum, Clone, Debug, Default)]
49pub enum BitmapType {
50    /// Pending signals (thread).
51    #[default]
52    SigPnd,
53
54    /// Pending signals (shared between threads in a process).
55    ShdPnd,
56
57    /// Blocked signals.
58    SigBlk,
59
60    /// Ignored signals.
61    SigIgn,
62
63    /// Caught signals.
64    SigCgt,
65}
66
67/// Interpret signal bitmaps for a process.
68#[derive(Parser, Debug)]
69#[command(version, about, long_about)]
70pub struct SigBitmapArgs {
71    /// PID of the process.
72    #[arg(short, long)]
73    pub pid: u32,
74
75    /// Type of bitmap to interpret.
76    #[arg(short, long, value_enum, default_value_t=BitmapType::SigPnd)]
77    pub map: BitmapType,
78}
79
80// String representation (line prefix in `/proc<pid>/status`)
81// of a signal bitmap type.
82impl 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
94// Return the string representation of a signal number.
95// This is specifically used for RT{MIN,MAX}+/-N.
96fn 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
104// Return a string describing the signal number
105// index passed in the argument `idx`.
106fn 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
122/// Returns a list of signals interpreted from the specified bitmap.
123/// # Arguments
124/// * `map` - Reference to an unsigned 64-bit integer holding
125///           the bitmap as its contents.
126///
127/// # Example
128/// ```
129/// use sig_bitmap::interpret;
130/// let bit_map: u64 = 0xdead;
131/// let sig_lst: Vec<String> = interpret(&bit_map);
132/// let sig_exp: Vec<&str> = vec![
133///     "HUP", "QUIT", "ILL", "ABRT", "FPE","USR1",
134///     "SEGV", "USR2", "PIPE", "TERM", "STKFLT",
135/// ];
136/// assert_eq!(sig_lst, sig_exp);
137/// ````
138pub 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
152// Return the parsed value of the string representation
153// of the signal bitmap.
154fn 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
175/// Displays the formatted string representaion of the specified
176/// type of signal bitmap for a given process. This function outputs
177/// an empty map if the process doesn't exist or if there is an error
178/// interpreting the signal bitmap.
179///
180/// # Arguments
181///
182/// * `args` - A reference to an `enum` containing the process
183///            ID (PID) and the signal bitmap type.
184/// # Returns
185///
186/// A `Vec<String>` containing a list of interpreted signals.
187///
188/// # Example
189/// ```
190/// // Print the list of signals ignored by a process with PID: 42.
191/// use sig_bitmap::{sig_bitmap, BitmapType, SigBitmapArgs};
192/// let args: SigBitmapArgs = SigBitmapArgs{pid: 42, map: BitmapType::SigIgn};
193/// sig_bitmap(&args);
194/// ````
195pub 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}