1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use std::{
7 fs::{self, File},
8 io::{self, BufRead},
9 process::{Command, ExitCode},
10};
11
12const DEFAULT_DEVICE: &str = "/dev/cdrom";
13
14const CDROMEJECT: libc::c_ulong = 0x5309;
16const CDROMCLOSETRAY: libc::c_ulong = 0x5319;
17const CDROM_LOCKDOOR: libc::c_ulong = 0x5329;
18const CDROM_DRIVE_STATUS: libc::c_ulong = 0x5326;
19
20const CDS_TRAY_OPEN: i32 = 2;
22
23const FDEJECT: libc::c_ulong = 0x025a;
25
26const MTIOCTOP: libc::c_ulong = 0x4d01;
28const MTOFFL: i16 = 7;
29
30const SG_IO: libc::c_ulong = 0x2285;
32const SG_DXFER_NONE: i32 = -1;
33
34#[repr(C)]
35struct MtOp {
36 mt_op: i16,
37 mt_count: i32,
38}
39
40#[repr(C)]
41struct SgIoHdr {
42 interface_id: i32,
43 dxfer_direction: i32,
44 cmd_len: u8,
45 mx_sb_len: u8,
46 iovec_count: u16,
47 dxfer_len: u32,
48 dxferp: *mut libc::c_void,
49 cmdp: *const u8,
50 sbp: *mut u8,
51 timeout: u32,
52 flags: u32,
53 pack_id: i32,
54 usr_ptr: *mut libc::c_void,
55 status: u8,
56 masked_status: u8,
57 msg_status: u8,
58 sb_len_wr: u8,
59 host_status: u16,
60 driver_status: u16,
61 resid: i32,
62 duration: u32,
63 info: u32,
64}
65
66#[derive(Parser)]
67#[command(name = "eject", about = "Eject removable media")]
68pub struct Args {
69 #[arg(short = 'd', long = "default")]
71 default: bool,
72
73 #[arg(short = 'F', long)]
75 force: bool,
76
77 #[arg(short = 'f', long)]
79 floppy: bool,
80
81 #[arg(short = 'i', long = "manualeject")]
83 manualeject: Option<String>,
84
85 #[arg(short = 'M', long = "no-partitions-unmount")]
87 no_partitions_unmount: bool,
88
89 #[arg(short = 'm', long = "no-unmount")]
91 no_unmount: bool,
92
93 #[arg(short = 'n', long)]
95 noop: bool,
96
97 #[arg(short = 'q', long)]
99 tape: bool,
100
101 #[arg(short = 'r', long)]
103 cdrom: bool,
104
105 #[arg(short = 's', long)]
107 scsi: bool,
108
109 #[arg(short = 'T', long)]
111 traytoggle: bool,
112
113 #[arg(short = 't', long)]
115 trayclose: bool,
116
117 #[arg(short = 'v', long)]
119 verbose: bool,
120
121 device: Option<String>,
123}
124
125fn resolve_device(device: &str) -> String {
126 if let Ok(file) = File::open("/proc/mounts") {
128 for line in io::BufReader::new(file).lines().map_while(Result::ok) {
129 let fields: Vec<&str> = line.split_whitespace().collect();
130 if fields.len() >= 2 && fields[1] == device {
131 return fields[0].to_string();
132 }
133 }
134 }
135 fs::canonicalize(device)
137 .ok()
138 .and_then(|p| p.to_str().map(|s| s.to_string()))
139 .unwrap_or_else(|| device.to_string())
140}
141
142fn find_mounts(device: &str) -> Vec<String> {
143 let mut mounts = Vec::new();
144 let Ok(file) = File::open("/proc/mounts") else {
145 return mounts;
146 };
147 for line in io::BufReader::new(file).lines().map_while(Result::ok) {
148 let fields: Vec<&str> = line.split_whitespace().collect();
149 if fields.len() >= 2 && fields[0] == device {
150 mounts.push(fields[1].to_string());
151 }
152 }
153 mounts
154}
155
156fn unmount(mountpoint: &str, verbose: bool) -> io::Result<()> {
157 if verbose {
158 eprintln!("eject: unmounting {mountpoint}");
159 }
160 let status = Command::new("umount").arg(mountpoint).status()?;
161 if status.success() {
162 Ok(())
163 } else {
164 Err(io::Error::other(format!("umount {mountpoint} failed")))
165 }
166}
167
168fn eject_cdrom(fd: i32) -> io::Result<()> {
169 if unsafe { libc::ioctl(fd, CDROMEJECT) } < 0 {
170 Err(io::Error::last_os_error())
171 } else {
172 Ok(())
173 }
174}
175
176fn eject_scsi(fd: i32) -> io::Result<()> {
177 let unlock_cmd: [u8; 6] = [0x1e, 0, 0, 0, 0, 0];
179 let mut sense: [u8; 32] = [0; 32];
180
181 let mut hdr: SgIoHdr = unsafe { std::mem::zeroed() };
182 hdr.interface_id = b'S' as i32;
183 hdr.dxfer_direction = SG_DXFER_NONE;
184 hdr.cmd_len = 6;
185 hdr.mx_sb_len = sense.len() as u8;
186 hdr.cmdp = unlock_cmd.as_ptr();
187 hdr.sbp = sense.as_mut_ptr();
188 hdr.timeout = 10000;
189
190 if unsafe { libc::ioctl(fd, SG_IO, &mut hdr) } < 0 {
191 return Err(io::Error::last_os_error());
192 }
193
194 let eject_cmd: [u8; 6] = [0x1b, 0, 0, 0, 0x02, 0];
196 let mut sense2: [u8; 32] = [0; 32];
197
198 let mut hdr2: SgIoHdr = unsafe { std::mem::zeroed() };
199 hdr2.interface_id = b'S' as i32;
200 hdr2.dxfer_direction = SG_DXFER_NONE;
201 hdr2.cmd_len = 6;
202 hdr2.mx_sb_len = sense2.len() as u8;
203 hdr2.cmdp = eject_cmd.as_ptr();
204 hdr2.sbp = sense2.as_mut_ptr();
205 hdr2.timeout = 10000;
206
207 if unsafe { libc::ioctl(fd, SG_IO, &mut hdr2) } < 0 {
208 Err(io::Error::last_os_error())
209 } else {
210 Ok(())
211 }
212}
213
214fn eject_floppy(fd: i32) -> io::Result<()> {
215 if unsafe { libc::ioctl(fd, FDEJECT) } < 0 {
216 Err(io::Error::last_os_error())
217 } else {
218 Ok(())
219 }
220}
221
222fn eject_tape(fd: i32) -> io::Result<()> {
223 let op = MtOp {
224 mt_op: MTOFFL,
225 mt_count: 0,
226 };
227 if unsafe { libc::ioctl(fd, MTIOCTOP, &op) } < 0 {
228 Err(io::Error::last_os_error())
229 } else {
230 Ok(())
231 }
232}
233
234fn close_tray(fd: i32) -> io::Result<()> {
235 if unsafe { libc::ioctl(fd, CDROMCLOSETRAY) } < 0 {
236 Err(io::Error::last_os_error())
237 } else {
238 Ok(())
239 }
240}
241
242fn toggle_tray(fd: i32) -> io::Result<()> {
243 let status = unsafe { libc::ioctl(fd, CDROM_DRIVE_STATUS, 0) };
244 if status < 0 {
245 return Err(io::Error::last_os_error());
246 }
247 if status == CDS_TRAY_OPEN {
248 close_tray(fd)
249 } else {
250 eject_cdrom(fd)
251 }
252}
253
254fn lock_door(fd: i32, lock: bool) -> io::Result<()> {
255 let arg: i32 = if lock { 1 } else { 0 };
256 if unsafe { libc::ioctl(fd, CDROM_LOCKDOOR, arg) } < 0 {
257 Err(io::Error::last_os_error())
258 } else {
259 Ok(())
260 }
261}
262
263pub fn run(args: Args) -> ExitCode {
264 if args.default {
265 println!("{DEFAULT_DEVICE}");
266 return ExitCode::SUCCESS;
267 }
268
269 let device = args.device.as_deref().unwrap_or(DEFAULT_DEVICE);
270 let device = resolve_device(device);
271
272 if args.noop {
273 println!("{device}");
274 return ExitCode::SUCCESS;
275 }
276
277 if args.verbose {
278 eprintln!("eject: device is '{device}'");
279 }
280
281 if !args.no_unmount
283 && args.manualeject.is_none()
284 && !args.trayclose
285 && !args.traytoggle
286 {
287 let mounts = find_mounts(&device);
288 for mp in &mounts {
289 if let Err(e) = unmount(mp, args.verbose) {
290 eprintln!("eject: {e}");
291 return ExitCode::FAILURE;
292 }
293 }
294 }
295
296 let flags = if args.force {
298 libc::O_RDONLY | libc::O_NONBLOCK
299 } else {
300 libc::O_RDONLY | libc::O_NONBLOCK | libc::O_EXCL
301 };
302 let dev_cstr = match std::ffi::CString::new(device.as_str()) {
303 Ok(c) => c,
304 Err(_) => {
305 eprintln!("eject: invalid device path");
306 return ExitCode::FAILURE;
307 }
308 };
309 let fd = unsafe { libc::open(dev_cstr.as_ptr(), flags) };
310 if fd < 0 {
311 eprintln!(
312 "eject: failed to open '{device}': {}",
313 io::Error::last_os_error()
314 );
315 return ExitCode::FAILURE;
316 }
317
318 let result = if let Some(ref val) = args.manualeject {
319 match val.as_str() {
320 "on" | "1" => {
321 if args.verbose {
322 eprintln!("eject: locking door");
323 }
324 lock_door(fd, true)
325 }
326 "off" | "0" => {
327 if args.verbose {
328 eprintln!("eject: unlocking door");
329 }
330 lock_door(fd, false)
331 }
332 other => {
333 eprintln!(
334 "eject: invalid --manualeject value '{other}' (expected on|off)"
335 );
336 unsafe { libc::close(fd) };
337 return ExitCode::FAILURE;
338 }
339 }
340 } else if args.trayclose {
341 if args.verbose {
342 eprintln!("eject: closing tray");
343 }
344 close_tray(fd)
345 } else if args.traytoggle {
346 if args.verbose {
347 eprintln!("eject: toggling tray");
348 }
349 toggle_tray(fd)
350 } else {
351 let specific = args.cdrom || args.scsi || args.floppy || args.tape;
353
354 let mut ok = false;
355
356 if !ok && (args.cdrom || !specific) {
357 if args.verbose {
358 eprintln!("eject: trying CD-ROM eject");
359 }
360 if eject_cdrom(fd).is_ok() {
361 ok = true;
362 }
363 }
364 if !ok && (args.scsi || !specific) {
365 if args.verbose {
366 eprintln!("eject: trying SCSI eject");
367 }
368 if eject_scsi(fd).is_ok() {
369 ok = true;
370 }
371 }
372 if !ok && (args.floppy || !specific) {
373 if args.verbose {
374 eprintln!("eject: trying floppy eject");
375 }
376 if eject_floppy(fd).is_ok() {
377 ok = true;
378 }
379 }
380 if !ok && (args.tape || !specific) {
381 if args.verbose {
382 eprintln!("eject: trying tape eject");
383 }
384 if eject_tape(fd).is_ok() {
385 ok = true;
386 }
387 }
388
389 if ok {
390 Ok(())
391 } else {
392 Err(io::Error::other("all eject methods failed"))
393 }
394 };
395
396 unsafe { libc::close(fd) };
397
398 match result {
399 Ok(()) => ExitCode::SUCCESS,
400 Err(e) => {
401 eprintln!("eject: {device}: {e}");
402 ExitCode::FAILURE
403 }
404 }
405}