1use thiserror::Error;
16
17#[cfg(target_os = "macos")]
18pub mod macos;
19
20#[cfg(target_os = "macos")]
21pub mod raw;
22
23#[derive(Debug, Error)]
24pub enum DiskError {
25 #[error("disk I/O")]
28 Io(#[from] std::io::Error),
29
30 #[error("refusing to write to {0}: this looks like the boot disk")]
31 RefusedBootDisk(String),
32
33 #[error("refusing to write to {0}: marked as internal storage")]
34 RefusedInternal(String),
35
36 #[error(
37 "refusing to write to {device} ({size_gb} GB): exceeds 256 GiB safety threshold. \
38 Pass --force if you really mean it."
39 )]
40 RefusedTooLarge { device: String, size_gb: u64 },
41
42 #[error("device path must be /dev/rdiskN, got: {0}")]
43 BadDevicePath(String),
44
45 #[error("DiskArbitration query failed: {0}")]
46 DaError(String),
47
48 #[error("external command failed: {cmd}: {stderr}")]
49 External { cmd: String, stderr: String },
50}
51
52pub type Result<T> = std::result::Result<T, DiskError>;
53
54#[derive(Debug, Clone, Default)]
56pub struct SafetyConfig {
57 pub force: bool,
59}
60
61#[derive(Debug, Clone)]
64pub struct DeviceInfo {
65 pub path: String, pub size_bytes: u64,
67 pub model: String, pub internal: bool,
69 pub is_boot_disk: bool,
70 pub removable: bool,
71}
72
73impl DeviceInfo {
74 pub fn check_writable(&self, safety: &SafetyConfig) -> Result<()> {
76 if self.is_boot_disk {
77 return Err(DiskError::RefusedBootDisk(self.path.clone()));
78 }
79 if self.internal && !safety.force {
80 return Err(DiskError::RefusedInternal(self.path.clone()));
81 }
82 let cap = 256u64 * 1024 * 1024 * 1024;
83 if self.size_bytes > cap && !safety.force {
84 return Err(DiskError::RefusedTooLarge {
85 device: self.path.clone(),
86 size_gb: self.size_bytes / 1_000_000_000,
87 });
88 }
89 Ok(())
90 }
91}