1pub mod capture;
10
11#[cfg(target_os = "linux")]
15pub(crate) mod linux;
16#[cfg(target_os = "macos")]
17pub(crate) mod macos;
18#[cfg(windows)]
19pub(crate) mod windows;
20
21use crate::error::{Error, Result};
22use crate::event::{Event, EventKind};
23use crate::identity::DriveId;
24use crate::platform::PlatformDriver;
25use crate::platform::mt1959::Mt1959;
26use crate::profile::{self, DriveProfile};
27use crate::scsi::ScsiTransport;
28use crate::sector::SectorReader;
29use std::path::Path;
30use std::sync::Arc;
31use std::sync::atomic::{AtomicBool, Ordering};
32
33#[derive(Debug, Clone, Copy, PartialEq)]
35pub enum DriveStatus {
36 TrayOpen,
38 NoDisc,
40 DiscPresent,
42 NotReady,
44 Unknown,
46}
47
48const SCSI_TEST_UNIT_READY: u8 = 0x00;
50const SCSI_START_STOP_UNIT: u8 = 0x1B;
51const SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL: u8 = 0x1E;
52const SCSI_GET_EVENT_STATUS: u8 = 0x4A;
53const SCSI_MODE_SENSE: u8 = 0x5A;
54const SCSI_REPORT_KEY: u8 = 0xA4;
55
56pub struct Drive {
58 scsi: Box<dyn ScsiTransport>,
59 driver: Option<Box<dyn PlatformDriver>>,
60 pub profile: Option<DriveProfile>,
61 pub platform: Option<profile::Platform>,
62 pub drive_id: DriveId,
63 device_path: String,
64 halt: Arc<AtomicBool>,
66 event_fn: Option<Box<dyn Fn(Event) + Send>>,
68}
69
70impl Drive {
71 pub fn open(device: &Path) -> Result<Self> {
72 let mut transport = crate::scsi::open(device)?;
73 let profiles = profile::load_bundled()?;
74 let drive_id = DriveId::from_drive(transport.as_mut())?;
75
76 let m = profile::find_by_drive_id(&profiles, &drive_id);
77 let (driver, platform, profile) = match m {
78 Some(m) => (
79 create_driver(m.platform, &m.profile).ok(),
80 Some(m.platform),
81 Some(m.profile),
82 ),
83 None => (None, None, None),
84 };
85
86 Ok(Drive {
87 scsi: transport,
88 driver,
89 platform,
90 profile,
91 drive_id,
92 device_path: device.to_string_lossy().to_string(),
93 halt: Arc::new(AtomicBool::new(false)),
94 event_fn: None,
95 })
96 }
97
98 pub fn halt_flag(&self) -> Arc<AtomicBool> {
100 self.halt.clone()
101 }
102
103 pub fn halt(&self) {
105 self.halt.store(true, Ordering::Relaxed);
106 }
107
108 pub fn clear_halt(&self) {
110 self.halt.store(false, Ordering::Relaxed);
111 }
112
113 pub fn on_event(&mut self, f: impl Fn(Event) + Send + 'static) {
115 self.event_fn = Some(Box::new(f));
116 }
117
118 #[allow(dead_code)] fn emit(&self, kind: EventKind) {
122 if let Some(ref f) = self.event_fn {
123 f(Event { kind });
124 }
125 }
126
127 fn is_halted(&self) -> bool {
128 self.halt.load(Ordering::Relaxed)
129 }
130
131 fn checked_exec(
136 &mut self,
137 cdb: &[u8],
138 dir: crate::scsi::DataDirection,
139 buf: &mut [u8],
140 timeout_ms: u32,
141 ) -> Result<crate::scsi::ScsiResult> {
142 if self.is_halted() {
143 return Err(Error::Halted);
144 }
145 let r = self.scsi.as_mut().execute(cdb, dir, buf, timeout_ms)?;
146 if self.is_halted() {
147 return Err(Error::Halted);
148 }
149 Ok(r)
150 }
151
152 pub fn close(self) {
155 }
157
158 fn cleanup(&mut self) {
160 self.unlock_tray();
161 }
162
163 pub fn device_path_owned(&self) -> String {
165 self.device_path.clone()
166 }
167
168 pub fn has_profile(&self) -> bool {
170 self.profile.is_some()
171 }
172
173 pub fn scsi_mut(&mut self) -> &mut dyn ScsiTransport {
175 self.scsi.as_mut()
176 }
177
178 pub fn wait_ready(&mut self) -> Result<()> {
179 let tur = [SCSI_TEST_UNIT_READY, 0x00, 0x00, 0x00, 0x00, 0x00];
180
181 for _ in 0..60 {
182 let mut buf = [0u8; 0];
183 if self
184 .scsi
185 .as_mut()
186 .execute(&tur, crate::scsi::DataDirection::None, &mut buf, 5_000)
187 .is_ok()
188 {
189 return Ok(());
190 }
191 std::thread::sleep(std::time::Duration::from_millis(500));
192 }
193 Err(Error::DeviceNotReady {
194 path: self.device_path.clone(),
195 })
196 }
197
198 pub fn drive_status(&mut self) -> DriveStatus {
201 let cdb = [
203 SCSI_GET_EVENT_STATUS,
204 0x01,
205 0x00,
206 0x00,
207 0x10,
208 0x00,
209 0x00,
210 0x00,
211 0x08,
212 0x00,
213 ];
214 let mut buf = [0u8; 8];
215 match self.scsi.as_mut().execute(
216 &cdb,
217 crate::scsi::DataDirection::FromDevice,
218 &mut buf,
219 5_000,
220 ) {
221 Ok(r) if r.bytes_transferred >= 6 => {
222 let media_status = buf[5];
223 match media_status & 0x03 {
226 0x00 => DriveStatus::NoDisc, 0x01 => DriveStatus::TrayOpen, 0x02 => DriveStatus::DiscPresent, 0x03 => DriveStatus::DiscPresent, _ => DriveStatus::Unknown,
231 }
232 }
233 _ => {
234 let tur = [SCSI_TEST_UNIT_READY, 0x00, 0x00, 0x00, 0x00, 0x00];
236 let mut empty = [0u8; 0];
237 match self.scsi.as_mut().execute(
238 &tur,
239 crate::scsi::DataDirection::None,
240 &mut empty,
241 5_000,
242 ) {
243 Ok(_) => DriveStatus::DiscPresent,
244 Err(Error::ScsiError { sense_key: 2, .. }) => DriveStatus::NotReady,
245 Err(Error::ScsiError { sense_key: 6, .. }) => DriveStatus::NotReady, _ => DriveStatus::Unknown,
247 }
248 }
249 }
250 }
251
252 pub fn platform_name(&self) -> &str {
253 match self.platform {
254 Some(ref p) => p.name(),
255 None => "Unknown",
256 }
257 }
258
259 pub fn device_path(&self) -> &str {
260 &self.device_path
261 }
262
263 pub fn init(&mut self) -> Result<()> {
266 match self.driver {
267 Some(ref mut d) => d.init(self.scsi.as_mut()),
268 None => Err(Error::UnsupportedDrive {
269 vendor_id: self.drive_id.vendor_id.trim().to_string(),
270 product_id: self.drive_id.product_id.trim().to_string(),
271 product_revision: self.drive_id.product_revision.trim().to_string(),
272 }),
273 }
274 }
275
276 pub fn probe_disc(&mut self) -> Result<()> {
280 match self.driver {
281 Some(ref mut d) => d.probe_disc(self.scsi.as_mut()),
282 None => Err(Error::UnsupportedDrive {
283 vendor_id: self.drive_id.vendor_id.trim().to_string(),
284 product_id: self.drive_id.product_id.trim().to_string(),
285 product_revision: self.drive_id.product_revision.trim().to_string(),
286 }),
287 }
288 }
289
290 pub fn get_config_feature(&mut self, feature_code: u16) -> Option<Vec<u8>> {
293 let cdb = [
294 crate::scsi::SCSI_GET_CONFIGURATION,
295 0x02,
296 (feature_code >> 8) as u8,
297 feature_code as u8,
298 0x00,
299 0x00,
300 0x00,
301 0x01,
302 0x00,
303 0x00,
304 ];
305 let mut buf = vec![0u8; 256];
306 let r = self
307 .scsi
308 .as_mut()
309 .execute(
310 &cdb,
311 crate::scsi::DataDirection::FromDevice,
312 &mut buf,
313 5_000,
314 )
315 .ok()?;
316 if r.bytes_transferred > 8 {
317 Some(buf[8..r.bytes_transferred].to_vec())
318 } else {
319 None
320 }
321 }
322
323 pub fn report_key_rpc_state(&mut self) -> Option<Vec<u8>> {
325 let cdb = [
326 SCSI_REPORT_KEY,
327 0x00,
328 0x00,
329 0x00,
330 0x00,
331 0x00,
332 0x00,
333 0x00,
334 0x00,
335 0x08,
336 0x08,
337 0x00,
338 ];
339 let mut buf = vec![0u8; 8];
340 let r = self
341 .scsi
342 .as_mut()
343 .execute(
344 &cdb,
345 crate::scsi::DataDirection::FromDevice,
346 &mut buf,
347 5_000,
348 )
349 .ok()?;
350 if r.bytes_transferred > 0 {
351 Some(buf[..r.bytes_transferred].to_vec())
352 } else {
353 None
354 }
355 }
356
357 pub fn mode_sense_page(&mut self, page: u8) -> Option<Vec<u8>> {
359 let cdb = [
360 SCSI_MODE_SENSE,
361 0x00,
362 page,
363 0x00,
364 0x00,
365 0x00,
366 0x00,
367 0x00,
368 0xFC,
369 0x00,
370 ];
371 let mut buf = vec![0u8; 252];
372 let r = self
373 .scsi
374 .as_mut()
375 .execute(
376 &cdb,
377 crate::scsi::DataDirection::FromDevice,
378 &mut buf,
379 5_000,
380 )
381 .ok()?;
382 if r.bytes_transferred > 0 {
383 Some(buf[..r.bytes_transferred].to_vec())
384 } else {
385 None
386 }
387 }
388
389 pub fn read_buffer(&mut self, mode: u8, buffer_id: u8, length: u16) -> Option<Vec<u8>> {
391 let cdb = crate::scsi::build_read_buffer(mode, buffer_id, 0, length as u32);
392 let mut buf = vec![0u8; length as usize];
393 let r = self
394 .scsi
395 .as_mut()
396 .execute(
397 &cdb,
398 crate::scsi::DataDirection::FromDevice,
399 &mut buf,
400 5_000,
401 )
402 .ok()?;
403 if r.bytes_transferred > 0 {
404 Some(buf[..r.bytes_transferred].to_vec())
405 } else {
406 None
407 }
408 }
409
410 pub fn is_ready(&self) -> bool {
411 match self.driver {
412 Some(ref d) => d.is_ready(),
413 None => false,
414 }
415 }
416
417 pub fn read(&mut self, lba: u32, count: u16, buf: &mut [u8], recovery: bool) -> Result<usize> {
435 let timeout_ms = if recovery { 30_000 } else { 1_500 };
436 let cdb = [
437 crate::scsi::SCSI_READ_10,
438 0x00,
439 (lba >> 24) as u8,
440 (lba >> 16) as u8,
441 (lba >> 8) as u8,
442 lba as u8,
443 0x00,
444 (count >> 8) as u8,
445 count as u8,
446 0x00,
447 ];
448
449 match self.checked_exec(
450 &cdb,
451 crate::scsi::DataDirection::FromDevice,
452 buf,
453 timeout_ms,
454 ) {
455 Ok(result) => Ok(result.bytes_transferred),
456 Err(Error::Halted) => Err(Error::Halted),
457 Err(_) => Err(Error::DiscRead { sector: lba as u64 }),
458 }
459 }
460
461 pub fn read_capacity(&mut self) -> Result<u32> {
463 let cdb = [
464 crate::scsi::SCSI_READ_CAPACITY,
465 0x00,
466 0x00,
467 0x00,
468 0x00,
469 0x00,
470 0x00,
471 0x00,
472 0x00,
473 0x00,
474 ];
475 let mut buf = [0u8; 8];
476 self.scsi.as_mut().execute(
477 &cdb,
478 crate::scsi::DataDirection::FromDevice,
479 &mut buf,
480 5_000,
481 )?;
482 let last_lba = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
483 Ok(last_lba + 1)
484 }
485
486 pub fn set_speed(&mut self, speed_kbs: u16) {
487 let cdb = crate::scsi::build_set_cd_speed(speed_kbs);
488 let mut dummy = [0u8; 0];
489 let _ = self.scsi_execute(&cdb, crate::scsi::DataDirection::None, &mut dummy, 5_000);
490 }
491
492 pub fn lock_tray(&mut self) {
494 let prevent = [
495 SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL,
496 0x00,
497 0x00,
498 0x00,
499 0x01,
500 0x00,
501 ];
502 let mut buf = [0u8; 0];
503 let _ =
504 self.scsi
505 .as_mut()
506 .execute(&prevent, crate::scsi::DataDirection::None, &mut buf, 5_000);
507 }
508
509 pub fn unlock_tray(&mut self) {
511 let allow = [
512 SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL,
513 0x00,
514 0x00,
515 0x00,
516 0x00,
517 0x00,
518 ];
519 let mut buf = [0u8; 0];
520 let _ =
521 self.scsi
522 .as_mut()
523 .execute(&allow, crate::scsi::DataDirection::None, &mut buf, 5_000);
524 }
525
526 pub fn eject(&mut self) -> Result<()> {
528 self.unlock_tray();
529 let eject_cdb = [SCSI_START_STOP_UNIT, 0, 0, 0, 0x02, 0];
530 let mut buf = [0u8; 0];
531 self.scsi.as_mut().execute(
532 &eject_cdb,
533 crate::scsi::DataDirection::None,
534 &mut buf,
535 30_000,
536 )?;
537 Ok(())
538 }
539
540 pub fn scsi_execute(
541 &mut self,
542 cdb: &[u8],
543 direction: crate::scsi::DataDirection,
544 buf: &mut [u8],
545 timeout_ms: u32,
546 ) -> Result<crate::scsi::ScsiResult> {
547 self.scsi.as_mut().execute(cdb, direction, buf, timeout_ms)
548 }
549}
550
551impl Drop for Drive {
552 fn drop(&mut self) {
553 self.cleanup();
554 }
556}
557
558impl SectorReader for Drive {
559 fn read_sectors(
560 &mut self,
561 lba: u32,
562 count: u16,
563 buf: &mut [u8],
564 recovery: bool,
565 ) -> Result<usize> {
566 self.read(lba, count, buf, recovery)
567 }
568}
569
570pub fn find_drive() -> Option<Drive> {
576 discover_drives()
577 .into_iter()
578 .find_map(|(path, _)| Drive::open(std::path::Path::new(&path)).ok())
579}
580
581#[cfg(test)]
586fn sleep_until_halted(halt: &AtomicBool, total: std::time::Duration) -> Result<()> {
587 const SLICE: std::time::Duration = std::time::Duration::from_millis(100);
588 let deadline = std::time::Instant::now() + total;
589 loop {
590 if halt.load(Ordering::Relaxed) {
591 return Err(Error::Halted);
592 }
593 let now = std::time::Instant::now();
594 if now >= deadline {
595 return Ok(());
596 }
597 let remaining = deadline - now;
598 std::thread::sleep(remaining.min(SLICE));
599 }
600}
601
602fn discover_drives() -> Vec<(String, DriveId)> {
604 #[cfg(target_os = "linux")]
605 {
606 linux::find_drives()
607 }
608 #[cfg(target_os = "macos")]
609 {
610 macos::find_drives()
611 }
612 #[cfg(windows)]
613 {
614 windows::find_drives()
615 }
616}
617
618#[allow(dead_code)]
620pub(crate) fn resolve_device(path: &str) -> Result<(String, Option<String>)> {
621 #[cfg(target_os = "linux")]
622 {
623 linux::resolve_device(path)
624 }
625 #[cfg(target_os = "macos")]
626 {
627 macos::resolve_device(path)
628 }
629 #[cfg(windows)]
630 {
631 windows::resolve_device(path)
632 }
633}
634
635fn create_driver(
636 platform: profile::Platform,
637 profile: &DriveProfile,
638) -> Result<Box<dyn PlatformDriver>> {
639 match platform {
640 profile::Platform::Mt1959A => Ok(Box::new(Mt1959::new(profile.clone(), false))),
641 profile::Platform::Mt1959B => Ok(Box::new(Mt1959::new(profile.clone(), true))),
642 profile::Platform::Renesas => Err(Error::PlatformNotImplemented {
643 platform: "renesas".to_string(),
644 }),
645 }
646}
647
648#[cfg(test)]
649mod halt_tests {
650 use super::*;
651 use std::time::{Duration, Instant};
652
653 #[test]
654 fn sleep_until_halted_completes_when_not_halted() {
655 let flag = AtomicBool::new(false);
656 let t0 = Instant::now();
657 let r = sleep_until_halted(&flag, Duration::from_millis(150));
658 assert!(r.is_ok());
659 assert!(t0.elapsed() >= Duration::from_millis(140));
660 }
661
662 #[test]
663 fn sleep_until_halted_returns_immediately_if_preflagged() {
664 let flag = AtomicBool::new(true);
665 let t0 = Instant::now();
666 let r = sleep_until_halted(&flag, Duration::from_secs(10));
667 assert!(matches!(r, Err(Error::Halted)));
668 assert!(t0.elapsed() < Duration::from_millis(200));
671 }
672
673 #[test]
674 fn sleep_until_halted_wakes_mid_sleep() {
675 let flag = Arc::new(AtomicBool::new(false));
676 let f2 = flag.clone();
677 let t0 = Instant::now();
678 std::thread::spawn(move || {
679 std::thread::sleep(Duration::from_millis(150));
680 f2.store(true, Ordering::Relaxed);
681 });
682 let r = sleep_until_halted(&flag, Duration::from_secs(10));
683 assert!(matches!(r, Err(Error::Halted)));
684 let waited = t0.elapsed();
685 assert!(waited < Duration::from_millis(350), "waited {waited:?}");
687 assert!(waited >= Duration::from_millis(140), "waited {waited:?}");
688 }
689
690 #[test]
691 fn sleep_until_halted_zero_duration_is_noop_when_not_halted() {
692 let flag = AtomicBool::new(false);
693 let r = sleep_until_halted(&flag, Duration::ZERO);
694 assert!(r.is_ok());
695 }
696}