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#[derive(Parser)]
19#[command(name = "lsipc", about = "List information on IPC facilities")]
20pub struct Args {
21 #[arg(short = 'm', long)]
23 shmems: bool,
24
25 #[arg(short = 'q', long)]
27 queues: bool,
28
29 #[arg(short = 's', long)]
31 semaphores: bool,
32
33 #[arg(short = 'g', long)]
35 global: bool,
36
37 #[arg(long)]
39 noheadings: bool,
40
41 #[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}