1use std::{
21 error::Error,
22 ffi::{CStr, CString, OsString},
23 fmt,
24 mem::MaybeUninit,
25 path::{Path, PathBuf},
26};
27
28use bitflags::bitflags;
29use libcdio_sys::cdio_hwinfo_t;
30use thiserror::Error;
31
32use crate::{
33 cdio::{CDIO_INIT_LOCK, Cdio},
34 logging,
35};
36
37pub struct Drive {
39 pub(crate) cdio: Cdio,
40}
41
42impl Drive {
43 pub fn drives() -> Vec<PathBuf> {
45 logging::init_logger();
46
47 let _lock = CDIO_INIT_LOCK.lock().unwrap();
51 let drive_list =
52 unsafe { libcdio_sys::cdio_get_devices(libcdio_sys::driver_id_t_DRIVER_DEVICE) };
53 if drive_list.is_null() {
54 return vec![];
55 }
56
57 let mut drives = Vec::new();
58 let mut ptr = drive_list;
59
60 while !ptr.is_null()
62 && let drive = unsafe { *ptr }
63 && !drive.is_null()
64 {
65 drives.push(PathBuf::from(unsafe {
67 OsString::from_encoded_bytes_unchecked(CStr::from_ptr(drive).to_bytes().to_vec())
68 }));
69 ptr = unsafe { ptr.offset(1) };
70 }
71
72 unsafe {
74 libcdio_sys::cdio_free_device_list(drive_list);
75 }
76
77 drives
78 }
79
80 pub fn new() -> Result<Self, DriveNotFoundError> {
82 Cdio::with_device(None)
83 .ok_or(DriveNotFoundError)
84 .map(|cdio| Self { cdio })
85 }
86
87 pub fn with_drive(drive: PathBuf) -> Result<Self, DriveOpenError> {
91 let drive = CString::new(drive.into_os_string().into_encoded_bytes())
92 .map_err(|err| DriveOpenError::new(err.clone().into_vec(), err.into()))?;
93 let cdio = Cdio::with_device(Some(&drive)).ok_or_else(|| {
94 DriveOpenError::new(drive.into_bytes(), "cdio_open_am() returned NULL".into())
95 })?;
96
97 Ok(Self { cdio })
98 }
99
100 pub fn hardware_identifiers(&self) -> Result<HardwareIdentifiers, DriveOperationError> {
102 let mut hwinfo: MaybeUninit<cdio_hwinfo_t> = MaybeUninit::uninit();
103 let ret = unsafe { libcdio_sys::cdio_get_hwinfo(self.cdio.as_ptr(), hwinfo.as_mut_ptr()) };
104 if !ret {
105 return Err(DriveOperationError);
106 }
107
108 let hwinfo = unsafe { hwinfo.assume_init() };
110
111 unsafe {
113 let model = CStr::from_ptr(hwinfo.psz_model.as_ptr());
114 let vendor = CStr::from_ptr(hwinfo.psz_vendor.as_ptr());
115 let revision = CStr::from_ptr(hwinfo.psz_revision.as_ptr());
116
117 Ok(HardwareIdentifiers {
118 model: model.to_string_lossy().trim_end().to_string(),
119 vendor: vendor.to_string_lossy().trim_end().to_string(),
120 revision: revision.to_string_lossy().trim_end().to_string(),
121 })
122 }
123 }
124
125 pub fn capabilities(&self) -> Result<DriveCapabilities, DriveOperationError> {
127 let mut read = 0;
128 let mut write = 0;
129 let mut misc = 0;
130 unsafe {
131 libcdio_sys::cdio_get_drive_cap(self.cdio.as_ptr(), &mut read, &mut write, &mut misc);
132 }
133
134 (|| {
135 Some(DriveCapabilities {
136 read: ReadCapabilities::from_bits(read)?,
137 write: WriteCapabilities::from_bits(write)?,
138 misc: MiscCapabilities::from_bits(misc)?,
139 })
140 })()
141 .ok_or(DriveOperationError)
142 }
143}
144
145#[non_exhaustive]
146#[derive(Debug, Error)]
147#[error("could not find any drives")]
148pub struct DriveNotFoundError;
149
150#[derive(Debug, Error)]
151#[error(transparent)]
152pub struct DriveOpenError(Box<OpenErrRepr>);
153
154#[derive(Debug, Error)]
155#[error("could not open drive at `{path}`")]
156struct OpenErrRepr {
157 path: PathBuf,
158 source: Box<dyn Error + Send + Sync>,
159}
160
161impl DriveOpenError {
162 pub fn path(&self) -> &Path {
164 &self.0.path
165 }
166
167 fn new(path_bytes: Vec<u8>, source: Box<dyn Error + Send + Sync>) -> Self {
168 Self(Box::new(OpenErrRepr {
169 path: unsafe { OsString::from_encoded_bytes_unchecked(path_bytes) }.into(),
171 source,
172 }))
173 }
174}
175
176#[non_exhaustive]
177#[derive(Debug, Error)]
178#[error("could not perform operation on the drive")]
179pub struct DriveOperationError;
180
181#[derive(Clone, Debug, PartialEq, Eq)]
183pub struct HardwareIdentifiers {
184 pub model: String,
185 pub vendor: String,
186 pub revision: String,
187}
188
189#[derive(Clone, Copy, Debug)]
191pub struct DriveCapabilities {
192 pub read: ReadCapabilities,
193 pub write: WriteCapabilities,
194 pub misc: MiscCapabilities,
195}
196bitflags! {
198 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
200 pub struct MiscCapabilities: u32 {
201 const CloseTray = libcdio_sys::cdio_drive_cap_misc_t_CDIO_DRIVE_CAP_MISC_CLOSE_TRAY as _;
203 const Eject = libcdio_sys::cdio_drive_cap_misc_t_CDIO_DRIVE_CAP_MISC_EJECT as _;
205 const Lock = libcdio_sys::cdio_drive_cap_misc_t_CDIO_DRIVE_CAP_MISC_LOCK as _;
207 const SelectSpeed = libcdio_sys::cdio_drive_cap_misc_t_CDIO_DRIVE_CAP_MISC_SELECT_SPEED as _;
209 const SelectDisc = libcdio_sys::cdio_drive_cap_misc_t_CDIO_DRIVE_CAP_MISC_SELECT_DISC as _;
211 const MultiSession = libcdio_sys::cdio_drive_cap_misc_t_CDIO_DRIVE_CAP_MISC_MULTI_SESSION as _;
213 const MediaChanged = libcdio_sys::cdio_drive_cap_misc_t_CDIO_DRIVE_CAP_MISC_MEDIA_CHANGED as _;
215 const Reset = libcdio_sys::cdio_drive_cap_misc_t_CDIO_DRIVE_CAP_MISC_RESET as _;
217 }
218}
219bitflags! {
221 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
223 pub struct ReadCapabilities: u32 {
224 const Audio = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_AUDIO as _;
226 const CdDa = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_CD_DA as _;
228 const CdPlusG = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_CD_G as _;
230 const CdR = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_CD_R as _;
232 const CdRw = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_CD_RW as _;
234 const DvdR = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_DVD_R as _;
236 const DvdPlusR = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_DVD_PR as _;
238 const DvdRam = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_DVD_RAM as _;
240 const DvdRom = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_DVD_ROM as _;
242 const DvdRw = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_DVD_RW as _;
244 const DvdPlusRw = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_DVD_RPW as _;
246 const C2Errors = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_C2_ERRS as _;
248 const Mode2Form1 = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_MODE2_FORM1 as _;
250 const Mode2Form2 = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_MODE2_FORM2 as _;
252 const Mcn = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_MCN as _;
254 const Isrc = libcdio_sys::cdio_drive_cap_read_t_CDIO_DRIVE_CAP_READ_ISRC as _;
256 }
257}
258bitflags! {
260 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
262 pub struct WriteCapabilities: u32 {
263 const CdR = libcdio_sys::cdio_drive_cap_write_t_CDIO_DRIVE_CAP_WRITE_CD_R as _;
265 const CdRw = libcdio_sys::cdio_drive_cap_write_t_CDIO_DRIVE_CAP_WRITE_CD_RW as _;
267 const DvdR = libcdio_sys::cdio_drive_cap_write_t_CDIO_DRIVE_CAP_WRITE_DVD_R as _;
269 const DvdPlusR = libcdio_sys::cdio_drive_cap_write_t_CDIO_DRIVE_CAP_WRITE_DVD_PR as _;
271 const DvdRam = libcdio_sys::cdio_drive_cap_write_t_CDIO_DRIVE_CAP_WRITE_DVD_RAM as _;
273 const DvdRw = libcdio_sys::cdio_drive_cap_write_t_CDIO_DRIVE_CAP_WRITE_DVD_RW as _;
275 const DvdPlusRw = libcdio_sys::cdio_drive_cap_write_t_CDIO_DRIVE_CAP_WRITE_DVD_RPW as _;
277 const Mrw = libcdio_sys::cdio_drive_cap_write_t_CDIO_DRIVE_CAP_WRITE_MT_RAINIER as _;
279 const BurnProof = libcdio_sys::cdio_drive_cap_write_t_CDIO_DRIVE_CAP_WRITE_BURN_PROOF as _;
281 }
282}
283
284impl fmt::Display for MiscCapabilities {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 match *self {
287 MiscCapabilities::CloseTray => write!(f, "Close Tray"),
288 MiscCapabilities::Eject => write!(f, "Eject"),
289 MiscCapabilities::Lock => write!(f, "Lock"),
290 MiscCapabilities::SelectSpeed => write!(f, "Select Speed"),
291 MiscCapabilities::SelectDisc => write!(f, "Select Disc"),
292 MiscCapabilities::MultiSession => write!(f, "Multi Session"),
293 MiscCapabilities::MediaChanged => write!(f, "Media Change Detection"),
294 MiscCapabilities::Reset => write!(f, "Hard Reset"),
295 _ => write!(f, "Unknown"),
296 }
297 }
298}
299impl fmt::Display for ReadCapabilities {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 match *self {
302 ReadCapabilities::Audio => write!(f, "Play Audio"),
303 ReadCapabilities::CdDa => write!(f, "CD-DA"),
304 ReadCapabilities::CdPlusG => write!(f, "CD+G"),
305 ReadCapabilities::CdR => write!(f, "CD-R"),
306 ReadCapabilities::CdRw => write!(f, "CD-RW"),
307 ReadCapabilities::DvdR => write!(f, "DVD-R"),
308 ReadCapabilities::DvdPlusR => write!(f, "DVD+R"),
309 ReadCapabilities::DvdRam => write!(f, "DVD-RAM"),
310 ReadCapabilities::DvdRom => write!(f, "DVD-ROM"),
311 ReadCapabilities::DvdRw => write!(f, "DVD-RW"),
312 ReadCapabilities::DvdPlusRw => write!(f, "DVD+RW"),
313 ReadCapabilities::C2Errors => write!(f, "C2 Errors"),
314 ReadCapabilities::Mode2Form1 => write!(f, "Mode 2 Form 1 (VCD)"),
315 ReadCapabilities::Mode2Form2 => write!(f, "Mode 2 Form 2 (VCD)"),
316 ReadCapabilities::Mcn => write!(f, "MCN"),
317 ReadCapabilities::Isrc => write!(f, "ISRC"),
318 _ => write!(f, "Unknown"),
319 }
320 }
321}
322impl fmt::Display for WriteCapabilities {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 match *self {
325 WriteCapabilities::CdR => write!(f, "CD-R"),
326 WriteCapabilities::CdRw => write!(f, "CD-RW"),
327 WriteCapabilities::DvdR => write!(f, "DVD-R"),
328 WriteCapabilities::DvdPlusR => write!(f, "DVD+R"),
329 WriteCapabilities::DvdRam => write!(f, "DVD-RAM"),
330 WriteCapabilities::DvdRw => write!(f, "DVD-RW"),
331 WriteCapabilities::DvdPlusRw => write!(f, "DVD+RW"),
332 WriteCapabilities::Mrw => write!(f, "MRW"),
333 WriteCapabilities::BurnProof => write!(f, "Burn Proof"),
334 _ => write!(f, "Unknown"),
335 }
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 #[test]
343 #[ignore = "requires a disc drive"]
344 fn with_drive() {
345 Drive::with_drive(PathBuf::from("/dev/cdrom")).unwrap();
346 }
347
348 #[test]
349 #[ignore = "requires a disc drive"]
350 fn drives() {
351 assert!(!Drive::drives().is_empty());
352 }
353
354 #[test]
355 #[ignore = "requires a disc drive"]
356 fn hardware_identifiers() {
357 Drive::new().unwrap().hardware_identifiers().unwrap();
358 }
359
360 #[test]
361 #[ignore = "requires a disc drive"]
362 fn capabilities() {
363 Drive::new().unwrap().capabilities().unwrap();
364 }
365}