1#![expect(missing_docs)]
9#![forbid(unsafe_code)]
10
11use guid::Guid;
12use inspect::Inspect;
13use mesh::MeshPayload;
14use serde::Serialize;
15
16mod errors;
17pub mod schema;
18
19const IDE_NUM_CHANNELS: u8 = 2;
21const IDE_MAX_DRIVES_PER_CHANNEL: u8 = 2;
22
23const SCSI_CONTROLLER_NUM: usize = 4;
25pub const SCSI_LUN_NUM: usize = 64;
26
27#[derive(Debug, Copy, Clone, Eq, PartialEq, MeshPayload, Inspect)]
28pub enum DeviceType {
29 NVMe,
30 VScsi,
31}
32
33#[derive(Debug, Clone, Eq, PartialEq, MeshPayload, Inspect)]
34pub struct PhysicalDevice {
35 pub device_type: DeviceType,
36 pub vmbus_instance_id: Guid,
39 pub sub_device_path: u32,
41}
42
43#[derive(Debug, Clone, Eq, PartialEq, MeshPayload, Inspect)]
44#[inspect(external_tag)]
45pub enum PhysicalDevices {
46 EmptyDrive,
47 Single {
48 device: PhysicalDevice,
49 },
50 Striped {
51 #[inspect(iter_by_index)]
52 devices: Vec<PhysicalDevice>,
53 chunk_size_in_kb: u32,
54 },
55}
56
57impl PhysicalDevices {
58 pub fn is_striping(&self) -> bool {
59 matches!(self, PhysicalDevices::Striped { .. })
60 }
61
62 pub fn is_empty(&self) -> bool {
63 matches!(self, PhysicalDevices::EmptyDrive)
64 }
65}
66
67#[derive(Debug, Clone, Eq, PartialEq, MeshPayload, Inspect)]
68pub enum GuestMediaType {
69 Hdd,
70 DvdLoaded,
71 DvdUnloaded,
72}
73
74#[derive(Debug, Clone, Eq, PartialEq, MeshPayload, Inspect)]
75pub struct DiskParameters {
76 pub device_id: String,
77 pub vendor_id: String,
78 pub product_id: String,
79 pub product_revision_level: String,
80 pub serial_number: String,
81 pub model_number: String,
82 pub medium_rotation_rate: u16,
83 pub physical_sector_size: Option<u32>,
84 pub fua: Option<bool>,
85 pub write_cache: Option<bool>,
86 pub scsi_disk_size_in_bytes: Option<u64>,
87 pub odx: Option<bool>,
88 pub unmap: Option<bool>,
89 pub max_transfer_length: Option<usize>,
90}
91
92#[derive(Debug)]
93pub enum StorageDisk {
94 Ide(IdeDisk),
95 Scsi(ScsiDisk),
96}
97
98impl StorageDisk {
99 pub fn physical_devices(&self) -> &PhysicalDevices {
100 match self {
101 StorageDisk::Ide(ide_disk) => &ide_disk.physical_devices,
102 StorageDisk::Scsi(scsi_disk) => &scsi_disk.physical_devices,
103 }
104 }
105
106 pub fn is_dvd(&self) -> bool {
107 match self {
108 StorageDisk::Ide(ide_disk) => ide_disk.is_dvd,
109 StorageDisk::Scsi(scsi_disk) => scsi_disk.is_dvd,
110 }
111 }
112 pub fn ntfs_guid(&self) -> Option<Guid> {
113 match self {
114 StorageDisk::Ide(ide_disk) => ide_disk.ntfs_guid,
115 StorageDisk::Scsi(scsi_disk) => scsi_disk.ntfs_guid,
116 }
117 }
118}
119
120#[derive(Debug, Clone, Eq, PartialEq, MeshPayload, Inspect)]
121pub struct IdeDisk {
122 pub channel: u8,
123 pub location: u8,
124 pub disk_params: DiskParameters,
125 pub physical_devices: PhysicalDevices,
126 pub ntfs_guid: Option<Guid>,
127 pub is_dvd: bool,
128}
129
130#[derive(Debug, Clone, Eq, PartialEq, MeshPayload, Inspect)]
131pub struct IdeController {
132 pub instance_id: Guid,
133 #[inspect(iter_by_index)]
134 pub disks: Vec<IdeDisk>,
135 pub io_queue_depth: Option<u32>,
136}
137
138#[derive(Debug, Clone, Eq, PartialEq, MeshPayload, Inspect)]
139pub struct ScsiDisk {
140 pub location: u8,
141 pub disk_params: DiskParameters,
142 pub physical_devices: PhysicalDevices,
143 pub ntfs_guid: Option<Guid>,
144 pub is_dvd: bool,
145}
146
147#[derive(Debug, Clone, Eq, PartialEq, MeshPayload, Inspect)]
148pub struct ScsiController {
149 pub instance_id: Guid,
150 #[inspect(iter_by_index)]
151 pub disks: Vec<ScsiDisk>,
152 pub io_queue_depth: Option<u32>,
153}
154
155#[derive(Debug, Clone, Eq, PartialEq, MeshPayload, Inspect)]
156pub struct NvmeNamespace {
157 pub nsid: u32,
158 pub disk_params: DiskParameters,
159 pub physical_devices: PhysicalDevices,
160}
161
162#[derive(Debug, Clone, Eq, PartialEq, MeshPayload, Inspect)]
163pub struct NvmeController {
164 pub instance_id: Guid,
165 #[inspect(iter_by_index)]
166 pub namespaces: Vec<NvmeNamespace>,
167}
168
169#[derive(Debug, Clone, MeshPayload, Inspect)]
170pub struct NicDevice {
171 pub instance_id: Guid,
172 pub subordinate_instance_id: Option<Guid>,
173 pub max_sub_channels: Option<u16>,
174}
175
176#[derive(Debug, Clone, MeshPayload, Inspect)]
177pub struct Vtl2SettingsFixed {
178 pub scsi_sub_channels: u16,
180 pub io_ring_size: u32,
182 pub max_bounce_buffer_pages: Option<u32>,
184}
185
186#[derive(Debug, Clone, MeshPayload, Inspect)]
187pub struct Vtl2SettingsDynamic {
188 pub ide_controller: Option<IdeController>,
190 #[inspect(iter_by_index)]
192 pub scsi_controllers: Vec<ScsiController>,
193 #[inspect(iter_by_index)]
195 pub nic_devices: Vec<NicDevice>,
196 #[inspect(iter_by_index)]
198 pub nvme_controllers: Vec<NvmeController>,
199}
200
201#[derive(Debug, Default, Clone, MeshPayload, Inspect)]
202pub struct Vtl2Settings {
203 pub fixed: Vtl2SettingsFixed,
205 pub dynamic: Vtl2SettingsDynamic,
207}
208
209enum Component {
210 Underhill,
211 Storage,
212 Network,
213}
214
215enum EscalationCategory {
216 Underhill,
217 VmCreation,
218 Configuration,
219}
220
221macro_rules! error_codes {
222 {
223 $(#[$enum_attr:meta])*
224 $vis:vis enum $enum_name:ident {
225 $(
226 $(#[$attr:meta])*
227 $name:ident => ($component:tt, $category:tt),
228 )*
229 }
230 } => {
231 $(#[$enum_attr])*
232 $vis enum $enum_name {
233 $(
234 $(#[$attr])*
235 $name,
236 )*
237 }
238
239 impl $enum_name {
240 fn name(&self) -> &'static str {
243 match self {
244 $(
245 $enum_name::$name => {
246 let _ = Component::$component;
248 let _ = EscalationCategory::$category;
249 concat!(stringify!($category), ".", stringify!($name))
250 }
251 )*
252 }
253 }
254 }
255 };
256}
257
258error_codes! {
259#[derive(Clone, Copy, Debug, PartialEq)]
282pub enum Vtl2SettingsErrorCode {
283 InternalFailure => (Underhill, Underhill),
285 JsonFormatError => (Underhill, Configuration),
287 NoVmbusServer => (Underhill, VmCreation),
289 UnsupportedSchemaVersion => (Underhill, Configuration),
291 InvalidInstanceId => (Underhill, Configuration),
293 ProtobufFormatError => (Underhill, Configuration),
295 UnsupportedSchemaNamespace => (Underhill, Configuration),
297 EmptyNamespaceChunk => (Underhill, Configuration),
299 StorageCannotAddRemoveControllerAtRuntime => (Storage, Configuration),
301 StorageLunLocationExceedsMaxLimits => (Storage, Configuration),
303 StorageLunLocationDuplicated => (Storage, Configuration),
305 StorageUnsupportedDeviceType => (Storage, Configuration),
307 StorageCannotFindVtl2Device => (Storage, Configuration),
309 EmptyDriveNotAllowed => (Storage, Configuration),
311
312 StorageCannotOpenVtl2Device => (Storage, Underhill),
314 StorageScsiControllerNotFound => (Storage, Underhill),
316 StorageAttachDiskFailed => (Storage, Underhill),
318 StorageRmDiskFailed => (Storage, Underhill),
320 StorageControllerGuidAlreadyExists => (Storage, Configuration),
322 StorageScsiControllerExceedsMaxLimits => (Storage, Configuration),
324 StorageInvalidVendorId => (Storage, Configuration),
326 StorageInvalidProductId => (Storage, Configuration),
328 StorageInvalidProductRevisionLevel => (Storage, Configuration),
330 StorageIdeChannelNotProvided => (Storage, Configuration),
332 StorageIdeChannelExceedsMaxLimits => (Storage, Configuration),
334 StorageIdeLocationExceedsMaxLimits => (Storage, Configuration),
336 StorageIdeChannelInvalidConfiguration => (Storage, Configuration),
338 StripedStorageCannotChangeControllerAtRuntime => (Storage, Configuration),
340 StorageInvalidPhysicalDiskCount => (Storage, Configuration),
342 StorageCannotModifyIdeAtRuntime => (Storage, Configuration),
344 StorageInvalidControllerType => (Storage, Configuration),
346 StorageInvalidDeviceId => (Storage, Configuration),
348 StorageChangeMediaFailed => (Storage, Underhill),
350 StorageInvalidNtfsFormatGuid => (Storage, Configuration),
352
353 NetworkingModifyNicFailed => (Network, Configuration),
355 NetworkingAddNicFailed => (Network, Configuration),
357 NetworkingRemoveNicFailed => (Network, Configuration),
359}
360}
361
362impl Serialize for Vtl2SettingsErrorCode {
363 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
364 ser.serialize_str(self.name())
365 }
366}
367
368#[derive(Debug, Serialize)]
369pub struct Vtl2SettingsErrorInfo {
370 error_id: Vtl2SettingsErrorCode,
371 message: String,
372 file_name: &'static str,
373 line: u32,
374}
375
376impl Vtl2SettingsErrorInfo {
377 #[track_caller]
378 pub fn new(code: Vtl2SettingsErrorCode, message: String) -> Self {
379 let caller = std::panic::Location::caller();
380 Vtl2SettingsErrorInfo {
381 error_id: code,
382 message,
383 file_name: caller.file(),
384 line: caller.line(),
385 }
386 }
387
388 pub fn code(&self) -> Vtl2SettingsErrorCode {
389 self.error_id
390 }
391}
392
393impl std::fmt::Display for Vtl2SettingsErrorInfo {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 let json = serde_json::to_string(self).map_err(|_| std::fmt::Error)?;
398 write!(f, "{}", json)
399 }
400}
401
402impl std::error::Error for Vtl2SettingsErrorInfo {}
403
404#[derive(Debug)]
405pub struct Vtl2SettingsErrorInfoVec {
406 pub errors: Vec<Vtl2SettingsErrorInfo>,
407}
408
409impl std::fmt::Display for Vtl2SettingsErrorInfoVec {
410 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411 for e in &self.errors {
412 writeln!(f, "{}", e)?;
413 }
414 Ok(())
415 }
416}
417
418impl std::error::Error for Vtl2SettingsErrorInfoVec {}