Skip to main content

linuxutils_system/
lsipc.rs

1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use cols::{Cols, print_table};
7use std::{
8    fs::{self, File},
9    io::{self, BufRead},
10    process::ExitCode,
11};
12
13/// List information on IPC facilities.
14///
15/// Modern replacement for ipcs. Reads /proc/sysvipc/* and
16/// /proc/sys/kernel/* to display IPC resource information
17/// in tabular format.
18#[derive(Parser)]
19#[command(name = "lsipc", about = "List information on IPC facilities")]
20pub struct Args {
21    /// List shared memory segments
22    #[arg(short = 'm', long)]
23    shmems: bool,
24
25    /// List message queues
26    #[arg(short = 'q', long)]
27    queues: bool,
28
29    /// List semaphore arrays
30    #[arg(short = 's', long)]
31    semaphores: bool,
32
33    /// Show system-wide usage and limits
34    #[arg(short = 'g', long)]
35    global: bool,
36
37    /// Suppress table headers
38    #[arg(long)]
39    noheadings: bool,
40
41    /// Show sizes in bytes
42    #[arg(short = 'b', long)]
43    bytes: bool,
44}
45
46fn read_val(path: &str) -> u64 {
47    fs::read_to_string(path)
48        .unwrap_or_default()
49        .trim()
50        .parse()
51        .unwrap_or(0)
52}
53
54fn count_entries(path: &str) -> u64 {
55    let Ok(file) = File::open(path) else {
56        return 0;
57    };
58    io::BufReader::new(file)
59        .lines()
60        .map_while(Result::ok)
61        .skip(1)
62        .count() as u64
63}
64
65#[derive(Cols)]
66struct GlobalRow {
67    #[column(header = "RESOURCE")]
68    resource: String,
69
70    #[column(header = "DESCRIPTION")]
71    description: String,
72
73    #[column(right, header = "LIMIT")]
74    limit: String,
75
76    #[column(right, header = "USED")]
77    used: String,
78
79    #[column(right, header = "USE%")]
80    use_pct: String,
81}
82
83#[derive(Cols)]
84struct ShmRow {
85    #[column(right, header = "KEY")]
86    key: String,
87
88    #[column(right, header = "ID")]
89    id: String,
90
91    #[column(header = "OWNER")]
92    owner: String,
93
94    #[column(right, header = "PERMS")]
95    perms: String,
96
97    #[column(right, header = "SIZE")]
98    size: String,
99
100    #[column(right, header = "NATTCH")]
101    nattch: String,
102}
103
104#[derive(Cols)]
105struct MsgRow {
106    #[column(right, header = "KEY")]
107    key: String,
108
109    #[column(right, header = "ID")]
110    id: String,
111
112    #[column(header = "OWNER")]
113    owner: String,
114
115    #[column(right, header = "PERMS")]
116    perms: String,
117
118    #[column(right, header = "USED-BYTES")]
119    used_bytes: String,
120
121    #[column(right, header = "MESSAGES")]
122    messages: String,
123}
124
125#[derive(Cols)]
126struct SemRow {
127    #[column(right, header = "KEY")]
128    key: String,
129
130    #[column(right, header = "ID")]
131    id: String,
132
133    #[column(header = "OWNER")]
134    owner: String,
135
136    #[column(right, header = "PERMS")]
137    perms: String,
138
139    #[column(right, header = "NSEMS")]
140    nsems: String,
141}
142
143fn uid_to_name(uid: u32) -> String {
144    fs::read_to_string("/etc/passwd")
145        .ok()
146        .and_then(|content| {
147            content.lines().find_map(|line| {
148                let parts: Vec<&str> = line.split(':').collect();
149                if parts.len() >= 3 && parts[2].parse::<u32>().ok() == Some(uid)
150                {
151                    Some(parts[0].to_string())
152                } else {
153                    None
154                }
155            })
156        })
157        .unwrap_or_else(|| uid.to_string())
158}
159
160fn pct(used: u64, limit: u64) -> String {
161    if limit == 0 {
162        "0.00%".to_string()
163    } else {
164        format!("{:.2}%", used as f64 / limit as f64 * 100.0)
165    }
166}
167
168fn show_global(noheadings: bool) {
169    let msgmni = read_val("/proc/sys/kernel/msgmni");
170    let msgmnb = read_val("/proc/sys/kernel/msgmnb");
171    let msgmax = read_val("/proc/sys/kernel/msgmax");
172    let shmmni = read_val("/proc/sys/kernel/shmmni");
173    let shmall = read_val("/proc/sys/kernel/shmall");
174    let shmmax = read_val("/proc/sys/kernel/shmmax");
175
176    let sem_str =
177        fs::read_to_string("/proc/sys/kernel/sem").unwrap_or_default();
178    let sem_parts: Vec<u64> = sem_str
179        .split_whitespace()
180        .filter_map(|s| s.parse().ok())
181        .collect();
182    let semmsl = sem_parts.first().copied().unwrap_or(0);
183    let semmns = sem_parts.get(1).copied().unwrap_or(0);
184    let semopm = sem_parts.get(2).copied().unwrap_or(0);
185    let semmni = sem_parts.get(3).copied().unwrap_or(0);
186
187    let msg_used = count_entries("/proc/sysvipc/msg");
188    let shm_used = count_entries("/proc/sysvipc/shm");
189    let sem_used = count_entries("/proc/sysvipc/sem");
190
191    let rows = vec![
192        GlobalRow {
193            resource: "MSGMNI".into(),
194            description: "Number of message queues".into(),
195            limit: msgmni.to_string(),
196            used: msg_used.to_string(),
197            use_pct: pct(msg_used, msgmni),
198        },
199        GlobalRow {
200            resource: "MSGMAX".into(),
201            description: "Max size of message (bytes)".into(),
202            limit: msgmax.to_string(),
203            used: "-".into(),
204            use_pct: "-".into(),
205        },
206        GlobalRow {
207            resource: "MSGMNB".into(),
208            description: "Default max size of queue (bytes)".into(),
209            limit: msgmnb.to_string(),
210            used: "-".into(),
211            use_pct: "-".into(),
212        },
213        GlobalRow {
214            resource: "SHMMNI".into(),
215            description: "Shared memory segments".into(),
216            limit: shmmni.to_string(),
217            used: shm_used.to_string(),
218            use_pct: pct(shm_used, shmmni),
219        },
220        GlobalRow {
221            resource: "SHMALL".into(),
222            description: "Shared memory pages".into(),
223            limit: shmall.to_string(),
224            used: "-".into(),
225            use_pct: "-".into(),
226        },
227        GlobalRow {
228            resource: "SHMMAX".into(),
229            description: "Max segment size (bytes)".into(),
230            limit: shmmax.to_string(),
231            used: "-".into(),
232            use_pct: "-".into(),
233        },
234        GlobalRow {
235            resource: "SEMMNI".into(),
236            description: "Number of semaphore identifiers".into(),
237            limit: semmni.to_string(),
238            used: sem_used.to_string(),
239            use_pct: pct(sem_used, semmni),
240        },
241        GlobalRow {
242            resource: "SEMMNS".into(),
243            description: "Total number of semaphores".into(),
244            limit: semmns.to_string(),
245            used: "-".into(),
246            use_pct: "-".into(),
247        },
248        GlobalRow {
249            resource: "SEMMSL".into(),
250            description: "Max semaphores per array".into(),
251            limit: semmsl.to_string(),
252            used: "-".into(),
253            use_pct: "-".into(),
254        },
255        GlobalRow {
256            resource: "SEMOPM".into(),
257            description: "Max ops per semop call".into(),
258            limit: semopm.to_string(),
259            used: "-".into(),
260            use_pct: "-".into(),
261        },
262    ];
263
264    let mut table = GlobalRow::to_table(&rows);
265    table.headings_set(!noheadings);
266    let _ = print_table(&table, &mut io::stdout().lock());
267}
268
269fn show_shm(noheadings: bool) {
270    let Ok(file) = File::open("/proc/sysvipc/shm") else {
271        return;
272    };
273    let mut rows = Vec::new();
274    for line in io::BufReader::new(file)
275        .lines()
276        .map_while(Result::ok)
277        .skip(1)
278    {
279        let f: Vec<&str> = line.split_whitespace().collect();
280        if f.len() >= 14 {
281            let uid: u32 = f[7].parse().unwrap_or(0);
282            rows.push(ShmRow {
283                key: format!("0x{:08x}", f[0].parse::<i64>().unwrap_or(0)),
284                id: f[1].to_string(),
285                owner: uid_to_name(uid),
286                perms: f[2].to_string(),
287                size: f[3].to_string(),
288                nattch: f[5].to_string(),
289            });
290        }
291    }
292    let mut table = ShmRow::to_table(&rows);
293    table.headings_set(!noheadings);
294    let _ = print_table(&table, &mut io::stdout().lock());
295}
296
297fn show_msg(noheadings: bool) {
298    let Ok(file) = File::open("/proc/sysvipc/msg") else {
299        return;
300    };
301    let mut rows = Vec::new();
302    for line in io::BufReader::new(file)
303        .lines()
304        .map_while(Result::ok)
305        .skip(1)
306    {
307        let f: Vec<&str> = line.split_whitespace().collect();
308        if f.len() >= 13 {
309            let uid: u32 = f[7].parse().unwrap_or(0);
310            rows.push(MsgRow {
311                key: format!("0x{:08x}", f[0].parse::<i64>().unwrap_or(0)),
312                id: f[1].to_string(),
313                owner: uid_to_name(uid),
314                perms: f[2].to_string(),
315                used_bytes: f[3].to_string(),
316                messages: f[4].to_string(),
317            });
318        }
319    }
320    let mut table = MsgRow::to_table(&rows);
321    table.headings_set(!noheadings);
322    let _ = print_table(&table, &mut io::stdout().lock());
323}
324
325fn show_sem(noheadings: bool) {
326    let Ok(file) = File::open("/proc/sysvipc/sem") else {
327        return;
328    };
329    let mut rows = Vec::new();
330    for line in io::BufReader::new(file)
331        .lines()
332        .map_while(Result::ok)
333        .skip(1)
334    {
335        let f: Vec<&str> = line.split_whitespace().collect();
336        if f.len() >= 9 {
337            let uid: u32 = f[4].parse().unwrap_or(0);
338            rows.push(SemRow {
339                key: format!("0x{:08x}", f[0].parse::<i64>().unwrap_or(0)),
340                id: f[1].to_string(),
341                owner: uid_to_name(uid),
342                perms: f[2].to_string(),
343                nsems: f[3].to_string(),
344            });
345        }
346    }
347    let mut table = SemRow::to_table(&rows);
348    table.headings_set(!noheadings);
349    let _ = print_table(&table, &mut io::stdout().lock());
350}
351
352pub fn run(args: Args) -> ExitCode {
353    let specific = args.shmems || args.queues || args.semaphores;
354
355    if args.global || !specific {
356        if !specific {
357            show_global(args.noheadings);
358            return ExitCode::SUCCESS;
359        }
360        show_global(args.noheadings);
361        return ExitCode::SUCCESS;
362    }
363
364    if args.shmems {
365        show_shm(args.noheadings);
366    }
367    if args.queues {
368        show_msg(args.noheadings);
369    }
370    if args.semaphores {
371        show_sem(args.noheadings);
372    }
373
374    ExitCode::SUCCESS
375}