1use std::fmt;
11use std::path::PathBuf;
12use thiserror::Error;
13
14pub mod device;
15pub mod plan;
16
17pub use device::Device;
18pub use plan::{BootMode, WritePlan};
19
20#[derive(Debug, Error)]
21pub enum Error {
22 #[error("I/O error")]
25 Io(#[from] std::io::Error),
26
27 #[error("device refuses to be written: {0}")]
28 DeviceRefused(String),
29
30 #[error("ISO does not fit on device (iso={iso_bytes} bytes, device={device_bytes} bytes)")]
31 IsoTooLarge { iso_bytes: u64, device_bytes: u64 },
32
33 #[error("ISO classification failed: {0}")]
34 IsoClassify(String),
35
36 #[error("boot record write failed: {0}")]
37 BootRecord(String),
38
39 #[error("verification failed at offset {offset}: expected {expected:02x?}, got {actual:02x?}")]
40 VerifyMismatch {
41 offset: u64,
42 expected: Vec<u8>,
43 actual: Vec<u8>,
44 },
45
46 #[error("unsupported boot mode for this ISO: {0}")]
47 UnsupportedMode(String),
48
49 #[error("external command failed: {cmd}: {stderr}")]
50 External { cmd: String, stderr: String },
51}
52
53pub type Result<T> = std::result::Result<T, Error>;
54
55#[derive(Debug, Clone)]
57pub struct Config {
58 pub iso_path: PathBuf,
59 pub device_path: PathBuf,
60 pub mode: ModeRequest,
61 pub label: Option<String>,
62 pub dry_run: bool,
63 pub force: bool,
64 pub verify: bool,
65 pub verbose: bool,
66 pub boot_record_impl: BootRecordImpl,
71 pub unattended: Option<UnattendedConfig>,
74 pub ahci_driver_dir: Option<PathBuf>,
79}
80
81#[derive(Clone)]
82pub struct UnattendedConfig {
83 pub product_key: Option<String>,
84 pub full_name: String,
85 pub organization: String,
86 pub computer_name: String,
87 pub admin_password: Option<String>,
88 pub timezone: Option<u16>,
89}
90
91impl fmt::Debug for UnattendedConfig {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 f.debug_struct("UnattendedConfig")
94 .field(
95 "product_key",
96 &self.product_key.as_ref().map(|_| "<redacted>"),
97 )
98 .field("full_name", &self.full_name)
99 .field("organization", &self.organization)
100 .field("computer_name", &self.computer_name)
101 .field(
102 "admin_password",
103 &self.admin_password.as_ref().map(|_| "<redacted>"),
104 )
105 .field("timezone", &self.timezone)
106 .finish()
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum BootRecordImpl {
113 MsSys,
115 Bootrec,
117}
118
119impl BootRecordImpl {
120 pub fn as_str(&self) -> &'static str {
121 match self {
122 BootRecordImpl::MsSys => "ms-sys",
123 BootRecordImpl::Bootrec => "bootrec",
124 }
125 }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum ModeRequest {
132 Auto,
133 Windows,
134 WindowsNtXp,
135 Windows2000,
136 IsolinuxLinux,
137 Hybrid,
138 UefiOnly,
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn unattended_debug_redacts_secrets() {
147 let config = UnattendedConfig {
148 product_key: Some("AAAAA-BBBBB-CCCCC-DDDDD-EEEEE".into()),
149 full_name: "QA User".into(),
150 organization: "bootsmith".into(),
151 computer_name: "XPTEST".into(),
152 admin_password: Some("secret".into()),
153 timezone: Some(35),
154 };
155
156 let debug = format!("{config:?}");
157 assert!(debug.contains("<redacted>"));
158 assert!(!debug.contains("AAAAA-BBBBB"));
159 assert!(!debug.contains("secret"));
160 }
161}