From 8999db68ea8689cbc6d3073c8e43e223c0d785fe Mon Sep 17 00:00:00 2001 From: MattJackson <1085847+MattJackson@users.noreply.github.com> Date: Sat, 11 Apr 2026 16:12:53 +0000 Subject: [PATCH] Refactor: drive discovery into platform files, no inline cfg - drive/unix.rs: find_drives() + resolve_device() for Linux/macOS - drive/windows.rs: find_drives() + resolve_device() + normalize_path() for Windows - drive/mod.rs: clean delegation, no cfg branches - scsi/windows.rs: SPTI transport only, no drive discovery --- src/{drive.rs => drive/mod.rs} | 68 ++++++---------------------- src/drive/unix.rs | 52 ++++++++++++++++++++++ src/drive/windows.rs | 80 +++++++++++++++++++++++++++++++++ src/scsi/mod.rs | 2 +- src/scsi/windows.rs | 81 ---------------------------------- 5 files changed, 146 insertions(+), 137 deletions(-) rename src/{drive.rs => drive/mod.rs} (76%) create mode 100644 src/drive/unix.rs create mode 100644 src/drive/windows.rs diff --git a/src/drive.rs b/src/drive/mod.rs similarity index 76% rename from src/drive.rs rename to src/drive/mod.rs index 6726a4d..36b108c 100644 --- a/src/drive.rs +++ b/src/drive/mod.rs @@ -6,6 +6,11 @@ //! 3. `init()` — activate custom firmware. Removes riplock. //! 4. `probe_disc()` — probe disc surface. Drive learns optimal speeds. +#[cfg(unix)] +mod unix; +#[cfg(windows)] +mod windows; + use std::path::Path; use crate::error::{Error, Result}; use crate::sector::SectorReader; @@ -168,25 +173,10 @@ impl SectorReader for DriveSession { } pub fn find_drives() -> Vec<(String, DriveId)> { - #[cfg(target_os = "windows")] - { crate::scsi::windows::find_drives() } - - #[cfg(not(target_os = "windows"))] - { - let mut drives = Vec::new(); - for i in 0..16 { - let path = format!("/dev/sg{}", i); - if !std::path::Path::new(&path).exists() { continue; } - if let Ok(mut transport) = crate::scsi::open(std::path::Path::new(&path)) { - if let Ok(id) = DriveId::from_drive(transport.as_mut()) { - if id.raw_inquiry.len() > 0 && (id.raw_inquiry[0] & 0x1F) == 0x05 { - drives.push((path, id)); - } - } - } - } - drives - } + #[cfg(unix)] + { unix::find_drives() } + #[cfg(windows)] + { windows::find_drives() } } pub fn find_drive() -> Option { @@ -194,42 +184,10 @@ pub fn find_drive() -> Option { } pub fn resolve_device(path: &str) -> Result<(String, Option)> { - // Windows: drive letters, CdRom paths, UNC paths — pass through directly - #[cfg(target_os = "windows")] - { - return Ok((crate::scsi::windows::normalize_device_path(path), None)); - } - - #[cfg(not(target_os = "windows"))] - if path.contains("/sg") { - if !std::path::Path::new(path).exists() { - return Err(Error::DeviceNotFound { path: path.to_string() }); - } - return Ok((path.to_string(), None)); - } - if path.contains("/sr") { - let mut sr_transport = crate::scsi::open(std::path::Path::new(path))?; - let sr_id = DriveId::from_drive(sr_transport.as_mut())?; - drop(sr_transport); - for (sg_path, sg_id) in find_drives() { - if sg_id.vendor_id == sr_id.vendor_id - && sg_id.product_id == sr_id.product_id - && sg_id.serial_number == sr_id.serial_number - { - let warning = format!( - "{} is a block device (sr) — using {} (sg) for raw access", path, sg_path - ); - return Ok((sg_path, Some(warning))); - } - } - return Ok((path.to_string(), Some(format!( - "{} is a block device (sr) — no matching sg device found", path - )))); - } - if !std::path::Path::new(path).exists() { - return Err(Error::DeviceNotFound { path: path.to_string() }); - } - Ok((path.to_string(), None)) + #[cfg(unix)] + { unix::resolve_device(path) } + #[cfg(windows)] + { windows::resolve_device(path) } } fn create_driver(platform: profile::Platform, profile: &DriveProfile) -> Result> { diff --git a/src/drive/unix.rs b/src/drive/unix.rs new file mode 100644 index 0000000..85b4ea4 --- /dev/null +++ b/src/drive/unix.rs @@ -0,0 +1,52 @@ +//! Unix (Linux/macOS) drive discovery and device resolution. + +use crate::error::{Error, Result}; +use crate::identity::DriveId; + +pub fn find_drives() -> Vec<(String, DriveId)> { + let mut drives = Vec::new(); + for i in 0..16 { + let path = format!("/dev/sg{}", i); + if !std::path::Path::new(&path).exists() { continue; } + if let Ok(mut transport) = crate::scsi::open(std::path::Path::new(&path)) { + if let Ok(id) = DriveId::from_drive(transport.as_mut()) { + if !id.raw_inquiry.is_empty() && (id.raw_inquiry[0] & 0x1F) == 0x05 { + drives.push((path, id)); + } + } + } + } + drives +} + +pub fn resolve_device(path: &str) -> Result<(String, Option)> { + if path.contains("/sg") { + if !std::path::Path::new(path).exists() { + return Err(Error::DeviceNotFound { path: path.to_string() }); + } + return Ok((path.to_string(), None)); + } + if path.contains("/sr") { + let mut sr_transport = crate::scsi::open(std::path::Path::new(path))?; + let sr_id = DriveId::from_drive(sr_transport.as_mut())?; + drop(sr_transport); + for (sg_path, sg_id) in find_drives() { + if sg_id.vendor_id == sr_id.vendor_id + && sg_id.product_id == sr_id.product_id + && sg_id.serial_number == sr_id.serial_number + { + let warning = format!( + "{} is a block device (sr) — using {} (sg) for raw access", path, sg_path + ); + return Ok((sg_path, Some(warning))); + } + } + return Ok((path.to_string(), Some(format!( + "{} is a block device (sr) — no matching sg device found", path + )))); + } + if !std::path::Path::new(path).exists() { + return Err(Error::DeviceNotFound { path: path.to_string() }); + } + Ok((path.to_string(), None)) +} diff --git a/src/drive/windows.rs b/src/drive/windows.rs new file mode 100644 index 0000000..a35972c --- /dev/null +++ b/src/drive/windows.rs @@ -0,0 +1,80 @@ +//! Windows drive discovery and device resolution. + +use crate::error::Result; +use crate::identity::DriveId; +use std::path::Path; + +pub fn find_drives() -> Vec<(String, DriveId)> { + let mut drives = Vec::new(); + + // Try CdRom0..CdRom15 + for i in 0..16 { + let path = format!("\\\\.\\CdRom{}", i); + if let Ok(mut transport) = crate::scsi::open(Path::new(&path)) { + if let Ok(id) = DriveId::from_drive(transport.as_mut()) { + if !id.raw_inquiry.is_empty() && (id.raw_inquiry[0] & 0x1F) == 0x05 { + drives.push((path, id)); + } + } + } + } + + // Also try drive letters if CdRom didn't find anything + if drives.is_empty() { + for letter in b'D'..=b'Z' { + let path = format!("{}:", letter as char); + if let Ok(mut transport) = crate::scsi::open(Path::new(&path)) { + if let Ok(id) = DriveId::from_drive(transport.as_mut()) { + if !id.raw_inquiry.is_empty() && (id.raw_inquiry[0] & 0x1F) == 0x05 { + drives.push((path, id)); + } + } + } + } + } + + drives +} + +pub fn resolve_device(path: &str) -> Result<(String, Option)> { + Ok((normalize_path(path), None)) +} + +/// Normalize a device path to Windows \\.\X: format. +/// +/// Accepts: "D:", "D:\\", "\\.\D:", "\\.\CdRom0" +fn normalize_path(path: &str) -> String { + if path.starts_with("\\\\.\\") { + return path.to_string(); + } + let trimmed = path.trim_end_matches('\\'); + if trimmed.len() == 2 && trimmed.as_bytes()[1] == b':' { + return format!("\\\\.\\{}", trimmed); + } + if path.to_lowercase().starts_with("cdrom") { + return format!("\\\\.\\{}", path); + } + format!("\\\\.\\{}", path) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn normalize_drive_letter() { + assert_eq!(normalize_path("D:"), "\\\\.\\D:"); + assert_eq!(normalize_path("E:\\"), "\\\\.\\E:"); + } + + #[test] + fn normalize_already_prefixed() { + assert_eq!(normalize_path("\\\\.\\D:"), "\\\\.\\D:"); + assert_eq!(normalize_path("\\\\.\\CdRom0"), "\\\\.\\CdRom0"); + } + + #[test] + fn normalize_cdrom() { + assert_eq!(normalize_path("CdRom0"), "\\\\.\\CdRom0"); + } +} diff --git a/src/scsi/mod.rs b/src/scsi/mod.rs index 5e8d17b..3b3f358 100644 --- a/src/scsi/mod.rs +++ b/src/scsi/mod.rs @@ -10,7 +10,7 @@ mod linux; #[cfg(target_os = "macos")] mod macos; #[cfg(target_os = "windows")] -pub(crate) mod windows; +mod windows; #[allow(unused_imports)] use crate::error::{Error, Result}; diff --git a/src/scsi/windows.rs b/src/scsi/windows.rs index a26f4ab..b92cb24 100644 --- a/src/scsi/windows.rs +++ b/src/scsi/windows.rs @@ -194,84 +194,3 @@ impl ScsiTransport for SptiTransport { } } -// ── Device path helpers ──────────────────────────────────────────────────── - -/// Normalize a device path to Windows \\.\X: format. -/// -/// Accepts: "D:", "D:\\", "\\.\D:", "\\.\CdRom0" -pub(crate) fn normalize_device_path(path: &str) -> String { - // Already in \\.\X format - if path.starts_with("\\\\.\\") { - return path.to_string(); - } - - // Single drive letter: "D:" or "D:\" - let trimmed = path.trim_end_matches('\\'); - if trimmed.len() == 2 && trimmed.as_bytes()[1] == b':' { - return format!("\\\\.\\{}", trimmed); - } - - // CdRomN format - if path.to_lowercase().starts_with("cdrom") { - return format!("\\\\.\\{}", path); - } - - // Fallback: wrap in \\.\ - format!("\\\\.\\{}", path) -} - -/// Find all optical drives on Windows. -/// Scans drive letters A-Z and CdRom0-15. -pub fn find_drives() -> Vec<(String, crate::identity::DriveId)> { - let mut drives = Vec::new(); - - // Try CdRom0..CdRom15 - for i in 0..16 { - let path = format!("\\\\.\\CdRom{}", i); - if let Ok(mut transport) = SptiTransport::open(Path::new(&path)) { - if let Ok(id) = crate::identity::DriveId::from_drive(&mut transport) { - if !id.raw_inquiry.is_empty() && (id.raw_inquiry[0] & 0x1F) == 0x05 { - drives.push((path, id)); - } - } - } - } - - // Also try drive letters if CdRom didn't find anything - if drives.is_empty() { - for letter in b'D'..=b'Z' { - let path = format!("{}:", letter as char); - if let Ok(mut transport) = SptiTransport::open(Path::new(&path)) { - if let Ok(id) = crate::identity::DriveId::from_drive(&mut transport) { - if !id.raw_inquiry.is_empty() && (id.raw_inquiry[0] & 0x1F) == 0x05 { - drives.push((path, id)); - } - } - } - } - } - - drives -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn normalize_drive_letter() { - assert_eq!(normalize_device_path("D:"), "\\\\.\\D:"); - assert_eq!(normalize_device_path("E:\\"), "\\\\.\\E:"); - } - - #[test] - fn normalize_already_prefixed() { - assert_eq!(normalize_device_path("\\\\.\\D:"), "\\\\.\\D:"); - assert_eq!(normalize_device_path("\\\\.\\CdRom0"), "\\\\.\\CdRom0"); - } - - #[test] - fn normalize_cdrom() { - assert_eq!(normalize_device_path("CdRom0"), "\\\\.\\CdRom0"); - } -}