Skip to main content

linuxutils_system/
ipcrm.rs

1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use std::{
7    fs::File,
8    io::{self, BufRead},
9    process::ExitCode,
10};
11
12/// Remove System V IPC resources.
13///
14/// Uses shmctl(2), msgctl(2), or semctl(2) with IPC_RMID to remove
15/// shared memory segments, message queues, or semaphore arrays.
16#[derive(Parser)]
17#[command(name = "ipcrm", about = "Remove System V IPC resources")]
18pub struct Args {
19    /// Remove shared memory segment by ID
20    #[arg(short = 'm', long = "shmem-id", action = clap::ArgAction::Append)]
21    shmem_id: Vec<String>,
22
23    /// Remove shared memory segment by key
24    #[arg(short = 'M', long = "shmem-key", action = clap::ArgAction::Append)]
25    shmem_key: Vec<String>,
26
27    /// Remove message queue by ID
28    #[arg(short = 'q', long = "queue-id", action = clap::ArgAction::Append)]
29    queue_id: Vec<String>,
30
31    /// Remove message queue by key
32    #[arg(short = 'Q', long = "queue-key", action = clap::ArgAction::Append)]
33    queue_key: Vec<String>,
34
35    /// Remove semaphore array by ID
36    #[arg(short = 's', long = "semaphore-id", action = clap::ArgAction::Append)]
37    semaphore_id: Vec<String>,
38
39    /// Remove semaphore array by key
40    #[arg(short = 'S', long = "semaphore-key", action = clap::ArgAction::Append)]
41    semaphore_key: Vec<String>,
42
43    /// Remove all IPC resources (optionally: shm, msg, sem)
44    #[arg(short = 'a', long, num_args = 0..=1, default_missing_value = "all")]
45    all: Option<String>,
46
47    /// Verbose output
48    #[arg(short = 'v', long)]
49    verbose: bool,
50}
51
52fn parse_id(s: &str) -> Result<i32, String> {
53    if let Some(hex) = s.strip_prefix("0x") {
54        i32::from_str_radix(hex, 16).map_err(|_| format!("invalid id: {s}"))
55    } else if s.starts_with('0') && s.len() > 1 {
56        i32::from_str_radix(s, 8).map_err(|_| format!("invalid id: {s}"))
57    } else {
58        s.parse().map_err(|_| format!("invalid id: {s}"))
59    }
60}
61
62fn rm_shm(id: i32) -> io::Result<()> {
63    if unsafe { libc::shmctl(id, libc::IPC_RMID, std::ptr::null_mut()) } < 0 {
64        Err(io::Error::last_os_error())
65    } else {
66        Ok(())
67    }
68}
69
70fn rm_msg(id: i32) -> io::Result<()> {
71    if unsafe { libc::msgctl(id, libc::IPC_RMID, std::ptr::null_mut()) } < 0 {
72        Err(io::Error::last_os_error())
73    } else {
74        Ok(())
75    }
76}
77
78fn rm_sem(id: i32) -> io::Result<()> {
79    if unsafe { libc::semctl(id, 0, libc::IPC_RMID) } < 0 {
80        Err(io::Error::last_os_error())
81    } else {
82        Ok(())
83    }
84}
85
86fn key_to_shmid(key: i32) -> io::Result<i32> {
87    let id = unsafe { libc::shmget(key, 0, 0) };
88    if id < 0 {
89        Err(io::Error::last_os_error())
90    } else {
91        Ok(id)
92    }
93}
94
95fn key_to_msgid(key: i32) -> io::Result<i32> {
96    let id = unsafe { libc::msgget(key, 0) };
97    if id < 0 {
98        Err(io::Error::last_os_error())
99    } else {
100        Ok(id)
101    }
102}
103
104fn key_to_semid(key: i32) -> io::Result<i32> {
105    let id = unsafe { libc::semget(key, 0, 0) };
106    if id < 0 {
107        Err(io::Error::last_os_error())
108    } else {
109        Ok(id)
110    }
111}
112
113fn read_ids_from_proc(path: &str) -> Vec<i32> {
114    let Ok(file) = File::open(path) else {
115        return Vec::new();
116    };
117    io::BufReader::new(file)
118        .lines()
119        .map_while(Result::ok)
120        .skip(1)
121        .filter_map(|line| {
122            line.split_whitespace().nth(1).and_then(|s| s.parse().ok())
123        })
124        .collect()
125}
126
127fn try_remove(
128    kind: &str,
129    id: i32,
130    f: fn(i32) -> io::Result<()>,
131    verbose: bool,
132) -> bool {
133    if verbose {
134        eprintln!("ipcrm: removing {kind} id {id}");
135    }
136    if let Err(e) = f(id) {
137        eprintln!("ipcrm: {kind} id {id}: {e}");
138        return true;
139    }
140    false
141}
142
143pub fn run(args: Args) -> ExitCode {
144    let mut failed = false;
145
146    if let Some(ref filter) = args.all {
147        let do_shm = filter == "all" || filter == "shm";
148        let do_msg = filter == "all" || filter == "msg";
149        let do_sem = filter == "all" || filter == "sem";
150
151        if do_shm {
152            for id in read_ids_from_proc("/proc/sysvipc/shm") {
153                failed |= try_remove("shm", id, rm_shm, args.verbose);
154            }
155        }
156        if do_msg {
157            for id in read_ids_from_proc("/proc/sysvipc/msg") {
158                failed |= try_remove("msg", id, rm_msg, args.verbose);
159            }
160        }
161        if do_sem {
162            for id in read_ids_from_proc("/proc/sysvipc/sem") {
163                failed |= try_remove("sem", id, rm_sem, args.verbose);
164            }
165        }
166    }
167
168    for s in &args.shmem_id {
169        match parse_id(s) {
170            Ok(id) => failed |= try_remove("shm", id, rm_shm, args.verbose),
171            Err(e) => {
172                eprintln!("ipcrm: {e}");
173                failed = true;
174            }
175        }
176    }
177    for s in &args.shmem_key {
178        match parse_id(s)
179            .and_then(|k| key_to_shmid(k).map_err(|e| e.to_string()))
180        {
181            Ok(id) => failed |= try_remove("shm", id, rm_shm, args.verbose),
182            Err(e) => {
183                eprintln!("ipcrm: shm key {s}: {e}");
184                failed = true;
185            }
186        }
187    }
188    for s in &args.queue_id {
189        match parse_id(s) {
190            Ok(id) => failed |= try_remove("msg", id, rm_msg, args.verbose),
191            Err(e) => {
192                eprintln!("ipcrm: {e}");
193                failed = true;
194            }
195        }
196    }
197    for s in &args.queue_key {
198        match parse_id(s)
199            .and_then(|k| key_to_msgid(k).map_err(|e| e.to_string()))
200        {
201            Ok(id) => failed |= try_remove("msg", id, rm_msg, args.verbose),
202            Err(e) => {
203                eprintln!("ipcrm: msg key {s}: {e}");
204                failed = true;
205            }
206        }
207    }
208    for s in &args.semaphore_id {
209        match parse_id(s) {
210            Ok(id) => failed |= try_remove("sem", id, rm_sem, args.verbose),
211            Err(e) => {
212                eprintln!("ipcrm: {e}");
213                failed = true;
214            }
215        }
216    }
217    for s in &args.semaphore_key {
218        match parse_id(s)
219            .and_then(|k| key_to_semid(k).map_err(|e| e.to_string()))
220        {
221            Ok(id) => failed |= try_remove("sem", id, rm_sem, args.verbose),
222            Err(e) => {
223                eprintln!("ipcrm: sem key {s}: {e}");
224                failed = true;
225            }
226        }
227    }
228
229    if failed {
230        ExitCode::FAILURE
231    } else {
232        ExitCode::SUCCESS
233    }
234}